diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a551ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.pyc +*.sw? +*.sqlite3 +.DS_STORE +*.egg-info +.venv +.tox +build +dist \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fb30bb1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (C) 2013-2015 by Ruben Vermeersch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..399e4d1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include README.txt +recursive-include xstatic/pkg/angular_gettext * + +global-exclude *.pyc +global-exclude *.pyo +global-exclude *.orig +global-exclude *.rej + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..5517106 --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +XStatic-Angular-Gettext +-------------- + +Angular-Gettext javascript library packaged for setuptools (easy_install) / pip. + +This package is intended to be used by **any** project that needs these files. + +It intentionally does **not** provide any extra code except some metadata +**nor** has any extra requirements. You MAY use some minimal support code from +the XStatic base package, if you like. + +You can find more info about the xstatic packaging way in the package `XStatic`. + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..04abe30 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +from xstatic.pkg import angular_gettext as xs + +# The README.txt file should be written in reST so that PyPI can use +# it to generate your project's PyPI page. +long_description = open('README.txt').read() + +from setuptools import setup, find_packages + +setup( + name=xs.PACKAGE_NAME, + version=xs.PACKAGE_VERSION, + description=xs.DESCRIPTION, + long_description=long_description, + classifiers=xs.CLASSIFIERS, + keywords=xs.KEYWORDS, + maintainer=xs.MAINTAINER, + maintainer_email=xs.MAINTAINER_EMAIL, + license=xs.LICENSE, + url=xs.HOMEPAGE, + platforms=xs.PLATFORMS, + packages=find_packages(), + namespace_packages=['xstatic', 'xstatic.pkg', ], + include_package_data=True, + zip_safe=False, + install_requires=[], # nothing! :) + # if you like, you MAY use the 'XStatic' package. +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..62cb782 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +minversion = 1.6 +skipsdist = True +envlist = py27,py33,py34 + +[testenv:venv] +commands = {posargs} + +[tox:jenkins] +downloadcache = ~/cache/pip \ No newline at end of file diff --git a/xstatic/__init__.py b/xstatic/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/__init__.py b/xstatic/pkg/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/xstatic/pkg/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/xstatic/pkg/angular_gettext/__init__.py b/xstatic/pkg/angular_gettext/__init__.py new file mode 100644 index 0000000..a46370e --- /dev/null +++ b/xstatic/pkg/angular_gettext/__init__.py @@ -0,0 +1,50 @@ +""" +XStatic resource package + +See package 'XStatic' for documentation and basic tools. +""" + +DISPLAY_NAME = 'Angular-Gettext' # official name, upper/lowercase allowed, no spaces +PACKAGE_NAME = 'XStatic-%s' % DISPLAY_NAME # name used for PyPi + +NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar') + # please use a all-lowercase valid python + # package name + +VERSION = '2.1.0' # version of the packaged files, please use the upstream + # version number +BUILD = '1' # our package build number, so we can release new builds + # with fixes for xstatic stuff. +PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi + +DESCRIPTION = "%s %s (XStatic packaging standard)" % (DISPLAY_NAME, VERSION) + +PLATFORMS = 'any' +CLASSIFIERS = [] +KEYWORDS = '%s xstatic' % NAME + +# XStatic-* package maintainer: +MAINTAINER = 'Thai Tran' +MAINTAINER_EMAIL = 'tqtran@us.ibm.com' + +# this refers to the project homepage of the stuff we packaged: +HOMEPAGE = 'https://angular-gettext.rocketeer.be/' + +# this refers to all files: +LICENSE = '(same as %s)' % DISPLAY_NAME + +from os.path import join, dirname +BASE_DIR = join(dirname(__file__), 'data') +# linux package maintainers just can point to their file locations like this: +#BASE_DIR = '/usr/share/javascript/jquery' + +LOCATIONS = { + # CDN locations (if no public CDN exists, use an empty dict) + # if value is a string, it is a base location, just append relative + # path/filename. if value is a dict, do another lookup using the + # relative path/filename you want. + # your relative path/filenames should usually be without version + # information, because either the base dir/url is exactly for this + # version or the mapping will care for accessing this version. +} + diff --git a/xstatic/pkg/angular_gettext/data/angular-gettext.js b/xstatic/pkg/angular_gettext/data/angular-gettext.js new file mode 100644 index 0000000..0103653 --- /dev/null +++ b/xstatic/pkg/angular_gettext/data/angular-gettext.js @@ -0,0 +1,352 @@ +angular.module('gettext', []); + +angular.module('gettext').constant('gettext', function (str) { + /* + * Does nothing, simply returns the input string. + * + * This function serves as a marker for `grunt-angular-gettext` to know that + * this string should be extracted for translations. + */ + return str; +}); + +angular.module('gettext').factory('gettextCatalog', ["gettextPlurals", "$http", "$cacheFactory", "$interpolate", "$rootScope", function (gettextPlurals, $http, $cacheFactory, $interpolate, $rootScope) { + var catalog; + var noContext = '$$noContext'; + + // IE8 returns UPPER CASE tags, even though the source is lower case. + // This can causes the (key) string in the DOM to have a different case to + // the string in the `po` files. + // IE9, IE10 and IE11 reorders the attributes of tags. + var test = 'test'; + var isHTMLModified = (angular.element('' + test + '').html() !== test); + + var prefixDebug = function (string) { + if (catalog.debug && catalog.currentLanguage !== catalog.baseLanguage) { + return catalog.debugPrefix + string; + } else { + return string; + } + }; + + var addTranslatedMarkers = function (string) { + if (catalog.showTranslatedMarkers) { + return catalog.translatedMarkerPrefix + string + catalog.translatedMarkerSuffix; + } else { + return string; + } + }; + + function broadcastUpdated() { + $rootScope.$broadcast('gettextLanguageChanged'); + } + + catalog = { + debug: false, + debugPrefix: '[MISSING]: ', + showTranslatedMarkers: false, + translatedMarkerPrefix: '[', + translatedMarkerSuffix: ']', + strings: {}, + baseLanguage: 'en', + currentLanguage: 'en', + cache: $cacheFactory('strings'), + + setCurrentLanguage: function (lang) { + this.currentLanguage = lang; + broadcastUpdated(); + }, + + getCurrentLanguage: function () { + return this.currentLanguage; + }, + + setStrings: function (language, strings) { + if (!this.strings[language]) { + this.strings[language] = {}; + } + + for (var key in strings) { + var val = strings[key]; + + if (isHTMLModified) { + // Use the DOM engine to render any HTML in the key (#131). + key = angular.element('' + key + '').html(); + } + + if (angular.isString(val) || angular.isArray(val)) { + // No context, wrap it in $$noContext. + var obj = {}; + obj[noContext] = val; + val = obj; + } + + // Expand single strings for each context. + for (var context in val) { + var str = val[context]; + val[context] = angular.isArray(str) ? str : [str]; + } + this.strings[language][key] = val; + } + + broadcastUpdated(); + }, + + getStringForm: function (string, n, context) { + var stringTable = this.strings[this.currentLanguage] || {}; + var contexts = stringTable[string] || {}; + var plurals = contexts[context || noContext] || []; + return plurals[n]; + }, + + getString: function (string, scope, context) { + string = this.getStringForm(string, 0, context) || prefixDebug(string); + string = scope ? $interpolate(string)(scope) : string; + return addTranslatedMarkers(string); + }, + + getPlural: function (n, string, stringPlural, scope, context) { + var form = gettextPlurals(this.currentLanguage, n); + string = this.getStringForm(string, form, context) || prefixDebug(n === 1 ? string : stringPlural); + if (scope) { + scope.$count = n; + string = $interpolate(string)(scope); + } + return addTranslatedMarkers(string); + }, + + loadRemote: function (url) { + return $http({ + method: 'GET', + url: url, + cache: catalog.cache + }).success(function (data) { + for (var lang in data) { + catalog.setStrings(lang, data[lang]); + } + }); + } + }; + + 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; + }; + })(); + + function assert(condition, missing, found) { + if (!condition) { + throw new Error('You should add a ' + missing + ' attribute whenever you add a ' + found + ' attribute.'); + } + } + + var msie = parseInt((/msie (\d+)/.exec(angular.lowercase($window.navigator.userAgent)) || [])[1], 10); + + 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'); + + var msgid = trim(element.html()); + var translatePlural = attrs.translatePlural; + var translateContext = attrs.translateContext; + + if (msie <= 8) { + // Workaround fix relating to angular adding a comment node to + // anchors. angular/angular.js/#1949 / angular/angular.js/#2013 + if (msgid.slice(-13) === '') { + msgid = msgid.slice(0, -13); + } + } + + return { + post: function (scope, element, attrs) { + var countFn = $parse(attrs.translateN); + var pluralScope = null; + var linking = true; + + function update() { + // 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); + } else { + translated = gettextCatalog.getString(msgid, null, translateContext); + } + + var oldContents = element.contents(); + + if (oldContents.length === 0){ + return; + } + + // Avoid redundant swaps + if (translated === trim(oldContents.html())){ + // Take care of unlinked content + if (linking){ + $compile(oldContents)(scope); + } + return; + } + + // Swap in the translation + var newWrapper = angular.element('' + translated + ''); + $compile(newWrapper.contents())(scope); + var newContents = newWrapper.contents(); + + $animate.enter(newContents, element); + $animate.leave(oldContents); + } + + if (attrs.translateN) { + scope.$watch(attrs.translateN, update); + } + + scope.$on('gettextLanguageChanged', update); + + update(); + linking = false; + } + }; + } + }; +}]); + +angular.module('gettext').filter('translate', ["gettextCatalog", function (gettextCatalog) { + function filter(input, context) { + return gettextCatalog.getString(input, null, context); + } + filter.$stateful = true; + return filter; +}]); + +// Do not edit this file, it is autogenerated using genplurals.py! +angular.module("gettext").factory("gettextPlurals", function () { + return function (langCode, n) { + switch (langCode) { + case "ay": // Aymará + case "bo": // Tibetan + case "cgg": // Chiga + case "dz": // Dzongkha + case "fa": // Persian + case "id": // Indonesian + case "ja": // Japanese + case "jbo": // Lojban + case "ka": // Georgian + case "kk": // Kazakh + case "km": // Khmer + case "ko": // Korean + case "ky": // Kyrgyz + case "lo": // Lao + case "ms": // Malay + case "my": // Burmese + case "sah": // Yakut + case "su": // Sundanese + case "th": // Thai + case "tt": // Tatar + case "ug": // Uyghur + case "vi": // Vietnamese + case "wo": // Wolof + case "zh": // Chinese + // 1 form + return 0; + case "is": // Icelandic + // 2 forms + return (n%10!=1 || n%100==11) ? 1 : 0; + case "jv": // Javanese + // 2 forms + return n!=0 ? 1 : 0; + case "mk": // Macedonian + // 2 forms + return n==1 || n%10==1 ? 0 : 1; + case "ach": // Acholi + case "ak": // Akan + case "am": // Amharic + case "arn": // Mapudungun + case "br": // Breton + case "fil": // Filipino + case "fr": // French + case "gun": // Gun + case "ln": // Lingala + case "mfe": // Mauritian Creole + case "mg": // Malagasy + case "mi": // Maori + case "oc": // Occitan + case "pt_BR": // Brazilian Portuguese + case "tg": // Tajik + case "ti": // Tigrinya + case "tr": // Turkish + case "uz": // Uzbek + case "wa": // Walloon + case "zh": // Chinese + // 2 forms + return n>1 ? 1 : 0; + case "lv": // Latvian + // 3 forms + return (n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2); + case "lt": // Lithuanian + // 3 forms + return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); + case "be": // Belarusian + case "bs": // Bosnian + case "hr": // Croatian + case "ru": // Russian + case "sr": // Serbian + case "uk": // Ukrainian + // 3 forms + return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + case "mnk": // Mandinka + // 3 forms + return (n==0 ? 0 : n==1 ? 1 : 2); + case "ro": // Romanian + // 3 forms + return (n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); + case "pl": // Polish + // 3 forms + return (n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + case "cs": // Czech + case "sk": // Slovak + // 3 forms + return (n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; + case "sl": // Slovenian + // 4 forms + return (n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); + case "mt": // Maltese + // 4 forms + return (n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); + case "gd": // Scottish Gaelic + // 4 forms + return (n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3; + case "cy": // Welsh + // 4 forms + return (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3; + case "kw": // Cornish + // 4 forms + return (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3; + case "ga": // Irish + // 5 forms + return n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4; + case "ar": // Arabic + // 6 forms + return (n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); + default: // Everything else + return n != 1 ? 1 : 0; + } + } +});