├── .gitignore ├── .gitreview ├── LICENSE ├── MANIFEST.in ├── README.txt ├── setup.cfg ├── setup.py ├── tox.ini └── xstatic ├── __init__.py └── pkg ├── __init__.py └── angular_gettext ├── __init__.py └── data └── angular-gettext.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.sw? 3 | *.sqlite3 4 | .DS_STORE 5 | *.egg-info 6 | .venv 7 | .tox 8 | build 9 | dist -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/xstatic-angular-gettext.git 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (C) 2013-2015 by Ruben Vermeersch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.txt 2 | recursive-include xstatic * 3 | global-exclude *.pyc 4 | global-exclude *.pyo 5 | global-exclude *.orig 6 | global-exclude *.rej 7 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | XStatic-Angular-Gettext 2 | ----------------------- 3 | 4 | Angular-Gettext javascript library packaged for setuptools (easy_install) / pip. 5 | 6 | This package is intended to be used by **any** project that needs these files. 7 | 8 | It intentionally does **not** provide any extra code except some metadata 9 | **nor** has any extra requirements. You MAY use some minimal support code from 10 | the XStatic base package, if you like. 11 | 12 | You can find more info about the xstatic packaging way in the package `XStatic`. 13 | 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = XStatic-Angular-Gettext 3 | description = Angular-Gettext 2.4.1 (XStatic packaging standard) 4 | description-file = README.rst 5 | maintainer = Radomir Dopieralski 6 | maintainer-email = openstack@sheep.art.pl 7 | home-page = https://angular-gettext.rocketeer.be/ 8 | keywords = angular_gettext xstatic 9 | license = MIT 10 | zip_safe = False 11 | namespace_packages = 12 | xstatic 13 | xstatic.pkg 14 | 15 | [files] 16 | packages = 17 | xstatic 18 | 19 | [bdist_wheel] 20 | universal = True 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from xstatic.pkg import angular_gettext as xs 3 | 4 | # The README.txt file should be written in reST so that PyPI can use 5 | # it to generate your project's PyPI page. 6 | long_description = open('README.txt').read() 7 | 8 | setup( 9 | name=xs.PACKAGE_NAME, 10 | version=xs.PACKAGE_VERSION, 11 | description=xs.DESCRIPTION, 12 | long_description=long_description, 13 | classifiers=xs.CLASSIFIERS, 14 | keywords=xs.KEYWORDS, 15 | maintainer=xs.MAINTAINER, 16 | maintainer_email=xs.MAINTAINER_EMAIL, 17 | license=xs.LICENSE, 18 | url=xs.HOMEPAGE, 19 | platforms=xs.PLATFORMS, 20 | packages=find_packages(), 21 | namespace_packages=['xstatic', 'xstatic.pkg'], 22 | include_package_data=True, 23 | zip_safe=False, 24 | install_requires=[], 25 | ) 26 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 1.6 3 | skipsdist = True 4 | envlist = py27,py34 5 | 6 | [testenv:venv] 7 | basepython = python3 8 | commands = {posargs} 9 | -------------------------------------------------------------------------------- /xstatic/__init__.py: -------------------------------------------------------------------------------- 1 | __import__('pkg_resources').declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /xstatic/pkg/__init__.py: -------------------------------------------------------------------------------- 1 | __import__('pkg_resources').declare_namespace(__name__) 2 | -------------------------------------------------------------------------------- /xstatic/pkg/angular_gettext/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | XStatic resource package 3 | 4 | See package 'XStatic' for documentation and basic tools. 5 | """ 6 | 7 | DISPLAY_NAME = 'Angular-Gettext' # official name, upper/lowercase allowed, no spaces 8 | PACKAGE_NAME = 'XStatic-%s' % DISPLAY_NAME # name used for PyPi 9 | 10 | NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar') 11 | # please use a all-lowercase valid python 12 | # package name 13 | 14 | VERSION = '2.4.1' # version of the packaged files, please use the upstream 15 | # version number 16 | BUILD = '0' # our package build number, so we can release new builds 17 | # with fixes for xstatic stuff. 18 | PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi 19 | 20 | DESCRIPTION = "%s %s (XStatic packaging standard)" % (DISPLAY_NAME, VERSION) 21 | 22 | PLATFORMS = 'any' 23 | CLASSIFIERS = [] 24 | KEYWORDS = '%s xstatic' % NAME 25 | 26 | # XStatic-* package maintainer: 27 | MAINTAINER = 'Radomir Dopieralski' 28 | MAINTAINER_EMAIL = 'openstack@sheep.art.pl' 29 | 30 | # this refers to the project homepage of the stuff we packaged: 31 | HOMEPAGE = 'https://angular-gettext.rocketeer.be/' 32 | 33 | # this refers to all files: 34 | LICENSE = 'MIT' 35 | 36 | from os.path import join, dirname 37 | BASE_DIR = join(dirname(__file__), 'data') 38 | # linux package maintainers just can point to their file locations like this: 39 | #BASE_DIR = '/usr/share/javascript/jquery' 40 | 41 | MAIN='angular-gettext.js' 42 | 43 | LOCATIONS = { 44 | # CDN locations (if no public CDN exists, use an empty dict) 45 | # if value is a string, it is a base location, just append relative 46 | # path/filename. if value is a dict, do another lookup using the 47 | # relative path/filename you want. 48 | # your relative path/filenames should usually be without version 49 | # information, because either the base dir/url is exactly for this 50 | # version or the mapping will care for accessing this version. 51 | } 52 | 53 | -------------------------------------------------------------------------------- /xstatic/pkg/angular_gettext/data/angular-gettext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc module 3 | * @name gettext 4 | * @packageName angular-gettext 5 | * @description Super simple Gettext for Angular.JS 6 | * 7 | * A sample application can be found at https://github.com/rubenv/angular-gettext-example. 8 | * 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. 9 | */ 10 | /** 11 | * @ngdoc factory 12 | * @module gettext 13 | * @name gettextPlurals 14 | * @param {String} [langCode=en] language code 15 | * @param {Number} [n=0] number to calculate form for 16 | * @returns {Number} plural form number 17 | * @description Provides correct plural form id for the given language 18 | * 19 | * Example 20 | * ```js 21 | * gettextPlurals('ru', 10); // 1 22 | * gettextPlurals('en', 1); // 0 23 | * gettextPlurals(); // 1 24 | * ``` 25 | */ 26 | angular.module('gettext', []); 27 | /** 28 | * @ngdoc object 29 | * @module gettext 30 | * @name gettext 31 | * @kind function 32 | * @param {String} str annotation key 33 | * @description Gettext constant function for annotating strings 34 | * 35 | * ```js 36 | * angular.module('myApp', ['gettext']).config(function(gettext) { 37 | * /// MyApp document title 38 | * gettext('my-app.title'); 39 | * ... 40 | * }) 41 | * ``` 42 | */ 43 | angular.module('gettext').constant('gettext', function (str) { 44 | /* 45 | * Does nothing, simply returns the input string. 46 | * 47 | * This function serves as a marker for `grunt-angular-gettext` to know that 48 | * this string should be extracted for translations. 49 | */ 50 | return str; 51 | }); 52 | 53 | /** 54 | * @ngdoc service 55 | * @module gettext 56 | * @name gettextCatalog 57 | * @requires gettextPlurals 58 | * @requires gettextFallbackLanguage 59 | * @requires https://docs.angularjs.org/api/ng/service/$http $http 60 | * @requires https://docs.angularjs.org/api/ng/service/$cacheFactory $cacheFactory 61 | * @requires https://docs.angularjs.org/api/ng/service/$interpolate $interpolate 62 | * @requires https://docs.angularjs.org/api/ng/service/$rootScope $rootScope 63 | * @description Provides set of method to translate strings 64 | */ 65 | angular.module('gettext').factory('gettextCatalog', ["gettextPlurals", "gettextFallbackLanguage", "$http", "$cacheFactory", "$interpolate", "$rootScope", function (gettextPlurals, gettextFallbackLanguage, $http, $cacheFactory, $interpolate, $rootScope) { 66 | var catalog; 67 | var noContext = '$$noContext'; 68 | 69 | // IE8 returns UPPER CASE tags, even though the source is lower case. 70 | // This can causes the (key) string in the DOM to have a different case to 71 | // the string in the `po` files. 72 | // IE9, IE10 and IE11 reorders the attributes of tags. 73 | var test = 'test'; 74 | var isHTMLModified = (angular.element('' + test + '').html() !== test); 75 | 76 | var prefixDebug = function (string) { 77 | if (catalog.debug && catalog.currentLanguage !== catalog.baseLanguage) { 78 | return catalog.debugPrefix + string; 79 | } else { 80 | return string; 81 | } 82 | }; 83 | 84 | var addTranslatedMarkers = function (string) { 85 | if (catalog.showTranslatedMarkers) { 86 | return catalog.translatedMarkerPrefix + string + catalog.translatedMarkerSuffix; 87 | } else { 88 | return string; 89 | } 90 | }; 91 | 92 | function broadcastUpdated() { 93 | /** 94 | * @ngdoc event 95 | * @name gettextCatalog#gettextLanguageChanged 96 | * @eventType broadcast on $rootScope 97 | * @description Fires language change notification without any additional parameters. 98 | */ 99 | $rootScope.$broadcast('gettextLanguageChanged'); 100 | } 101 | 102 | catalog = { 103 | /** 104 | * @ngdoc property 105 | * @name gettextCatalog#debug 106 | * @public 107 | * @type {Boolean} false 108 | * @see gettextCatalog#debug 109 | * @description Whether or not to prefix untranslated strings with `[MISSING]:` or a custom prefix. 110 | */ 111 | debug: false, 112 | /** 113 | * @ngdoc property 114 | * @name gettextCatalog#debugPrefix 115 | * @public 116 | * @type {String} [MISSING]: 117 | * @description Custom prefix for untranslated strings when {@link gettextCatalog#debug gettextCatalog#debug} set to `true`. 118 | */ 119 | debugPrefix: '[MISSING]: ', 120 | /** 121 | * @ngdoc property 122 | * @name gettextCatalog#showTranslatedMarkers 123 | * @public 124 | * @type {Boolean} false 125 | * @description Whether or not to wrap all processed text with markers. 126 | * 127 | * Example output: `[Welcome]` 128 | */ 129 | showTranslatedMarkers: false, 130 | /** 131 | * @ngdoc property 132 | * @name gettextCatalog#translatedMarkerPrefix 133 | * @public 134 | * @type {String} [ 135 | * @description Custom prefix to mark strings that have been run through {@link angular-gettext angular-gettext}. 136 | */ 137 | translatedMarkerPrefix: '[', 138 | /** 139 | * @ngdoc property 140 | * @name gettextCatalog#translatedMarkerSuffix 141 | * @public 142 | * @type {String} ] 143 | * @description Custom suffix to mark strings that have been run through {@link angular-gettext angular-gettext}. 144 | */ 145 | translatedMarkerSuffix: ']', 146 | /** 147 | * @ngdoc property 148 | * @name gettextCatalog#strings 149 | * @private 150 | * @type {Object} 151 | * @description An object of loaded translation strings. Shouldn't be used directly. 152 | */ 153 | strings: {}, 154 | /** 155 | * @ngdoc property 156 | * @name gettextCatalog#baseLanguage 157 | * @protected 158 | * @deprecated 159 | * @since 2.0 160 | * @type {String} en 161 | * @description The default language, in which you're application is written. 162 | * 163 | * This defaults to English and it's generally a bad idea to use anything else: 164 | * if your language has different pluralization rules you'll end up with incorrect translations. 165 | */ 166 | baseLanguage: 'en', 167 | /** 168 | * @ngdoc property 169 | * @name gettextCatalog#currentLanguage 170 | * @public 171 | * @type {String} 172 | * @description Active language. 173 | */ 174 | currentLanguage: 'en', 175 | /** 176 | * @ngdoc property 177 | * @name gettextCatalog#cache 178 | * @public 179 | * @type {String} en 180 | * @description Language cache for lazy load 181 | */ 182 | cache: $cacheFactory('strings'), 183 | 184 | /** 185 | * @ngdoc method 186 | * @name gettextCatalog#setCurrentLanguage 187 | * @public 188 | * @param {String} lang language name 189 | * @description Sets the current language and makes sure that all translations get updated correctly. 190 | */ 191 | setCurrentLanguage: function (lang) { 192 | this.currentLanguage = lang; 193 | broadcastUpdated(); 194 | }, 195 | 196 | /** 197 | * @ngdoc method 198 | * @name gettextCatalog#getCurrentLanguage 199 | * @public 200 | * @returns {String} current language 201 | * @description Returns the current language. 202 | */ 203 | getCurrentLanguage: function () { 204 | return this.currentLanguage; 205 | }, 206 | 207 | /** 208 | * @ngdoc method 209 | * @name gettextCatalog#setStrings 210 | * @public 211 | * @param {String} language language name 212 | * @param {Object.} strings set of strings where the key is the translation `key` and `value` is the translated text 213 | * @description Processes an object of string definitions. {@link guide:manual-setstrings More details here}. 214 | */ 215 | setStrings: function (language, strings) { 216 | if (!this.strings[language]) { 217 | this.strings[language] = {}; 218 | } 219 | 220 | var defaultPlural = gettextPlurals(language, 1); 221 | for (var key in strings) { 222 | var val = strings[key]; 223 | 224 | if (isHTMLModified) { 225 | // Use the DOM engine to render any HTML in the key (#131). 226 | key = angular.element('' + key + '').html(); 227 | } 228 | 229 | if (angular.isString(val) || angular.isArray(val)) { 230 | // No context, wrap it in $$noContext. 231 | var obj = {}; 232 | obj[noContext] = val; 233 | val = obj; 234 | } 235 | 236 | if (!this.strings[language][key]) { 237 | this.strings[language][key] = {}; 238 | } 239 | 240 | for (var context in val) { 241 | var str = val[context]; 242 | if (!angular.isArray(str)) { 243 | // Expand single strings 244 | this.strings[language][key][context] = []; 245 | this.strings[language][key][context][defaultPlural] = str; 246 | } else { 247 | this.strings[language][key][context] = str; 248 | } 249 | } 250 | } 251 | 252 | broadcastUpdated(); 253 | }, 254 | 255 | /** 256 | * @ngdoc method 257 | * @name gettextCatalog#getStringFormFor 258 | * @protected 259 | * @param {String} language language name 260 | * @param {String} string translation key 261 | * @param {Number=} n number to build string form for 262 | * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} 263 | * @returns {String|Null} translated or annotated string or null if language is not set 264 | * @description Translate a string with the given language, count and context. 265 | */ 266 | getStringFormFor: function (language, string, n, context) { 267 | if (!language) { 268 | return null; 269 | } 270 | var stringTable = this.strings[language] || {}; 271 | var contexts = stringTable[string] || {}; 272 | var plurals = contexts[context || noContext] || []; 273 | return plurals[gettextPlurals(language, n)]; 274 | }, 275 | 276 | /** 277 | * @ngdoc method 278 | * @name gettextCatalog#getString 279 | * @public 280 | * @param {String} string translation key 281 | * @param {$rootScope.Scope=} scope scope to do interpolation against 282 | * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} 283 | * @returns {String} translated or annotated string 284 | * @description Translate a string with the given scope and context. 285 | * 286 | * First it tries {@link gettextCatalog#currentLanguage gettextCatalog#currentLanguage} (e.g. `en-US`) then {@link gettextFallbackLanguage fallback} (e.g. `en`). 287 | * 288 | * When `scope` is supplied it uses Angular.JS interpolation, so something like this will do what you expect: 289 | * ```js 290 | * var hello = gettextCatalog.getString("Hello {{name}}!", { name: "Ruben" }); 291 | * // var hello will be "Hallo Ruben!" in Dutch. 292 | * ``` 293 | * Avoid using scopes - this skips interpolation and is a lot faster. 294 | */ 295 | getString: function (string, scope, context) { 296 | var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage); 297 | string = this.getStringFormFor(this.currentLanguage, string, 1, context) || 298 | this.getStringFormFor(fallbackLanguage, string, 1, context) || 299 | prefixDebug(string); 300 | string = scope ? $interpolate(string)(scope) : string; 301 | return addTranslatedMarkers(string); 302 | }, 303 | 304 | /** 305 | * @ngdoc method 306 | * @name gettextCatalog#getPlural 307 | * @public 308 | * @param {Number} n number to build string form for 309 | * @param {String} string translation key 310 | * @param {String} stringPlural plural translation key 311 | * @param {$rootScope.Scope=} scope scope to do interpolation against 312 | * @param {String=} context translation key context, e.g. {@link doc:context Verb, Noun} 313 | * @returns {String} translated or annotated string 314 | * @see {@link gettextCatalog#getString gettextCatalog#getString} for details 315 | * @description Translate a plural string with the given context. 316 | */ 317 | getPlural: function (n, string, stringPlural, scope, context) { 318 | var fallbackLanguage = gettextFallbackLanguage(this.currentLanguage); 319 | string = this.getStringFormFor(this.currentLanguage, string, n, context) || 320 | this.getStringFormFor(fallbackLanguage, string, n, context) || 321 | prefixDebug(n === 1 ? string : stringPlural); 322 | if (scope) { 323 | scope.$count = n; 324 | string = $interpolate(string)(scope); 325 | } 326 | return addTranslatedMarkers(string); 327 | }, 328 | 329 | /** 330 | * @ngdoc method 331 | * @name gettextCatalog#loadRemote 332 | * @public 333 | * @param {String} url location of the translations 334 | * @description Load a set of translation strings from a given URL. 335 | * 336 | * This should be a JSON catalog generated with [angular-gettext-tools](https://github.com/rubenv/angular-gettext-tools). 337 | * {@link guide:lazy-loading More details here}. 338 | */ 339 | loadRemote: function (url) { 340 | return $http({ 341 | method: 'GET', 342 | url: url, 343 | cache: catalog.cache 344 | }).then(function (response) { 345 | var data = response.data; 346 | for (var lang in data) { 347 | catalog.setStrings(lang, data[lang]); 348 | } 349 | return response; 350 | }); 351 | } 352 | }; 353 | 354 | return catalog; 355 | }]); 356 | 357 | /** 358 | * @ngdoc directive 359 | * @module gettext 360 | * @name translate 361 | * @requires gettextCatalog 362 | * @requires gettextUtil 363 | * @requires https://docs.angularjs.org/api/ng/service/$parse $parse 364 | * @requires https://docs.angularjs.org/api/ng/service/$animate $animate 365 | * @requires https://docs.angularjs.org/api/ng/service/$compile $compile 366 | * @requires https://docs.angularjs.org/api/ng/service/$window $window 367 | * @restrict AE 368 | * @param {String} [translatePlural] plural form 369 | * @param {Number} translateN value to watch to substitute correct plural form 370 | * @param {String} translateContext context value, e.g. {@link doc:context Verb, Noun} 371 | * @description Annotates and translates text inside directive 372 | * 373 | * Full interpolation support is available in translated strings, so the following will work as expected: 374 | * ```js 375 | *
Hello {{name}}!
376 | * ``` 377 | * 378 | * You can also use custom context parameters while interpolating. This approach allows usage 379 | * of angular filters as well as custom logic inside your translated messages without unnecessary impact on translations. 380 | * 381 | * So for example when you have message like this: 382 | * ```js 383 | *
Last modified {{modificationDate | date:'yyyy-MM-dd HH:mm:ss Z'}} by {{author}}.
384 | * ``` 385 | * you will have it extracted in exact same version so it would look like this: 386 | * `Last modified {{modificationDate | date:'yyyy-MM-dd HH:mm:ss Z'}} by {{author}}`. 387 | * To start with it might be too complicated to read and handle by non technical translator. It's easy to make mistake 388 | * when copying format for example. Secondly if you decide to change format by some point of the project translation will broke 389 | * as it won't be the same string anymore. 390 | * 391 | * Instead your translator should only be concerned to place {{modificationDate}} correctly and you should have a free hand 392 | * to modify implementation details on how to present the results. This is how you can achieve the goal: 393 | * ```js 394 | *
Last modified {{modificationDate}} by {{author}}.
395 | * ``` 396 | * 397 | * There's a few more things worth to point out: 398 | * 1. You can use as many parameters as you want. Each parameter begins with `translate-params-` followed by snake-case parameter name. 399 | * Each parameter will be available for interpolation in camelCase manner (just like angular directive works by default). 400 | * ```js 401 | *
Param {{myCustomParam}} has been changed by {{name}}.
402 | * ``` 403 | * 2. You can rename your variables from current scope to simple ones if you like. 404 | * ```js 405 | *
Today's date is: {{date}}.
406 | * ``` 407 | * 3. You can use translate-params only for some interpolations. Rest would be treated as usual. 408 | * ```js 409 | *
This product: {{product}} costs {{cost}}.
410 | * ``` 411 | */ 412 | angular.module('gettext').directive('translate', ["gettextCatalog", "$parse", "$animate", "$compile", "$window", "gettextUtil", function (gettextCatalog, $parse, $animate, $compile, $window, gettextUtil) { 413 | var msie = parseInt((/msie (\d+)/i.exec($window.navigator.userAgent) || [])[1], 10); 414 | var PARAMS_PREFIX = 'translateParams'; 415 | 416 | function getCtxAttr(key) { 417 | return gettextUtil.lcFirst(key.replace(PARAMS_PREFIX, '')); 418 | } 419 | 420 | function handleInterpolationContext(scope, attrs, update) { 421 | var attributes = Object.keys(attrs).filter(function (key) { 422 | return gettextUtil.startsWith(key, PARAMS_PREFIX) && key !== PARAMS_PREFIX; 423 | }); 424 | 425 | if (!attributes.length) { 426 | return null; 427 | } 428 | 429 | var interpolationContext = scope.$new(); 430 | var unwatchers = []; 431 | attributes.forEach(function (attribute) { 432 | var unwatch = scope.$watch(attrs[attribute], function (newVal) { 433 | var key = getCtxAttr(attribute); 434 | interpolationContext[key] = newVal; 435 | update(interpolationContext); 436 | }); 437 | unwatchers.push(unwatch); 438 | }); 439 | scope.$on('$destroy', function () { 440 | unwatchers.forEach(function (unwatch) { 441 | unwatch(); 442 | }); 443 | 444 | interpolationContext.$destroy(); 445 | }); 446 | return interpolationContext; 447 | } 448 | 449 | return { 450 | restrict: 'AE', 451 | terminal: true, 452 | compile: function compile(element, attrs) { 453 | // Validate attributes 454 | gettextUtil.assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); 455 | gettextUtil.assert(!attrs.translateN || attrs.translatePlural, 'translate-plural', 'translate-n'); 456 | 457 | var msgid = gettextUtil.trim(element.html()); 458 | var translatePlural = attrs.translatePlural; 459 | var translateContext = attrs.translateContext; 460 | 461 | if (msie <= 8) { 462 | // Workaround fix relating to angular adding a comment node to 463 | // anchors. angular/angular.js/#1949 / angular/angular.js/#2013 464 | if (msgid.slice(-13) === '') { 465 | msgid = msgid.slice(0, -13); 466 | } 467 | } 468 | 469 | return { 470 | post: function (scope, element, attrs) { 471 | var countFn = $parse(attrs.translateN); 472 | var pluralScope = null; 473 | var linking = true; 474 | 475 | function update(interpolationContext) { 476 | interpolationContext = interpolationContext || null; 477 | 478 | // Fetch correct translated string. 479 | var translated; 480 | if (translatePlural) { 481 | scope = pluralScope || (pluralScope = scope.$new()); 482 | scope.$count = countFn(scope); 483 | translated = gettextCatalog.getPlural(scope.$count, msgid, translatePlural, null, translateContext); 484 | } else { 485 | translated = gettextCatalog.getString(msgid, null, translateContext); 486 | } 487 | var oldContents = element.contents(); 488 | 489 | if (!oldContents && !translated){ 490 | return; 491 | } 492 | 493 | // Avoid redundant swaps 494 | if (translated === gettextUtil.trim(oldContents.html())){ 495 | // Take care of unlinked content 496 | if (linking){ 497 | $compile(oldContents)(scope); 498 | } 499 | return; 500 | } 501 | 502 | // Swap in the translation 503 | var newWrapper = angular.element('' + translated + ''); 504 | $compile(newWrapper.contents())(interpolationContext || scope); 505 | var newContents = newWrapper.contents(); 506 | 507 | $animate.enter(newContents, element); 508 | $animate.leave(oldContents); 509 | } 510 | 511 | var interpolationContext = handleInterpolationContext(scope, attrs, update); 512 | update(interpolationContext); 513 | linking = false; 514 | 515 | if (attrs.translateN) { 516 | scope.$watch(attrs.translateN, function () { 517 | update(interpolationContext); 518 | }); 519 | } 520 | 521 | /** 522 | * @ngdoc event 523 | * @name translate#gettextLanguageChanged 524 | * @eventType listen on scope 525 | * @description Listens for language updates and changes translation accordingly 526 | */ 527 | scope.$on('gettextLanguageChanged', function () { 528 | update(interpolationContext); 529 | }); 530 | 531 | } 532 | }; 533 | } 534 | }; 535 | }]); 536 | 537 | /** 538 | * @ngdoc factory 539 | * @module gettext 540 | * @name gettextFallbackLanguage 541 | * @param {String} langCode language code 542 | * @returns {String|Null} fallback language 543 | * @description Strips regional code and returns language code only 544 | * 545 | * Example 546 | * ```js 547 | * gettextFallbackLanguage('ru'); // "null" 548 | * gettextFallbackLanguage('en_GB'); // "en" 549 | * gettextFallbackLanguage(); // null 550 | * ``` 551 | */ 552 | angular.module("gettext").factory("gettextFallbackLanguage", function () { 553 | var cache = {}; 554 | var pattern = /([^_]+)_[^_]+$/; 555 | 556 | return function (langCode) { 557 | if (cache[langCode]) { 558 | return cache[langCode]; 559 | } 560 | 561 | var matches = pattern.exec(langCode); 562 | if (matches) { 563 | cache[langCode] = matches[1]; 564 | return matches[1]; 565 | } 566 | 567 | return null; 568 | }; 569 | }); 570 | /** 571 | * @ngdoc filter 572 | * @module gettext 573 | * @name translate 574 | * @requires gettextCatalog 575 | * @param {String} input translation key 576 | * @param {String} context context to evaluate key against 577 | * @returns {String} translated string or annotated key 578 | * @see {@link doc:context Verb, Noun} 579 | * @description Takes key and returns string 580 | * 581 | * Sometimes it's not an option to use an attribute (e.g. when you want to annotate an attribute value). 582 | * There's a `translate` filter available for this purpose. 583 | * 584 | * ```html 585 | * 586 | * ``` 587 | * This filter does not support plural strings. 588 | * 589 | * You may want to use {@link guide:custom-annotations custom annotations} to avoid using the `translate` filter all the time. * Is 590 | */ 591 | angular.module('gettext').filter('translate', ["gettextCatalog", function (gettextCatalog) { 592 | function filter(input, context) { 593 | return gettextCatalog.getString(input, null, context); 594 | } 595 | filter.$stateful = true; 596 | return filter; 597 | }]); 598 | 599 | // Do not edit this file, it is autogenerated using genplurals.py! 600 | angular.module("gettext").factory("gettextPlurals", function () { 601 | var languageCodes = { 602 | "pt_BR": "pt_BR", 603 | "pt-BR": "pt_BR" 604 | }; 605 | return function (langCode, n) { 606 | switch (getLanguageCode(langCode)) { 607 | case "ay": // Aymará 608 | case "bo": // Tibetan 609 | case "cgg": // Chiga 610 | case "dz": // Dzongkha 611 | case "fa": // Persian 612 | case "id": // Indonesian 613 | case "ja": // Japanese 614 | case "jbo": // Lojban 615 | case "ka": // Georgian 616 | case "kk": // Kazakh 617 | case "km": // Khmer 618 | case "ko": // Korean 619 | case "ky": // Kyrgyz 620 | case "lo": // Lao 621 | case "ms": // Malay 622 | case "my": // Burmese 623 | case "sah": // Yakut 624 | case "su": // Sundanese 625 | case "th": // Thai 626 | case "tt": // Tatar 627 | case "ug": // Uyghur 628 | case "vi": // Vietnamese 629 | case "wo": // Wolof 630 | case "zh": // Chinese 631 | // 1 form 632 | return 0; 633 | case "is": // Icelandic 634 | // 2 forms 635 | return (n%10!=1 || n%100==11) ? 1 : 0; 636 | case "jv": // Javanese 637 | // 2 forms 638 | return n!=0 ? 1 : 0; 639 | case "mk": // Macedonian 640 | // 2 forms 641 | return n==1 || n%10==1 ? 0 : 1; 642 | case "ach": // Acholi 643 | case "ak": // Akan 644 | case "am": // Amharic 645 | case "arn": // Mapudungun 646 | case "br": // Breton 647 | case "fil": // Filipino 648 | case "fr": // French 649 | case "gun": // Gun 650 | case "ln": // Lingala 651 | case "mfe": // Mauritian Creole 652 | case "mg": // Malagasy 653 | case "mi": // Maori 654 | case "oc": // Occitan 655 | case "pt_BR": // Brazilian Portuguese 656 | case "tg": // Tajik 657 | case "ti": // Tigrinya 658 | case "tr": // Turkish 659 | case "uz": // Uzbek 660 | case "wa": // Walloon 661 | case "zh": // Chinese 662 | // 2 forms 663 | return n>1 ? 1 : 0; 664 | case "lv": // Latvian 665 | // 3 forms 666 | return (n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2); 667 | case "lt": // Lithuanian 668 | // 3 forms 669 | return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); 670 | case "be": // Belarusian 671 | case "bs": // Bosnian 672 | case "hr": // Croatian 673 | case "ru": // Russian 674 | case "sr": // Serbian 675 | case "uk": // Ukrainian 676 | // 3 forms 677 | return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); 678 | case "mnk": // Mandinka 679 | // 3 forms 680 | return (n==0 ? 0 : n==1 ? 1 : 2); 681 | case "ro": // Romanian 682 | // 3 forms 683 | return (n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); 684 | case "pl": // Polish 685 | // 3 forms 686 | return (n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); 687 | case "cs": // Czech 688 | case "sk": // Slovak 689 | // 3 forms 690 | return (n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; 691 | case "sl": // Slovenian 692 | // 4 forms 693 | return (n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); 694 | case "mt": // Maltese 695 | // 4 forms 696 | return (n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); 697 | case "gd": // Scottish Gaelic 698 | // 4 forms 699 | return (n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3; 700 | case "cy": // Welsh 701 | // 4 forms 702 | return (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3; 703 | case "kw": // Cornish 704 | // 4 forms 705 | return (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3; 706 | case "ga": // Irish 707 | // 5 forms 708 | return n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4; 709 | case "ar": // Arabic 710 | // 6 forms 711 | return (n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); 712 | default: // Everything else 713 | return n != 1 ? 1 : 0; 714 | } 715 | }; 716 | 717 | /** 718 | * Method extracts iso639-2 language code from code with locale e.g. pl_PL, en_US, etc. 719 | * If it's provided with standalone iso639-2 language code it simply returns it. 720 | * @param {String} langCode 721 | * @returns {String} iso639-2 language Code 722 | */ 723 | function getLanguageCode(langCode) { 724 | if (!languageCodes[langCode]) { 725 | languageCodes[langCode] = langCode.split(/\-|_/).shift(); 726 | } 727 | return languageCodes[langCode]; 728 | } 729 | }); 730 | 731 | /** 732 | * @ngdoc factory 733 | * @module gettext 734 | * @name gettextUtil 735 | * @description Utility service for common operations and polyfills. 736 | */ 737 | angular.module('gettext').factory('gettextUtil', function gettextUtil() { 738 | /** 739 | * @ngdoc method 740 | * @name gettextUtil#trim 741 | * @public 742 | * @param {string} value String to be trimmed. 743 | * @description Trim polyfill for old browsers (instead of jQuery). Based on AngularJS-v1.2.2 (angular.js#620). 744 | * 745 | * Example 746 | * ```js 747 | * gettextUtil.assert(' no blanks '); // "no blanks" 748 | * ``` 749 | */ 750 | var trim = (function () { 751 | if (!String.prototype.trim) { 752 | return function (value) { 753 | return (typeof value === 'string') ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; 754 | }; 755 | } 756 | return function (value) { 757 | return (typeof value === 'string') ? value.trim() : value; 758 | }; 759 | })(); 760 | 761 | /** 762 | * @ngdoc method 763 | * @name gettextUtil#assert 764 | * @public 765 | * @param {bool} condition condition to check 766 | * @param {String} missing name of the directive missing attribute 767 | * @param {String} found name of attribute that has been used with directive 768 | * @description Throws error if condition is not met, which means that directive was used with certain parameter 769 | * that requires another one (which is missing). 770 | * 771 | * Example 772 | * ```js 773 | * gettextUtil.assert(!attrs.translatePlural || attrs.translateN, 'translate-n', 'translate-plural'); 774 | * //You should add a translate-n attribute whenever you add a translate-plural attribute. 775 | * ``` 776 | */ 777 | function assert(condition, missing, found) { 778 | if (!condition) { 779 | throw new Error('You should add a ' + missing + ' attribute whenever you add a ' + found + ' attribute.'); 780 | } 781 | } 782 | 783 | /** 784 | * @ngdoc method 785 | * @name gettextUtil#startsWith 786 | * @public 787 | * @param {string} target String on which checking will occur. 788 | * @param {string} query String expected to be at the beginning of target. 789 | * @returns {boolean} Returns true if object has no ownProperties. For arrays returns true if length == 0. 790 | * @description Checks if string starts with another string. 791 | * 792 | * Example 793 | * ```js 794 | * gettextUtil.startsWith('Home sweet home.', 'Home'); //true 795 | * gettextUtil.startsWith('Home sweet home.', 'sweet'); //false 796 | * ``` 797 | */ 798 | function startsWith(target, query) { 799 | return target.indexOf(query) === 0; 800 | } 801 | 802 | /** 803 | * @ngdoc method 804 | * @name gettextUtil#lcFirst 805 | * @public 806 | * @param {string} target String to transform. 807 | * @returns {string} Strings beginning with lowercase letter. 808 | * @description Makes first letter of the string lower case 809 | * 810 | * Example 811 | * ```js 812 | * gettextUtil.lcFirst('Home Sweet Home.'); //'home Sweet Home' 813 | * gettextUtil.lcFirst('ShouldBeCamelCase.'); //'shouldBeCamelCase' 814 | * ``` 815 | */ 816 | function lcFirst(target) { 817 | var first = target.charAt(0).toLowerCase(); 818 | return first + target.substr(1); 819 | } 820 | 821 | return { 822 | trim: trim, 823 | assert: assert, 824 | startsWith: startsWith, 825 | lcFirst: lcFirst 826 | }; 827 | }); 828 | --------------------------------------------------------------------------------