diff -Nru libjs-angular-gettext-2.1.0/bower.json libjs-angular-gettext-2.3.8/bower.json --- libjs-angular-gettext-2.1.0/bower.json 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/bower.json 2016-09-20 07:58:56.000000000 +0000 @@ -1,6 +1,6 @@ { "name": "angular-gettext", - "version": "2.1.0", + "version": "2.3.8", "main": "dist/angular-gettext.js", "ignore": [ "**/.*", diff -Nru libjs-angular-gettext-2.1.0/debian/changelog libjs-angular-gettext-2.3.8/debian/changelog --- libjs-angular-gettext-2.1.0/debian/changelog 2015-07-29 09:04:16.000000000 +0000 +++ libjs-angular-gettext-2.3.8/debian/changelog 2017-11-04 20:25:52.000000000 +0000 @@ -1,3 +1,22 @@ +libjs-angular-gettext (2.3.8-2) unstable; urgency=medium + + * Add missing build-depends on openstack-pkg-tools (Closes: #878452). + + -- Thomas Goirand Sat, 04 Nov 2017 20:25:52 +0000 + +libjs-angular-gettext (2.3.8-1) unstable; urgency=medium + + * New upstream release. + * Fixed VCS URLs to use HTTPS. + * Fixed debian/copyright ordering. + * Fixed Section: to be javascript. + * Fixed typo in long desc. + * Ran wrap-and-sort -bast. + * Switch compat/debhelper to version 10. + * Standards-Version set to 4.1.1. + + -- Thomas Goirand Fri, 13 Oct 2017 12:40:38 +0200 + libjs-angular-gettext (2.1.0-2) unstable; urgency=medium * Initial release (Closes: #793949). diff -Nru libjs-angular-gettext-2.1.0/debian/compat libjs-angular-gettext-2.3.8/debian/compat --- libjs-angular-gettext-2.1.0/debian/compat 2015-07-29 09:04:16.000000000 +0000 +++ libjs-angular-gettext-2.3.8/debian/compat 2017-11-04 20:25:52.000000000 +0000 @@ -1 +1 @@ -9 +10 diff -Nru libjs-angular-gettext-2.1.0/debian/control libjs-angular-gettext-2.3.8/debian/control --- libjs-angular-gettext-2.1.0/debian/control 2015-07-29 09:04:16.000000000 +0000 +++ libjs-angular-gettext-2.3.8/debian/control 2017-11-04 20:25:52.000000000 +0000 @@ -1,19 +1,26 @@ Source: libjs-angular-gettext -Section: web +Section: javascript Priority: extra Maintainer: Debian Javascript Maintainers -Uploaders: Thomas Goirand -Build-Depends: debhelper (>= 9), yui-compressor -Standards-Version: 3.9.6 -Vcs-Git: git://anonscm.debian.org/pkg-javascript/libjs-angular-gettext.git -Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-javascript/libjs-angular-gettext.git +Uploaders: + Thomas Goirand , +Build-Depends: + debhelper (>= 10), + openstack-pkg-tools, + yui-compressor, +Standards-Version: 4.1.1 +Vcs-Git: https://anonscm.debian.org/git/pkg-javascript/libjs-angular-gettext.git +Vcs-Browser: https://anonscm.debian.org/cgit/pkg-javascript/libjs-angular-gettext.git/ Homepage: https://github.com/rubenv/angular-gettext Package: libjs-angular-gettext Architecture: all -Depends: libjs-angularjs (>= 1.2.25), ${misc:Depends} -Recommends: javascript-common +Depends: + libjs-angularjs (>= 1.2.25), + ${misc:Depends}, +Recommends: + javascript-common, Description: gettext utilities for angular.js - Angular-gettext let's you focus on developing your application. Just write + Angular-gettext lets you focus on developing your application. Just write everything in English and annotate which parts should be translated. The tools do the rest. diff -Nru libjs-angular-gettext-2.1.0/debian/copyright libjs-angular-gettext-2.3.8/debian/copyright --- libjs-angular-gettext-2.1.0/debian/copyright 2015-07-29 09:04:16.000000000 +0000 +++ libjs-angular-gettext-2.3.8/debian/copyright 2017-11-04 20:25:52.000000000 +0000 @@ -2,10 +2,6 @@ Upstream-Name: angularjs-gettext Source: git://github.com/rubenv/angular-gettext.git -Files: debian/* -Copyright: (c) 2015, Thomas Goirand -License: Expat - Files: * Copyright: (c) 2013-2015, Ruben Vermeersch (c) 2014-2015, Rickert Mulder @@ -23,6 +19,10 @@ (c) 2012, Rdio INC. License: Expat +Files: debian/* +Copyright: (c) 2015-2017, Thomas Goirand +License: Expat + License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff -Nru libjs-angular-gettext-2.1.0/debian/install libjs-angular-gettext-2.3.8/debian/install --- libjs-angular-gettext-2.1.0/debian/install 2015-07-29 09:04:16.000000000 +0000 +++ libjs-angular-gettext-2.3.8/debian/install 2017-11-04 20:25:52.000000000 +0000 @@ -1,2 +1,2 @@ -dist/angular-gettext.js usr/share/javascript/angular.js angular-gettext.min.js usr/share/javascript/angular.js +dist/angular-gettext.js usr/share/javascript/angular.js diff -Nru libjs-angular-gettext-2.1.0/debian/rules libjs-angular-gettext-2.3.8/debian/rules --- libjs-angular-gettext-2.1.0/debian/rules 2015-07-29 09:04:16.000000000 +0000 +++ libjs-angular-gettext-2.3.8/debian/rules 2017-11-04 20:25:52.000000000 +0000 @@ -1,7 +1,7 @@ #!/usr/bin/make -f -UPSTREAM_GIT = git://github.com/rubenv/angular-gettext.git --include /usr/share/openstack-pkg-tools/pkgos.make +UPSTREAM_GIT = https://github.com/rubenv/angular-gettext.git +include /usr/share/openstack-pkg-tools/pkgos.make %: dh $@ diff -Nru libjs-angular-gettext-2.1.0/dist/angular-gettext.js libjs-angular-gettext-2.3.8/dist/angular-gettext.js --- libjs-angular-gettext-2.1.0/dist/angular-gettext.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/dist/angular-gettext.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,5 +1,45 @@ +/** + * @ngdoc module + * @name gettext + * @packageName angular-gettext + * @description Super simple Gettext for Angular.JS + * + * A sample application can be found at https://github.com/rubenv/angular-gettext-example. + * This is an adaptation of the [TodoMVC](http://todomvc.com/) example. You can use this as a guideline while adding {@link angular-gettext angular-gettext} to your own application. + */ +/** + * @ngdoc factory + * @module gettext + * @name gettextPlurals + * @param {String} [langCode=en] language code + * @param {Number} [n=0] number to calculate form for + * @returns {Number} plural form number + * @description Provides correct plural form id for the given language + * + * Example + * ```js + * gettextPlurals('ru', 10); // 1 + * gettextPlurals('en', 1); // 0 + * gettextPlurals(); // 1 + * ``` + */ angular.module('gettext', []); - +/** + * @ngdoc object + * @module gettext + * @name gettext + * @kind function + * @param {String} str annotation key + * @description Gettext constant function for annotating strings + * + * ```js + * angular.module('myApp', ['gettext']).config(function(gettext) { + * /// MyApp document title + * gettext('my-app.title'); + * ... + * }) + * ``` + */ angular.module('gettext').constant('gettext', function (str) { /* * Does nothing, simply returns the input string. @@ -10,7 +50,19 @@ return str; }); -angular.module('gettext').factory('gettextCatalog', ["gettextPlurals", "$http", "$cacheFactory", "$interpolate", "$rootScope", function (gettextPlurals, $http, $cacheFactory, $interpolate, $rootScope) { +/** + * @ngdoc service + * @module gettext + * @name gettextCatalog + * @requires gettextPlurals + * @requires gettextFallbackLanguage + * @requires https://docs.angularjs.org/api/ng/service/$http $http + * @requires https://docs.angularjs.org/api/ng/service/$cacheFactory $cacheFactory + * @requires https://docs.angularjs.org/api/ng/service/$interpolate $interpolate + * @requires https://docs.angularjs.org/api/ng/service/$rootScope $rootScope + * @description Provides set of method to translate stings + */ +angular.module('gettext').factory('gettextCatalog', ["gettextPlurals", "gettextFallbackLanguage", "$http", "$cacheFactory", "$interpolate", "$rootScope", function (gettextPlurals, gettextFallbackLanguage, $http, $cacheFactory, $interpolate, $rootScope) { var catalog; var noContext = '$$noContext'; @@ -38,34 +90,134 @@ }; function broadcastUpdated() { + /** + * @ngdoc event + * @name gettextCatalog#gettextLanguageChanged + * @eventType broadcast on $rootScope + * @description Fires language change notification without any additional parameters. + */ $rootScope.$broadcast('gettextLanguageChanged'); } catalog = { + /** + * @ngdoc property + * @name gettextCatalog#debug + * @public + * @type {Boolean} false + * @see gettextCatalog#debug + * @description Whether or not to prefix untranslated strings with `[MISSING]:` or a custom prefix. + */ debug: false, + /** + * @ngdoc property + * @name gettextCatalog#debugPrefix + * @public + * @type {String} [MISSING]: + * @description Custom prefix for untranslated strings when {@link gettextCatalog#debug gettextCatalog#debug} set to `true`. + */ debugPrefix: '[MISSING]: ', + /** + * @ngdoc property + * @name gettextCatalog#showTranslatedMarkers + * @public + * @type {Boolean} false + * @description Whether or not to wrap all processed text with markers. + * + * Example output: `[Welcome]` + */ showTranslatedMarkers: false, + /** + * @ngdoc property + * @name gettextCatalog#translatedMarkerPrefix + * @public + * @type {String} [ + * @description Custom prefix to mark strings that have been run through {@link angular-gettext angular-gettext}. + */ translatedMarkerPrefix: '[', + /** + * @ngdoc property + * @name gettextCatalog#translatedMarkerSuffix + * @public + * @type {String} ] + * @description Custom suffix to mark strings that have been run through {@link angular-gettext angular-gettext}. + */ translatedMarkerSuffix: ']', + /** + * @ngdoc property + * @name gettextCatalog#strings + * @private + * @type {Object} + * @description An object of loaded translation strings. Shouldn't be used directly. + */ strings: {}, + /** + * @ngdoc property + * @name gettextCatalog#baseLanguage + * @protected + * @deprecated + * @since 2.0 + * @type {String} en + * @description The default language, in which you're application is written. + * + * This defaults to English and it's generally a bad idea to use anything else: + * if your language has different pluralization rules you'll end up with incorrect translations. + */ baseLanguage: 'en', + /** + * @ngdoc property + * @name gettextCatalog#currentLanguage + * @public + * @type {String} + * @description Active language. + */ currentLanguage: 'en', + /** + * @ngdoc property + * @name gettextCatalog#cache + * @public + * @type {String} en + * @description Language cache for lazy load + */ cache: $cacheFactory('strings'), + /** + * @ngdoc method + * @name gettextCatalog#setCurrentLanguage + * @public + * @param {String} lang language name + * @description Sets the current language and makes sure that all translations get updated correctly. + */ setCurrentLanguage: function (lang) { this.currentLanguage = lang; broadcastUpdated(); }, + /** + * @ngdoc method + * @name gettextCatalog#getCurrentLanguage + * @public + * @returns {String} current language + * @description Returns the current language. + */ getCurrentLanguage: function () { return this.currentLanguage; }, + /** + * @ngdoc method + * @name gettextCatalog#setStrings + * @public + * @param {String} language language name + * @param {Object.} strings set of strings where the key is the translation `key` and `value` is the translated text + * @description Processes an object of string definitions. {@link guide:manual-setstrings More details here}. + */ setStrings: function (language, strings) { if (!this.strings[language]) { this.strings[language] = {}; } + var defaultPlural = gettextPlurals(language, 1); for (var key in strings) { var val = strings[key]; @@ -84,7 +236,10 @@ // Expand single strings for each context. for (var context in val) { var str = val[context]; - val[context] = angular.isArray(str) ? str : [str]; + if (!angular.isArray(str)) { + val[context] = []; + val[context][defaultPlural] = str; + } } this.strings[language][key] = val; } @@ -92,22 +247,73 @@ broadcastUpdated(); }, - getStringForm: function (string, n, context) { - var stringTable = this.strings[this.currentLanguage] || {}; + /** + * @ngdoc method + * @name gettextCatalog#getStringFormFor + * @protected + * @param {String} language language name + * @param {String} string translation key + * @param {Number=} n number to build sting form for + * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} + * @returns {String|Null} translated or annotated string or null if language is not set + * @description Translate a string with the given language, count and context. + */ + getStringFormFor: function (language, string, n, context) { + if (!language) { + return null; + } + var stringTable = this.strings[language] || {}; var contexts = stringTable[string] || {}; var plurals = contexts[context || noContext] || []; - return plurals[n]; + return plurals[gettextPlurals(language, n)]; }, + /** + * @ngdoc method + * @name gettextCatalog#getString + * @public + * @param {String} string translation key + * @param {$rootScope.Scope=} scope scope to do interpolation against + * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} + * @returns {String} translated or annotated string + * @description Translate a string with the given scope and context. + * + * First it tries {@link gettextCatalog#currentLanguage gettextCatalog#currentLanguage} (e.g. `en-US`) then {@link gettextFallbackLanguage fallback} (e.g. `en`). + * + * When `scope` is supplied it uses Angular.JS interpolation, so something like this will do what you expect: + * ```js + * var hello = gettextCatalog.getString("Hello {{name}}!", { name: "Ruben" }); + * // var hello will be "Hallo Ruben!" in Dutch. + * ``` + * Avoid using scopes - this skips interpolation and is a lot faster. + */ getString: function (string, scope, context) { - string = this.getStringForm(string, 0, context) || prefixDebug(string); + var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage); + string = this.getStringFormFor(this.currentLanguage, string, 1, context) || + this.getStringFormFor(fallbackLanguage, string, 1, context) || + prefixDebug(string); string = scope ? $interpolate(string)(scope) : string; return addTranslatedMarkers(string); }, + /** + * @ngdoc method + * @name gettextCatalog#getPlural + * @public + * @param {Number} n number to build sting form for + * @param {String} string translation key + * @param {String} stringPlural plural translation key + * @param {$rootScope.Scope=} scope scope to do interpolation against + * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} + * @returns {String} translated or annotated string + * @see {@link gettextCatalog#getString gettextCatalog#getString} for details + * @description Translate a plural string with the given context. + */ getPlural: function (n, string, stringPlural, scope, context) { - var form = gettextPlurals(this.currentLanguage, n); - string = this.getStringForm(string, form, context) || prefixDebug(n === 1 ? string : stringPlural); + var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage); + string = this.getStringFormFor(this.currentLanguage, string, n, context) || + this.getStringFormFor(fallbackLanguage, string, n, context) || + prefixDebug(n === 1 ? string : stringPlural); if (scope) { scope.$count = n; string = $interpolate(string)(scope); @@ -115,15 +321,27 @@ return addTranslatedMarkers(string); }, + /** + * @ngdoc method + * @name gettextCatalog#loadRemote + * @public + * @param {String} url location of the translations + * @description Load a set of translation strings from a given URL. + * + * This should be a JSON catalog generated with [angular-gettext-tools](https://github.com/rubenv/angular-gettext-tools). + * {@link guide:lazy-loading More details here}. + */ loadRemote: function (url) { return $http({ method: 'GET', url: url, cache: catalog.cache - }).success(function (data) { + }).then(function (response) { + var data = response.data; for (var lang in data) { catalog.setStrings(lang, data[lang]); } + return response; }); } }; @@ -131,37 +349,105 @@ return catalog; }]); -angular.module('gettext').directive('translate', ["gettextCatalog", "$parse", "$animate", "$compile", "$window", function (gettextCatalog, $parse, $animate, $compile, $window) { - // Trim polyfill for old browsers (instead of jQuery) - // Based on AngularJS-v1.2.2 (angular.js#620) - var trim = (function () { - if (!String.prototype.trim) { - return function (value) { - return (typeof value === 'string') ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; - }; - } - return function (value) { - return (typeof value === 'string') ? value.trim() : value; - }; - })(); +/** + * @ngdoc directive + * @module gettext + * @name translate + * @requires gettextCatalog + * @requires gettextUtil + * @requires https://docs.angularjs.org/api/ng/service/$parse $parse + * @requires https://docs.angularjs.org/api/ng/service/$animate $animate + * @requires https://docs.angularjs.org/api/ng/service/$compile $compile + * @requires https://docs.angularjs.org/api/ng/service/$window $window + * @restrict AE + * @param {String} [translatePlural] plural form + * @param {Number} translateN value to watch to substitute correct plural form + * @param {String} translateContext context value, e.g. {@link doc:context Verb, Noun} + * @description Annotates and translates text inside directive + * + * Full interpolation support is available in translated strings, so the following will work as expected: + * ```js + *
Hello {{name}}!
+ * ``` + * + * You can also use custom context parameters while interpolating. This approach allows usage + * of angular filters as well as custom logic inside your translated messages without unnecessary impact on translations. + * + * So for example when you have message like this: + * ```js + *
Last modified {{modificationDate | date:'yyyy-MM-dd HH:mm:ss Z'}} by {{author}}.
+ * ``` + * you will have it extracted in exact same version so it would look like this: + * `Last modified {{modificationDate | date:'yyyy-MM-dd HH:mm:ss Z'}} by {{author}}`. + * To start with it might be too complicated to read and handle by non technical translator. It's easy to make mistake + * when copying format for example. Secondly if you decide to change format by some point of the project translation will broke + * as it won't be the same string anymore. + * + * Instead your translator should only be concerned to place {{modificationDate}} correctly and you should have a free hand + * to modify implementation details on how to present the results. This is how you can achieve the goal: + * ```js + *
Last modified {{modificationDate}} by {{author}}.
+ * ``` + * + * There's a few more things worth to point out: + * 1. You can use as many parameters as you want. Each parameter begins with `translate-params-` followed by snake-case parameter name. + * Each parameter will be available for interpolation in camelCase manner (just like angular directive works by default). + * ```js + *
Param {{myCustomParam}} has been changed by {{name}}.
+ * ``` + * 2. You can rename your variables from current scope to simple ones if you like. + * ```js + *
Today's date is: {{date}}.
+ * ``` + * 3. You can use translate-params only for some interpolations. Rest would be treated as usual. + * ```js + *
This product: {{product}} costs {{cost}}.
+ * ``` + */ +angular.module('gettext').directive('translate', ["gettextCatalog", "$parse", "$animate", "$compile", "$window", "gettextUtil", function (gettextCatalog, $parse, $animate, $compile, $window, gettextUtil) { + var msie = parseInt((/msie (\d+)/.exec(angular.lowercase($window.navigator.userAgent)) || [])[1], 10); + var PARAMS_PREFIX = 'translateParams'; - function assert(condition, missing, found) { - if (!condition) { - throw new Error('You should add a ' + missing + ' attribute whenever you add a ' + found + ' attribute.'); - } + function getCtxAttr(key) { + return gettextUtil.lcFirst(key.replace(PARAMS_PREFIX, '')); } - var msie = parseInt((/msie (\d+)/.exec(angular.lowercase($window.navigator.userAgent)) || [])[1], 10); + function handleInterpolationContext(scope, attrs, update) { + var attributes = Object.keys(attrs).filter(function (key) { + return gettextUtil.startsWith(key, PARAMS_PREFIX) && key !== PARAMS_PREFIX; + }); + + if (!attributes.length) { + return null; + } + + var interpolationContext = angular.extend({}, scope); + var unwatchers = []; + attributes.forEach(function (attribute) { + var unwatch = scope.$watch(attrs[attribute], function (newVal) { + var key = getCtxAttr(attribute); + interpolationContext[key] = newVal; + update(interpolationContext); + }); + unwatchers.push(unwatch); + }); + scope.$on('$destroy', function () { + unwatchers.forEach(function (unwatch) { + unwatch(); + }); + }); + return interpolationContext; + } return { restrict: 'AE', terminal: true, compile: function compile(element, attrs) { // Validate attributes - assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); - assert(!attrs.translateN || attrs.translatePlural, 'translate-plural', 'translate-n'); + gettextUtil.assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); + gettextUtil.assert(!attrs.translateN || attrs.translatePlural, 'translate-plural', 'translate-n'); - var msgid = trim(element.html()); + var msgid = gettextUtil.trim(element.html()); var translatePlural = attrs.translatePlural; var translateContext = attrs.translateContext; @@ -179,17 +465,18 @@ var pluralScope = null; var linking = true; - function update() { + function update(interpolationContext) { + interpolationContext = interpolationContext || null; + // Fetch correct translated string. var translated; if (translatePlural) { scope = pluralScope || (pluralScope = scope.$new()); scope.$count = countFn(scope); - translated = gettextCatalog.getPlural(scope.$count, msgid, translatePlural, null, translateContext); + translated = gettextCatalog.getPlural(scope.$count, msgid, translatePlural, interpolationContext, translateContext); } else { - translated = gettextCatalog.getString(msgid, null, translateContext); + translated = gettextCatalog.getString(msgid, interpolationContext, translateContext); } - var oldContents = element.contents(); if (oldContents.length === 0){ @@ -197,7 +484,7 @@ } // Avoid redundant swaps - if (translated === trim(oldContents.html())){ + if (translated === gettextUtil.trim(oldContents.html())){ // Take care of unlinked content if (linking){ $compile(oldContents)(scope); @@ -214,20 +501,86 @@ $animate.leave(oldContents); } + var interpolationContext = handleInterpolationContext(scope, attrs, update); + update(interpolationContext); + linking = false; + if (attrs.translateN) { - scope.$watch(attrs.translateN, update); + scope.$watch(attrs.translateN, function () { + update(interpolationContext); + }); } - scope.$on('gettextLanguageChanged', update); + /** + * @ngdoc event + * @name translate#gettextLanguageChanged + * @eventType listen on scope + * @description Listens for language updates and changes translation accordingly + */ + scope.$on('gettextLanguageChanged', function () { + update(interpolationContext); + }); - update(); - linking = false; } }; } }; }]); +/** + * @ngdoc factory + * @module gettext + * @name gettextFallbackLanguage + * @param {String} langCode language code + * @returns {String|Null} fallback language + * @description Strips regional code and returns language code only + * + * Example + * ```js + * gettextFallbackLanguage('ru'); // "null" + * gettextFallbackLanguage('en_GB'); // "en" + * gettextFallbackLanguage(); // null + * ``` + */ +angular.module("gettext").factory("gettextFallbackLanguage", function () { + var cache = {}; + var pattern = /([^_]+)_[^_]+$/; + + return function (langCode) { + if (cache[langCode]) { + return cache[langCode]; + } + + var matches = pattern.exec(langCode); + if (matches) { + cache[langCode] = matches[1]; + return matches[1]; + } + + return null; + }; +}); +/** + * @ngdoc filter + * @module gettext + * @name translate + * @requires gettextCatalog + * @param {String} input translation key + * @param {String} context context to evaluate key against + * @returns {String} translated string or annotated key + * @see {@link doc:context Verb, Noun} + * @description Takes key and returns string + * + * Sometimes it's not an option to use an attribute (e.g. when you want to annotate an attribute value). + * There's a `translate` filter available for this purpose. + * + * ```html + * + * ``` + * This filter does not support plural strings. + * + * You may want to use {@link guide:custom-annotations custom annotations} to avoid using the `translate` filter all the time. * Is + */ angular.module('gettext').filter('translate', ["gettextCatalog", function (gettextCatalog) { function filter(input, context) { return gettextCatalog.getString(input, null, context); @@ -238,8 +591,12 @@ // Do not edit this file, it is autogenerated using genplurals.py! angular.module("gettext").factory("gettextPlurals", function () { + var languageCodes = { + "pt_BR": "pt_BR", + "pt-BR": "pt_BR" + }; return function (langCode, n) { - switch (langCode) { + switch (getLanguageCode(langCode)) { case "ay": // Aymará case "bo": // Tibetan case "cgg": // Chiga @@ -348,5 +705,116 @@ default: // Everything else return n != 1 ? 1 : 0; } + }; + + /** + * Method extracts iso639-2 language code from code with locale e.g. pl_PL, en_US, etc. + * If it's provided with standalone iso639-2 language code it simply returns it. + * @param {String} langCode + * @returns {String} iso639-2 language Code + */ + function getLanguageCode(langCode) { + if (!languageCodes[langCode]) { + languageCodes[langCode] = langCode.split(/\-|_/).shift(); + } + return languageCodes[langCode]; } }); + +/** + * @ngdoc factory + * @module gettext + * @name gettextUtil + * @description Utility service for common operations and polyfills. + */ +angular.module('gettext').factory('gettextUtil', function gettextUtil() { + /** + * @ngdoc method + * @name gettextUtil#trim + * @public + * @param {string} value String to be trimmed. + * @description Trim polyfill for old browsers (instead of jQuery). Based on AngularJS-v1.2.2 (angular.js#620). + * + * Example + * ```js + * gettextUtil.assert(' no blanks '); // "no blanks" + * ``` + */ + var trim = (function () { + if (!String.prototype.trim) { + return function (value) { + return (typeof value === 'string') ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; + }; + } + return function (value) { + return (typeof value === 'string') ? value.trim() : value; + }; + })(); + + /** + * @ngdoc method + * @name gettextUtil#assert + * @public + * @param {bool} condition condition to check + * @param {String} missing name of the directive missing attribute + * @param {String} found name of attribute that has been used with directive + * @description Throws error if condition is not met, which means that directive was used with certain parameter + * that requires another one (which is missing). + * + * Example + * ```js + * gettextUtil.assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); + * //You should add a translate-n attribute whenever you add a translate-plural attribute. + * ``` + */ + function assert(condition, missing, found) { + if (!condition) { + throw new Error('You should add a ' + missing + ' attribute whenever you add a ' + found + ' attribute.'); + } + } + + /** + * @ngdoc method + * @name gettextUtil#startsWith + * @public + * @param {string} target String on which checking will occur. + * @param {string} query String expected to be at the beginning of target. + * @returns {boolean} Returns true if object has no ownProperties. For arrays returns true if length == 0. + * @description Checks if string starts with another string. + * + * Example + * ```js + * gettextUtil.startsWith('Home sweet home.', 'Home'); //true + * gettextUtil.startsWith('Home sweet home.', 'sweet'); //false + * ``` + */ + function startsWith(target, query) { + return target.indexOf(query) === 0; + } + + /** + * @ngdoc method + * @name gettextUtil#lcFirst + * @public + * @param {string} target String to transform. + * @returns {string} Strings beginning with lowercase letter. + * @description Makes first letter of the string lower case + * + * Example + * ```js + * gettextUtil.lcFirst('Home Sweet Home.'); //'home Sweet Home' + * gettextUtil.lcFirst('ShouldBeCamelCase.'); //'shouldBeCamelCase' + * ``` + */ + function lcFirst(target) { + var first = target.charAt(0).toLowerCase(); + return first + target.substr(1); + } + + return { + trim: trim, + assert: assert, + startsWith: startsWith, + lcFirst: lcFirst + }; +}); diff -Nru libjs-angular-gettext-2.1.0/dist/angular-gettext.min.js libjs-angular-gettext-2.3.8/dist/angular-gettext.min.js --- libjs-angular-gettext-2.1.0/dist/angular-gettext.min.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/dist/angular-gettext.min.js 2016-09-20 07:58:56.000000000 +0000 @@ -1 +1 @@ -angular.module("gettext",[]),angular.module("gettext").constant("gettext",function(a){return a}),angular.module("gettext").factory("gettextCatalog",["gettextPlurals","$http","$cacheFactory","$interpolate","$rootScope",function(a,b,c,d,e){function f(){e.$broadcast("gettextLanguageChanged")}var g,h="$$noContext",i='test',j=angular.element(""+i+"").html()!==i,k=function(a){return g.debug&&g.currentLanguage!==g.baseLanguage?g.debugPrefix+a:a},l=function(a){return g.showTranslatedMarkers?g.translatedMarkerPrefix+a+g.translatedMarkerSuffix:a};return g={debug:!1,debugPrefix:"[MISSING]: ",showTranslatedMarkers:!1,translatedMarkerPrefix:"[",translatedMarkerSuffix:"]",strings:{},baseLanguage:"en",currentLanguage:"en",cache:c("strings"),setCurrentLanguage:function(a){this.currentLanguage=a,f()},getCurrentLanguage:function(){return this.currentLanguage},setStrings:function(a,b){this.strings[a]||(this.strings[a]={});for(var c in b){var d=b[c];if(j&&(c=angular.element(""+c+"").html()),angular.isString(d)||angular.isArray(d)){var e={};e[h]=d,d=e}for(var g in d){var i=d[g];d[g]=angular.isArray(i)?i:[i]}this.strings[a][c]=d}f()},getStringForm:function(a,b,c){var d=this.strings[this.currentLanguage]||{},e=d[a]||{},f=e[c||h]||[];return f[b]},getString:function(a,b,c){return a=this.getStringForm(a,0,c)||k(a),a=b?d(a)(b):a,l(a)},getPlural:function(b,c,e,f,g){var h=a(this.currentLanguage,b);return c=this.getStringForm(c,h,g)||k(1===b?c:e),f&&(f.$count=b,c=d(c)(f)),l(c)},loadRemote:function(a){return b({method:"GET",url:a,cache:g.cache}).success(function(a){for(var b in a)g.setStrings(b,a[b])})}}}]),angular.module("gettext").directive("translate",["gettextCatalog","$parse","$animate","$compile","$window",function(a,b,c,d,e){function f(a,b,c){if(!a)throw new Error("You should add a "+b+" attribute whenever you add a "+c+" attribute.")}var g=function(){return String.prototype.trim?function(a){return"string"==typeof a?a.trim():a}:function(a){return"string"==typeof a?a.replace(/^\s*/,"").replace(/\s*$/,""):a}}(),h=parseInt((/msie (\d+)/.exec(angular.lowercase(e.navigator.userAgent))||[])[1],10);return{restrict:"AE",terminal:!0,compile:function(e,i){f(!i.translatePlural||i.translateN,"translate-n","translate-plural"),f(!i.translateN||i.translatePlural,"translate-plural","translate-n");var j=g(e.html()),k=i.translatePlural,l=i.translateContext;return 8>=h&&""===j.slice(-13)&&(j=j.slice(0,-13)),{post:function(e,f,h){function i(){var b;k?(e=n||(n=e.$new()),e.$count=m(e),b=a.getPlural(e.$count,j,k,null,l)):b=a.getString(j,null,l);var h=f.contents();if(0!==h.length){if(b===g(h.html()))return void(o&&d(h)(e));var i=angular.element(""+b+"");d(i.contents())(e);var p=i.contents();c.enter(p,f),c.leave(h)}}var m=b(h.translateN),n=null,o=!0;h.translateN&&e.$watch(h.translateN,i),e.$on("gettextLanguageChanged",i),i(),o=!1}}}}}]),angular.module("gettext").filter("translate",["gettextCatalog",function(a){function b(b,c){return a.getString(b,null,c)}return b.$stateful=!0,b}]),angular.module("gettext").factory("gettextPlurals",function(){return function(a,b){switch(a){case"ay":case"bo":case"cgg":case"dz":case"fa":case"id":case"ja":case"jbo":case"ka":case"kk":case"km":case"ko":case"ky":case"lo":case"ms":case"my":case"sah":case"su":case"th":case"tt":case"ug":case"vi":case"wo":case"zh":return 0;case"is":return b%10!=1||b%100==11?1:0;case"jv":return 0!=b?1:0;case"mk":return 1==b||b%10==1?0:1;case"ach":case"ak":case"am":case"arn":case"br":case"fil":case"fr":case"gun":case"ln":case"mfe":case"mg":case"mi":case"oc":case"pt_BR":case"tg":case"ti":case"tr":case"uz":case"wa":case"zh":return b>1?1:0;case"lv":return b%10==1&&b%100!=11?0:0!=b?1:2;case"lt":return b%10==1&&b%100!=11?0:b%10>=2&&(10>b%100||b%100>=20)?1:2;case"be":case"bs":case"hr":case"ru":case"sr":case"uk":return b%10==1&&b%100!=11?0:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?1:2;case"mnk":return 0==b?0:1==b?1:2;case"ro":return 1==b?0:0==b||b%100>0&&20>b%100?1:2;case"pl":return 1==b?0:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?1:2;case"cs":case"sk":return 1==b?0:b>=2&&4>=b?1:2;case"sl":return b%100==1?1:b%100==2?2:b%100==3||b%100==4?3:0;case"mt":return 1==b?0:0==b||b%100>1&&11>b%100?1:b%100>10&&20>b%100?2:3;case"gd":return 1==b||11==b?0:2==b||12==b?1:b>2&&20>b?2:3;case"cy":return 1==b?0:2==b?1:8!=b&&11!=b?2:3;case"kw":return 1==b?0:2==b?1:3==b?2:3;case"ga":return 1==b?0:2==b?1:7>b?2:11>b?3:4;case"ar":return 0==b?0:1==b?1:2==b?2:b%100>=3&&10>=b%100?3:b%100>=11?4:5;default:return 1!=b?1:0}}}); \ No newline at end of file +angular.module("gettext",[]),angular.module("gettext").constant("gettext",function(a){return a}),angular.module("gettext").factory("gettextCatalog",["gettextPlurals","gettextFallbackLanguage","$http","$cacheFactory","$interpolate","$rootScope",function(a,b,c,d,e,f){function g(){f.$broadcast("gettextLanguageChanged")}var h,i="$$noContext",j='test',k=angular.element(""+j+"").html()!==j,l=function(a){return h.debug&&h.currentLanguage!==h.baseLanguage?h.debugPrefix+a:a},m=function(a){return h.showTranslatedMarkers?h.translatedMarkerPrefix+a+h.translatedMarkerSuffix:a};return h={debug:!1,debugPrefix:"[MISSING]: ",showTranslatedMarkers:!1,translatedMarkerPrefix:"[",translatedMarkerSuffix:"]",strings:{},baseLanguage:"en",currentLanguage:"en",cache:d("strings"),setCurrentLanguage:function(a){this.currentLanguage=a,g()},getCurrentLanguage:function(){return this.currentLanguage},setStrings:function(b,c){this.strings[b]||(this.strings[b]={});var d=a(b,1);for(var e in c){var f=c[e];if(k&&(e=angular.element(""+e+"").html()),angular.isString(f)||angular.isArray(f)){var h={};h[i]=f,f=h}for(var j in f){var l=f[j];angular.isArray(l)||(f[j]=[],f[j][d]=l)}this.strings[b][e]=f}g()},getStringFormFor:function(b,c,d,e){if(!b)return null;var f=this.strings[b]||{},g=f[c]||{},h=g[e||i]||[];return h[a(b,d)]},getString:function(a,c,d){var f=b(this.currentLanguage);return a=this.getStringFormFor(this.currentLanguage,a,1,d)||this.getStringFormFor(f,a,1,d)||l(a),a=c?e(a)(c):a,m(a)},getPlural:function(a,c,d,f,g){var h=b(this.currentLanguage);return c=this.getStringFormFor(this.currentLanguage,c,a,g)||this.getStringFormFor(h,c,a,g)||l(1===a?c:d),f&&(f.$count=a,c=e(c)(f)),m(c)},loadRemote:function(a){return c({method:"GET",url:a,cache:h.cache}).then(function(a){var b=a.data;for(var c in b)h.setStrings(c,b[c]);return a})}}}]),angular.module("gettext").directive("translate",["gettextCatalog","$parse","$animate","$compile","$window","gettextUtil",function(a,b,c,d,e,f){function g(a){return f.lcFirst(a.replace(j,""))}function h(a,b,c){var d=Object.keys(b).filter(function(a){return f.startsWith(a,j)&&a!==j});if(!d.length)return null;var e=angular.extend({},a),h=[];return d.forEach(function(d){var f=a.$watch(b[d],function(a){var b=g(d);e[b]=a,c(e)});h.push(f)}),a.$on("$destroy",function(){h.forEach(function(a){a()})}),e}var i=parseInt((/msie (\d+)/.exec(angular.lowercase(e.navigator.userAgent))||[])[1],10),j="translateParams";return{restrict:"AE",terminal:!0,compile:function(e,g){f.assert(!g.translatePlural||g.translateN,"translate-n","translate-plural"),f.assert(!g.translateN||g.translatePlural,"translate-plural","translate-n");var j=f.trim(e.html()),k=g.translatePlural,l=g.translateContext;return 8>=i&&""===j.slice(-13)&&(j=j.slice(0,-13)),{post:function(e,g,i){function m(b){b=b||null;var h;k?(e=o||(o=e.$new()),e.$count=n(e),h=a.getPlural(e.$count,j,k,b,l)):h=a.getString(j,b,l);var i=g.contents();if(0!==i.length){if(h===f.trim(i.html()))return void(p&&d(i)(e));var m=angular.element(""+h+"");d(m.contents())(e);var q=m.contents();c.enter(q,g),c.leave(i)}}var n=b(i.translateN),o=null,p=!0,q=h(e,i,m);m(q),p=!1,i.translateN&&e.$watch(i.translateN,function(){m(q)}),e.$on("gettextLanguageChanged",function(){m(q)})}}}}}]),angular.module("gettext").factory("gettextFallbackLanguage",function(){var a={},b=/([^_]+)_[^_]+$/;return function(c){if(a[c])return a[c];var d=b.exec(c);return d?(a[c]=d[1],d[1]):null}}),angular.module("gettext").filter("translate",["gettextCatalog",function(a){function b(b,c){return a.getString(b,null,c)}return b.$stateful=!0,b}]),angular.module("gettext").factory("gettextPlurals",function(){function a(a){return b[a]||(b[a]=a.split(/\-|_/).shift()),b[a]}var b={pt_BR:"pt_BR","pt-BR":"pt_BR"};return function(b,c){switch(a(b)){case"ay":case"bo":case"cgg":case"dz":case"fa":case"id":case"ja":case"jbo":case"ka":case"kk":case"km":case"ko":case"ky":case"lo":case"ms":case"my":case"sah":case"su":case"th":case"tt":case"ug":case"vi":case"wo":case"zh":return 0;case"is":return c%10!=1||c%100==11?1:0;case"jv":return 0!=c?1:0;case"mk":return 1==c||c%10==1?0:1;case"ach":case"ak":case"am":case"arn":case"br":case"fil":case"fr":case"gun":case"ln":case"mfe":case"mg":case"mi":case"oc":case"pt_BR":case"tg":case"ti":case"tr":case"uz":case"wa":case"zh":return c>1?1:0;case"lv":return c%10==1&&c%100!=11?0:0!=c?1:2;case"lt":return c%10==1&&c%100!=11?0:c%10>=2&&(10>c%100||c%100>=20)?1:2;case"be":case"bs":case"hr":case"ru":case"sr":case"uk":return c%10==1&&c%100!=11?0:c%10>=2&&4>=c%10&&(10>c%100||c%100>=20)?1:2;case"mnk":return 0==c?0:1==c?1:2;case"ro":return 1==c?0:0==c||c%100>0&&20>c%100?1:2;case"pl":return 1==c?0:c%10>=2&&4>=c%10&&(10>c%100||c%100>=20)?1:2;case"cs":case"sk":return 1==c?0:c>=2&&4>=c?1:2;case"sl":return c%100==1?1:c%100==2?2:c%100==3||c%100==4?3:0;case"mt":return 1==c?0:0==c||c%100>1&&11>c%100?1:c%100>10&&20>c%100?2:3;case"gd":return 1==c||11==c?0:2==c||12==c?1:c>2&&20>c?2:3;case"cy":return 1==c?0:2==c?1:8!=c&&11!=c?2:3;case"kw":return 1==c?0:2==c?1:3==c?2:3;case"ga":return 1==c?0:2==c?1:7>c?2:11>c?3:4;case"ar":return 0==c?0:1==c?1:2==c?2:c%100>=3&&10>=c%100?3:c%100>=11?4:5;default:return 1!=c?1:0}}}),angular.module("gettext").factory("gettextUtil",function(){function a(a,b,c){if(!a)throw new Error("You should add a "+b+" attribute whenever you add a "+c+" attribute.")}function b(a,b){return 0===a.indexOf(b)}function c(a){var b=a.charAt(0).toLowerCase();return b+a.substr(1)}var d=function(){return String.prototype.trim?function(a){return"string"==typeof a?a.trim():a}:function(a){return"string"==typeof a?a.replace(/^\s*/,"").replace(/\s*$/,""):a}}();return{trim:d,assert:a,startsWith:b,lcFirst:c}}); \ No newline at end of file diff -Nru libjs-angular-gettext-2.1.0/docs/api/index.ngdoc libjs-angular-gettext-2.3.8/docs/api/index.ngdoc --- libjs-angular-gettext-2.1.0/docs/api/index.ngdoc 1970-01-01 00:00:00.000000000 +0000 +++ libjs-angular-gettext-2.3.8/docs/api/index.ngdoc 2016-09-20 07:58:56.000000000 +0000 @@ -0,0 +1,45 @@ +@ngdoc overview +@name angular-gettext +@area api +@id api-index +@description Super-simple translation support for Angular.JS + +## Effortless translations +Angular-gettext let's you focus on developing your application. +Just write everything in English and annotate which parts should be translated. The tools do the rest. + +Marking a string as translatable is as simple as adding an attribute: +```html +Home +``` + +No need to maintain translation codes or magic values, that just causes headaches! + +## Seamless Angular.JS integration +Translating your application doesn't mean you have to give up any of the good stuff that Angular.JS provides. +Interpolation and everything else we love and are used to just keep on working: + +```html +Hello {{name}} +``` + +And with a minified footprint of less than 4kb, you don't have to worry about the size of your application. +Add gzip compression and it amounts to less than 1.5kb. + +## Correct plurals in all languages +Not every language works like English. Did you know that Polish uses three plural forms? Or that Irish uses five? No worries, `angular-gettext` handles all of this for you. Just provide a plural string where needed. + +```html +1 new message +``` + +The span above will always show a correctly pluralized message, even if the language uses wildly different pluralization rules. + +A full list of supported languages (over 130) can be found [here](http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms). + +## Rich tool support +The widely used [gettext](http://en.wikipedia.org/wiki/Gettext) format is used in `angular-gettext` (hence the name). +This means you can use widely established translation tools like [Poedit](http://www.poedit.net/). +Or you can use an online translation platform like [Pootle](http://pootle.translatehouse.org/), [Transifex](https://www.transifex.com/), or [Zanata](http://www.zanata.org/). + +The upside of this? Non-technical users can help out by translating. Or you can use professional translation services. Better results, faster. \ No newline at end of file diff -Nru libjs-angular-gettext-2.1.0/.gitignore libjs-angular-gettext-2.3.8/.gitignore --- libjs-angular-gettext-2.1.0/.gitignore 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/.gitignore 2016-09-20 07:58:56.000000000 +0000 @@ -6,3 +6,4 @@ /e2e-results.xml /.idea *.iml +.DS_Store \ No newline at end of file diff -Nru libjs-angular-gettext-2.1.0/Gruntfile.js libjs-angular-gettext-2.3.8/Gruntfile.js --- libjs-angular-gettext-2.1.0/Gruntfile.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/Gruntfile.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,4 +1,7 @@ +var serveStatic = require("serve-static"); + module.exports = function (grunt) { + grunt.loadNpmTasks("dgeni-alive"); grunt.loadNpmTasks("grunt-bump"); grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-concat"); @@ -9,8 +12,12 @@ grunt.loadNpmTasks("grunt-jscs"); grunt.loadNpmTasks("grunt-karma"); grunt.loadNpmTasks("grunt-ng-annotate"); + grunt.loadNpmTasks("grunt-protractor-runner"); + grunt.loadNpmTasks("grunt-shell"); grunt.initConfig({ + pkg: grunt.file.readJSON("package.json"), + jshint: { all: ["Gruntfile.js", "{src,test}/**/*.js", "!src/plural.js"], options: { @@ -55,7 +62,7 @@ }, all: { files: ["src/**.js", "test/*/*"], - tasks: ["build", "karma:unit:run", "karma:unit_nojquery:run", "karma:e2e:run"] + tasks: ["build", "karma:unit:run", "karma:unit_nojquery:run", "protractor:dev"] }, unit: { files: ["src/**.js", "test/unit/*"], @@ -63,7 +70,7 @@ }, e2e: { files: ["src/**.js", "test/{e2e,fixtures}/*"], - tasks: ["build", "karma:e2e:run"] + tasks: ["build", "protractor:dev"] } }, @@ -80,10 +87,8 @@ options: { port: 9000, hostname: "0.0.0.0", - middleware: function (connect) { - return [ - connect["static"](__dirname) - ]; + middleware: function () { + return [serveStatic(__dirname)]; } } } @@ -104,32 +109,34 @@ configFile: "test/configs/unit.conf.js", browsers: ["Firefox", "PhantomJS"], singleRun: true, - reporters: ["dots", "junit"], - junitReporter: { - outputFile: "unit-results.xml" - } + reporters: ["dots"] }, unitci_nojquery: { configFile: "test/configs/unit-nojquery.conf.js", browsers: ["Firefox", "PhantomJS"], singleRun: true, - reporters: ["dots", "junit"], - junitReporter: { - outputFile: "unit-results.xml" - } + reporters: ["dots"] + } + }, + + protractor: { + options: { + noColor: false, + configFile: "test/configs/e2e.conf.js" }, - e2e: { - configFile: "test/configs/e2e.conf.js", - browsers: ["PhantomJS"], - background: true + dev: { + options: { + keepAlive: true, + args: { + directConnect: true + } + } }, - e2eci: { - configFile: "test/configs/e2e.conf.js", - browsers: ["Firefox", "PhantomJS"], - singleRun: true, - reporters: ["dots", "junit"], - junitReporter: { - outputFile: "e2e-results.xml" + ci: { + options: { + args: { + browser: "firefox" + } } } }, @@ -140,13 +147,41 @@ commitFiles: ["-a"], pushTo: "origin" } + }, + + shell: { + protractor_update: { + command: "./node_modules/.bin/webdriver-manager update", + options: { + stdout: true + } + } + }, + + "dgeni-alive": { + options: { + serve: { + port: "10000", + openBrowser: true + } + }, + api: { + title: "<%= pkg.title %>", + version: "<%= pkg.version %>", + expand: false, + src: [ + "src/**/*.js", + "docs/**/*.ngdoc" + ], + dest: "dist/docs" + } } }); grunt.registerTask("default", ["test"]); grunt.registerTask("build", ["clean", "jshint", "jscs", "concat", "ngAnnotate", "uglify"]); - grunt.registerTask("test", ["build", "connect:e2e", "karma:unit", "karma:unit_nojquery", "karma:e2e", "watch:all"]); - grunt.registerTask("test_unit", ["build", "karma:unit", "karma:unit_nojquery", "watch:unit"]); - grunt.registerTask("test_e2e", ["build", "connect:e2e", "karma:e2e", "watch:e2e"]); - grunt.registerTask("ci", ["build", "karma:unitci", "karma:unitci_nojquery", "connect:e2e", "karma:e2eci"]); + grunt.registerTask("test", ["build", "shell:protractor_update", "connect:e2e", "karma:unit", "karma:unit_nojquery", "protractor:dev", "watch:all"]); + grunt.registerTask("test_unit", ["build", "shell:protractor_update", "karma:unit", "karma:unit_nojquery", "watch:unit"]); + grunt.registerTask("test_e2e", ["build", "shell:protractor_update", "connect:e2e", "protractor:dev", "watch:e2e"]); + grunt.registerTask("ci", ["build", "shell:protractor_update", "karma:unitci", "karma:unitci_nojquery", "connect:e2e", "protractor:ci"]); }; diff -Nru libjs-angular-gettext-2.1.0/index.js libjs-angular-gettext-2.3.8/index.js --- libjs-angular-gettext-2.1.0/index.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-angular-gettext-2.3.8/index.js 2016-09-20 07:58:56.000000000 +0000 @@ -0,0 +1,2 @@ +require('./dist/angular-gettext.js'); +module.exports = 'gettext'; diff -Nru libjs-angular-gettext-2.1.0/.jshintrc libjs-angular-gettext-2.3.8/.jshintrc --- libjs-angular-gettext-2.1.0/.jshintrc 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/.jshintrc 2016-09-20 07:58:56.000000000 +0000 @@ -22,6 +22,7 @@ "before", "beforeEach", "browser", + "by", "describe", "element", "expect", diff -Nru libjs-angular-gettext-2.1.0/package.json libjs-angular-gettext-2.3.8/package.json --- libjs-angular-gettext-2.1.0/package.json 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/package.json 2016-09-20 07:58:56.000000000 +0000 @@ -1,8 +1,9 @@ { "name": "angular-gettext", - "version": "2.1.0", + "version": "2.3.8", + "title": "Angular Gettext", "description": "Gettext support for Angular.js", - "main": "dist/angular-gettext.js", + "main": "index.js", "directories": { "test": "test" }, @@ -22,24 +23,29 @@ "homepage": "http://angular-gettext.rocketeer.be/", "license": "MIT", "devDependencies": { - "grunt": "~0.4.1", - "grunt-bump": "0.0.13", - "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-connect": "~0.7.1", - "grunt-contrib-jshint": "~0.10.0", - "grunt-contrib-uglify": "~0.4.0", - "grunt-contrib-watch": "~0.5.1", - "grunt-jscs": "^0.6.2", - "grunt-karma": "~0.8.3", - "grunt-ng-annotate": "^0.3.2", - "karma": "~0.12.16", + "chai": "^3.5.0", + "dgeni-alive": "~0.2.1", + "grunt": "~1.0.1", + "grunt-bump": "0.8.0", + "grunt-contrib-clean": "~1.0.0", + "grunt-contrib-concat": "~1.0.0", + "grunt-contrib-connect": "~1.0.0", + "grunt-contrib-jshint": "~1.0.0", + "grunt-contrib-uglify": "~1.0.0", + "grunt-contrib-watch": "~1.0.0", + "grunt-jscs": "^3.0.0", + "grunt-karma": "~2.0.0", + "grunt-ng-annotate": "^2.0.2", + "grunt-protractor-runner": "^3.2.0", + "grunt-shell": "^1.3.0", + "karma": "~0.13.22", "karma-chai": "~0.1.0", - "karma-firefox-launcher": "~0.1.0", - "karma-junit-reporter": "~0.2.2", - "karma-mocha": "~0.1.0", - "karma-ng-scenario": "~0.1.0", - "karma-phantomjs-launcher": "^0.1.4" + "karma-firefox-launcher": "~1.0.0", + "karma-mocha": "~1.0.1", + "karma-phantomjs-launcher": "^1.0.0", + "mocha": "^2.5.3", + "phantomjs-prebuilt": "^2.1.7", + "serve-static": "^1.11.1" }, "repository": { "type": "git", diff -Nru libjs-angular-gettext-2.1.0/src/catalog.js libjs-angular-gettext-2.3.8/src/catalog.js --- libjs-angular-gettext-2.1.0/src/catalog.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/catalog.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,4 +1,16 @@ -angular.module('gettext').factory('gettextCatalog', function (gettextPlurals, $http, $cacheFactory, $interpolate, $rootScope) { +/** + * @ngdoc service + * @module gettext + * @name gettextCatalog + * @requires gettextPlurals + * @requires gettextFallbackLanguage + * @requires https://docs.angularjs.org/api/ng/service/$http $http + * @requires https://docs.angularjs.org/api/ng/service/$cacheFactory $cacheFactory + * @requires https://docs.angularjs.org/api/ng/service/$interpolate $interpolate + * @requires https://docs.angularjs.org/api/ng/service/$rootScope $rootScope + * @description Provides set of method to translate stings + */ +angular.module('gettext').factory('gettextCatalog', function (gettextPlurals, gettextFallbackLanguage, $http, $cacheFactory, $interpolate, $rootScope) { var catalog; var noContext = '$$noContext'; @@ -26,34 +38,134 @@ }; function broadcastUpdated() { + /** + * @ngdoc event + * @name gettextCatalog#gettextLanguageChanged + * @eventType broadcast on $rootScope + * @description Fires language change notification without any additional parameters. + */ $rootScope.$broadcast('gettextLanguageChanged'); } catalog = { + /** + * @ngdoc property + * @name gettextCatalog#debug + * @public + * @type {Boolean} false + * @see gettextCatalog#debug + * @description Whether or not to prefix untranslated strings with `[MISSING]:` or a custom prefix. + */ debug: false, + /** + * @ngdoc property + * @name gettextCatalog#debugPrefix + * @public + * @type {String} [MISSING]: + * @description Custom prefix for untranslated strings when {@link gettextCatalog#debug gettextCatalog#debug} set to `true`. + */ debugPrefix: '[MISSING]: ', + /** + * @ngdoc property + * @name gettextCatalog#showTranslatedMarkers + * @public + * @type {Boolean} false + * @description Whether or not to wrap all processed text with markers. + * + * Example output: `[Welcome]` + */ showTranslatedMarkers: false, + /** + * @ngdoc property + * @name gettextCatalog#translatedMarkerPrefix + * @public + * @type {String} [ + * @description Custom prefix to mark strings that have been run through {@link angular-gettext angular-gettext}. + */ translatedMarkerPrefix: '[', + /** + * @ngdoc property + * @name gettextCatalog#translatedMarkerSuffix + * @public + * @type {String} ] + * @description Custom suffix to mark strings that have been run through {@link angular-gettext angular-gettext}. + */ translatedMarkerSuffix: ']', + /** + * @ngdoc property + * @name gettextCatalog#strings + * @private + * @type {Object} + * @description An object of loaded translation strings. Shouldn't be used directly. + */ strings: {}, + /** + * @ngdoc property + * @name gettextCatalog#baseLanguage + * @protected + * @deprecated + * @since 2.0 + * @type {String} en + * @description The default language, in which you're application is written. + * + * This defaults to English and it's generally a bad idea to use anything else: + * if your language has different pluralization rules you'll end up with incorrect translations. + */ baseLanguage: 'en', + /** + * @ngdoc property + * @name gettextCatalog#currentLanguage + * @public + * @type {String} + * @description Active language. + */ currentLanguage: 'en', + /** + * @ngdoc property + * @name gettextCatalog#cache + * @public + * @type {String} en + * @description Language cache for lazy load + */ cache: $cacheFactory('strings'), + /** + * @ngdoc method + * @name gettextCatalog#setCurrentLanguage + * @public + * @param {String} lang language name + * @description Sets the current language and makes sure that all translations get updated correctly. + */ setCurrentLanguage: function (lang) { this.currentLanguage = lang; broadcastUpdated(); }, + /** + * @ngdoc method + * @name gettextCatalog#getCurrentLanguage + * @public + * @returns {String} current language + * @description Returns the current language. + */ getCurrentLanguage: function () { return this.currentLanguage; }, + /** + * @ngdoc method + * @name gettextCatalog#setStrings + * @public + * @param {String} language language name + * @param {Object.} strings set of strings where the key is the translation `key` and `value` is the translated text + * @description Processes an object of string definitions. {@link guide:manual-setstrings More details here}. + */ setStrings: function (language, strings) { if (!this.strings[language]) { this.strings[language] = {}; } + var defaultPlural = gettextPlurals(language, 1); for (var key in strings) { var val = strings[key]; @@ -72,7 +184,10 @@ // Expand single strings for each context. for (var context in val) { var str = val[context]; - val[context] = angular.isArray(str) ? str : [str]; + if (!angular.isArray(str)) { + val[context] = []; + val[context][defaultPlural] = str; + } } this.strings[language][key] = val; } @@ -80,22 +195,73 @@ broadcastUpdated(); }, - getStringForm: function (string, n, context) { - var stringTable = this.strings[this.currentLanguage] || {}; + /** + * @ngdoc method + * @name gettextCatalog#getStringFormFor + * @protected + * @param {String} language language name + * @param {String} string translation key + * @param {Number=} n number to build sting form for + * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} + * @returns {String|Null} translated or annotated string or null if language is not set + * @description Translate a string with the given language, count and context. + */ + getStringFormFor: function (language, string, n, context) { + if (!language) { + return null; + } + var stringTable = this.strings[language] || {}; var contexts = stringTable[string] || {}; var plurals = contexts[context || noContext] || []; - return plurals[n]; + return plurals[gettextPlurals(language, n)]; }, + /** + * @ngdoc method + * @name gettextCatalog#getString + * @public + * @param {String} string translation key + * @param {$rootScope.Scope=} scope scope to do interpolation against + * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} + * @returns {String} translated or annotated string + * @description Translate a string with the given scope and context. + * + * First it tries {@link gettextCatalog#currentLanguage gettextCatalog#currentLanguage} (e.g. `en-US`) then {@link gettextFallbackLanguage fallback} (e.g. `en`). + * + * When `scope` is supplied it uses Angular.JS interpolation, so something like this will do what you expect: + * ```js + * var hello = gettextCatalog.getString("Hello {{name}}!", { name: "Ruben" }); + * // var hello will be "Hallo Ruben!" in Dutch. + * ``` + * Avoid using scopes - this skips interpolation and is a lot faster. + */ getString: function (string, scope, context) { - string = this.getStringForm(string, 0, context) || prefixDebug(string); + var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage); + string = this.getStringFormFor(this.currentLanguage, string, 1, context) || + this.getStringFormFor(fallbackLanguage, string, 1, context) || + prefixDebug(string); string = scope ? $interpolate(string)(scope) : string; return addTranslatedMarkers(string); }, + /** + * @ngdoc method + * @name gettextCatalog#getPlural + * @public + * @param {Number} n number to build sting form for + * @param {String} string translation key + * @param {String} stringPlural plural translation key + * @param {$rootScope.Scope=} scope scope to do interpolation against + * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} + * @returns {String} translated or annotated string + * @see {@link gettextCatalog#getString gettextCatalog#getString} for details + * @description Translate a plural string with the given context. + */ getPlural: function (n, string, stringPlural, scope, context) { - var form = gettextPlurals(this.currentLanguage, n); - string = this.getStringForm(string, form, context) || prefixDebug(n === 1 ? string : stringPlural); + var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage); + string = this.getStringFormFor(this.currentLanguage, string, n, context) || + this.getStringFormFor(fallbackLanguage, string, n, context) || + prefixDebug(n === 1 ? string : stringPlural); if (scope) { scope.$count = n; string = $interpolate(string)(scope); @@ -103,15 +269,27 @@ return addTranslatedMarkers(string); }, + /** + * @ngdoc method + * @name gettextCatalog#loadRemote + * @public + * @param {String} url location of the translations + * @description Load a set of translation strings from a given URL. + * + * This should be a JSON catalog generated with [angular-gettext-tools](https://github.com/rubenv/angular-gettext-tools). + * {@link guide:lazy-loading More details here}. + */ loadRemote: function (url) { return $http({ method: 'GET', url: url, cache: catalog.cache - }).success(function (data) { + }).then(function (response) { + var data = response.data; for (var lang in data) { catalog.setStrings(lang, data[lang]); } + return response; }); } }; diff -Nru libjs-angular-gettext-2.1.0/src/directive.js libjs-angular-gettext-2.3.8/src/directive.js --- libjs-angular-gettext-2.1.0/src/directive.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/directive.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,34 +1,102 @@ -angular.module('gettext').directive('translate', function (gettextCatalog, $parse, $animate, $compile, $window) { - // Trim polyfill for old browsers (instead of jQuery) - // Based on AngularJS-v1.2.2 (angular.js#620) - var trim = (function () { - if (!String.prototype.trim) { - return function (value) { - return (typeof value === 'string') ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; - }; - } - return function (value) { - return (typeof value === 'string') ? value.trim() : value; - }; - })(); - - function assert(condition, missing, found) { - if (!condition) { - throw new Error('You should add a ' + missing + ' attribute whenever you add a ' + found + ' attribute.'); - } +/** + * @ngdoc directive + * @module gettext + * @name translate + * @requires gettextCatalog + * @requires gettextUtil + * @requires https://docs.angularjs.org/api/ng/service/$parse $parse + * @requires https://docs.angularjs.org/api/ng/service/$animate $animate + * @requires https://docs.angularjs.org/api/ng/service/$compile $compile + * @requires https://docs.angularjs.org/api/ng/service/$window $window + * @restrict AE + * @param {String} [translatePlural] plural form + * @param {Number} translateN value to watch to substitute correct plural form + * @param {String} translateContext context value, e.g. {@link doc:context Verb, Noun} + * @description Annotates and translates text inside directive + * + * Full interpolation support is available in translated strings, so the following will work as expected: + * ```js + *
Hello {{name}}!
+ * ``` + * + * You can also use custom context parameters while interpolating. This approach allows usage + * of angular filters as well as custom logic inside your translated messages without unnecessary impact on translations. + * + * So for example when you have message like this: + * ```js + *
Last modified {{modificationDate | date:'yyyy-MM-dd HH:mm:ss Z'}} by {{author}}.
+ * ``` + * you will have it extracted in exact same version so it would look like this: + * `Last modified {{modificationDate | date:'yyyy-MM-dd HH:mm:ss Z'}} by {{author}}`. + * To start with it might be too complicated to read and handle by non technical translator. It's easy to make mistake + * when copying format for example. Secondly if you decide to change format by some point of the project translation will broke + * as it won't be the same string anymore. + * + * Instead your translator should only be concerned to place {{modificationDate}} correctly and you should have a free hand + * to modify implementation details on how to present the results. This is how you can achieve the goal: + * ```js + *
Last modified {{modificationDate}} by {{author}}.
+ * ``` + * + * There's a few more things worth to point out: + * 1. You can use as many parameters as you want. Each parameter begins with `translate-params-` followed by snake-case parameter name. + * Each parameter will be available for interpolation in camelCase manner (just like angular directive works by default). + * ```js + *
Param {{myCustomParam}} has been changed by {{name}}.
+ * ``` + * 2. You can rename your variables from current scope to simple ones if you like. + * ```js + *
Today's date is: {{date}}.
+ * ``` + * 3. You can use translate-params only for some interpolations. Rest would be treated as usual. + * ```js + *
This product: {{product}} costs {{cost}}.
+ * ``` + */ +angular.module('gettext').directive('translate', function (gettextCatalog, $parse, $animate, $compile, $window, gettextUtil) { + var msie = parseInt((/msie (\d+)/.exec(angular.lowercase($window.navigator.userAgent)) || [])[1], 10); + var PARAMS_PREFIX = 'translateParams'; + + function getCtxAttr(key) { + return gettextUtil.lcFirst(key.replace(PARAMS_PREFIX, '')); } - var msie = parseInt((/msie (\d+)/.exec(angular.lowercase($window.navigator.userAgent)) || [])[1], 10); + function handleInterpolationContext(scope, attrs, update) { + var attributes = Object.keys(attrs).filter(function (key) { + return gettextUtil.startsWith(key, PARAMS_PREFIX) && key !== PARAMS_PREFIX; + }); + + if (!attributes.length) { + return null; + } + + var interpolationContext = angular.extend({}, scope); + var unwatchers = []; + attributes.forEach(function (attribute) { + var unwatch = scope.$watch(attrs[attribute], function (newVal) { + var key = getCtxAttr(attribute); + interpolationContext[key] = newVal; + update(interpolationContext); + }); + unwatchers.push(unwatch); + }); + scope.$on('$destroy', function () { + unwatchers.forEach(function (unwatch) { + unwatch(); + }); + }); + return interpolationContext; + } return { restrict: 'AE', terminal: true, compile: function compile(element, attrs) { // Validate attributes - assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); - assert(!attrs.translateN || attrs.translatePlural, 'translate-plural', 'translate-n'); + gettextUtil.assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); + gettextUtil.assert(!attrs.translateN || attrs.translatePlural, 'translate-plural', 'translate-n'); - var msgid = trim(element.html()); + var msgid = gettextUtil.trim(element.html()); var translatePlural = attrs.translatePlural; var translateContext = attrs.translateContext; @@ -46,17 +114,18 @@ var pluralScope = null; var linking = true; - function update() { + function update(interpolationContext) { + interpolationContext = interpolationContext || null; + // Fetch correct translated string. var translated; if (translatePlural) { scope = pluralScope || (pluralScope = scope.$new()); scope.$count = countFn(scope); - translated = gettextCatalog.getPlural(scope.$count, msgid, translatePlural, null, translateContext); + translated = gettextCatalog.getPlural(scope.$count, msgid, translatePlural, interpolationContext, translateContext); } else { - translated = gettextCatalog.getString(msgid, null, translateContext); + translated = gettextCatalog.getString(msgid, interpolationContext, translateContext); } - var oldContents = element.contents(); if (oldContents.length === 0){ @@ -64,7 +133,7 @@ } // Avoid redundant swaps - if (translated === trim(oldContents.html())){ + if (translated === gettextUtil.trim(oldContents.html())){ // Take care of unlinked content if (linking){ $compile(oldContents)(scope); @@ -81,14 +150,26 @@ $animate.leave(oldContents); } + var interpolationContext = handleInterpolationContext(scope, attrs, update); + update(interpolationContext); + linking = false; + if (attrs.translateN) { - scope.$watch(attrs.translateN, update); + scope.$watch(attrs.translateN, function () { + update(interpolationContext); + }); } - scope.$on('gettextLanguageChanged', update); + /** + * @ngdoc event + * @name translate#gettextLanguageChanged + * @eventType listen on scope + * @description Listens for language updates and changes translation accordingly + */ + scope.$on('gettextLanguageChanged', function () { + update(interpolationContext); + }); - update(); - linking = false; } }; } diff -Nru libjs-angular-gettext-2.1.0/src/fallback_language.js libjs-angular-gettext-2.3.8/src/fallback_language.js --- libjs-angular-gettext-2.1.0/src/fallback_language.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/fallback_language.js 2016-09-20 07:58:56.000000000 +0000 @@ -0,0 +1,33 @@ +/** + * @ngdoc factory + * @module gettext + * @name gettextFallbackLanguage + * @param {String} langCode language code + * @returns {String|Null} fallback language + * @description Strips regional code and returns language code only + * + * Example + * ```js + * gettextFallbackLanguage('ru'); // "null" + * gettextFallbackLanguage('en_GB'); // "en" + * gettextFallbackLanguage(); // null + * ``` + */ +angular.module("gettext").factory("gettextFallbackLanguage", function () { + var cache = {}; + var pattern = /([^_]+)_[^_]+$/; + + return function (langCode) { + if (cache[langCode]) { + return cache[langCode]; + } + + var matches = pattern.exec(langCode); + if (matches) { + cache[langCode] = matches[1]; + return matches[1]; + } + + return null; + }; +}); \ No newline at end of file diff -Nru libjs-angular-gettext-2.1.0/src/filter.js libjs-angular-gettext-2.3.8/src/filter.js --- libjs-angular-gettext-2.1.0/src/filter.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/filter.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,3 +1,24 @@ +/** + * @ngdoc filter + * @module gettext + * @name translate + * @requires gettextCatalog + * @param {String} input translation key + * @param {String} context context to evaluate key against + * @returns {String} translated string or annotated key + * @see {@link doc:context Verb, Noun} + * @description Takes key and returns string + * + * Sometimes it's not an option to use an attribute (e.g. when you want to annotate an attribute value). + * There's a `translate` filter available for this purpose. + * + * ```html + * + * ``` + * This filter does not support plural strings. + * + * You may want to use {@link guide:custom-annotations custom annotations} to avoid using the `translate` filter all the time. * Is + */ angular.module('gettext').filter('translate', function (gettextCatalog) { function filter(input, context) { return gettextCatalog.getString(input, null, context); diff -Nru libjs-angular-gettext-2.1.0/src/index.js libjs-angular-gettext-2.3.8/src/index.js --- libjs-angular-gettext-2.1.0/src/index.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/index.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,5 +1,45 @@ +/** + * @ngdoc module + * @name gettext + * @packageName angular-gettext + * @description Super simple Gettext for Angular.JS + * + * A sample application can be found at https://github.com/rubenv/angular-gettext-example. + * This is an adaptation of the [TodoMVC](http://todomvc.com/) example. You can use this as a guideline while adding {@link angular-gettext angular-gettext} to your own application. + */ +/** + * @ngdoc factory + * @module gettext + * @name gettextPlurals + * @param {String} [langCode=en] language code + * @param {Number} [n=0] number to calculate form for + * @returns {Number} plural form number + * @description Provides correct plural form id for the given language + * + * Example + * ```js + * gettextPlurals('ru', 10); // 1 + * gettextPlurals('en', 1); // 0 + * gettextPlurals(); // 1 + * ``` + */ angular.module('gettext', []); - +/** + * @ngdoc object + * @module gettext + * @name gettext + * @kind function + * @param {String} str annotation key + * @description Gettext constant function for annotating strings + * + * ```js + * angular.module('myApp', ['gettext']).config(function(gettext) { + * /// MyApp document title + * gettext('my-app.title'); + * ... + * }) + * ``` + */ angular.module('gettext').constant('gettext', function (str) { /* * Does nothing, simply returns the input string. diff -Nru libjs-angular-gettext-2.1.0/src/plural.js libjs-angular-gettext-2.3.8/src/plural.js --- libjs-angular-gettext-2.1.0/src/plural.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/plural.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,7 +1,11 @@ // Do not edit this file, it is autogenerated using genplurals.py! angular.module("gettext").factory("gettextPlurals", function () { + var languageCodes = { + "pt_BR": "pt_BR", + "pt-BR": "pt_BR" + }; return function (langCode, n) { - switch (langCode) { + switch (getLanguageCode(langCode)) { case "ay": // Aymará case "bo": // Tibetan case "cgg": // Chiga @@ -110,5 +114,18 @@ default: // Everything else return n != 1 ? 1 : 0; } + }; + + /** + * Method extracts iso639-2 language code from code with locale e.g. pl_PL, en_US, etc. + * If it's provided with standalone iso639-2 language code it simply returns it. + * @param {String} langCode + * @returns {String} iso639-2 language Code + */ + function getLanguageCode(langCode) { + if (!languageCodes[langCode]) { + languageCodes[langCode] = langCode.split(/\-|_/).shift(); + } + return languageCodes[langCode]; } }); diff -Nru libjs-angular-gettext-2.1.0/src/util.js libjs-angular-gettext-2.3.8/src/util.js --- libjs-angular-gettext-2.1.0/src/util.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-angular-gettext-2.3.8/src/util.js 2016-09-20 07:58:56.000000000 +0000 @@ -0,0 +1,97 @@ +/** + * @ngdoc factory + * @module gettext + * @name gettextUtil + * @description Utility service for common operations and polyfills. + */ +angular.module('gettext').factory('gettextUtil', function gettextUtil() { + /** + * @ngdoc method + * @name gettextUtil#trim + * @public + * @param {string} value String to be trimmed. + * @description Trim polyfill for old browsers (instead of jQuery). Based on AngularJS-v1.2.2 (angular.js#620). + * + * Example + * ```js + * gettextUtil.assert(' no blanks '); // "no blanks" + * ``` + */ + var trim = (function () { + if (!String.prototype.trim) { + return function (value) { + return (typeof value === 'string') ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; + }; + } + return function (value) { + return (typeof value === 'string') ? value.trim() : value; + }; + })(); + + /** + * @ngdoc method + * @name gettextUtil#assert + * @public + * @param {bool} condition condition to check + * @param {String} missing name of the directive missing attribute + * @param {String} found name of attribute that has been used with directive + * @description Throws error if condition is not met, which means that directive was used with certain parameter + * that requires another one (which is missing). + * + * Example + * ```js + * gettextUtil.assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); + * //You should add a translate-n attribute whenever you add a translate-plural attribute. + * ``` + */ + function assert(condition, missing, found) { + if (!condition) { + throw new Error('You should add a ' + missing + ' attribute whenever you add a ' + found + ' attribute.'); + } + } + + /** + * @ngdoc method + * @name gettextUtil#startsWith + * @public + * @param {string} target String on which checking will occur. + * @param {string} query String expected to be at the beginning of target. + * @returns {boolean} Returns true if object has no ownProperties. For arrays returns true if length == 0. + * @description Checks if string starts with another string. + * + * Example + * ```js + * gettextUtil.startsWith('Home sweet home.', 'Home'); //true + * gettextUtil.startsWith('Home sweet home.', 'sweet'); //false + * ``` + */ + function startsWith(target, query) { + return target.indexOf(query) === 0; + } + + /** + * @ngdoc method + * @name gettextUtil#lcFirst + * @public + * @param {string} target String to transform. + * @returns {string} Strings beginning with lowercase letter. + * @description Makes first letter of the string lower case + * + * Example + * ```js + * gettextUtil.lcFirst('Home Sweet Home.'); //'home Sweet Home' + * gettextUtil.lcFirst('ShouldBeCamelCase.'); //'shouldBeCamelCase' + * ``` + */ + function lcFirst(target) { + var first = target.charAt(0).toLowerCase(); + return first + target.substr(1); + } + + return { + trim: trim, + assert: assert, + startsWith: startsWith, + lcFirst: lcFirst + }; +}); diff -Nru libjs-angular-gettext-2.1.0/test/configs/e2e.conf.js libjs-angular-gettext-2.3.8/test/configs/e2e.conf.js --- libjs-angular-gettext-2.1.0/test/configs/e2e.conf.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/configs/e2e.conf.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,15 +1,3 @@ -module.exports = function (config) { - config.set({ - basePath: "../..", - - frameworks: ["ng-scenario"], - - files: ["test/e2e/**/*.js"], - - urlRoot: "/karma/", - - proxies: { - "/": "http://localhost:9000/" - } - }); +exports.config = { + specs: ["../e2e/**/*.js"] }; diff -Nru libjs-angular-gettext-2.1.0/test/e2e/click.js libjs-angular-gettext-2.3.8/test/e2e/click.js --- libjs-angular-gettext-2.1.0/test/e2e/click.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/e2e/click.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,25 +1,25 @@ var tests = { "Should not break ng-click": function (file) { return function () { - browser().navigateTo("/test/fixtures/click" + file + ".html"); - element("button#test1").click(); - expect(element("#field").text()).toBe("Success"); + browser.get("http://localhost:9000/test/fixtures/click" + file + ".html"); + element(by.css("button#test1")).click(); + expect(element(by.css("#field")).getText()).toBe("Success"); }; }, "Should not break ng-click for translated strings": function (file) { return function () { - browser().navigateTo("/test/fixtures/click" + file + ".html"); - element("button#test2").click(); - expect(element("#field").text()).toBe("Success"); + browser.get("http://localhost:9000/test/fixtures/click" + file + ".html"); + element(by.css("button#test2")).click(); + expect(element(by.css("#field")).getText()).toBe("Success"); }; }, "Should compile ng-click": function (file) { return function () { - browser().navigateTo("/test/fixtures/click" + file + ".html"); - element("#test3 button").click(); - expect(element("#field").text()).toBe("Success"); + browser.get("http://localhost:9000/test/fixtures/click" + file + ".html"); + element(by.css("#test3 button")).click(); + expect(element(by.css("#field")).getText()).toBe("Success"); }; } }; diff -Nru libjs-angular-gettext-2.1.0/test/e2e/xss.js libjs-angular-gettext-2.3.8/test/e2e/xss.js --- libjs-angular-gettext-2.1.0/test/e2e/xss.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/e2e/xss.js 2016-09-20 07:58:56.000000000 +0000 @@ -1,8 +1,8 @@ var tests = { "Should not allow XSS": function (file) { return function () { - browser().navigateTo("/test/fixtures/xss" + file + ".html"); - expect(element("body").text()).not().toBe("fail"); + browser.get("http://localhost:9000/test/fixtures/xss" + file + ".html"); + expect(element(by.css("body")).getText()).not.toBe("fail"); }; } }; diff -Nru libjs-angular-gettext-2.1.0/test/unit/catalog.js libjs-angular-gettext-2.3.8/test/unit/catalog.js --- libjs-angular-gettext-2.1.0/test/unit/catalog.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/unit/catalog.js 2016-09-20 07:58:56.000000000 +0000 @@ -21,6 +21,13 @@ assert.equal(catalog.getString("Hello"), "Hallo"); }); + it("Can set and retrieve strings when default plural is not zero", function () { + var strings = { Hello: "Hallo" }; + catalog.setStrings("ar", strings); + catalog.setCurrentLanguage("ar"); + assert.equal(catalog.getString("Hello"), "Hallo"); + }); + it("Should return original for unknown strings", function () { var strings = { Hello: "Hallo" }; catalog.setStrings("nl", strings); @@ -145,4 +152,22 @@ assert.equal(catalog.getString("Archive", {}, "verb"), "Archiveren"); assert.equal(catalog.getString("Archive", {}, "noun"), "Archief"); }); + + it("Should return string from fallback language if current language has no translation", function () { + var strings = { Hello: "Hallo" }; + catalog.setStrings("nl", strings); + catalog.setCurrentLanguage("nl_NL"); + assert.equal(catalog.getString("Bye"), "Bye"); + assert.equal(catalog.getString("Hello"), "Hallo"); + }); + + it("Should not return string from fallback language if current language has translation", function () { + var stringsEn = { Baggage: "Baggage" }; + var stringsEnGB = { Baggage: "Luggage" }; + catalog.setStrings("en", stringsEn); + catalog.setStrings("en_GB", stringsEnGB); + catalog.setCurrentLanguage("en_GB"); + assert.equal(catalog.getString("Bye"), "Bye"); + assert.equal(catalog.getString("Baggage"), "Luggage"); + }); }); diff -Nru libjs-angular-gettext-2.1.0/test/unit/directive.js libjs-angular-gettext-2.3.8/test/unit/directive.js --- libjs-angular-gettext-2.1.0/test/unit/directive.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/unit/directive.js 2016-09-20 07:58:56.000000000 +0000 @@ -12,12 +12,18 @@ catalog.setStrings("nl", { Hello: "Hallo", "Hello {{name}}!": "Hallo {{name}}!", + "Hello {{author}}!": "Hallo {{author}}!", "One boat": ["Een boot", "{{count}} boten"], Archive: { verb: "Archiveren", noun: "Archief" } }); catalog.setStrings("af", { "This link: {{url}} will have the 'ng-binding' class attached before the translate directive can capture it.": "Die skakel: {{url}} sal die 'ng-binding' klass aangevoeg hê voor die translate directive dit kan vasvat." }); + catalog.setStrings("pl", { + "This product: {{product}} costs {{cost}}.": "Ten produkt: {{product}} kosztuje {{cost}}.", + "This product: {{product}} costs {{cost}}{{currency}}.": "Ten produkt: {{product}} kosztuje {{cost}}{{currency}}.", + "Product of the week: {{product}}.": "Produkt tygodnia: {{product}}." + }); })); it("Should work on empty strings", function () { @@ -182,6 +188,17 @@ assert.equal(el.text(), "Hello"); }); + it("Changing language should translate again not loosing scope", function () { + catalog.setCurrentLanguage("nl"); + $rootScope.providedName = "Ruben"; + var el = $compile("
Hello {{name}}!
")($rootScope); + $rootScope.$digest(); + assert.equal(el.text(), "Hallo Ruben!"); + catalog.setCurrentLanguage("en"); + $rootScope.$digest(); + assert.equal(el.text(), "Hello Ruben!"); + }); + it("Should warn if you forget to add attributes (n)", function () { assert.throws(function () { $compile("
Hello {{name}} (one message)!
")($rootScope); @@ -224,4 +241,179 @@ $rootScope.$digest(); assert.equal(el.text(), "Hallo"); }); + + it("Should translate with context param", function () { + $rootScope.name = "Ernest"; + catalog.setCurrentLanguage("nl"); + var el = $compile("

Hello {{author}}!

")($rootScope); + $rootScope.$digest(); + assert.equal(el.text(), "Hallo Ernest!"); + }); + + it("Should translate with filters used in translate params", function () { + $rootScope.name = "Ernest"; + catalog.setCurrentLanguage("nl"); + var el = $compile("

Hello {{author}}!

")($rootScope); + $rootScope.$digest(); + assert.equal(el.text(), "Hallo ERNEST!"); + }); + + it("Should translate with multiple translate params", function () { + $rootScope.item = "Headphones"; + $rootScope.cost = 5; + catalog.setCurrentLanguage("pl"); + var el = $compile("

This product: {{product}} costs {{cost}}.

")($rootScope); + $rootScope.$digest(); + assert.equal(el.text(), "Ten produkt: HEADPHONES kosztuje $5.00."); + }); + + it("Should translate with multiple translate params along with normal scope interpolation", function () { + $rootScope.item = "Headphones"; + $rootScope.cost = 5; + $rootScope.currency = "$"; + catalog.setCurrentLanguage("pl"); + var el = $compile("

This product: {{product}} costs {{cost}}{{currency}}.

")($rootScope); + $rootScope.$digest(); + assert.equal(el.text(), "Ten produkt: HEADPHONES kosztuje 5$."); + }); + + it("Should update translation with translate params when context changes", function () { + $rootScope.item = "Headphones"; + catalog.setCurrentLanguage("pl"); + var el = $compile("

Product of the week: {{product}}.

")($rootScope); + $rootScope.$digest(); + assert.equal(el.text(), "Produkt tygodnia: HEADPHONES."); + + $rootScope.item = "Smart TV"; + $rootScope.$digest(); + assert.equal(el.text(), "Produkt tygodnia: SMART TV."); + }); + + describe("Translation's with plurals", function () { + var sourceString; + + beforeEach(inject(function () { + sourceString = "Today {{someone}} meets with {{someoneElse}} for {{duration}} minute."; + + catalog.setStrings("pl", { + "Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.": [ + "Dziś {{someone}} spotyka się z {{someoneElse}} przez jedną minutę.", + "Dziś {{someone}} spotyka się z {{someoneElse}} przez {{duration}} minuty.", + "Dziś {{someone}} spotyka się z {{someoneElse}} przez {{duration}} minut." + ] + }); + })); + + it("Should properly handle plural translation for 1", function () { + catalog.setCurrentLanguage("pl"); + $rootScope.someone = "Ruben"; + $rootScope.someoneElse = "Ernest"; + + var el = $compile("

Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.

")($rootScope); + + $rootScope.duration = 1; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez jedną minutę."); + }); + + it("Should properly handle plural translation for 0, 5, 6, 7, ...", function () { + catalog.setCurrentLanguage("pl"); + $rootScope.someone = "Ruben"; + $rootScope.someoneElse = "Ernest"; + + var el = $compile("

Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.

")($rootScope); + + $rootScope.duration = 0; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 0 minut."); + + $rootScope.duration = 5; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 5 minut."); + + $rootScope.duration = 26; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 26 minut."); + }); + + it("Should properly handle plural translation for 2, 3, 4, 22, ...", function () { + catalog.setCurrentLanguage("pl"); + $rootScope.someone = "Ruben"; + $rootScope.someoneElse = "Ernest"; + + var el = $compile("

Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.

")($rootScope); + + $rootScope.duration = 2; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 2 minuty."); + + $rootScope.duration = 3; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 3 minuty."); + + $rootScope.duration = 4; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 4 minuty."); + + $rootScope.duration = 22; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 22 minuty."); + }); + + it("Should properly handle plural translation for 1 with language code that includes locale (e.g. pl_PL, en_US)", function () { + catalog.setCurrentLanguage("pl_PL"); + $rootScope.someone = "Ruben"; + $rootScope.someoneElse = "Ernest"; + + var el = $compile("

Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.

")($rootScope); + + $rootScope.duration = 1; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez jedną minutę."); + }); + + it("Should properly handle plural translation for 0, 5, 6, 7, ... with language code that includes locale (e.g. pl_PL, en_US)", function () { + catalog.setCurrentLanguage("pl_PL"); + $rootScope.someone = "Ruben"; + $rootScope.someoneElse = "Ernest"; + + var el = $compile("

Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.

")($rootScope); + + $rootScope.duration = 0; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 0 minut."); + + $rootScope.duration = 5; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 5 minut."); + + $rootScope.duration = 26; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 26 minut."); + }); + + it("Should properly handle plural translation for 2, 3, 4, 22, ... with language code that includes locale (e.g. pl_PL, en_US)", function () { + catalog.setCurrentLanguage("pl_PL"); + $rootScope.someone = "Ruben"; + $rootScope.someoneElse = "Ernest"; + + var el = $compile("

Today {{someone}} meets with {{someoneElse}} for {{duration}} minute.

")($rootScope); + + $rootScope.duration = 2; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 2 minuty."); + + $rootScope.duration = 3; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 3 minuty."); + + $rootScope.duration = 4; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 4 minuty."); + + $rootScope.duration = 22; + $rootScope.$digest(); + assert.equal(el.text(), "Dziś Ruben spotyka się z Ernest przez 22 minuty."); + }); + }); }); diff -Nru libjs-angular-gettext-2.1.0/test/unit/fallback_language.js libjs-angular-gettext-2.3.8/test/unit/fallback_language.js --- libjs-angular-gettext-2.1.0/test/unit/fallback_language.js 1970-01-01 00:00:00.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/unit/fallback_language.js 2016-09-20 07:58:56.000000000 +0000 @@ -0,0 +1,22 @@ +describe("Fallback languages", function () { + var fallback = null; + + beforeEach(module("gettext")); + + beforeEach(inject(function (gettextFallbackLanguage) { + fallback = gettextFallbackLanguage; + })); + + it("returns base language as fallback", function () { + assert.equal(fallback("en_GB"), "en"); + }); + it("returns null for simple language codes", function () { + assert.isNull(fallback("nl"), null); + }); + it("returns null for null", function () { + assert.isNull(fallback(null)); + }); + it("returns consistent values", function () { + assert.equal(fallback("de_CH"), fallback("de_CH")); + }); +}); diff -Nru libjs-angular-gettext-2.1.0/test/unit/loading.js libjs-angular-gettext-2.3.8/test/unit/loading.js --- libjs-angular-gettext-2.1.0/test/unit/loading.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/unit/loading.js 2016-09-20 07:58:56.000000000 +0000 @@ -64,6 +64,16 @@ assert(failedCalled); }); + it("Exposes the loaded data in the promise", function () { + var responseIsPresent = null; + catalog.loadRemote("/strings/nl.json").then(function (response) { + responseIsPresent = response; + }); + $httpBackend.expectGET("/strings/nl.json").respond(200); + $httpBackend.flush(); + assert(responseIsPresent); + }); + it("Caches strings", function () { catalog.loadRemote("/strings/nl.json"); $httpBackend.expectGET("/strings/nl.json").respond(200, { diff -Nru libjs-angular-gettext-2.1.0/test/unit/plurals.js libjs-angular-gettext-2.3.8/test/unit/plurals.js --- libjs-angular-gettext-2.1.0/test/unit/plurals.js 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/test/unit/plurals.js 2016-09-20 07:58:56.000000000 +0000 @@ -16,6 +16,24 @@ it("Plural form of zero in english is 1", function () { assert.equal(plurals("en", 0), 1); }); + it("Plural form of singular english (locale en_US) is 0", function () { + assert.equal(plurals("en_US", 1), 0); + }); + it("Plural form of plural english (locale en_US) is 1", function () { + assert.equal(plurals("en_US", 2), 1); + }); + it("Plural form of zero in english (locale en_US) is 1", function () { + assert.equal(plurals("en_US", 0), 1); + }); + it("Plural form of singular english (locale en-US) is 0", function () { + assert.equal(plurals("en-US", 1), 0); + }); + it("Plural form of plural english (locale en-US) is 1", function () { + assert.equal(plurals("en-US", 2), 1); + }); + it("Plural form of zero in english (locale en-US) is 1", function () { + assert.equal(plurals("en-US", 0), 1); + }); it("Plural form of singular dutch is 0", function () { assert.equal(plurals("nl", 1), 0); }); @@ -31,7 +49,7 @@ it("Plural form of plural french is 1", function () { assert.equal(plurals("fr", 2), 1); }); - it("Plural form of zero in french is 1", function () { + it("Plural form of zero in french is 0", function () { assert.equal(plurals("fr", 0), 0); }); it("Plural form of 27 in arabic is 4", function () { diff -Nru libjs-angular-gettext-2.1.0/.travis.yml libjs-angular-gettext-2.3.8/.travis.yml --- libjs-angular-gettext-2.1.0/.travis.yml 2015-05-19 08:32:14.000000000 +0000 +++ libjs-angular-gettext-2.3.8/.travis.yml 2016-09-20 07:58:56.000000000 +0000 @@ -1,6 +1,7 @@ language: node_js node_js: - - "0.10" + - "node" +sudo: false before_install: - npm install -g grunt-cli bower - bower install