diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 0356424..7be7d3c --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,36 @@ -*.pyc -.project +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +#*.mo + +#Mr Developer +.mr.developer.cfg + +# IDEs +.DS_Store +.idea +*.lint .pydevproject -.settings/ +.project +.settings* + diff --git a/.gitmodules b/.gitmodules index 0da09d5..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "uplusmessaging/externals/bcrypt"] - path = uplusmessaging/externals/bcrypt - url = https://github.com/erlichmen/py-bcrypt.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d43342c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: python +python: + - "2.7" +# command to install dependencies +install: + - curl -O http://googleappengine.googlecode.com/files/google_appengine_1.7.2.zip && unzip -q google_appengine_1.7.2.zip + - pip install webtest + - pip install pyquery + - pip install pycrypto + - pip install mock +# command to run tests +script: python testrunner.py google_appengine . diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000..eb43f95 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,184 @@ +Google App Engine Boilerplate is released under the LGPL + +Copyright 2012 + +What's LGPL? + LGPL (LESSER GENERAL PUBLIC LICENSE) is a free software license published by + the Free Software Foundation (FSF). + It was designed as a compromise between the strong-copyleft GNU General Public + License (GPL) and permissive licenses such as the BSD licenses and the MIT License. + +Why this Licence for Google App Engine Boilerplate? + Because with this licence you can use "Google App Engine Boilerplate" regardless + of whether it is free software or proprietary software. + + If you make changes to this Boilerplate, in order to improve something or fix + an issue, you should share under the same licence those changes. + +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 71716db..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2012 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ff3a7c --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ + +GAEPATH = $(HOME)/bin/google_appengine +PORT=8089 + +PYLINTS = $(wildcard *.py \ + libs/*.py \ + ) +PYLINTFILES = $(patsubst %.py,.%.lint,$(notdir $(PYLINTS))) +PYLINT = $(join $(dir $(PYLINTS)),$(PYLINTFILES)) + +PYTHONPATH=$(GAEPATH):$(GAEPATH)/lib/yaml/lib:$(GAEPATH)/lib/webob:$(GAEPATH)/lib/webapp2:$(GAEPATH)/lib/jinja2:. + +APP=$(shell grep 'application:' app.yaml | sed -e 's/^application:\s*//g') +BRANCH=master + +run: + $(GAEPATH)/dev_appserver.py ./ --port=$(PORT) --datastore_path=/tmp/$(APP).dev_appserver.datastore + + +.%.lint: %.py + @PYTHONPATH=$(PYTHONPATH) pychecker --only --no-miximport --no-override -Z __website__,__author__,__all__,__version__ $? + @touch $@ + + +lint: $(PYLINT) + + +clean: + -rm ${PYLINT} + + +push: lint + git push origin $(BRANCH) + + +upload: + $(GAEPATH)/appcfg.py update --skip_sdk_update_check ./ + + +push-and-upload: push upload + + +python: + PYTHONPATH=$(PYTHONPATH) python + + +.PHONY: run lint clean push upload push-and-upload python diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 7fbcac5..19b38fa --- a/README.md +++ b/README.md @@ -1,8 +1,22 @@ -udacityplus -=========== +General +------- +Application is in `web` directory. Top level directories take precedence over ones in `boilerplate`, hence some are duplicated. Please don't alter anything in `boilerplate` directory. -stylized **U+** +Some files are unused, I just didn't get around to cleanup. Some code may crash and be insecure against unauthorized requests. -The Udacity Collaborative Social Network +~~Users don't get automatically added to search index due to processing required to do that being executed only after user edits their profile. Registration triggers boilerplate User class (in boilerplate/models), profile triggers app specific User (in web/models).~~done, so new users get auto-put in search -http://udacityplus.appspot.com \ No newline at end of file +Bulk data +--------- +Course data is in `data/` directory. For remote deployment, upload the application before uploading data. Instructions for uploading data are in `bulkuploader.md`, some convenience code in `bulkup.sh` (for local) and `bulkup_remote.sh` (for remote). + +update gae-boilerplate with: +---------------------------- + +``` +git pull bootstrap master +``` + +OpenID +------ +Not configured \ No newline at end of file diff --git a/uplusmessaging/controllers/__init__.py b/admin/__init__.py similarity index 100% rename from uplusmessaging/controllers/__init__.py rename to admin/__init__.py diff --git a/admin/routes.py b/admin/routes.py new file mode 100644 index 0000000..a4a9f06 --- /dev/null +++ b/admin/routes.py @@ -0,0 +1,20 @@ +from webapp2_extras.routes import RedirectRoute +from webapp2_extras.routes import PathPrefixRoute +import users + + +_routes = [ + PathPrefixRoute('/admin', [ + RedirectRoute('/logout/', users.Logout, name='admin-logout', strict_slash=True), + RedirectRoute('/geochart/', users.Geochart, name='geochart', strict_slash=True), + RedirectRoute('/users/', users.List, name='user-list', strict_slash=True), + RedirectRoute('/users//', users.Edit, name='user-edit', strict_slash=True, handler_method='edit') + ]) +] + +def get_routes(): + return _routes + +def add_routes(app): + for r in _routes: + app.router.add(r) diff --git a/admin/templates/admin/base.html b/admin/templates/admin/base.html new file mode 100644 index 0000000..d37cac2 --- /dev/null +++ b/admin/templates/admin/base.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} +{% import 'admin/macros.html' as lib with context %} + +{% block navbar %} + +{% endblock %} + +{% block subnavbar %} +{% endblock %} diff --git a/admin/templates/admin/edit.html b/admin/templates/admin/edit.html new file mode 100644 index 0000000..a3014c4 --- /dev/null +++ b/admin/templates/admin/edit.html @@ -0,0 +1,21 @@ +{% extends 'admin/base.html' %} + +{% block header_title %} + {% trans %}User Details{% endtrans %} +{% endblock %} + +{% block content %} + +{% if user %} +
+
E-Mail
+
{{ user.email }}
+
Created
+
{{ user.created }}
+
Updated
+
{{ user.updated }}
+
+{% endif %} + + {{ lib.render_form(form, uri_for('user-list'), btn='Update') }} +{% endblock %} \ No newline at end of file diff --git a/admin/templates/admin/geochart.html b/admin/templates/admin/geochart.html new file mode 100644 index 0000000..10064a2 --- /dev/null +++ b/admin/templates/admin/geochart.html @@ -0,0 +1,32 @@ +{% extends 'admin/base.html' %} + +{% block header_title %} + {% trans %}Big Picture{% endtrans %} +{% endblock %} + +{% block content %} +
+{% endblock %} + +{% block mediaJS %} + + + {% endblock %} + \ No newline at end of file diff --git a/admin/templates/admin/list.html b/admin/templates/admin/list.html new file mode 100644 index 0000000..ba0548f --- /dev/null +++ b/admin/templates/admin/list.html @@ -0,0 +1,46 @@ +{% extends 'admin/base.html' %} + +{% block header_title %} + {% trans %}Search User{% endtrans %} +{% endblock %} + +{% block content %} + + + + + + {% for attr, name in list_columns %} + + {% endfor %} + + + {% for user in users %} + + + {% for attr, name in list_columns %} + + {% endfor %} + + {% endfor %} +
  + {{ name }} +
+ + + {{ getattr(user, attr) }} +
+{{ lib.render_pager() }} +{% endblock %} diff --git a/admin/templates/admin/macros.html b/admin/templates/admin/macros.html new file mode 100644 index 0000000..fe8aa33 --- /dev/null +++ b/admin/templates/admin/macros.html @@ -0,0 +1,60 @@ +<{% macro render_form_fields(form) %} + + {% for field in form %} +
+ {{ field.label(class='control-label') }} +
+ {{ field()|safe }} + {% if field.errors %} + {% for error in field.errors %} + {{ error }} + {% endfor %} + {% endif %} +
+
+ {% endfor %} +{% endmacro %} + +{% macro form_tag(legend) %} +
+
+ {% if legend %} + {{ legend }} + {% endif %} + {{ caller() }} +
+
+{% endmacro %} + +{% macro render_form_buttons(return_url, btn) %} +
+
+ + {% if return_url %} + {{ _('Cancel') }} + {% endif %} +
+
+{% endmacro %} + +{% macro render_form(form, return_url, btn='Save', legend='') -%} + {% call form_tag(legend) %} + {{ render_form_fields(form) }} + {{ render_form_buttons(return_url, btn) }} + {% endcall %} +{% endmacro %} + +{% macro render_pager() -%} + +{% endmacro %} diff --git a/admin/tests.py b/admin/tests.py new file mode 100644 index 0000000..889c87a --- /dev/null +++ b/admin/tests.py @@ -0,0 +1,49 @@ +import unittest +from google.appengine.ext import testbed +from google.appengine.ext import ndb + + +class CursorTests(unittest.TestCase): + def setUp(self): + self.testbed = testbed.Testbed() + self.testbed.activate() + self.testbed.init_datastore_v3_stub() + self.testbed.init_memcache_stub() + + def tearDown(self): + self.testbed.deactivate() + + def test_paging(self): + class Bar(ndb.Model): + value = ndb.IntegerProperty() + + for i in range(18): + Bar(value=i+1).put() + + q = Bar.query() + bars1, _, _ = q.order(Bar.key).fetch_page(3) + next_cursor = None + for i in (1,2,3,4,5,6): + bars, next_cursor, more = q.order(Bar.key).fetch_page(3, start_cursor=next_cursor) +# print bars, next_cursor, more + self.assertTrue(more if i != 6 else not more) + self.assertIsNotNone(next_cursor) + + bars2 = bars + + rev_cursor = next_cursor.reversed() + for i in (1,2,3,4,5,6): + barz, rev_cursor, more2 = q.order(-Bar.key).fetch_page(3, start_cursor=rev_cursor) +# print barz + + self.assertEqual(bars1, list(reversed(barz))) + self.assertFalse(more2) +# self.assertEqual(bars, bary) + + rev_cursor = rev_cursor.reversed() + for i in (1,2,3,4,5,6): + bars, rev_cursor, more2 = q.order(Bar.key).fetch_page(3, start_cursor=rev_cursor) +# print bars + + self.assertEqual(bars2, bars) + \ No newline at end of file diff --git a/admin/users.py b/admin/users.py new file mode 100644 index 0000000..58656d3 --- /dev/null +++ b/admin/users.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +import webapp2 +from boilerplate import models +from boilerplate import forms +from boilerplate.handlers import BaseHandler +from google.appengine.datastore.datastore_query import Cursor +from google.appengine.ext import ndb +from google.appengine.api import users +from collections import OrderedDict, Counter +from wtforms import fields + + +class Logout(BaseHandler): + def get(self): + self.redirect(users.create_logout_url(dest_url=self.uri_for('home'))) + + +class Geochart(BaseHandler): + def get(self): + users = models.User.query().fetch(projection=['country']) + users_by_country = Counter() + for user in users: + if user.country: + users_by_country[user.country] += 1 + params = { + "data": users_by_country.items() + } + return self.render_template('admin/geochart.html', **params) + + +class EditProfileForm(forms.EditProfileForm): + activated = fields.BooleanField('Activated') + + +class List(BaseHandler): + def get(self): + p = self.request.get('p') + q = self.request.get('q') + c = self.request.get('c') + forward = True if p not in ['prev'] else False + cursor = Cursor(urlsafe=c) + + if q: + qry = models.User.query(ndb.OR(models.User.last_name == q, + models.User.email == q, + models.User.username == q)) + else: + qry = models.User.query() + + PAGE_SIZE = 5 + if forward: + users, next_cursor, more = qry.order(models.User.key).fetch_page(PAGE_SIZE, start_cursor=cursor) + if next_cursor and more: + self.view.next_cursor = next_cursor + if c: + self.view.prev_cursor = cursor.reversed() + else: + users, next_cursor, more = qry.order(-models.User.key).fetch_page(PAGE_SIZE, start_cursor=cursor) + users = list(reversed(users)) + if next_cursor and more: + self.view.prev_cursor = next_cursor + self.view.next_cursor = cursor.reversed() + + def pager_url(p, cursor): + params = OrderedDict() + if q: + params['q'] = q + if p in ['prev']: + params['p'] = p + if cursor: + params['c'] = cursor.urlsafe() + return self.uri_for('user-list', **params) + + self.view.pager_url = pager_url + self.view.q = q + + params = { + "list_columns": [('username', 'Username'), + ('last_name', 'Last Name'), + ('email', 'E-Mail'), + ('country', 'Country')], + "users" : users, + "count" : qry.count() + } + return self.render_template('admin/list.html', **params) + + +class Edit(BaseHandler): + def get_or_404(self, user_id): + try: + user = models.User.get_by_id(long(user_id)) + if user: + return user + except ValueError: + pass + self.abort(404) + + def edit(self, user_id): + if self.request.POST: + user = self.get_or_404(user_id) + if self.form.validate(): + self.form.populate_obj(user) + user.put() + self.add_message("Changes saved!", 'success') + return self.redirect_to("user-edit", user_id=user_id) + else: + self.add_message("Could not save changes!", 'error') + else: + user = self.get_or_404(user_id) + self.form.process(obj=user) + + params = { + 'user' : user + } + return self.render_template('admin/edit.html', **params) + + @webapp2.cached_property + def form(self): + return EditProfileForm(self) + diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..f3ba335 --- /dev/null +++ b/app.yaml @@ -0,0 +1,106 @@ +application: socialmooc +version: 2-2 +runtime: python27 +api_version: 1 +threadsafe: true + +# In production, uncomment line below. +default_expiration: "30d" + +skip_files: +- ^(.*/)?#.*# +- ^(.*/)?.*~ +- ^(.*/)?.*\.py[co] +- ^(.*/)?.*/RCS/.* +- ^(.*/)?\..* +- ^(.*/)?test$ +- ^(.*/)?test$ +- ^Makefile +- ^COPYING.LESSER +- ^README.md +- \.gitignore +- ^\.git/.* +- \.*\.lint$ + +builtins: +- appstats: on #/_ah/stats/ +- remote_api: on #/_ah/remote_api/ + +inbound_services: +- channel_presence + +handlers: +- url: /admin/.* + script: main.app + login: admin + +- url: /favicon\.ico + mime_type: image/vnd.microsoft.icon + static_files: static/favicon.ico + upload: static/favicon.ico + +- url: /favicon.png + static_files: /img/favicon.png + upload: /img/favicon.png + +- url: /apple-touch-icon\.png + static_files: static/apple-touch-icon.png + upload: static/apple-touch-icon.png + +- url: /apple-touch-icon-precomposed\.png + static_files: static/apple-touch-icon-precomposed.png + upload: static/apple-touch-icon-precomposed.png + +- url: /(robots\.txt|humans\.txt|crossdomain\.xml) + static_files: static/\1 + upload: static/(robots\.txt|humans\.txt|crossdomain\.xml) + +- url: /boilerplate/css + mime_type: text/css + static_dir: boilerplate/static/css + +- url: /boilerplate/js + mime_type: text/javascript + static_dir: boilerplate/static/js + +- url: /boilerplate/img/(.*\.(gif|png|jpg)) + static_files: boilerplate/static/img/\1 + upload: boilerplate/static/img/(.*\.(gif|png|jpg)) + +- url: /css + mime_type: text/css + static_dir: static/css + +- url: /js + mime_type: text/javascript + static_dir: static/js + +- url: /img/(.*\.(gif|png|jpg)) + static_files: static/img/\1 + upload: static/img/(.*\.(gif|png|jpg)) + +- url: /.* + script: main.app + secure: always + +libraries: +- name: jinja2 + version: "2.6" +- name: webapp2 + version: "2.5.1" +- name: markupsafe + version: "0.15" +- name: pycrypto + version: "2.6" + +error_handlers: + # Only errors with error_code, don't put a default error here + - error_code: over_quota + file: boilerplate/templates/errors/over_quota.html + + - error_code: dos_api_denial + file: boilerplate/templates/errors/dos_api_denial.html + + - error_code: timeout + file: boilerplate/templates/errors/timeout.html + diff --git a/appengine_config.py b/appengine_config.py new file mode 100644 index 0000000..752219a --- /dev/null +++ b/appengine_config.py @@ -0,0 +1,5 @@ +from google.appengine.ext.appstats import recording + +def webapp_add_wsgi_middleware(app): + app = recording.appstats_wsgi_middleware(app) + return app diff --git a/uplusmessaging/controllers/helpers/__init__.py b/boilerplate/__init__.py similarity index 100% rename from uplusmessaging/controllers/helpers/__init__.py rename to boilerplate/__init__.py diff --git a/boilerplate/config.py b/boilerplate/config.py new file mode 100755 index 0000000..ac9013f --- /dev/null +++ b/boilerplate/config.py @@ -0,0 +1,100 @@ +""" +This is the boilerplate default configuration file. +Changes and additions to settings should be done in the config module +located in the application root rather than this config. +""" +config = { + +# webapp2 sessions +'webapp2_extras.sessions' : {'secret_key': '_PUT_KEY_HERE_YOUR_SECRET_KEY_'}, + +# webapp2 authentication +'webapp2_extras.auth' : {'user_model': 'boilerplate.models.User', + 'cookie_name': 'session_name'}, + +# jinja2 templates +'webapp2_extras.jinja2' : {'template_path': ['templates','boilerplate/templates', 'admin/templates'], + 'environment_args': {'extensions': ['jinja2.ext.i18n']}}, + +# application name +'app_name' : "Google App Engine Boilerplate", + +# the default language code for the application. +# should match whatever language the site uses when i18n is disabled +'app_lang' : 'en', + +# Locale code = _ (ie 'en_US') +# to pick locale codes see http://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code +# also see http://www.sil.org/iso639-3/codes.asp +# Language codes defined under iso 639-1 http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +# Territory codes defined under iso 3166-1 alpha-2 http://en.wikipedia.org/wiki/ISO_3166-1 +# disable i18n if locales array is empty or None +'locales' : ['en_US', 'es_ES', 'it_IT', 'zh_CN', 'id_ID', 'fr_FR', 'de_DE', 'ru_RU', 'pt_BR'], + +# contact page email settings +'contact_sender' : "PUT_SENDER_EMAIL_HERE", +'contact_recipient' : "PUT_RECIPIENT_EMAIL_HERE", + +# Password AES Encryption Parameters +'aes_key' : "12_24_32_BYTES_KEY_FOR_PASSWORDS", +'salt' : "_PUT_SALT_HERE_TO_SHA512_PASSWORDS_", + +# get your own consumer key and consumer secret by registering at https://dev.twitter.com/apps +# callback url must be: http://[YOUR DOMAIN]/login/twitter/complete +'twitter_consumer_key' : 'PUT_YOUR_TWITTER_CONSUMER_KEY_HERE', +'twitter_consumer_secret' : 'PUT_YOUR_TWITTER_CONSUMER_SECRET_HERE', + +#Facebook Login +# get your own consumer key and consumer secret by registering at https://developers.facebook.com/apps +#Very Important: set the site_url= your domain in the application settings in the facebook app settings page +# callback url must be: http://[YOUR DOMAIN]/login/facebook/complete +'fb_api_key' : 'PUT_YOUR_FACEBOOK_PUBLIC_KEY_HERE', +'fb_secret' : 'PUT_YOUR_FACEBOOK_PUBLIC_KEY_HERE', + +#Linkedin Login +#Get you own api key and secret from https://www.linkedin.com/secure/developer +'linkedin_api' : 'PUT_YOUR_LINKEDIN_PUBLIC_KEY_HERE', +'linkedin_secret' : 'PUT_YOUR_LINKEDIN_PUBLIC_KEY_HERE', + +# Github login +# Register apps here: https://github.com/settings/applications/new +'github_server' : 'github.com', +'github_redirect_uri' : 'http://www.example.com/social_login/github/complete', +'github_client_id' : 'PUT_YOUR_GITHUB_CLIENT_ID_HERE', +'github_client_secret' : 'PUT_YOUR_GITHUB_CLIENT_SECRET_HERE', + +# get your own recaptcha keys by registering at http://www.google.com/recaptcha/ +'captcha_public_key' : "PUT_YOUR_RECAPCHA_PUBLIC_KEY_HERE", +'captcha_private_key' : "PUT_YOUR_RECAPCHA_PRIVATE_KEY_HERE", + +# Leave blank "google_analytics_domain" if you only want Analytics code +'google_analytics_domain' : "YOUR_PRIMARY_DOMAIN (e.g. google.com)", +'google_analytics_code' : "UA-XXXXX-X", + +# add status codes and templates used to catch and display errors +# if a status code is not listed here it will use the default app engine +# stacktrace error page or browser error page +'error_templates' : { + 403: 'errors/default_error.html', + 404: 'errors/default_error.html', + 500: 'errors/default_error.html', +}, + +# Enable Federated login (OpenID and OAuth) +# Google App Engine Settings must be set to Authentication Options: Federated Login +'enable_federated_login' : True, + +# jinja2 base layout template +'base_layout' : 'base.html', + +# send error emails to developers +'send_mail_developer' : True, + +# fellas' list +'developers' : ( + ('Santa Klauss', 'snowypal@northpole.com'), +), + +# ----> ADD MORE CONFIGURATION OPTIONS HERE <---- + +} # end config diff --git a/boilerplate/external/babel/__init__.py b/boilerplate/external/babel/__init__.py new file mode 100644 index 0000000..5c54827 --- /dev/null +++ b/boilerplate/external/babel/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2008 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Integrated collection of utilities that assist in internationalizing and +localizing applications. + +This package is basically composed of two major parts: + + * tools to build and work with ``gettext`` message catalogs + * a Python interface to the CLDR (Common Locale Data Repository), providing + access to various locale display names, localized number and date + formatting, etc. + +:see: http://www.gnu.org/software/gettext/ +:see: http://docs.python.org/lib/module-gettext.html +:see: http://www.unicode.org/cldr/ +""" + +from babel.core import * + +__docformat__ = 'restructuredtext en' +try: + from pkg_resources import get_distribution, ResolutionError + try: + __version__ = get_distribution('Babel').version + except ResolutionError: + __version__ = None # unknown +except ImportError: + __version__ = None # unknown diff --git a/boilerplate/external/babel/core.py b/boilerplate/external/babel/core.py new file mode 100644 index 0000000..e6ef5db --- /dev/null +++ b/boilerplate/external/babel/core.py @@ -0,0 +1,790 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Core locale representation and locale data access.""" + +import os +import pickle + +from babel import localedata + +__all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale', + 'parse_locale'] +__docformat__ = 'restructuredtext en' + +_global_data = None + +def get_global(key): + """Return the dictionary for the given key in the global data. + + The global data is stored in the ``babel/global.dat`` file and contains + information independent of individual locales. + + >>> get_global('zone_aliases')['UTC'] + 'Etc/GMT' + >>> get_global('zone_territories')['Europe/Berlin'] + 'DE' + + :param key: the data key + :return: the dictionary found in the global data under the given key + :rtype: `dict` + :since: version 0.9 + """ + global _global_data + if _global_data is None: + dirname = os.path.join(os.path.dirname(__file__)) + filename = os.path.join(dirname, 'global.dat') + fileobj = open(filename, 'rb') + try: + _global_data = pickle.load(fileobj) + finally: + fileobj.close() + return _global_data.get(key, {}) + + +LOCALE_ALIASES = { + 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', + 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', + 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', + 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', + 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', + 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', + 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', + 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA' +} + + +class UnknownLocaleError(Exception): + """Exception thrown when a locale is requested for which no locale data + is available. + """ + + def __init__(self, identifier): + """Create the exception. + + :param identifier: the identifier string of the unsupported locale + """ + Exception.__init__(self, 'unknown locale %r' % identifier) + self.identifier = identifier + + +class Locale(object): + """Representation of a specific locale. + + >>> locale = Locale('en', 'US') + >>> repr(locale) + '' + >>> locale.display_name + u'English (United States)' + + A `Locale` object can also be instantiated from a raw locale string: + + >>> locale = Locale.parse('en-US', sep='-') + >>> repr(locale) + '' + + `Locale` objects provide access to a collection of locale data, such as + territory and language names, number and date format patterns, and more: + + >>> locale.number_symbols['decimal'] + u'.' + + If a locale is requested for which no locale data is available, an + `UnknownLocaleError` is raised: + + >>> Locale.parse('en_DE') + Traceback (most recent call last): + ... + UnknownLocaleError: unknown locale 'en_DE' + + :see: `IETF RFC 3066 `_ + """ + + def __init__(self, language, territory=None, script=None, variant=None): + """Initialize the locale object from the given identifier components. + + >>> locale = Locale('en', 'US') + >>> locale.language + 'en' + >>> locale.territory + 'US' + + :param language: the language code + :param territory: the territory (country or region) code + :param script: the script code + :param variant: the variant code + :raise `UnknownLocaleError`: if no locale data is available for the + requested locale + """ + self.language = language + self.territory = territory + self.script = script + self.variant = variant + self.__data = None + + identifier = str(self) + if not localedata.exists(identifier): + raise UnknownLocaleError(identifier) + + def default(cls, category=None, aliases=LOCALE_ALIASES): + """Return the system default locale for the specified category. + + >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']: + ... os.environ[name] = '' + >>> os.environ['LANG'] = 'fr_FR.UTF-8' + >>> Locale.default('LC_MESSAGES') + + + :param category: one of the ``LC_XXX`` environment variable names + :param aliases: a dictionary of aliases for locale identifiers + :return: the value of the variable, or any of the fallbacks + (``LANGUAGE``, ``LC_ALL``, ``LC_CTYPE``, and ``LANG``) + :rtype: `Locale` + :see: `default_locale` + """ + return cls(default_locale(category, aliases=aliases)) + default = classmethod(default) + + def negotiate(cls, preferred, available, sep='_', aliases=LOCALE_ALIASES): + """Find the best match between available and requested locale strings. + + >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT']) + + >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de']) + + >>> Locale.negotiate(['de_DE', 'de'], ['en_US']) + + You can specify the character used in the locale identifiers to separate + the differnet components. This separator is applied to both lists. Also, + case is ignored in the comparison: + + >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-') + + + :param preferred: the list of locale identifers preferred by the user + :param available: the list of locale identifiers available + :param aliases: a dictionary of aliases for locale identifiers + :return: the `Locale` object for the best match, or `None` if no match + was found + :rtype: `Locale` + :see: `negotiate_locale` + """ + identifier = negotiate_locale(preferred, available, sep=sep, + aliases=aliases) + if identifier: + return Locale.parse(identifier, sep=sep) + negotiate = classmethod(negotiate) + + def parse(cls, identifier, sep='_'): + """Create a `Locale` instance for the given locale identifier. + + >>> l = Locale.parse('de-DE', sep='-') + >>> l.display_name + u'Deutsch (Deutschland)' + + If the `identifier` parameter is not a string, but actually a `Locale` + object, that object is returned: + + >>> Locale.parse(l) + + + :param identifier: the locale identifier string + :param sep: optional component separator + :return: a corresponding `Locale` instance + :rtype: `Locale` + :raise `ValueError`: if the string does not appear to be a valid locale + identifier + :raise `UnknownLocaleError`: if no locale data is available for the + requested locale + :see: `parse_locale` + """ + if isinstance(identifier, basestring): + return cls(*parse_locale(identifier, sep=sep)) + return identifier + parse = classmethod(parse) + + def __eq__(self, other): + return str(self) == str(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return '' % str(self) + + def __str__(self): + return '_'.join(filter(None, [self.language, self.script, + self.territory, self.variant])) + + def _data(self): + if self.__data is None: + self.__data = localedata.LocaleDataDict(localedata.load(str(self))) + return self.__data + _data = property(_data) + + def get_display_name(self, locale=None): + """Return the display name of the locale using the given locale. + + The display name will include the language, territory, script, and + variant, if those are specified. + + >>> Locale('zh', 'CN', script='Hans').get_display_name('en') + u'Chinese (Simplified Han, China)' + + :param locale: the locale to use + :return: the display name + """ + if locale is None: + locale = self + locale = Locale.parse(locale) + retval = locale.languages.get(self.language) + if self.territory or self.script or self.variant: + details = [] + if self.script: + details.append(locale.scripts.get(self.script)) + if self.territory: + details.append(locale.territories.get(self.territory)) + if self.variant: + details.append(locale.variants.get(self.variant)) + details = filter(None, details) + if details: + retval += ' (%s)' % u', '.join(details) + return retval + + display_name = property(get_display_name, doc="""\ + The localized display name of the locale. + + >>> Locale('en').display_name + u'English' + >>> Locale('en', 'US').display_name + u'English (United States)' + >>> Locale('sv').display_name + u'svenska' + + :type: `unicode` + """) + + def english_name(self): + return self.get_display_name(Locale('en')) + english_name = property(english_name, doc="""\ + The english display name of the locale. + + >>> Locale('de').english_name + u'German' + >>> Locale('de', 'DE').english_name + u'German (Germany)' + + :type: `unicode` + """) + + #{ General Locale Display Names + + def languages(self): + return self._data['languages'] + languages = property(languages, doc="""\ + Mapping of language codes to translated language names. + + >>> Locale('de', 'DE').languages['ja'] + u'Japanisch' + + :type: `dict` + :see: `ISO 639 `_ + """) + + def scripts(self): + return self._data['scripts'] + scripts = property(scripts, doc="""\ + Mapping of script codes to translated script names. + + >>> Locale('en', 'US').scripts['Hira'] + u'Hiragana' + + :type: `dict` + :see: `ISO 15924 `_ + """) + + def territories(self): + return self._data['territories'] + territories = property(territories, doc="""\ + Mapping of script codes to translated script names. + + >>> Locale('es', 'CO').territories['DE'] + u'Alemania' + + :type: `dict` + :see: `ISO 3166 `_ + """) + + def variants(self): + return self._data['variants'] + variants = property(variants, doc="""\ + Mapping of script codes to translated script names. + + >>> Locale('de', 'DE').variants['1901'] + u'Alte deutsche Rechtschreibung' + + :type: `dict` + """) + + #{ Number Formatting + + def currencies(self): + return self._data['currency_names'] + currencies = property(currencies, doc="""\ + Mapping of currency codes to translated currency names. + + >>> Locale('en').currencies['COP'] + u'Colombian Peso' + >>> Locale('de', 'DE').currencies['COP'] + u'Kolumbianischer Peso' + + :type: `dict` + """) + + def currency_symbols(self): + return self._data['currency_symbols'] + currency_symbols = property(currency_symbols, doc="""\ + Mapping of currency codes to symbols. + + >>> Locale('en', 'US').currency_symbols['USD'] + u'$' + >>> Locale('es', 'CO').currency_symbols['USD'] + u'US$' + + :type: `dict` + """) + + def number_symbols(self): + return self._data['number_symbols'] + number_symbols = property(number_symbols, doc="""\ + Symbols used in number formatting. + + >>> Locale('fr', 'FR').number_symbols['decimal'] + u',' + + :type: `dict` + """) + + def decimal_formats(self): + return self._data['decimal_formats'] + decimal_formats = property(decimal_formats, doc="""\ + Locale patterns for decimal number formatting. + + >>> Locale('en', 'US').decimal_formats[None] + + + :type: `dict` + """) + + def currency_formats(self): + return self._data['currency_formats'] + currency_formats = property(currency_formats, doc=r"""\ + Locale patterns for currency number formatting. + + >>> print Locale('en', 'US').currency_formats[None] + + + :type: `dict` + """) + + def percent_formats(self): + return self._data['percent_formats'] + percent_formats = property(percent_formats, doc="""\ + Locale patterns for percent number formatting. + + >>> Locale('en', 'US').percent_formats[None] + + + :type: `dict` + """) + + def scientific_formats(self): + return self._data['scientific_formats'] + scientific_formats = property(scientific_formats, doc="""\ + Locale patterns for scientific number formatting. + + >>> Locale('en', 'US').scientific_formats[None] + + + :type: `dict` + """) + + #{ Calendar Information and Date Formatting + + def periods(self): + return self._data['periods'] + periods = property(periods, doc="""\ + Locale display names for day periods (AM/PM). + + >>> Locale('en', 'US').periods['am'] + u'AM' + + :type: `dict` + """) + + def days(self): + return self._data['days'] + days = property(days, doc="""\ + Locale display names for weekdays. + + >>> Locale('de', 'DE').days['format']['wide'][3] + u'Donnerstag' + + :type: `dict` + """) + + def months(self): + return self._data['months'] + months = property(months, doc="""\ + Locale display names for months. + + >>> Locale('de', 'DE').months['format']['wide'][10] + u'Oktober' + + :type: `dict` + """) + + def quarters(self): + return self._data['quarters'] + quarters = property(quarters, doc="""\ + Locale display names for quarters. + + >>> Locale('de', 'DE').quarters['format']['wide'][1] + u'1. Quartal' + + :type: `dict` + """) + + def eras(self): + return self._data['eras'] + eras = property(eras, doc="""\ + Locale display names for eras. + + >>> Locale('en', 'US').eras['wide'][1] + u'Anno Domini' + >>> Locale('en', 'US').eras['abbreviated'][0] + u'BC' + + :type: `dict` + """) + + def time_zones(self): + return self._data['time_zones'] + time_zones = property(time_zones, doc="""\ + Locale display names for time zones. + + >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight'] + u'British Summer Time' + >>> Locale('en', 'US').time_zones['America/St_Johns']['city'] + u"St. John's" + + :type: `dict` + """) + + def meta_zones(self): + return self._data['meta_zones'] + meta_zones = property(meta_zones, doc="""\ + Locale display names for meta time zones. + + Meta time zones are basically groups of different Olson time zones that + have the same GMT offset and daylight savings time. + + >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight'] + u'Central European Summer Time' + + :type: `dict` + :since: version 0.9 + """) + + def zone_formats(self): + return self._data['zone_formats'] + zone_formats = property(zone_formats, doc=r"""\ + Patterns related to the formatting of time zones. + + >>> Locale('en', 'US').zone_formats['fallback'] + u'%(1)s (%(0)s)' + >>> Locale('pt', 'BR').zone_formats['region'] + u'Hor\xe1rio %s' + + :type: `dict` + :since: version 0.9 + """) + + def first_week_day(self): + return self._data['week_data']['first_day'] + first_week_day = property(first_week_day, doc="""\ + The first day of a week, with 0 being Monday. + + >>> Locale('de', 'DE').first_week_day + 0 + >>> Locale('en', 'US').first_week_day + 6 + + :type: `int` + """) + + def weekend_start(self): + return self._data['week_data']['weekend_start'] + weekend_start = property(weekend_start, doc="""\ + The day the weekend starts, with 0 being Monday. + + >>> Locale('de', 'DE').weekend_start + 5 + + :type: `int` + """) + + def weekend_end(self): + return self._data['week_data']['weekend_end'] + weekend_end = property(weekend_end, doc="""\ + The day the weekend ends, with 0 being Monday. + + >>> Locale('de', 'DE').weekend_end + 6 + + :type: `int` + """) + + def min_week_days(self): + return self._data['week_data']['min_days'] + min_week_days = property(min_week_days, doc="""\ + The minimum number of days in a week so that the week is counted as the + first week of a year or month. + + >>> Locale('de', 'DE').min_week_days + 4 + + :type: `int` + """) + + def date_formats(self): + return self._data['date_formats'] + date_formats = property(date_formats, doc="""\ + Locale patterns for date formatting. + + >>> Locale('en', 'US').date_formats['short'] + + >>> Locale('fr', 'FR').date_formats['long'] + + + :type: `dict` + """) + + def time_formats(self): + return self._data['time_formats'] + time_formats = property(time_formats, doc="""\ + Locale patterns for time formatting. + + >>> Locale('en', 'US').time_formats['short'] + + >>> Locale('fr', 'FR').time_formats['long'] + + + :type: `dict` + """) + + def datetime_formats(self): + return self._data['datetime_formats'] + datetime_formats = property(datetime_formats, doc="""\ + Locale patterns for datetime formatting. + + >>> Locale('en').datetime_formats[None] + u'{1} {0}' + >>> Locale('th').datetime_formats[None] + u'{1}, {0}' + + :type: `dict` + """) + + +def default_locale(category=None, aliases=LOCALE_ALIASES): + """Returns the system default locale for a given category, based on + environment variables. + + >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']: + ... os.environ[name] = '' + >>> os.environ['LANG'] = 'fr_FR.UTF-8' + >>> default_locale('LC_MESSAGES') + 'fr_FR' + + The "C" or "POSIX" pseudo-locales are treated as aliases for the + "en_US_POSIX" locale: + + >>> os.environ['LC_MESSAGES'] = 'POSIX' + >>> default_locale('LC_MESSAGES') + 'en_US_POSIX' + + :param category: one of the ``LC_XXX`` environment variable names + :param aliases: a dictionary of aliases for locale identifiers + :return: the value of the variable, or any of the fallbacks (``LANGUAGE``, + ``LC_ALL``, ``LC_CTYPE``, and ``LANG``) + :rtype: `str` + """ + varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG') + for name in filter(None, varnames): + locale = os.getenv(name) + if locale: + if name == 'LANGUAGE' and ':' in locale: + # the LANGUAGE variable may contain a colon-separated list of + # language codes; we just pick the language on the list + locale = locale.split(':')[0] + if locale in ('C', 'POSIX'): + locale = 'en_US_POSIX' + elif aliases and locale in aliases: + locale = aliases[locale] + try: + return '_'.join(filter(None, parse_locale(locale))) + except ValueError: + pass + +def negotiate_locale(preferred, available, sep='_', aliases=LOCALE_ALIASES): + """Find the best match between available and requested locale strings. + + >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT']) + 'de_DE' + >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de']) + 'de' + + Case is ignored by the algorithm, the result uses the case of the preferred + locale identifier: + + >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) + 'de_DE' + + >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) + 'de_DE' + + By default, some web browsers unfortunately do not include the territory + in the locale identifier for many locales, and some don't even allow the + user to easily add the territory. So while you may prefer using qualified + locale identifiers in your web-application, they would not normally match + the language-only locale sent by such browsers. To workaround that, this + function uses a default mapping of commonly used langauge-only locale + identifiers to identifiers including the territory: + + >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US']) + 'ja_JP' + + Some browsers even use an incorrect or outdated language code, such as "no" + for Norwegian, where the correct locale identifier would actually be "nb_NO" + (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of + such cases, too: + + >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE']) + 'nb_NO' + + You can override this default mapping by passing a different `aliases` + dictionary to this function, or you can bypass the behavior althogher by + setting the `aliases` parameter to `None`. + + :param preferred: the list of locale strings preferred by the user + :param available: the list of locale strings available + :param sep: character that separates the different parts of the locale + strings + :param aliases: a dictionary of aliases for locale identifiers + :return: the locale identifier for the best match, or `None` if no match + was found + :rtype: `str` + """ + available = [a.lower() for a in available if a] + for locale in preferred: + ll = locale.lower() + if ll in available: + return locale + if aliases: + alias = aliases.get(ll) + if alias: + alias = alias.replace('_', sep) + if alias.lower() in available: + return alias + parts = locale.split(sep) + if len(parts) > 1 and parts[0].lower() in available: + return parts[0] + return None + +def parse_locale(identifier, sep='_'): + """Parse a locale identifier into a tuple of the form:: + + ``(language, territory, script, variant)`` + + >>> parse_locale('zh_CN') + ('zh', 'CN', None, None) + >>> parse_locale('zh_Hans_CN') + ('zh', 'CN', 'Hans', None) + + The default component separator is "_", but a different separator can be + specified using the `sep` parameter: + + >>> parse_locale('zh-CN', sep='-') + ('zh', 'CN', None, None) + + If the identifier cannot be parsed into a locale, a `ValueError` exception + is raised: + + >>> parse_locale('not_a_LOCALE_String') + Traceback (most recent call last): + ... + ValueError: 'not_a_LOCALE_String' is not a valid locale identifier + + Encoding information and locale modifiers are removed from the identifier: + + >>> parse_locale('it_IT@euro') + ('it', 'IT', None, None) + >>> parse_locale('en_US.UTF-8') + ('en', 'US', None, None) + >>> parse_locale('de_DE.iso885915@euro') + ('de', 'DE', None, None) + + :param identifier: the locale identifier string + :param sep: character that separates the different components of the locale + identifier + :return: the ``(language, territory, script, variant)`` tuple + :rtype: `tuple` + :raise `ValueError`: if the string does not appear to be a valid locale + identifier + + :see: `IETF RFC 4646 `_ + """ + if '.' in identifier: + # this is probably the charset/encoding, which we don't care about + identifier = identifier.split('.', 1)[0] + if '@' in identifier: + # this is a locale modifier such as @euro, which we don't care about + # either + identifier = identifier.split('@', 1)[0] + + parts = identifier.split(sep) + lang = parts.pop(0).lower() + if not lang.isalpha(): + raise ValueError('expected only letters, got %r' % lang) + + script = territory = variant = None + if parts: + if len(parts[0]) == 4 and parts[0].isalpha(): + script = parts.pop(0).title() + + if parts: + if len(parts[0]) == 2 and parts[0].isalpha(): + territory = parts.pop(0).upper() + elif len(parts[0]) == 3 and parts[0].isdigit(): + territory = parts.pop(0) + + if parts: + if len(parts[0]) == 4 and parts[0][0].isdigit() or \ + len(parts[0]) >= 5 and parts[0][0].isalpha(): + variant = parts.pop() + + if parts: + raise ValueError('%r is not a valid locale identifier' % identifier) + + return lang, territory, script, variant diff --git a/boilerplate/external/babel/dates.py b/boilerplate/external/babel/dates.py new file mode 100644 index 0000000..a8ac0f1 --- /dev/null +++ b/boilerplate/external/babel/dates.py @@ -0,0 +1,991 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Locale dependent formatting and parsing of dates and times. + +The default locale for the functions in this module is determined by the +following environment variables, in that order: + + * ``LC_TIME``, + * ``LC_ALL``, and + * ``LANG`` +""" + +from datetime import date, datetime, time, timedelta, tzinfo +import re + +from babel.core import default_locale, get_global, Locale +from babel.util import UTC + +__all__ = ['format_date', 'format_datetime', 'format_time', + 'get_timezone_name', 'parse_date', 'parse_datetime', 'parse_time'] +__docformat__ = 'restructuredtext en' + +LC_TIME = default_locale('LC_TIME') + +# Aliases for use in scopes where the modules are shadowed by local variables +date_ = date +datetime_ = datetime +time_ = time + +def get_period_names(locale=LC_TIME): + """Return the names for day periods (AM/PM) used by the locale. + + >>> get_period_names(locale='en_US')['am'] + u'AM' + + :param locale: the `Locale` object, or a locale string + :return: the dictionary of period names + :rtype: `dict` + """ + return Locale.parse(locale).periods + +def get_day_names(width='wide', context='format', locale=LC_TIME): + """Return the day names used by the locale for the specified format. + + >>> get_day_names('wide', locale='en_US')[1] + u'Tuesday' + >>> get_day_names('abbreviated', locale='es')[1] + u'mar' + >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1] + u'D' + + :param width: the width to use, one of "wide", "abbreviated", or "narrow" + :param context: the context, either "format" or "stand-alone" + :param locale: the `Locale` object, or a locale string + :return: the dictionary of day names + :rtype: `dict` + """ + return Locale.parse(locale).days[context][width] + +def get_month_names(width='wide', context='format', locale=LC_TIME): + """Return the month names used by the locale for the specified format. + + >>> get_month_names('wide', locale='en_US')[1] + u'January' + >>> get_month_names('abbreviated', locale='es')[1] + u'ene' + >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1] + u'J' + + :param width: the width to use, one of "wide", "abbreviated", or "narrow" + :param context: the context, either "format" or "stand-alone" + :param locale: the `Locale` object, or a locale string + :return: the dictionary of month names + :rtype: `dict` + """ + return Locale.parse(locale).months[context][width] + +def get_quarter_names(width='wide', context='format', locale=LC_TIME): + """Return the quarter names used by the locale for the specified format. + + >>> get_quarter_names('wide', locale='en_US')[1] + u'1st quarter' + >>> get_quarter_names('abbreviated', locale='de_DE')[1] + u'Q1' + + :param width: the width to use, one of "wide", "abbreviated", or "narrow" + :param context: the context, either "format" or "stand-alone" + :param locale: the `Locale` object, or a locale string + :return: the dictionary of quarter names + :rtype: `dict` + """ + return Locale.parse(locale).quarters[context][width] + +def get_era_names(width='wide', locale=LC_TIME): + """Return the era names used by the locale for the specified format. + + >>> get_era_names('wide', locale='en_US')[1] + u'Anno Domini' + >>> get_era_names('abbreviated', locale='de_DE')[1] + u'n. Chr.' + + :param width: the width to use, either "wide", "abbreviated", or "narrow" + :param locale: the `Locale` object, or a locale string + :return: the dictionary of era names + :rtype: `dict` + """ + return Locale.parse(locale).eras[width] + +def get_date_format(format='medium', locale=LC_TIME): + """Return the date formatting patterns used by the locale for the specified + format. + + >>> get_date_format(locale='en_US') + + >>> get_date_format('full', locale='de_DE') + + + :param format: the format to use, one of "full", "long", "medium", or + "short" + :param locale: the `Locale` object, or a locale string + :return: the date format pattern + :rtype: `DateTimePattern` + """ + return Locale.parse(locale).date_formats[format] + +def get_datetime_format(format='medium', locale=LC_TIME): + """Return the datetime formatting patterns used by the locale for the + specified format. + + >>> get_datetime_format(locale='en_US') + u'{1} {0}' + + :param format: the format to use, one of "full", "long", "medium", or + "short" + :param locale: the `Locale` object, or a locale string + :return: the datetime format pattern + :rtype: `unicode` + """ + patterns = Locale.parse(locale).datetime_formats + if format not in patterns: + format = None + return patterns[format] + +def get_time_format(format='medium', locale=LC_TIME): + """Return the time formatting patterns used by the locale for the specified + format. + + >>> get_time_format(locale='en_US') + + >>> get_time_format('full', locale='de_DE') + + + :param format: the format to use, one of "full", "long", "medium", or + "short" + :param locale: the `Locale` object, or a locale string + :return: the time format pattern + :rtype: `DateTimePattern` + """ + return Locale.parse(locale).time_formats[format] + +def get_timezone_gmt(datetime=None, width='long', locale=LC_TIME): + """Return the timezone associated with the given `datetime` object formatted + as string indicating the offset from GMT. + + >>> dt = datetime(2007, 4, 1, 15, 30) + >>> get_timezone_gmt(dt, locale='en') + u'GMT+00:00' + + >>> from pytz import timezone + >>> tz = timezone('America/Los_Angeles') + >>> dt = datetime(2007, 4, 1, 15, 30, tzinfo=tz) + >>> get_timezone_gmt(dt, locale='en') + u'GMT-08:00' + >>> get_timezone_gmt(dt, 'short', locale='en') + u'-0800' + + The long format depends on the locale, for example in France the acronym + UTC string is used instead of GMT: + + >>> get_timezone_gmt(dt, 'long', locale='fr_FR') + u'UTC-08:00' + + :param datetime: the ``datetime`` object; if `None`, the current date and + time in UTC is used + :param width: either "long" or "short" + :param locale: the `Locale` object, or a locale string + :return: the GMT offset representation of the timezone + :rtype: `unicode` + :since: version 0.9 + """ + if datetime is None: + datetime = datetime_.utcnow() + elif isinstance(datetime, (int, long)): + datetime = datetime_.utcfromtimestamp(datetime).time() + if datetime.tzinfo is None: + datetime = datetime.replace(tzinfo=UTC) + locale = Locale.parse(locale) + + offset = datetime.tzinfo.utcoffset(datetime) + seconds = offset.days * 24 * 60 * 60 + offset.seconds + hours, seconds = divmod(seconds, 3600) + if width == 'short': + pattern = u'%+03d%02d' + else: + pattern = locale.zone_formats['gmt'] % '%+03d:%02d' + return pattern % (hours, seconds // 60) + +def get_timezone_location(dt_or_tzinfo=None, locale=LC_TIME): + """Return a representation of the given timezone using "location format". + + The result depends on both the local display name of the country and the + city associated with the time zone: + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + >>> get_timezone_location(tz, locale='de_DE') + u"Kanada (St. John's)" + >>> tz = timezone('America/Mexico_City') + >>> get_timezone_location(tz, locale='de_DE') + u'Mexiko (Mexiko-Stadt)' + + If the timezone is associated with a country that uses only a single + timezone, just the localized country name is returned: + + >>> tz = timezone('Europe/Berlin') + >>> get_timezone_name(tz, locale='de_DE') + u'Deutschland' + + :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines + the timezone; if `None`, the current date and time in + UTC is assumed + :param locale: the `Locale` object, or a locale string + :return: the localized timezone name using location format + :rtype: `unicode` + :since: version 0.9 + """ + if dt_or_tzinfo is None or isinstance(dt_or_tzinfo, (int, long)): + dt = None + tzinfo = UTC + elif isinstance(dt_or_tzinfo, (datetime, time)): + dt = dt_or_tzinfo + if dt.tzinfo is not None: + tzinfo = dt.tzinfo + else: + tzinfo = UTC + else: + dt = None + tzinfo = dt_or_tzinfo + locale = Locale.parse(locale) + + if hasattr(tzinfo, 'zone'): + zone = tzinfo.zone + else: + zone = tzinfo.tzname(dt or datetime.utcnow()) + + # Get the canonical time-zone code + zone = get_global('zone_aliases').get(zone, zone) + + info = locale.time_zones.get(zone, {}) + + # Otherwise, if there is only one timezone for the country, return the + # localized country name + region_format = locale.zone_formats['region'] + territory = get_global('zone_territories').get(zone) + if territory not in locale.territories: + territory = 'ZZ' # invalid/unknown + territory_name = locale.territories[territory] + if territory and len(get_global('territory_zones').get(territory, [])) == 1: + return region_format % (territory_name) + + # Otherwise, include the city in the output + fallback_format = locale.zone_formats['fallback'] + if 'city' in info: + city_name = info['city'] + else: + metazone = get_global('meta_zones').get(zone) + metazone_info = locale.meta_zones.get(metazone, {}) + if 'city' in metazone_info: + city_name = metainfo['city'] + elif '/' in zone: + city_name = zone.split('/', 1)[1].replace('_', ' ') + else: + city_name = zone.replace('_', ' ') + + return region_format % (fallback_format % { + '0': city_name, + '1': territory_name + }) + +def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False, + locale=LC_TIME): + r"""Return the localized display name for the given timezone. The timezone + may be specified using a ``datetime`` or `tzinfo` object. + + >>> from pytz import timezone + >>> dt = time(15, 30, tzinfo=timezone('America/Los_Angeles')) + >>> get_timezone_name(dt, locale='en_US') + u'Pacific Standard Time' + >>> get_timezone_name(dt, width='short', locale='en_US') + u'PST' + + If this function gets passed only a `tzinfo` object and no concrete + `datetime`, the returned display name is indenpendent of daylight savings + time. This can be used for example for selecting timezones, or to set the + time of events that recur across DST changes: + + >>> tz = timezone('America/Los_Angeles') + >>> get_timezone_name(tz, locale='en_US') + u'Pacific Time' + >>> get_timezone_name(tz, 'short', locale='en_US') + u'PT' + + If no localized display name for the timezone is available, and the timezone + is associated with a country that uses only a single timezone, the name of + that country is returned, formatted according to the locale: + + >>> tz = timezone('Europe/Berlin') + >>> get_timezone_name(tz, locale='de_DE') + u'Deutschland' + >>> get_timezone_name(tz, locale='pt_BR') + u'Hor\xe1rio Alemanha' + + On the other hand, if the country uses multiple timezones, the city is also + included in the representation: + + >>> tz = timezone('America/St_Johns') + >>> get_timezone_name(tz, locale='de_DE') + u"Kanada (St. John's)" + + The `uncommon` parameter can be set to `True` to enable the use of timezone + representations that are not commonly used by the requested locale. For + example, while in French the central European timezone is usually + abbreviated as "HEC", in Canadian French, this abbreviation is not in + common use, so a generic name would be chosen by default: + + >>> tz = timezone('Europe/Paris') + >>> get_timezone_name(tz, 'short', locale='fr_CA') + u'France' + >>> get_timezone_name(tz, 'short', uncommon=True, locale='fr_CA') + u'HEC' + + :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines + the timezone; if a ``tzinfo`` object is used, the + resulting display name will be generic, i.e. + independent of daylight savings time; if `None`, the + current date in UTC is assumed + :param width: either "long" or "short" + :param uncommon: whether even uncommon timezone abbreviations should be used + :param locale: the `Locale` object, or a locale string + :return: the timezone display name + :rtype: `unicode` + :since: version 0.9 + :see: `LDML Appendix J: Time Zone Display Names + `_ + """ + if dt_or_tzinfo is None or isinstance(dt_or_tzinfo, (int, long)): + dt = None + tzinfo = UTC + elif isinstance(dt_or_tzinfo, (datetime, time)): + dt = dt_or_tzinfo + if dt.tzinfo is not None: + tzinfo = dt.tzinfo + else: + tzinfo = UTC + else: + dt = None + tzinfo = dt_or_tzinfo + locale = Locale.parse(locale) + + if hasattr(tzinfo, 'zone'): + zone = tzinfo.zone + else: + zone = tzinfo.tzname(dt) + + # Get the canonical time-zone code + zone = get_global('zone_aliases').get(zone, zone) + + info = locale.time_zones.get(zone, {}) + # Try explicitly translated zone names first + if width in info: + if dt is None: + field = 'generic' + else: + dst = tzinfo.dst(dt) + if dst is None: + field = 'generic' + elif dst == 0: + field = 'standard' + else: + field = 'daylight' + if field in info[width]: + return info[width][field] + + metazone = get_global('meta_zones').get(zone) + if metazone: + metazone_info = locale.meta_zones.get(metazone, {}) + if width in metazone_info and (uncommon or metazone_info.get('common')): + if dt is None: + field = 'generic' + else: + field = tzinfo.dst(dt) and 'daylight' or 'standard' + if field in metazone_info[width]: + return metazone_info[width][field] + + # If we have a concrete datetime, we assume that the result can't be + # independent of daylight savings time, so we return the GMT offset + if dt is not None: + return get_timezone_gmt(dt, width=width, locale=locale) + + return get_timezone_location(dt_or_tzinfo, locale=locale) + +def format_date(date=None, format='medium', locale=LC_TIME): + """Return a date formatted according to the given pattern. + + >>> d = date(2007, 04, 01) + >>> format_date(d, locale='en_US') + u'Apr 1, 2007' + >>> format_date(d, format='full', locale='de_DE') + u'Sonntag, 1. April 2007' + + If you don't want to use the locale default formats, you can specify a + custom date pattern: + + >>> format_date(d, "EEE, MMM d, ''yy", locale='en') + u"Sun, Apr 1, '07" + + :param date: the ``date`` or ``datetime`` object; if `None`, the current + date is used + :param format: one of "full", "long", "medium", or "short", or a custom + date/time pattern + :param locale: a `Locale` object or a locale identifier + :rtype: `unicode` + + :note: If the pattern contains time fields, an `AttributeError` will be + raised when trying to apply the formatting. This is also true if + the value of ``date`` parameter is actually a ``datetime`` object, + as this function automatically converts that to a ``date``. + """ + if date is None: + date = date_.today() + elif isinstance(date, datetime): + date = date.date() + + locale = Locale.parse(locale) + if format in ('full', 'long', 'medium', 'short'): + format = get_date_format(format, locale=locale) + pattern = parse_pattern(format) + return pattern.apply(date, locale) + +def format_datetime(datetime=None, format='medium', tzinfo=None, + locale=LC_TIME): + """Return a date formatted according to the given pattern. + + >>> dt = datetime(2007, 04, 01, 15, 30) + >>> format_datetime(dt, locale='en_US') + u'Apr 1, 2007 3:30:00 PM' + + For any pattern requiring the display of the time-zone, the third-party + ``pytz`` package is needed to explicitly specify the time-zone: + + >>> from pytz import timezone + >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Paris'), + ... locale='fr_FR') + u'dimanche 1 avril 2007 17:30:00 HEC' + >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz", + ... tzinfo=timezone('US/Eastern'), locale='en') + u'2007.04.01 AD at 11:30:00 EDT' + + :param datetime: the `datetime` object; if `None`, the current date and + time is used + :param format: one of "full", "long", "medium", or "short", or a custom + date/time pattern + :param tzinfo: the timezone to apply to the time for display + :param locale: a `Locale` object or a locale identifier + :rtype: `unicode` + """ + if datetime is None: + datetime = datetime_.utcnow() + elif isinstance(datetime, (int, long)): + datetime = datetime_.utcfromtimestamp(datetime) + elif isinstance(datetime, time): + datetime = datetime_.combine(date.today(), datetime) + if datetime.tzinfo is None: + datetime = datetime.replace(tzinfo=UTC) + if tzinfo is not None: + datetime = datetime.astimezone(tzinfo) + if hasattr(tzinfo, 'normalize'): # pytz + datetime = tzinfo.normalize(datetime) + + locale = Locale.parse(locale) + if format in ('full', 'long', 'medium', 'short'): + return get_datetime_format(format, locale=locale) \ + .replace('{0}', format_time(datetime, format, tzinfo=None, + locale=locale)) \ + .replace('{1}', format_date(datetime, format, locale=locale)) + else: + return parse_pattern(format).apply(datetime, locale) + +def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME): + """Return a time formatted according to the given pattern. + + >>> t = time(15, 30) + >>> format_time(t, locale='en_US') + u'3:30:00 PM' + >>> format_time(t, format='short', locale='de_DE') + u'15:30' + + If you don't want to use the locale default formats, you can specify a + custom time pattern: + + >>> format_time(t, "hh 'o''clock' a", locale='en') + u"03 o'clock PM" + + For any pattern requiring the display of the time-zone, the third-party + ``pytz`` package is needed to explicitly specify the time-zone: + + >>> from pytz import timezone + >>> t = datetime(2007, 4, 1, 15, 30) + >>> tzinfo = timezone('Europe/Paris') + >>> t = tzinfo.localize(t) + >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR') + u'15:30:00 HEC' + >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'), + ... locale='en') + u"09 o'clock AM, Eastern Daylight Time" + + As that example shows, when this function gets passed a + ``datetime.datetime`` value, the actual time in the formatted string is + adjusted to the timezone specified by the `tzinfo` parameter. If the + ``datetime`` is "naive" (i.e. it has no associated timezone information), + it is assumed to be in UTC. + + These timezone calculations are **not** performed if the value is of type + ``datetime.time``, as without date information there's no way to determine + what a given time would translate to in a different timezone without + information about whether daylight savings time is in effect or not. This + means that time values are left as-is, and the value of the `tzinfo` + parameter is only used to display the timezone name if needed: + + >>> t = time(15, 30) + >>> format_time(t, format='full', tzinfo=timezone('Europe/Paris'), + ... locale='fr_FR') + u'15:30:00 HEC' + >>> format_time(t, format='full', tzinfo=timezone('US/Eastern'), + ... locale='en_US') + u'3:30:00 PM ET' + + :param time: the ``time`` or ``datetime`` object; if `None`, the current + time in UTC is used + :param format: one of "full", "long", "medium", or "short", or a custom + date/time pattern + :param tzinfo: the time-zone to apply to the time for display + :param locale: a `Locale` object or a locale identifier + :rtype: `unicode` + + :note: If the pattern contains date fields, an `AttributeError` will be + raised when trying to apply the formatting. This is also true if + the value of ``time`` parameter is actually a ``datetime`` object, + as this function automatically converts that to a ``time``. + """ + if time is None: + time = datetime.utcnow() + elif isinstance(time, (int, long)): + time = datetime.utcfromtimestamp(time) + if time.tzinfo is None: + time = time.replace(tzinfo=UTC) + if isinstance(time, datetime): + if tzinfo is not None: + time = time.astimezone(tzinfo) + if hasattr(tzinfo, 'normalize'): # pytz + time = tzinfo.normalize(time) + time = time.timetz() + elif tzinfo is not None: + time = time.replace(tzinfo=tzinfo) + + locale = Locale.parse(locale) + if format in ('full', 'long', 'medium', 'short'): + format = get_time_format(format, locale=locale) + return parse_pattern(format).apply(time, locale) + +def parse_date(string, locale=LC_TIME): + """Parse a date from a string. + + This function uses the date format for the locale as a hint to determine + the order in which the date fields appear in the string. + + >>> parse_date('4/1/04', locale='en_US') + datetime.date(2004, 4, 1) + >>> parse_date('01.04.2004', locale='de_DE') + datetime.date(2004, 4, 1) + + :param string: the string containing the date + :param locale: a `Locale` object or a locale identifier + :return: the parsed date + :rtype: `date` + """ + # TODO: try ISO format first? + format = get_date_format(locale=locale).pattern.lower() + year_idx = format.index('y') + month_idx = format.index('m') + if month_idx < 0: + month_idx = format.index('l') + day_idx = format.index('d') + + indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')] + indexes.sort() + indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)]) + + # FIXME: this currently only supports numbers, but should also support month + # names, both in the requested locale, and english + + numbers = re.findall('(\d+)', string) + year = numbers[indexes['Y']] + if len(year) == 2: + year = 2000 + int(year) + else: + year = int(year) + month = int(numbers[indexes['M']]) + day = int(numbers[indexes['D']]) + if month > 12: + month, day = day, month + return date(year, month, day) + +def parse_datetime(string, locale=LC_TIME): + """Parse a date and time from a string. + + This function uses the date and time formats for the locale as a hint to + determine the order in which the time fields appear in the string. + + :param string: the string containing the date and time + :param locale: a `Locale` object or a locale identifier + :return: the parsed date/time + :rtype: `datetime` + """ + raise NotImplementedError + +def parse_time(string, locale=LC_TIME): + """Parse a time from a string. + + This function uses the time format for the locale as a hint to determine + the order in which the time fields appear in the string. + + >>> parse_time('15:30:00', locale='en_US') + datetime.time(15, 30) + + :param string: the string containing the time + :param locale: a `Locale` object or a locale identifier + :return: the parsed time + :rtype: `time` + """ + # TODO: try ISO format first? + format = get_time_format(locale=locale).pattern.lower() + hour_idx = format.index('h') + if hour_idx < 0: + hour_idx = format.index('k') + min_idx = format.index('m') + sec_idx = format.index('s') + + indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')] + indexes.sort() + indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)]) + + # FIXME: support 12 hour clock, and 0-based hour specification + # and seconds should be optional, maybe minutes too + # oh, and time-zones, of course + + numbers = re.findall('(\d+)', string) + hour = int(numbers[indexes['H']]) + minute = int(numbers[indexes['M']]) + second = int(numbers[indexes['S']]) + return time(hour, minute, second) + + +class DateTimePattern(object): + + def __init__(self, pattern, format): + self.pattern = pattern + self.format = format + + def __repr__(self): + return '<%s %r>' % (type(self).__name__, self.pattern) + + def __unicode__(self): + return self.pattern + + def __mod__(self, other): + assert type(other) is DateTimeFormat + return self.format % other + + def apply(self, datetime, locale): + return self % DateTimeFormat(datetime, locale) + + +class DateTimeFormat(object): + + def __init__(self, value, locale): + assert isinstance(value, (date, datetime, time)) + if isinstance(value, (datetime, time)) and value.tzinfo is None: + value = value.replace(tzinfo=UTC) + self.value = value + self.locale = Locale.parse(locale) + + def __getitem__(self, name): + char = name[0] + num = len(name) + if char == 'G': + return self.format_era(char, num) + elif char in ('y', 'Y', 'u'): + return self.format_year(char, num) + elif char in ('Q', 'q'): + return self.format_quarter(char, num) + elif char in ('M', 'L'): + return self.format_month(char, num) + elif char in ('w', 'W'): + return self.format_week(char, num) + elif char == 'd': + return self.format(self.value.day, num) + elif char == 'D': + return self.format_day_of_year(num) + elif char == 'F': + return self.format_day_of_week_in_month() + elif char in ('E', 'e', 'c'): + return self.format_weekday(char, num) + elif char == 'a': + return self.format_period(char) + elif char == 'h': + if self.value.hour % 12 == 0: + return self.format(12, num) + else: + return self.format(self.value.hour % 12, num) + elif char == 'H': + return self.format(self.value.hour, num) + elif char == 'K': + return self.format(self.value.hour % 12, num) + elif char == 'k': + if self.value.hour == 0: + return self.format(24, num) + else: + return self.format(self.value.hour, num) + elif char == 'm': + return self.format(self.value.minute, num) + elif char == 's': + return self.format(self.value.second, num) + elif char == 'S': + return self.format_frac_seconds(num) + elif char == 'A': + return self.format_milliseconds_in_day(num) + elif char in ('z', 'Z', 'v', 'V'): + return self.format_timezone(char, num) + else: + raise KeyError('Unsupported date/time field %r' % char) + + def format_era(self, char, num): + width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] + era = int(self.value.year >= 0) + return get_era_names(width, self.locale)[era] + + def format_year(self, char, num): + value = self.value.year + if char.isupper(): + week = self.get_week_number(self.get_day_of_year()) + if week == 0: + value -= 1 + year = self.format(value, num) + if num == 2: + year = year[-2:] + return year + + def format_quarter(self, char, num): + quarter = (self.value.month - 1) // 3 + 1 + if num <= 2: + return ('%%0%dd' % num) % quarter + width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] + context = {'Q': 'format', 'q': 'stand-alone'}[char] + return get_quarter_names(width, context, self.locale)[quarter] + + def format_month(self, char, num): + if num <= 2: + return ('%%0%dd' % num) % self.value.month + width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] + context = {'M': 'format', 'L': 'stand-alone'}[char] + return get_month_names(width, context, self.locale)[self.value.month] + + def format_week(self, char, num): + if char.islower(): # week of year + day_of_year = self.get_day_of_year() + week = self.get_week_number(day_of_year) + if week == 0: + date = self.value - timedelta(days=day_of_year) + week = self.get_week_number(self.get_day_of_year(date), + date.weekday()) + return self.format(week, num) + else: # week of month + week = self.get_week_number(self.value.day) + if week == 0: + date = self.value - timedelta(days=self.value.day) + week = self.get_week_number(date.day, date.weekday()) + pass + return '%d' % week + + def format_weekday(self, char, num): + if num < 3: + if char.islower(): + value = 7 - self.locale.first_week_day + self.value.weekday() + return self.format(value % 7 + 1, num) + num = 3 + weekday = self.value.weekday() + width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] + context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] + return get_day_names(width, context, self.locale)[weekday] + + def format_day_of_year(self, num): + return self.format(self.get_day_of_year(), num) + + def format_day_of_week_in_month(self): + return '%d' % ((self.value.day - 1) / 7 + 1) + + def format_period(self, char): + period = {0: 'am', 1: 'pm'}[int(self.value.hour >= 12)] + return get_period_names(locale=self.locale)[period] + + def format_frac_seconds(self, num): + value = str(self.value.microsecond) + return self.format(round(float('.%s' % value), num) * 10**num, num) + + def format_milliseconds_in_day(self, num): + msecs = self.value.microsecond // 1000 + self.value.second * 1000 + \ + self.value.minute * 60000 + self.value.hour * 3600000 + return self.format(msecs, num) + + def format_timezone(self, char, num): + width = {3: 'short', 4: 'long'}[max(3, num)] + if char == 'z': + return get_timezone_name(self.value, width, locale=self.locale) + elif char == 'Z': + return get_timezone_gmt(self.value, width, locale=self.locale) + elif char == 'v': + return get_timezone_name(self.value.tzinfo, width, + locale=self.locale) + elif char == 'V': + if num == 1: + return get_timezone_name(self.value.tzinfo, width, + uncommon=True, locale=self.locale) + return get_timezone_location(self.value.tzinfo, locale=self.locale) + + def format(self, value, length): + return ('%%0%dd' % length) % value + + def get_day_of_year(self, date=None): + if date is None: + date = self.value + return (date - date_(date.year, 1, 1)).days + 1 + + def get_week_number(self, day_of_period, day_of_week=None): + """Return the number of the week of a day within a period. This may be + the week number in a year or the week number in a month. + + Usually this will return a value equal to or greater than 1, but if the + first week of the period is so short that it actually counts as the last + week of the previous period, this function will return 0. + + >>> format = DateTimeFormat(date(2006, 1, 8), Locale.parse('de_DE')) + >>> format.get_week_number(6) + 1 + + >>> format = DateTimeFormat(date(2006, 1, 8), Locale.parse('en_US')) + >>> format.get_week_number(6) + 2 + + :param day_of_period: the number of the day in the period (usually + either the day of month or the day of year) + :param day_of_week: the week day; if ommitted, the week day of the + current date is assumed + """ + if day_of_week is None: + day_of_week = self.value.weekday() + first_day = (day_of_week - self.locale.first_week_day - + day_of_period + 1) % 7 + if first_day < 0: + first_day += 7 + week_number = (day_of_period + first_day - 1) / 7 + if 7 - first_day >= self.locale.min_week_days: + week_number += 1 + return week_number + + +PATTERN_CHARS = { + 'G': [1, 2, 3, 4, 5], # era + 'y': None, 'Y': None, 'u': None, # year + 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter + 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month + 'w': [1, 2], 'W': [1], # week + 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day + 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day + 'a': [1], # period + 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour + 'm': [1, 2], # minute + 's': [1, 2], 'S': None, 'A': None, # second + 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4], 'V': [1, 4] # zone +} + +def parse_pattern(pattern): + """Parse date, time, and datetime format patterns. + + >>> parse_pattern("MMMMd").format + u'%(MMMM)s%(d)s' + >>> parse_pattern("MMM d, yyyy").format + u'%(MMM)s %(d)s, %(yyyy)s' + + Pattern can contain literal strings in single quotes: + + >>> parse_pattern("H:mm' Uhr 'z").format + u'%(H)s:%(mm)s Uhr %(z)s' + + An actual single quote can be used by using two adjacent single quote + characters: + + >>> parse_pattern("hh' o''clock'").format + u"%(hh)s o'clock" + + :param pattern: the formatting pattern to parse + """ + if type(pattern) is DateTimePattern: + return pattern + + result = [] + quotebuf = None + charbuf = [] + fieldchar = [''] + fieldnum = [0] + + def append_chars(): + result.append(''.join(charbuf).replace('%', '%%')) + del charbuf[:] + + def append_field(): + limit = PATTERN_CHARS[fieldchar[0]] + if limit and fieldnum[0] not in limit: + raise ValueError('Invalid length for field: %r' + % (fieldchar[0] * fieldnum[0])) + result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) + fieldchar[0] = '' + fieldnum[0] = 0 + + for idx, char in enumerate(pattern.replace("''", '\0')): + if quotebuf is None: + if char == "'": # quote started + if fieldchar[0]: + append_field() + elif charbuf: + append_chars() + quotebuf = [] + elif char in PATTERN_CHARS: + if charbuf: + append_chars() + if char == fieldchar[0]: + fieldnum[0] += 1 + else: + if fieldchar[0]: + append_field() + fieldchar[0] = char + fieldnum[0] = 1 + else: + if fieldchar[0]: + append_field() + charbuf.append(char) + + elif quotebuf is not None: + if char == "'": # end of quote + charbuf.extend(quotebuf) + quotebuf = None + else: # inside quote + quotebuf.append(char) + + if fieldchar[0]: + append_field() + elif charbuf: + append_chars() + + return DateTimePattern(pattern, u''.join(result).replace('\0', "'")) diff --git a/boilerplate/external/babel/global.dat b/boilerplate/external/babel/global.dat new file mode 100644 index 0000000..aadb90d Binary files /dev/null and b/boilerplate/external/babel/global.dat differ diff --git a/boilerplate/external/babel/localedata.py b/boilerplate/external/babel/localedata.py new file mode 100644 index 0000000..9e70f0b --- /dev/null +++ b/boilerplate/external/babel/localedata.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Low-level locale data access. + +:note: The `Locale` class, which uses this module under the hood, provides a + more convenient interface for accessing the locale data. +""" + +import os +import pickle +try: + import threading +except ImportError: + import dummy_threading as threading +from UserDict import DictMixin + +__all__ = ['exists', 'list', 'load'] +__docformat__ = 'restructuredtext en' + +_cache = {} +_cache_lock = threading.RLock() +_dirname = os.path.join(os.path.dirname(__file__), 'localedata') + + +def exists(name): + """Check whether locale data is available for the given locale. + + :param name: the locale identifier string + :return: `True` if the locale data exists, `False` otherwise + :rtype: `bool` + """ + if name in _cache: + return True + return os.path.exists(os.path.join(_dirname, '%s.dat' % name)) + + +def list(): + """Return a list of all locale identifiers for which locale data is + available. + + :return: a list of locale identifiers (strings) + :rtype: `list` + :since: version 0.8.1 + """ + return [stem for stem, extension in [ + os.path.splitext(filename) for filename in os.listdir(_dirname) + ] if extension == '.dat' and stem != 'root'] + + +def load(name, merge_inherited=True): + """Load the locale data for the given locale. + + The locale data is a dictionary that contains much of the data defined by + the Common Locale Data Repository (CLDR). This data is stored as a + collection of pickle files inside the ``babel`` package. + + >>> d = load('en_US') + >>> d['languages']['sv'] + u'Swedish' + + Note that the results are cached, and subsequent requests for the same + locale return the same dictionary: + + >>> d1 = load('en_US') + >>> d2 = load('en_US') + >>> d1 is d2 + True + + :param name: the locale identifier string (or "root") + :param merge_inherited: whether the inherited data should be merged into + the data of the requested locale + :return: the locale data + :rtype: `dict` + :raise `IOError`: if no locale data file is found for the given locale + identifer, or one of the locales it inherits from + """ + _cache_lock.acquire() + try: + data = _cache.get(name) + if not data: + # Load inherited data + if name == 'root' or not merge_inherited: + data = {} + else: + parts = name.split('_') + if len(parts) == 1: + parent = 'root' + else: + parent = '_'.join(parts[:-1]) + data = load(parent).copy() + filename = os.path.join(_dirname, '%s.dat' % name) + fileobj = open(filename, 'rb') + try: + if name != 'root' and merge_inherited: + merge(data, pickle.load(fileobj)) + else: + data = pickle.load(fileobj) + _cache[name] = data + finally: + fileobj.close() + return data + finally: + _cache_lock.release() + + +def merge(dict1, dict2): + """Merge the data from `dict2` into the `dict1` dictionary, making copies + of nested dictionaries. + + >>> d = {1: 'foo', 3: 'baz'} + >>> merge(d, {1: 'Foo', 2: 'Bar'}) + >>> items = d.items(); items.sort(); items + [(1, 'Foo'), (2, 'Bar'), (3, 'baz')] + + :param dict1: the dictionary to merge into + :param dict2: the dictionary containing the data that should be merged + """ + for key, val2 in dict2.items(): + if val2 is not None: + val1 = dict1.get(key) + if isinstance(val2, dict): + if val1 is None: + val1 = {} + if isinstance(val1, Alias): + val1 = (val1, val2) + elif isinstance(val1, tuple): + alias, others = val1 + others = others.copy() + merge(others, val2) + val1 = (alias, others) + else: + val1 = val1.copy() + merge(val1, val2) + else: + val1 = val2 + dict1[key] = val1 + + +class Alias(object): + """Representation of an alias in the locale data. + + An alias is a value that refers to some other part of the locale data, + as specified by the `keys`. + """ + + def __init__(self, keys): + self.keys = tuple(keys) + + def __repr__(self): + return '<%s %r>' % (type(self).__name__, self.keys) + + def resolve(self, data): + """Resolve the alias based on the given data. + + This is done recursively, so if one alias resolves to a second alias, + that second alias will also be resolved. + + :param data: the locale data + :type data: `dict` + """ + base = data + for key in self.keys: + data = data[key] + if isinstance(data, Alias): + data = data.resolve(base) + elif isinstance(data, tuple): + alias, others = data + data = alias.resolve(base) + return data + + +class LocaleDataDict(DictMixin, dict): + """Dictionary wrapper that automatically resolves aliases to the actual + values. + """ + + def __init__(self, data, base=None): + dict.__init__(self, data) + if base is None: + base = data + self.base = base + + def __getitem__(self, key): + orig = val = dict.__getitem__(self, key) + if isinstance(val, Alias): # resolve an alias + val = val.resolve(self.base) + if isinstance(val, tuple): # Merge a partial dict with an alias + alias, others = val + val = alias.resolve(self.base).copy() + merge(val, others) + if type(val) is dict: # Return a nested alias-resolving dict + val = LocaleDataDict(val, base=self.base) + if val is not orig: + self[key] = val + return val + + def copy(self): + return LocaleDataDict(dict.copy(self), base=self.base) diff --git a/boilerplate/external/babel/localedata/aa.dat b/boilerplate/external/babel/localedata/aa.dat new file mode 100644 index 0000000..4285296 Binary files /dev/null and b/boilerplate/external/babel/localedata/aa.dat differ diff --git a/boilerplate/external/babel/localedata/aa_DJ.dat b/boilerplate/external/babel/localedata/aa_DJ.dat new file mode 100644 index 0000000..1f3ee97 Binary files /dev/null and b/boilerplate/external/babel/localedata/aa_DJ.dat differ diff --git a/boilerplate/external/babel/localedata/aa_ER.dat b/boilerplate/external/babel/localedata/aa_ER.dat new file mode 100644 index 0000000..e0f2275 Binary files /dev/null and b/boilerplate/external/babel/localedata/aa_ER.dat differ diff --git a/boilerplate/external/babel/localedata/aa_ER_SAAHO.dat b/boilerplate/external/babel/localedata/aa_ER_SAAHO.dat new file mode 100644 index 0000000..47256c5 Binary files /dev/null and b/boilerplate/external/babel/localedata/aa_ER_SAAHO.dat differ diff --git a/boilerplate/external/babel/localedata/aa_ET.dat b/boilerplate/external/babel/localedata/aa_ET.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/aa_ET.dat differ diff --git a/boilerplate/external/babel/localedata/af.dat b/boilerplate/external/babel/localedata/af.dat new file mode 100644 index 0000000..cd419dc Binary files /dev/null and b/boilerplate/external/babel/localedata/af.dat differ diff --git a/boilerplate/external/babel/localedata/af_NA.dat b/boilerplate/external/babel/localedata/af_NA.dat new file mode 100644 index 0000000..f4c7471 Binary files /dev/null and b/boilerplate/external/babel/localedata/af_NA.dat differ diff --git a/boilerplate/external/babel/localedata/af_ZA.dat b/boilerplate/external/babel/localedata/af_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/af_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/ak.dat b/boilerplate/external/babel/localedata/ak.dat new file mode 100644 index 0000000..2bf97a3 Binary files /dev/null and b/boilerplate/external/babel/localedata/ak.dat differ diff --git a/boilerplate/external/babel/localedata/ak_GH.dat b/boilerplate/external/babel/localedata/ak_GH.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ak_GH.dat differ diff --git a/boilerplate/external/babel/localedata/am.dat b/boilerplate/external/babel/localedata/am.dat new file mode 100644 index 0000000..4c44c22 Binary files /dev/null and b/boilerplate/external/babel/localedata/am.dat differ diff --git a/boilerplate/external/babel/localedata/am_ET.dat b/boilerplate/external/babel/localedata/am_ET.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/am_ET.dat differ diff --git a/boilerplate/external/babel/localedata/ar.dat b/boilerplate/external/babel/localedata/ar.dat new file mode 100644 index 0000000..ddf9359 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar.dat differ diff --git a/boilerplate/external/babel/localedata/ar_AE.dat b/boilerplate/external/babel/localedata/ar_AE.dat new file mode 100644 index 0000000..c3e7bc1 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_AE.dat differ diff --git a/boilerplate/external/babel/localedata/ar_BH.dat b/boilerplate/external/babel/localedata/ar_BH.dat new file mode 100644 index 0000000..17fc057 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_BH.dat differ diff --git a/boilerplate/external/babel/localedata/ar_DZ.dat b/boilerplate/external/babel/localedata/ar_DZ.dat new file mode 100644 index 0000000..5a7309d Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_DZ.dat differ diff --git a/boilerplate/external/babel/localedata/ar_EG.dat b/boilerplate/external/babel/localedata/ar_EG.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_EG.dat differ diff --git a/boilerplate/external/babel/localedata/ar_IQ.dat b/boilerplate/external/babel/localedata/ar_IQ.dat new file mode 100644 index 0000000..17fc057 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_IQ.dat differ diff --git a/boilerplate/external/babel/localedata/ar_JO.dat b/boilerplate/external/babel/localedata/ar_JO.dat new file mode 100644 index 0000000..ae633e9 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_JO.dat differ diff --git a/boilerplate/external/babel/localedata/ar_KW.dat b/boilerplate/external/babel/localedata/ar_KW.dat new file mode 100644 index 0000000..17fc057 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_KW.dat differ diff --git a/boilerplate/external/babel/localedata/ar_LB.dat b/boilerplate/external/babel/localedata/ar_LB.dat new file mode 100644 index 0000000..30fdd21 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_LB.dat differ diff --git a/boilerplate/external/babel/localedata/ar_LY.dat b/boilerplate/external/babel/localedata/ar_LY.dat new file mode 100644 index 0000000..17fc057 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_LY.dat differ diff --git a/boilerplate/external/babel/localedata/ar_MA.dat b/boilerplate/external/babel/localedata/ar_MA.dat new file mode 100644 index 0000000..5a7309d Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_MA.dat differ diff --git a/boilerplate/external/babel/localedata/ar_OM.dat b/boilerplate/external/babel/localedata/ar_OM.dat new file mode 100644 index 0000000..17fc057 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_OM.dat differ diff --git a/boilerplate/external/babel/localedata/ar_QA.dat b/boilerplate/external/babel/localedata/ar_QA.dat new file mode 100644 index 0000000..540ff0c Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_QA.dat differ diff --git a/boilerplate/external/babel/localedata/ar_SA.dat b/boilerplate/external/babel/localedata/ar_SA.dat new file mode 100644 index 0000000..540ff0c Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_SA.dat differ diff --git a/boilerplate/external/babel/localedata/ar_SD.dat b/boilerplate/external/babel/localedata/ar_SD.dat new file mode 100644 index 0000000..17fc057 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_SD.dat differ diff --git a/boilerplate/external/babel/localedata/ar_SY.dat b/boilerplate/external/babel/localedata/ar_SY.dat new file mode 100644 index 0000000..dda3e63 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_SY.dat differ diff --git a/boilerplate/external/babel/localedata/ar_TN.dat b/boilerplate/external/babel/localedata/ar_TN.dat new file mode 100644 index 0000000..7f5d124 Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_TN.dat differ diff --git a/boilerplate/external/babel/localedata/ar_YE.dat b/boilerplate/external/babel/localedata/ar_YE.dat new file mode 100644 index 0000000..540ff0c Binary files /dev/null and b/boilerplate/external/babel/localedata/ar_YE.dat differ diff --git a/boilerplate/external/babel/localedata/as.dat b/boilerplate/external/babel/localedata/as.dat new file mode 100644 index 0000000..cc759ba Binary files /dev/null and b/boilerplate/external/babel/localedata/as.dat differ diff --git a/boilerplate/external/babel/localedata/as_IN.dat b/boilerplate/external/babel/localedata/as_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/as_IN.dat differ diff --git a/boilerplate/external/babel/localedata/az.dat b/boilerplate/external/babel/localedata/az.dat new file mode 100644 index 0000000..e71371a Binary files /dev/null and b/boilerplate/external/babel/localedata/az.dat differ diff --git a/boilerplate/external/babel/localedata/az_AZ.dat b/boilerplate/external/babel/localedata/az_AZ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/az_AZ.dat differ diff --git a/boilerplate/external/babel/localedata/az_Cyrl.dat b/boilerplate/external/babel/localedata/az_Cyrl.dat new file mode 100644 index 0000000..cfb4bd7 Binary files /dev/null and b/boilerplate/external/babel/localedata/az_Cyrl.dat differ diff --git a/boilerplate/external/babel/localedata/az_Cyrl_AZ.dat b/boilerplate/external/babel/localedata/az_Cyrl_AZ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/az_Cyrl_AZ.dat differ diff --git a/boilerplate/external/babel/localedata/az_Latn.dat b/boilerplate/external/babel/localedata/az_Latn.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/az_Latn.dat differ diff --git a/boilerplate/external/babel/localedata/az_Latn_AZ.dat b/boilerplate/external/babel/localedata/az_Latn_AZ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/az_Latn_AZ.dat differ diff --git a/boilerplate/external/babel/localedata/be.dat b/boilerplate/external/babel/localedata/be.dat new file mode 100644 index 0000000..1592f10 Binary files /dev/null and b/boilerplate/external/babel/localedata/be.dat differ diff --git a/boilerplate/external/babel/localedata/be_BY.dat b/boilerplate/external/babel/localedata/be_BY.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/be_BY.dat differ diff --git a/boilerplate/external/babel/localedata/bg.dat b/boilerplate/external/babel/localedata/bg.dat new file mode 100644 index 0000000..eba5034 Binary files /dev/null and b/boilerplate/external/babel/localedata/bg.dat differ diff --git a/boilerplate/external/babel/localedata/bg_BG.dat b/boilerplate/external/babel/localedata/bg_BG.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/bg_BG.dat differ diff --git a/boilerplate/external/babel/localedata/bn.dat b/boilerplate/external/babel/localedata/bn.dat new file mode 100644 index 0000000..5d41b21 Binary files /dev/null and b/boilerplate/external/babel/localedata/bn.dat differ diff --git a/boilerplate/external/babel/localedata/bn_BD.dat b/boilerplate/external/babel/localedata/bn_BD.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/bn_BD.dat differ diff --git a/boilerplate/external/babel/localedata/bn_IN.dat b/boilerplate/external/babel/localedata/bn_IN.dat new file mode 100644 index 0000000..7bac36e Binary files /dev/null and b/boilerplate/external/babel/localedata/bn_IN.dat differ diff --git a/boilerplate/external/babel/localedata/bs.dat b/boilerplate/external/babel/localedata/bs.dat new file mode 100644 index 0000000..73b67ed Binary files /dev/null and b/boilerplate/external/babel/localedata/bs.dat differ diff --git a/boilerplate/external/babel/localedata/bs_BA.dat b/boilerplate/external/babel/localedata/bs_BA.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/bs_BA.dat differ diff --git a/boilerplate/external/babel/localedata/byn.dat b/boilerplate/external/babel/localedata/byn.dat new file mode 100644 index 0000000..735c272 Binary files /dev/null and b/boilerplate/external/babel/localedata/byn.dat differ diff --git a/boilerplate/external/babel/localedata/byn_ER.dat b/boilerplate/external/babel/localedata/byn_ER.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/byn_ER.dat differ diff --git a/boilerplate/external/babel/localedata/ca.dat b/boilerplate/external/babel/localedata/ca.dat new file mode 100644 index 0000000..9b82a5c Binary files /dev/null and b/boilerplate/external/babel/localedata/ca.dat differ diff --git a/boilerplate/external/babel/localedata/ca_ES.dat b/boilerplate/external/babel/localedata/ca_ES.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ca_ES.dat differ diff --git a/boilerplate/external/babel/localedata/cch.dat b/boilerplate/external/babel/localedata/cch.dat new file mode 100644 index 0000000..a5d378e Binary files /dev/null and b/boilerplate/external/babel/localedata/cch.dat differ diff --git a/boilerplate/external/babel/localedata/cch_NG.dat b/boilerplate/external/babel/localedata/cch_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/cch_NG.dat differ diff --git a/boilerplate/external/babel/localedata/cop.dat b/boilerplate/external/babel/localedata/cop.dat new file mode 100644 index 0000000..63c2a7b Binary files /dev/null and b/boilerplate/external/babel/localedata/cop.dat differ diff --git a/boilerplate/external/babel/localedata/cs.dat b/boilerplate/external/babel/localedata/cs.dat new file mode 100644 index 0000000..7ef0f10 Binary files /dev/null and b/boilerplate/external/babel/localedata/cs.dat differ diff --git a/boilerplate/external/babel/localedata/cs_CZ.dat b/boilerplate/external/babel/localedata/cs_CZ.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/cs_CZ.dat differ diff --git a/boilerplate/external/babel/localedata/cy.dat b/boilerplate/external/babel/localedata/cy.dat new file mode 100644 index 0000000..2286e84 Binary files /dev/null and b/boilerplate/external/babel/localedata/cy.dat differ diff --git a/boilerplate/external/babel/localedata/cy_GB.dat b/boilerplate/external/babel/localedata/cy_GB.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/cy_GB.dat differ diff --git a/boilerplate/external/babel/localedata/da.dat b/boilerplate/external/babel/localedata/da.dat new file mode 100644 index 0000000..74caf84 Binary files /dev/null and b/boilerplate/external/babel/localedata/da.dat differ diff --git a/boilerplate/external/babel/localedata/da_DK.dat b/boilerplate/external/babel/localedata/da_DK.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/da_DK.dat differ diff --git a/boilerplate/external/babel/localedata/de.dat b/boilerplate/external/babel/localedata/de.dat new file mode 100644 index 0000000..030f9b0 Binary files /dev/null and b/boilerplate/external/babel/localedata/de.dat differ diff --git a/boilerplate/external/babel/localedata/de_AT.dat b/boilerplate/external/babel/localedata/de_AT.dat new file mode 100644 index 0000000..962c059 Binary files /dev/null and b/boilerplate/external/babel/localedata/de_AT.dat differ diff --git a/boilerplate/external/babel/localedata/de_BE.dat b/boilerplate/external/babel/localedata/de_BE.dat new file mode 100644 index 0000000..08a0118 Binary files /dev/null and b/boilerplate/external/babel/localedata/de_BE.dat differ diff --git a/boilerplate/external/babel/localedata/de_CH.dat b/boilerplate/external/babel/localedata/de_CH.dat new file mode 100644 index 0000000..53cca9c Binary files /dev/null and b/boilerplate/external/babel/localedata/de_CH.dat differ diff --git a/boilerplate/external/babel/localedata/de_DE.dat b/boilerplate/external/babel/localedata/de_DE.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/de_DE.dat differ diff --git a/boilerplate/external/babel/localedata/de_LI.dat b/boilerplate/external/babel/localedata/de_LI.dat new file mode 100644 index 0000000..551b018 Binary files /dev/null and b/boilerplate/external/babel/localedata/de_LI.dat differ diff --git a/boilerplate/external/babel/localedata/de_LU.dat b/boilerplate/external/babel/localedata/de_LU.dat new file mode 100644 index 0000000..c0996ff Binary files /dev/null and b/boilerplate/external/babel/localedata/de_LU.dat differ diff --git a/boilerplate/external/babel/localedata/dv.dat b/boilerplate/external/babel/localedata/dv.dat new file mode 100644 index 0000000..1dc5cd4 Binary files /dev/null and b/boilerplate/external/babel/localedata/dv.dat differ diff --git a/boilerplate/external/babel/localedata/dv_MV.dat b/boilerplate/external/babel/localedata/dv_MV.dat new file mode 100644 index 0000000..57a3635 Binary files /dev/null and b/boilerplate/external/babel/localedata/dv_MV.dat differ diff --git a/boilerplate/external/babel/localedata/dz.dat b/boilerplate/external/babel/localedata/dz.dat new file mode 100644 index 0000000..1014df2 Binary files /dev/null and b/boilerplate/external/babel/localedata/dz.dat differ diff --git a/boilerplate/external/babel/localedata/dz_BT.dat b/boilerplate/external/babel/localedata/dz_BT.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/dz_BT.dat differ diff --git a/boilerplate/external/babel/localedata/ee.dat b/boilerplate/external/babel/localedata/ee.dat new file mode 100644 index 0000000..d8bb4a7 Binary files /dev/null and b/boilerplate/external/babel/localedata/ee.dat differ diff --git a/boilerplate/external/babel/localedata/ee_GH.dat b/boilerplate/external/babel/localedata/ee_GH.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ee_GH.dat differ diff --git a/boilerplate/external/babel/localedata/ee_TG.dat b/boilerplate/external/babel/localedata/ee_TG.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ee_TG.dat differ diff --git a/boilerplate/external/babel/localedata/el.dat b/boilerplate/external/babel/localedata/el.dat new file mode 100644 index 0000000..9ab2096 Binary files /dev/null and b/boilerplate/external/babel/localedata/el.dat differ diff --git a/boilerplate/external/babel/localedata/el_CY.dat b/boilerplate/external/babel/localedata/el_CY.dat new file mode 100644 index 0000000..7f9570a Binary files /dev/null and b/boilerplate/external/babel/localedata/el_CY.dat differ diff --git a/boilerplate/external/babel/localedata/el_GR.dat b/boilerplate/external/babel/localedata/el_GR.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/el_GR.dat differ diff --git a/boilerplate/external/babel/localedata/el_POLYTON.dat b/boilerplate/external/babel/localedata/el_POLYTON.dat new file mode 100644 index 0000000..4e982f8 Binary files /dev/null and b/boilerplate/external/babel/localedata/el_POLYTON.dat differ diff --git a/boilerplate/external/babel/localedata/en.dat b/boilerplate/external/babel/localedata/en.dat new file mode 100644 index 0000000..221efb8 Binary files /dev/null and b/boilerplate/external/babel/localedata/en.dat differ diff --git a/boilerplate/external/babel/localedata/en_AS.dat b/boilerplate/external/babel/localedata/en_AS.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_AS.dat differ diff --git a/boilerplate/external/babel/localedata/en_AU.dat b/boilerplate/external/babel/localedata/en_AU.dat new file mode 100644 index 0000000..1ede0f5 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_AU.dat differ diff --git a/boilerplate/external/babel/localedata/en_BE.dat b/boilerplate/external/babel/localedata/en_BE.dat new file mode 100644 index 0000000..462ce35 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_BE.dat differ diff --git a/boilerplate/external/babel/localedata/en_BW.dat b/boilerplate/external/babel/localedata/en_BW.dat new file mode 100644 index 0000000..477f787 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_BW.dat differ diff --git a/boilerplate/external/babel/localedata/en_BZ.dat b/boilerplate/external/babel/localedata/en_BZ.dat new file mode 100644 index 0000000..1341fdd Binary files /dev/null and b/boilerplate/external/babel/localedata/en_BZ.dat differ diff --git a/boilerplate/external/babel/localedata/en_CA.dat b/boilerplate/external/babel/localedata/en_CA.dat new file mode 100644 index 0000000..22a53a3 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_CA.dat differ diff --git a/boilerplate/external/babel/localedata/en_Dsrt.dat b/boilerplate/external/babel/localedata/en_Dsrt.dat new file mode 100644 index 0000000..23ae03d Binary files /dev/null and b/boilerplate/external/babel/localedata/en_Dsrt.dat differ diff --git a/boilerplate/external/babel/localedata/en_Dsrt_US.dat b/boilerplate/external/babel/localedata/en_Dsrt_US.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_Dsrt_US.dat differ diff --git a/boilerplate/external/babel/localedata/en_GB.dat b/boilerplate/external/babel/localedata/en_GB.dat new file mode 100644 index 0000000..ce5e5b2 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_GB.dat differ diff --git a/boilerplate/external/babel/localedata/en_GU.dat b/boilerplate/external/babel/localedata/en_GU.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_GU.dat differ diff --git a/boilerplate/external/babel/localedata/en_HK.dat b/boilerplate/external/babel/localedata/en_HK.dat new file mode 100644 index 0000000..32424a9 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_HK.dat differ diff --git a/boilerplate/external/babel/localedata/en_IE.dat b/boilerplate/external/babel/localedata/en_IE.dat new file mode 100644 index 0000000..7504c6f Binary files /dev/null and b/boilerplate/external/babel/localedata/en_IE.dat differ diff --git a/boilerplate/external/babel/localedata/en_IN.dat b/boilerplate/external/babel/localedata/en_IN.dat new file mode 100644 index 0000000..cee9a9b Binary files /dev/null and b/boilerplate/external/babel/localedata/en_IN.dat differ diff --git a/boilerplate/external/babel/localedata/en_JM.dat b/boilerplate/external/babel/localedata/en_JM.dat new file mode 100644 index 0000000..c4f63cb Binary files /dev/null and b/boilerplate/external/babel/localedata/en_JM.dat differ diff --git a/boilerplate/external/babel/localedata/en_MH.dat b/boilerplate/external/babel/localedata/en_MH.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_MH.dat differ diff --git a/boilerplate/external/babel/localedata/en_MP.dat b/boilerplate/external/babel/localedata/en_MP.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_MP.dat differ diff --git a/boilerplate/external/babel/localedata/en_MT.dat b/boilerplate/external/babel/localedata/en_MT.dat new file mode 100644 index 0000000..303195b Binary files /dev/null and b/boilerplate/external/babel/localedata/en_MT.dat differ diff --git a/boilerplate/external/babel/localedata/en_NA.dat b/boilerplate/external/babel/localedata/en_NA.dat new file mode 100644 index 0000000..7f9570a Binary files /dev/null and b/boilerplate/external/babel/localedata/en_NA.dat differ diff --git a/boilerplate/external/babel/localedata/en_NZ.dat b/boilerplate/external/babel/localedata/en_NZ.dat new file mode 100644 index 0000000..72cfe54 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_NZ.dat differ diff --git a/boilerplate/external/babel/localedata/en_PH.dat b/boilerplate/external/babel/localedata/en_PH.dat new file mode 100644 index 0000000..22bd69c Binary files /dev/null and b/boilerplate/external/babel/localedata/en_PH.dat differ diff --git a/boilerplate/external/babel/localedata/en_PK.dat b/boilerplate/external/babel/localedata/en_PK.dat new file mode 100644 index 0000000..da0f17e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_PK.dat differ diff --git a/boilerplate/external/babel/localedata/en_SG.dat b/boilerplate/external/babel/localedata/en_SG.dat new file mode 100644 index 0000000..c3969a8 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_SG.dat differ diff --git a/boilerplate/external/babel/localedata/en_Shaw.dat b/boilerplate/external/babel/localedata/en_Shaw.dat new file mode 100644 index 0000000..d6b1afa Binary files /dev/null and b/boilerplate/external/babel/localedata/en_Shaw.dat differ diff --git a/boilerplate/external/babel/localedata/en_TT.dat b/boilerplate/external/babel/localedata/en_TT.dat new file mode 100644 index 0000000..c4f63cb Binary files /dev/null and b/boilerplate/external/babel/localedata/en_TT.dat differ diff --git a/boilerplate/external/babel/localedata/en_UM.dat b/boilerplate/external/babel/localedata/en_UM.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_UM.dat differ diff --git a/boilerplate/external/babel/localedata/en_US.dat b/boilerplate/external/babel/localedata/en_US.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_US.dat differ diff --git a/boilerplate/external/babel/localedata/en_US_POSIX.dat b/boilerplate/external/babel/localedata/en_US_POSIX.dat new file mode 100644 index 0000000..0286f9c Binary files /dev/null and b/boilerplate/external/babel/localedata/en_US_POSIX.dat differ diff --git a/boilerplate/external/babel/localedata/en_VI.dat b/boilerplate/external/babel/localedata/en_VI.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/en_VI.dat differ diff --git a/boilerplate/external/babel/localedata/en_ZA.dat b/boilerplate/external/babel/localedata/en_ZA.dat new file mode 100644 index 0000000..a3748f4 Binary files /dev/null and b/boilerplate/external/babel/localedata/en_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/en_ZW.dat b/boilerplate/external/babel/localedata/en_ZW.dat new file mode 100644 index 0000000..88e59ab Binary files /dev/null and b/boilerplate/external/babel/localedata/en_ZW.dat differ diff --git a/boilerplate/external/babel/localedata/eo.dat b/boilerplate/external/babel/localedata/eo.dat new file mode 100644 index 0000000..885c684 Binary files /dev/null and b/boilerplate/external/babel/localedata/eo.dat differ diff --git a/boilerplate/external/babel/localedata/es.dat b/boilerplate/external/babel/localedata/es.dat new file mode 100644 index 0000000..3c7985c Binary files /dev/null and b/boilerplate/external/babel/localedata/es.dat differ diff --git a/boilerplate/external/babel/localedata/es_AR.dat b/boilerplate/external/babel/localedata/es_AR.dat new file mode 100644 index 0000000..d0dacac Binary files /dev/null and b/boilerplate/external/babel/localedata/es_AR.dat differ diff --git a/boilerplate/external/babel/localedata/es_BO.dat b/boilerplate/external/babel/localedata/es_BO.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_BO.dat differ diff --git a/boilerplate/external/babel/localedata/es_CL.dat b/boilerplate/external/babel/localedata/es_CL.dat new file mode 100644 index 0000000..f905582 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_CL.dat differ diff --git a/boilerplate/external/babel/localedata/es_CO.dat b/boilerplate/external/babel/localedata/es_CO.dat new file mode 100644 index 0000000..52c098c Binary files /dev/null and b/boilerplate/external/babel/localedata/es_CO.dat differ diff --git a/boilerplate/external/babel/localedata/es_CR.dat b/boilerplate/external/babel/localedata/es_CR.dat new file mode 100644 index 0000000..bbdb657 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_CR.dat differ diff --git a/boilerplate/external/babel/localedata/es_DO.dat b/boilerplate/external/babel/localedata/es_DO.dat new file mode 100644 index 0000000..687bdf7 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_DO.dat differ diff --git a/boilerplate/external/babel/localedata/es_EC.dat b/boilerplate/external/babel/localedata/es_EC.dat new file mode 100644 index 0000000..d7071ad Binary files /dev/null and b/boilerplate/external/babel/localedata/es_EC.dat differ diff --git a/boilerplate/external/babel/localedata/es_ES.dat b/boilerplate/external/babel/localedata/es_ES.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/es_ES.dat differ diff --git a/boilerplate/external/babel/localedata/es_GT.dat b/boilerplate/external/babel/localedata/es_GT.dat new file mode 100644 index 0000000..833f5f1 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_GT.dat differ diff --git a/boilerplate/external/babel/localedata/es_HN.dat b/boilerplate/external/babel/localedata/es_HN.dat new file mode 100644 index 0000000..9255941 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_HN.dat differ diff --git a/boilerplate/external/babel/localedata/es_MX.dat b/boilerplate/external/babel/localedata/es_MX.dat new file mode 100644 index 0000000..c7ad26c Binary files /dev/null and b/boilerplate/external/babel/localedata/es_MX.dat differ diff --git a/boilerplate/external/babel/localedata/es_NI.dat b/boilerplate/external/babel/localedata/es_NI.dat new file mode 100644 index 0000000..ab5b8c8 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_NI.dat differ diff --git a/boilerplate/external/babel/localedata/es_PA.dat b/boilerplate/external/babel/localedata/es_PA.dat new file mode 100644 index 0000000..85db9e2 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_PA.dat differ diff --git a/boilerplate/external/babel/localedata/es_PE.dat b/boilerplate/external/babel/localedata/es_PE.dat new file mode 100644 index 0000000..2975d58 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_PE.dat differ diff --git a/boilerplate/external/babel/localedata/es_PR.dat b/boilerplate/external/babel/localedata/es_PR.dat new file mode 100644 index 0000000..1311327 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_PR.dat differ diff --git a/boilerplate/external/babel/localedata/es_PY.dat b/boilerplate/external/babel/localedata/es_PY.dat new file mode 100644 index 0000000..fc3303c Binary files /dev/null and b/boilerplate/external/babel/localedata/es_PY.dat differ diff --git a/boilerplate/external/babel/localedata/es_SV.dat b/boilerplate/external/babel/localedata/es_SV.dat new file mode 100644 index 0000000..ab5b8c8 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_SV.dat differ diff --git a/boilerplate/external/babel/localedata/es_US.dat b/boilerplate/external/babel/localedata/es_US.dat new file mode 100644 index 0000000..ce278bd Binary files /dev/null and b/boilerplate/external/babel/localedata/es_US.dat differ diff --git a/boilerplate/external/babel/localedata/es_UY.dat b/boilerplate/external/babel/localedata/es_UY.dat new file mode 100644 index 0000000..5cfd9fd Binary files /dev/null and b/boilerplate/external/babel/localedata/es_UY.dat differ diff --git a/boilerplate/external/babel/localedata/es_VE.dat b/boilerplate/external/babel/localedata/es_VE.dat new file mode 100644 index 0000000..244e594 Binary files /dev/null and b/boilerplate/external/babel/localedata/es_VE.dat differ diff --git a/boilerplate/external/babel/localedata/et.dat b/boilerplate/external/babel/localedata/et.dat new file mode 100644 index 0000000..a15e9bf Binary files /dev/null and b/boilerplate/external/babel/localedata/et.dat differ diff --git a/boilerplate/external/babel/localedata/et_EE.dat b/boilerplate/external/babel/localedata/et_EE.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/et_EE.dat differ diff --git a/boilerplate/external/babel/localedata/eu.dat b/boilerplate/external/babel/localedata/eu.dat new file mode 100644 index 0000000..e664420 Binary files /dev/null and b/boilerplate/external/babel/localedata/eu.dat differ diff --git a/boilerplate/external/babel/localedata/eu_ES.dat b/boilerplate/external/babel/localedata/eu_ES.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/eu_ES.dat differ diff --git a/boilerplate/external/babel/localedata/fa.dat b/boilerplate/external/babel/localedata/fa.dat new file mode 100644 index 0000000..c70b057 Binary files /dev/null and b/boilerplate/external/babel/localedata/fa.dat differ diff --git a/boilerplate/external/babel/localedata/fa_AF.dat b/boilerplate/external/babel/localedata/fa_AF.dat new file mode 100644 index 0000000..c1dcd91 Binary files /dev/null and b/boilerplate/external/babel/localedata/fa_AF.dat differ diff --git a/boilerplate/external/babel/localedata/fa_IR.dat b/boilerplate/external/babel/localedata/fa_IR.dat new file mode 100644 index 0000000..c33cab6 Binary files /dev/null and b/boilerplate/external/babel/localedata/fa_IR.dat differ diff --git a/boilerplate/external/babel/localedata/fi.dat b/boilerplate/external/babel/localedata/fi.dat new file mode 100644 index 0000000..e08ee99 Binary files /dev/null and b/boilerplate/external/babel/localedata/fi.dat differ diff --git a/boilerplate/external/babel/localedata/fi_FI.dat b/boilerplate/external/babel/localedata/fi_FI.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/fi_FI.dat differ diff --git a/boilerplate/external/babel/localedata/fil.dat b/boilerplate/external/babel/localedata/fil.dat new file mode 100644 index 0000000..c3200ba Binary files /dev/null and b/boilerplate/external/babel/localedata/fil.dat differ diff --git a/boilerplate/external/babel/localedata/fil_PH.dat b/boilerplate/external/babel/localedata/fil_PH.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/fil_PH.dat differ diff --git a/boilerplate/external/babel/localedata/fo.dat b/boilerplate/external/babel/localedata/fo.dat new file mode 100644 index 0000000..8dbb75b Binary files /dev/null and b/boilerplate/external/babel/localedata/fo.dat differ diff --git a/boilerplate/external/babel/localedata/fo_FO.dat b/boilerplate/external/babel/localedata/fo_FO.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/fo_FO.dat differ diff --git a/boilerplate/external/babel/localedata/fr.dat b/boilerplate/external/babel/localedata/fr.dat new file mode 100644 index 0000000..1885a2b Binary files /dev/null and b/boilerplate/external/babel/localedata/fr.dat differ diff --git a/boilerplate/external/babel/localedata/fr_BE.dat b/boilerplate/external/babel/localedata/fr_BE.dat new file mode 100644 index 0000000..7c7583d Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_BE.dat differ diff --git a/boilerplate/external/babel/localedata/fr_CA.dat b/boilerplate/external/babel/localedata/fr_CA.dat new file mode 100644 index 0000000..3ea140d Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_CA.dat differ diff --git a/boilerplate/external/babel/localedata/fr_CH.dat b/boilerplate/external/babel/localedata/fr_CH.dat new file mode 100644 index 0000000..11de7e2 Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_CH.dat differ diff --git a/boilerplate/external/babel/localedata/fr_FR.dat b/boilerplate/external/babel/localedata/fr_FR.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_FR.dat differ diff --git a/boilerplate/external/babel/localedata/fr_LU.dat b/boilerplate/external/babel/localedata/fr_LU.dat new file mode 100644 index 0000000..e65a929 Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_LU.dat differ diff --git a/boilerplate/external/babel/localedata/fr_MC.dat b/boilerplate/external/babel/localedata/fr_MC.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_MC.dat differ diff --git a/boilerplate/external/babel/localedata/fr_SN.dat b/boilerplate/external/babel/localedata/fr_SN.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/fr_SN.dat differ diff --git a/boilerplate/external/babel/localedata/fur.dat b/boilerplate/external/babel/localedata/fur.dat new file mode 100644 index 0000000..a6eb434 Binary files /dev/null and b/boilerplate/external/babel/localedata/fur.dat differ diff --git a/boilerplate/external/babel/localedata/fur_IT.dat b/boilerplate/external/babel/localedata/fur_IT.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/fur_IT.dat differ diff --git a/boilerplate/external/babel/localedata/ga.dat b/boilerplate/external/babel/localedata/ga.dat new file mode 100644 index 0000000..410331f Binary files /dev/null and b/boilerplate/external/babel/localedata/ga.dat differ diff --git a/boilerplate/external/babel/localedata/ga_IE.dat b/boilerplate/external/babel/localedata/ga_IE.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ga_IE.dat differ diff --git a/boilerplate/external/babel/localedata/gaa.dat b/boilerplate/external/babel/localedata/gaa.dat new file mode 100644 index 0000000..4601843 Binary files /dev/null and b/boilerplate/external/babel/localedata/gaa.dat differ diff --git a/boilerplate/external/babel/localedata/gaa_GH.dat b/boilerplate/external/babel/localedata/gaa_GH.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/gaa_GH.dat differ diff --git a/boilerplate/external/babel/localedata/gez.dat b/boilerplate/external/babel/localedata/gez.dat new file mode 100644 index 0000000..ac8ac8c Binary files /dev/null and b/boilerplate/external/babel/localedata/gez.dat differ diff --git a/boilerplate/external/babel/localedata/gez_ER.dat b/boilerplate/external/babel/localedata/gez_ER.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/gez_ER.dat differ diff --git a/boilerplate/external/babel/localedata/gez_ET.dat b/boilerplate/external/babel/localedata/gez_ET.dat new file mode 100644 index 0000000..40e0ef1 Binary files /dev/null and b/boilerplate/external/babel/localedata/gez_ET.dat differ diff --git a/boilerplate/external/babel/localedata/gl.dat b/boilerplate/external/babel/localedata/gl.dat new file mode 100644 index 0000000..20980ea Binary files /dev/null and b/boilerplate/external/babel/localedata/gl.dat differ diff --git a/boilerplate/external/babel/localedata/gl_ES.dat b/boilerplate/external/babel/localedata/gl_ES.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/gl_ES.dat differ diff --git a/boilerplate/external/babel/localedata/gu.dat b/boilerplate/external/babel/localedata/gu.dat new file mode 100644 index 0000000..39069d7 Binary files /dev/null and b/boilerplate/external/babel/localedata/gu.dat differ diff --git a/boilerplate/external/babel/localedata/gu_IN.dat b/boilerplate/external/babel/localedata/gu_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/gu_IN.dat differ diff --git a/boilerplate/external/babel/localedata/gv.dat b/boilerplate/external/babel/localedata/gv.dat new file mode 100644 index 0000000..b51a48c Binary files /dev/null and b/boilerplate/external/babel/localedata/gv.dat differ diff --git a/boilerplate/external/babel/localedata/gv_GB.dat b/boilerplate/external/babel/localedata/gv_GB.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/gv_GB.dat differ diff --git a/boilerplate/external/babel/localedata/ha.dat b/boilerplate/external/babel/localedata/ha.dat new file mode 100644 index 0000000..d558fc6 Binary files /dev/null and b/boilerplate/external/babel/localedata/ha.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Arab.dat b/boilerplate/external/babel/localedata/ha_Arab.dat new file mode 100644 index 0000000..1d171f0 Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Arab.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Arab_NG.dat b/boilerplate/external/babel/localedata/ha_Arab_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Arab_NG.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Arab_SD.dat b/boilerplate/external/babel/localedata/ha_Arab_SD.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Arab_SD.dat differ diff --git a/boilerplate/external/babel/localedata/ha_GH.dat b/boilerplate/external/babel/localedata/ha_GH.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_GH.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Latn.dat b/boilerplate/external/babel/localedata/ha_Latn.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Latn.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Latn_GH.dat b/boilerplate/external/babel/localedata/ha_Latn_GH.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Latn_GH.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Latn_NE.dat b/boilerplate/external/babel/localedata/ha_Latn_NE.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Latn_NE.dat differ diff --git a/boilerplate/external/babel/localedata/ha_Latn_NG.dat b/boilerplate/external/babel/localedata/ha_Latn_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_Latn_NG.dat differ diff --git a/boilerplate/external/babel/localedata/ha_NE.dat b/boilerplate/external/babel/localedata/ha_NE.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_NE.dat differ diff --git a/boilerplate/external/babel/localedata/ha_NG.dat b/boilerplate/external/babel/localedata/ha_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_NG.dat differ diff --git a/boilerplate/external/babel/localedata/ha_SD.dat b/boilerplate/external/babel/localedata/ha_SD.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/ha_SD.dat differ diff --git a/boilerplate/external/babel/localedata/haw.dat b/boilerplate/external/babel/localedata/haw.dat new file mode 100644 index 0000000..e17af06 Binary files /dev/null and b/boilerplate/external/babel/localedata/haw.dat differ diff --git a/boilerplate/external/babel/localedata/haw_US.dat b/boilerplate/external/babel/localedata/haw_US.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/haw_US.dat differ diff --git a/boilerplate/external/babel/localedata/he.dat b/boilerplate/external/babel/localedata/he.dat new file mode 100644 index 0000000..6ef54b4 Binary files /dev/null and b/boilerplate/external/babel/localedata/he.dat differ diff --git a/boilerplate/external/babel/localedata/he_IL.dat b/boilerplate/external/babel/localedata/he_IL.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/he_IL.dat differ diff --git a/boilerplate/external/babel/localedata/hi.dat b/boilerplate/external/babel/localedata/hi.dat new file mode 100644 index 0000000..af3c0a0 Binary files /dev/null and b/boilerplate/external/babel/localedata/hi.dat differ diff --git a/boilerplate/external/babel/localedata/hi_IN.dat b/boilerplate/external/babel/localedata/hi_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/hi_IN.dat differ diff --git a/boilerplate/external/babel/localedata/hr.dat b/boilerplate/external/babel/localedata/hr.dat new file mode 100644 index 0000000..9e37099 Binary files /dev/null and b/boilerplate/external/babel/localedata/hr.dat differ diff --git a/boilerplate/external/babel/localedata/hr_HR.dat b/boilerplate/external/babel/localedata/hr_HR.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/hr_HR.dat differ diff --git a/boilerplate/external/babel/localedata/hu.dat b/boilerplate/external/babel/localedata/hu.dat new file mode 100644 index 0000000..123fbff Binary files /dev/null and b/boilerplate/external/babel/localedata/hu.dat differ diff --git a/boilerplate/external/babel/localedata/hu_HU.dat b/boilerplate/external/babel/localedata/hu_HU.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/hu_HU.dat differ diff --git a/boilerplate/external/babel/localedata/hy.dat b/boilerplate/external/babel/localedata/hy.dat new file mode 100644 index 0000000..c45b62b Binary files /dev/null and b/boilerplate/external/babel/localedata/hy.dat differ diff --git a/boilerplate/external/babel/localedata/hy_AM.dat b/boilerplate/external/babel/localedata/hy_AM.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/hy_AM.dat differ diff --git a/boilerplate/external/babel/localedata/hy_AM_REVISED.dat b/boilerplate/external/babel/localedata/hy_AM_REVISED.dat new file mode 100644 index 0000000..f5691b4 Binary files /dev/null and b/boilerplate/external/babel/localedata/hy_AM_REVISED.dat differ diff --git a/boilerplate/external/babel/localedata/ia.dat b/boilerplate/external/babel/localedata/ia.dat new file mode 100644 index 0000000..6ec3b32 Binary files /dev/null and b/boilerplate/external/babel/localedata/ia.dat differ diff --git a/boilerplate/external/babel/localedata/id.dat b/boilerplate/external/babel/localedata/id.dat new file mode 100644 index 0000000..1b13dd5 Binary files /dev/null and b/boilerplate/external/babel/localedata/id.dat differ diff --git a/boilerplate/external/babel/localedata/id_ID.dat b/boilerplate/external/babel/localedata/id_ID.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/id_ID.dat differ diff --git a/boilerplate/external/babel/localedata/ig.dat b/boilerplate/external/babel/localedata/ig.dat new file mode 100644 index 0000000..f694d99 Binary files /dev/null and b/boilerplate/external/babel/localedata/ig.dat differ diff --git a/boilerplate/external/babel/localedata/ig_NG.dat b/boilerplate/external/babel/localedata/ig_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ig_NG.dat differ diff --git a/boilerplate/external/babel/localedata/ii.dat b/boilerplate/external/babel/localedata/ii.dat new file mode 100644 index 0000000..3068257 Binary files /dev/null and b/boilerplate/external/babel/localedata/ii.dat differ diff --git a/boilerplate/external/babel/localedata/ii_CN.dat b/boilerplate/external/babel/localedata/ii_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ii_CN.dat differ diff --git a/boilerplate/external/babel/localedata/in.dat b/boilerplate/external/babel/localedata/in.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/in.dat differ diff --git a/boilerplate/external/babel/localedata/is.dat b/boilerplate/external/babel/localedata/is.dat new file mode 100644 index 0000000..9c0ce7f Binary files /dev/null and b/boilerplate/external/babel/localedata/is.dat differ diff --git a/boilerplate/external/babel/localedata/is_IS.dat b/boilerplate/external/babel/localedata/is_IS.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/is_IS.dat differ diff --git a/boilerplate/external/babel/localedata/it.dat b/boilerplate/external/babel/localedata/it.dat new file mode 100644 index 0000000..ed69b4c Binary files /dev/null and b/boilerplate/external/babel/localedata/it.dat differ diff --git a/boilerplate/external/babel/localedata/it_CH.dat b/boilerplate/external/babel/localedata/it_CH.dat new file mode 100644 index 0000000..ee9cdc3 Binary files /dev/null and b/boilerplate/external/babel/localedata/it_CH.dat differ diff --git a/boilerplate/external/babel/localedata/it_IT.dat b/boilerplate/external/babel/localedata/it_IT.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/it_IT.dat differ diff --git a/boilerplate/external/babel/localedata/iu.dat b/boilerplate/external/babel/localedata/iu.dat new file mode 100644 index 0000000..5004f1d Binary files /dev/null and b/boilerplate/external/babel/localedata/iu.dat differ diff --git a/boilerplate/external/babel/localedata/iw.dat b/boilerplate/external/babel/localedata/iw.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/iw.dat differ diff --git a/boilerplate/external/babel/localedata/ja.dat b/boilerplate/external/babel/localedata/ja.dat new file mode 100644 index 0000000..d1f96e7 Binary files /dev/null and b/boilerplate/external/babel/localedata/ja.dat differ diff --git a/boilerplate/external/babel/localedata/ja_JP.dat b/boilerplate/external/babel/localedata/ja_JP.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ja_JP.dat differ diff --git a/boilerplate/external/babel/localedata/ka.dat b/boilerplate/external/babel/localedata/ka.dat new file mode 100644 index 0000000..788721c Binary files /dev/null and b/boilerplate/external/babel/localedata/ka.dat differ diff --git a/boilerplate/external/babel/localedata/ka_GE.dat b/boilerplate/external/babel/localedata/ka_GE.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ka_GE.dat differ diff --git a/boilerplate/external/babel/localedata/kaj.dat b/boilerplate/external/babel/localedata/kaj.dat new file mode 100644 index 0000000..b1a952e Binary files /dev/null and b/boilerplate/external/babel/localedata/kaj.dat differ diff --git a/boilerplate/external/babel/localedata/kaj_NG.dat b/boilerplate/external/babel/localedata/kaj_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/kaj_NG.dat differ diff --git a/boilerplate/external/babel/localedata/kam.dat b/boilerplate/external/babel/localedata/kam.dat new file mode 100644 index 0000000..570ebb3 Binary files /dev/null and b/boilerplate/external/babel/localedata/kam.dat differ diff --git a/boilerplate/external/babel/localedata/kam_KE.dat b/boilerplate/external/babel/localedata/kam_KE.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/kam_KE.dat differ diff --git a/boilerplate/external/babel/localedata/kcg.dat b/boilerplate/external/babel/localedata/kcg.dat new file mode 100644 index 0000000..8112c0d Binary files /dev/null and b/boilerplate/external/babel/localedata/kcg.dat differ diff --git a/boilerplate/external/babel/localedata/kcg_NG.dat b/boilerplate/external/babel/localedata/kcg_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/kcg_NG.dat differ diff --git a/boilerplate/external/babel/localedata/kfo.dat b/boilerplate/external/babel/localedata/kfo.dat new file mode 100644 index 0000000..a29a358 Binary files /dev/null and b/boilerplate/external/babel/localedata/kfo.dat differ diff --git a/boilerplate/external/babel/localedata/kfo_CI.dat b/boilerplate/external/babel/localedata/kfo_CI.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/kfo_CI.dat differ diff --git a/boilerplate/external/babel/localedata/kk.dat b/boilerplate/external/babel/localedata/kk.dat new file mode 100644 index 0000000..8c5a4ee Binary files /dev/null and b/boilerplate/external/babel/localedata/kk.dat differ diff --git a/boilerplate/external/babel/localedata/kk_Cyrl.dat b/boilerplate/external/babel/localedata/kk_Cyrl.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/kk_Cyrl.dat differ diff --git a/boilerplate/external/babel/localedata/kk_Cyrl_KZ.dat b/boilerplate/external/babel/localedata/kk_Cyrl_KZ.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/kk_Cyrl_KZ.dat differ diff --git a/boilerplate/external/babel/localedata/kk_KZ.dat b/boilerplate/external/babel/localedata/kk_KZ.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/kk_KZ.dat differ diff --git a/boilerplate/external/babel/localedata/kl.dat b/boilerplate/external/babel/localedata/kl.dat new file mode 100644 index 0000000..e4e1143 Binary files /dev/null and b/boilerplate/external/babel/localedata/kl.dat differ diff --git a/boilerplate/external/babel/localedata/kl_GL.dat b/boilerplate/external/babel/localedata/kl_GL.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/kl_GL.dat differ diff --git a/boilerplate/external/babel/localedata/km.dat b/boilerplate/external/babel/localedata/km.dat new file mode 100644 index 0000000..a539418 Binary files /dev/null and b/boilerplate/external/babel/localedata/km.dat differ diff --git a/boilerplate/external/babel/localedata/km_KH.dat b/boilerplate/external/babel/localedata/km_KH.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/km_KH.dat differ diff --git a/boilerplate/external/babel/localedata/kn.dat b/boilerplate/external/babel/localedata/kn.dat new file mode 100644 index 0000000..71162b3 Binary files /dev/null and b/boilerplate/external/babel/localedata/kn.dat differ diff --git a/boilerplate/external/babel/localedata/kn_IN.dat b/boilerplate/external/babel/localedata/kn_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/kn_IN.dat differ diff --git a/boilerplate/external/babel/localedata/ko.dat b/boilerplate/external/babel/localedata/ko.dat new file mode 100644 index 0000000..4ac5c08 Binary files /dev/null and b/boilerplate/external/babel/localedata/ko.dat differ diff --git a/boilerplate/external/babel/localedata/ko_KR.dat b/boilerplate/external/babel/localedata/ko_KR.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ko_KR.dat differ diff --git a/boilerplate/external/babel/localedata/kok.dat b/boilerplate/external/babel/localedata/kok.dat new file mode 100644 index 0000000..ff6c9ef Binary files /dev/null and b/boilerplate/external/babel/localedata/kok.dat differ diff --git a/boilerplate/external/babel/localedata/kok_IN.dat b/boilerplate/external/babel/localedata/kok_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/kok_IN.dat differ diff --git a/boilerplate/external/babel/localedata/kpe.dat b/boilerplate/external/babel/localedata/kpe.dat new file mode 100644 index 0000000..63c2a7b Binary files /dev/null and b/boilerplate/external/babel/localedata/kpe.dat differ diff --git a/boilerplate/external/babel/localedata/kpe_GN.dat b/boilerplate/external/babel/localedata/kpe_GN.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/kpe_GN.dat differ diff --git a/boilerplate/external/babel/localedata/kpe_LR.dat b/boilerplate/external/babel/localedata/kpe_LR.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/kpe_LR.dat differ diff --git a/boilerplate/external/babel/localedata/ku.dat b/boilerplate/external/babel/localedata/ku.dat new file mode 100644 index 0000000..ef4d14a Binary files /dev/null and b/boilerplate/external/babel/localedata/ku.dat differ diff --git a/boilerplate/external/babel/localedata/ku_Arab.dat b/boilerplate/external/babel/localedata/ku_Arab.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ku_Arab.dat differ diff --git a/boilerplate/external/babel/localedata/ku_Latn.dat b/boilerplate/external/babel/localedata/ku_Latn.dat new file mode 100644 index 0000000..0b4312f Binary files /dev/null and b/boilerplate/external/babel/localedata/ku_Latn.dat differ diff --git a/boilerplate/external/babel/localedata/ku_Latn_TR.dat b/boilerplate/external/babel/localedata/ku_Latn_TR.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ku_Latn_TR.dat differ diff --git a/boilerplate/external/babel/localedata/ku_TR.dat b/boilerplate/external/babel/localedata/ku_TR.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ku_TR.dat differ diff --git a/boilerplate/external/babel/localedata/kw.dat b/boilerplate/external/babel/localedata/kw.dat new file mode 100644 index 0000000..3ff6507 Binary files /dev/null and b/boilerplate/external/babel/localedata/kw.dat differ diff --git a/boilerplate/external/babel/localedata/kw_GB.dat b/boilerplate/external/babel/localedata/kw_GB.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/kw_GB.dat differ diff --git a/boilerplate/external/babel/localedata/ky.dat b/boilerplate/external/babel/localedata/ky.dat new file mode 100644 index 0000000..07a66e6 Binary files /dev/null and b/boilerplate/external/babel/localedata/ky.dat differ diff --git a/boilerplate/external/babel/localedata/ky_KG.dat b/boilerplate/external/babel/localedata/ky_KG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ky_KG.dat differ diff --git a/boilerplate/external/babel/localedata/ln.dat b/boilerplate/external/babel/localedata/ln.dat new file mode 100644 index 0000000..3fc41d2 Binary files /dev/null and b/boilerplate/external/babel/localedata/ln.dat differ diff --git a/boilerplate/external/babel/localedata/ln_CD.dat b/boilerplate/external/babel/localedata/ln_CD.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/ln_CD.dat differ diff --git a/boilerplate/external/babel/localedata/ln_CG.dat b/boilerplate/external/babel/localedata/ln_CG.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ln_CG.dat differ diff --git a/boilerplate/external/babel/localedata/lo.dat b/boilerplate/external/babel/localedata/lo.dat new file mode 100644 index 0000000..e38896c Binary files /dev/null and b/boilerplate/external/babel/localedata/lo.dat differ diff --git a/boilerplate/external/babel/localedata/lo_LA.dat b/boilerplate/external/babel/localedata/lo_LA.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/lo_LA.dat differ diff --git a/boilerplate/external/babel/localedata/lt.dat b/boilerplate/external/babel/localedata/lt.dat new file mode 100644 index 0000000..de10a1d Binary files /dev/null and b/boilerplate/external/babel/localedata/lt.dat differ diff --git a/boilerplate/external/babel/localedata/lt_LT.dat b/boilerplate/external/babel/localedata/lt_LT.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/lt_LT.dat differ diff --git a/boilerplate/external/babel/localedata/lv.dat b/boilerplate/external/babel/localedata/lv.dat new file mode 100644 index 0000000..1a7f72a Binary files /dev/null and b/boilerplate/external/babel/localedata/lv.dat differ diff --git a/boilerplate/external/babel/localedata/lv_LV.dat b/boilerplate/external/babel/localedata/lv_LV.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/lv_LV.dat differ diff --git a/boilerplate/external/babel/localedata/mk.dat b/boilerplate/external/babel/localedata/mk.dat new file mode 100644 index 0000000..2eea0ef Binary files /dev/null and b/boilerplate/external/babel/localedata/mk.dat differ diff --git a/boilerplate/external/babel/localedata/mk_MK.dat b/boilerplate/external/babel/localedata/mk_MK.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/mk_MK.dat differ diff --git a/boilerplate/external/babel/localedata/ml.dat b/boilerplate/external/babel/localedata/ml.dat new file mode 100644 index 0000000..171f8f7 Binary files /dev/null and b/boilerplate/external/babel/localedata/ml.dat differ diff --git a/boilerplate/external/babel/localedata/ml_IN.dat b/boilerplate/external/babel/localedata/ml_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ml_IN.dat differ diff --git a/boilerplate/external/babel/localedata/mn.dat b/boilerplate/external/babel/localedata/mn.dat new file mode 100644 index 0000000..9b04711 Binary files /dev/null and b/boilerplate/external/babel/localedata/mn.dat differ diff --git a/boilerplate/external/babel/localedata/mn_CN.dat b/boilerplate/external/babel/localedata/mn_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/mn_CN.dat differ diff --git a/boilerplate/external/babel/localedata/mn_Cyrl.dat b/boilerplate/external/babel/localedata/mn_Cyrl.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/mn_Cyrl.dat differ diff --git a/boilerplate/external/babel/localedata/mn_Cyrl_MN.dat b/boilerplate/external/babel/localedata/mn_Cyrl_MN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/mn_Cyrl_MN.dat differ diff --git a/boilerplate/external/babel/localedata/mn_MN.dat b/boilerplate/external/babel/localedata/mn_MN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/mn_MN.dat differ diff --git a/boilerplate/external/babel/localedata/mn_Mong.dat b/boilerplate/external/babel/localedata/mn_Mong.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/mn_Mong.dat differ diff --git a/boilerplate/external/babel/localedata/mn_Mong_CN.dat b/boilerplate/external/babel/localedata/mn_Mong_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/mn_Mong_CN.dat differ diff --git a/boilerplate/external/babel/localedata/mo.dat b/boilerplate/external/babel/localedata/mo.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/mo.dat differ diff --git a/boilerplate/external/babel/localedata/mr.dat b/boilerplate/external/babel/localedata/mr.dat new file mode 100644 index 0000000..46e3a70 Binary files /dev/null and b/boilerplate/external/babel/localedata/mr.dat differ diff --git a/boilerplate/external/babel/localedata/mr_IN.dat b/boilerplate/external/babel/localedata/mr_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/mr_IN.dat differ diff --git a/boilerplate/external/babel/localedata/ms.dat b/boilerplate/external/babel/localedata/ms.dat new file mode 100644 index 0000000..0c5a0f3 Binary files /dev/null and b/boilerplate/external/babel/localedata/ms.dat differ diff --git a/boilerplate/external/babel/localedata/ms_BN.dat b/boilerplate/external/babel/localedata/ms_BN.dat new file mode 100644 index 0000000..e05ce71 Binary files /dev/null and b/boilerplate/external/babel/localedata/ms_BN.dat differ diff --git a/boilerplate/external/babel/localedata/ms_MY.dat b/boilerplate/external/babel/localedata/ms_MY.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/ms_MY.dat differ diff --git a/boilerplate/external/babel/localedata/mt.dat b/boilerplate/external/babel/localedata/mt.dat new file mode 100644 index 0000000..46e203f Binary files /dev/null and b/boilerplate/external/babel/localedata/mt.dat differ diff --git a/boilerplate/external/babel/localedata/mt_MT.dat b/boilerplate/external/babel/localedata/mt_MT.dat new file mode 100644 index 0000000..77fe0cd Binary files /dev/null and b/boilerplate/external/babel/localedata/mt_MT.dat differ diff --git a/boilerplate/external/babel/localedata/my.dat b/boilerplate/external/babel/localedata/my.dat new file mode 100644 index 0000000..3d90a79 Binary files /dev/null and b/boilerplate/external/babel/localedata/my.dat differ diff --git a/boilerplate/external/babel/localedata/my_MM.dat b/boilerplate/external/babel/localedata/my_MM.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/my_MM.dat differ diff --git a/boilerplate/external/babel/localedata/nb.dat b/boilerplate/external/babel/localedata/nb.dat new file mode 100644 index 0000000..a64c67f Binary files /dev/null and b/boilerplate/external/babel/localedata/nb.dat differ diff --git a/boilerplate/external/babel/localedata/nb_NO.dat b/boilerplate/external/babel/localedata/nb_NO.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/nb_NO.dat differ diff --git a/boilerplate/external/babel/localedata/ne.dat b/boilerplate/external/babel/localedata/ne.dat new file mode 100644 index 0000000..698b463 Binary files /dev/null and b/boilerplate/external/babel/localedata/ne.dat differ diff --git a/boilerplate/external/babel/localedata/ne_IN.dat b/boilerplate/external/babel/localedata/ne_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ne_IN.dat differ diff --git a/boilerplate/external/babel/localedata/ne_NP.dat b/boilerplate/external/babel/localedata/ne_NP.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/ne_NP.dat differ diff --git a/boilerplate/external/babel/localedata/nl.dat b/boilerplate/external/babel/localedata/nl.dat new file mode 100644 index 0000000..9e3fc13 Binary files /dev/null and b/boilerplate/external/babel/localedata/nl.dat differ diff --git a/boilerplate/external/babel/localedata/nl_BE.dat b/boilerplate/external/babel/localedata/nl_BE.dat new file mode 100644 index 0000000..8326a2f Binary files /dev/null and b/boilerplate/external/babel/localedata/nl_BE.dat differ diff --git a/boilerplate/external/babel/localedata/nl_NL.dat b/boilerplate/external/babel/localedata/nl_NL.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/nl_NL.dat differ diff --git a/boilerplate/external/babel/localedata/nn.dat b/boilerplate/external/babel/localedata/nn.dat new file mode 100644 index 0000000..a3977db Binary files /dev/null and b/boilerplate/external/babel/localedata/nn.dat differ diff --git a/boilerplate/external/babel/localedata/nn_NO.dat b/boilerplate/external/babel/localedata/nn_NO.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/nn_NO.dat differ diff --git a/boilerplate/external/babel/localedata/no.dat b/boilerplate/external/babel/localedata/no.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/no.dat differ diff --git a/boilerplate/external/babel/localedata/nr.dat b/boilerplate/external/babel/localedata/nr.dat new file mode 100644 index 0000000..6069b35 Binary files /dev/null and b/boilerplate/external/babel/localedata/nr.dat differ diff --git a/boilerplate/external/babel/localedata/nr_ZA.dat b/boilerplate/external/babel/localedata/nr_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/nr_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/nso.dat b/boilerplate/external/babel/localedata/nso.dat new file mode 100644 index 0000000..6ad60fe Binary files /dev/null and b/boilerplate/external/babel/localedata/nso.dat differ diff --git a/boilerplate/external/babel/localedata/nso_ZA.dat b/boilerplate/external/babel/localedata/nso_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/nso_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/ny.dat b/boilerplate/external/babel/localedata/ny.dat new file mode 100644 index 0000000..c7b6ce7 Binary files /dev/null and b/boilerplate/external/babel/localedata/ny.dat differ diff --git a/boilerplate/external/babel/localedata/ny_MW.dat b/boilerplate/external/babel/localedata/ny_MW.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ny_MW.dat differ diff --git a/boilerplate/external/babel/localedata/om.dat b/boilerplate/external/babel/localedata/om.dat new file mode 100644 index 0000000..1b81192 Binary files /dev/null and b/boilerplate/external/babel/localedata/om.dat differ diff --git a/boilerplate/external/babel/localedata/om_ET.dat b/boilerplate/external/babel/localedata/om_ET.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/om_ET.dat differ diff --git a/boilerplate/external/babel/localedata/om_KE.dat b/boilerplate/external/babel/localedata/om_KE.dat new file mode 100644 index 0000000..e0f2275 Binary files /dev/null and b/boilerplate/external/babel/localedata/om_KE.dat differ diff --git a/boilerplate/external/babel/localedata/or.dat b/boilerplate/external/babel/localedata/or.dat new file mode 100644 index 0000000..1407072 Binary files /dev/null and b/boilerplate/external/babel/localedata/or.dat differ diff --git a/boilerplate/external/babel/localedata/or_IN.dat b/boilerplate/external/babel/localedata/or_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/or_IN.dat differ diff --git a/boilerplate/external/babel/localedata/pa.dat b/boilerplate/external/babel/localedata/pa.dat new file mode 100644 index 0000000..8de50bb Binary files /dev/null and b/boilerplate/external/babel/localedata/pa.dat differ diff --git a/boilerplate/external/babel/localedata/pa_Arab.dat b/boilerplate/external/babel/localedata/pa_Arab.dat new file mode 100644 index 0000000..3645230 Binary files /dev/null and b/boilerplate/external/babel/localedata/pa_Arab.dat differ diff --git a/boilerplate/external/babel/localedata/pa_Arab_PK.dat b/boilerplate/external/babel/localedata/pa_Arab_PK.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/pa_Arab_PK.dat differ diff --git a/boilerplate/external/babel/localedata/pa_Guru.dat b/boilerplate/external/babel/localedata/pa_Guru.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/pa_Guru.dat differ diff --git a/boilerplate/external/babel/localedata/pa_Guru_IN.dat b/boilerplate/external/babel/localedata/pa_Guru_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/pa_Guru_IN.dat differ diff --git a/boilerplate/external/babel/localedata/pa_IN.dat b/boilerplate/external/babel/localedata/pa_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/pa_IN.dat differ diff --git a/boilerplate/external/babel/localedata/pa_PK.dat b/boilerplate/external/babel/localedata/pa_PK.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/pa_PK.dat differ diff --git a/boilerplate/external/babel/localedata/pl.dat b/boilerplate/external/babel/localedata/pl.dat new file mode 100644 index 0000000..f0d1899 Binary files /dev/null and b/boilerplate/external/babel/localedata/pl.dat differ diff --git a/boilerplate/external/babel/localedata/pl_PL.dat b/boilerplate/external/babel/localedata/pl_PL.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/pl_PL.dat differ diff --git a/boilerplate/external/babel/localedata/ps.dat b/boilerplate/external/babel/localedata/ps.dat new file mode 100644 index 0000000..fa1f01a Binary files /dev/null and b/boilerplate/external/babel/localedata/ps.dat differ diff --git a/boilerplate/external/babel/localedata/ps_AF.dat b/boilerplate/external/babel/localedata/ps_AF.dat new file mode 100644 index 0000000..c33cab6 Binary files /dev/null and b/boilerplate/external/babel/localedata/ps_AF.dat differ diff --git a/boilerplate/external/babel/localedata/pt.dat b/boilerplate/external/babel/localedata/pt.dat new file mode 100644 index 0000000..206b49b Binary files /dev/null and b/boilerplate/external/babel/localedata/pt.dat differ diff --git a/boilerplate/external/babel/localedata/pt_BR.dat b/boilerplate/external/babel/localedata/pt_BR.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/pt_BR.dat differ diff --git a/boilerplate/external/babel/localedata/pt_PT.dat b/boilerplate/external/babel/localedata/pt_PT.dat new file mode 100644 index 0000000..df8bdd2 Binary files /dev/null and b/boilerplate/external/babel/localedata/pt_PT.dat differ diff --git a/boilerplate/external/babel/localedata/ro.dat b/boilerplate/external/babel/localedata/ro.dat new file mode 100644 index 0000000..fea45b1 Binary files /dev/null and b/boilerplate/external/babel/localedata/ro.dat differ diff --git a/boilerplate/external/babel/localedata/ro_MD.dat b/boilerplate/external/babel/localedata/ro_MD.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ro_MD.dat differ diff --git a/boilerplate/external/babel/localedata/ro_RO.dat b/boilerplate/external/babel/localedata/ro_RO.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ro_RO.dat differ diff --git a/boilerplate/external/babel/localedata/root.dat b/boilerplate/external/babel/localedata/root.dat new file mode 100644 index 0000000..f07137d Binary files /dev/null and b/boilerplate/external/babel/localedata/root.dat differ diff --git a/boilerplate/external/babel/localedata/ru.dat b/boilerplate/external/babel/localedata/ru.dat new file mode 100644 index 0000000..278ed8e Binary files /dev/null and b/boilerplate/external/babel/localedata/ru.dat differ diff --git a/boilerplate/external/babel/localedata/ru_RU.dat b/boilerplate/external/babel/localedata/ru_RU.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ru_RU.dat differ diff --git a/boilerplate/external/babel/localedata/ru_UA.dat b/boilerplate/external/babel/localedata/ru_UA.dat new file mode 100644 index 0000000..2202484 Binary files /dev/null and b/boilerplate/external/babel/localedata/ru_UA.dat differ diff --git a/boilerplate/external/babel/localedata/rw.dat b/boilerplate/external/babel/localedata/rw.dat new file mode 100644 index 0000000..30aef73 Binary files /dev/null and b/boilerplate/external/babel/localedata/rw.dat differ diff --git a/boilerplate/external/babel/localedata/rw_RW.dat b/boilerplate/external/babel/localedata/rw_RW.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/rw_RW.dat differ diff --git a/boilerplate/external/babel/localedata/sa.dat b/boilerplate/external/babel/localedata/sa.dat new file mode 100644 index 0000000..bc3e1e6 Binary files /dev/null and b/boilerplate/external/babel/localedata/sa.dat differ diff --git a/boilerplate/external/babel/localedata/sa_IN.dat b/boilerplate/external/babel/localedata/sa_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/sa_IN.dat differ diff --git a/boilerplate/external/babel/localedata/se.dat b/boilerplate/external/babel/localedata/se.dat new file mode 100644 index 0000000..a92cea4 Binary files /dev/null and b/boilerplate/external/babel/localedata/se.dat differ diff --git a/boilerplate/external/babel/localedata/se_FI.dat b/boilerplate/external/babel/localedata/se_FI.dat new file mode 100644 index 0000000..26be320 Binary files /dev/null and b/boilerplate/external/babel/localedata/se_FI.dat differ diff --git a/boilerplate/external/babel/localedata/se_NO.dat b/boilerplate/external/babel/localedata/se_NO.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/se_NO.dat differ diff --git a/boilerplate/external/babel/localedata/sh.dat b/boilerplate/external/babel/localedata/sh.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/sh.dat differ diff --git a/boilerplate/external/babel/localedata/sh_BA.dat b/boilerplate/external/babel/localedata/sh_BA.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sh_BA.dat differ diff --git a/boilerplate/external/babel/localedata/sh_CS.dat b/boilerplate/external/babel/localedata/sh_CS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sh_CS.dat differ diff --git a/boilerplate/external/babel/localedata/sh_YU.dat b/boilerplate/external/babel/localedata/sh_YU.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sh_YU.dat differ diff --git a/boilerplate/external/babel/localedata/si.dat b/boilerplate/external/babel/localedata/si.dat new file mode 100644 index 0000000..0c60e6f Binary files /dev/null and b/boilerplate/external/babel/localedata/si.dat differ diff --git a/boilerplate/external/babel/localedata/si_LK.dat b/boilerplate/external/babel/localedata/si_LK.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/si_LK.dat differ diff --git a/boilerplate/external/babel/localedata/sid.dat b/boilerplate/external/babel/localedata/sid.dat new file mode 100644 index 0000000..870a420 Binary files /dev/null and b/boilerplate/external/babel/localedata/sid.dat differ diff --git a/boilerplate/external/babel/localedata/sid_ET.dat b/boilerplate/external/babel/localedata/sid_ET.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/sid_ET.dat differ diff --git a/boilerplate/external/babel/localedata/sk.dat b/boilerplate/external/babel/localedata/sk.dat new file mode 100644 index 0000000..6931ff5 Binary files /dev/null and b/boilerplate/external/babel/localedata/sk.dat differ diff --git a/boilerplate/external/babel/localedata/sk_SK.dat b/boilerplate/external/babel/localedata/sk_SK.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/sk_SK.dat differ diff --git a/boilerplate/external/babel/localedata/sl.dat b/boilerplate/external/babel/localedata/sl.dat new file mode 100644 index 0000000..56697b7 Binary files /dev/null and b/boilerplate/external/babel/localedata/sl.dat differ diff --git a/boilerplate/external/babel/localedata/sl_SI.dat b/boilerplate/external/babel/localedata/sl_SI.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/sl_SI.dat differ diff --git a/boilerplate/external/babel/localedata/so.dat b/boilerplate/external/babel/localedata/so.dat new file mode 100644 index 0000000..0adb6e0 Binary files /dev/null and b/boilerplate/external/babel/localedata/so.dat differ diff --git a/boilerplate/external/babel/localedata/so_DJ.dat b/boilerplate/external/babel/localedata/so_DJ.dat new file mode 100644 index 0000000..b487e1b Binary files /dev/null and b/boilerplate/external/babel/localedata/so_DJ.dat differ diff --git a/boilerplate/external/babel/localedata/so_ET.dat b/boilerplate/external/babel/localedata/so_ET.dat new file mode 100644 index 0000000..3477cd3 Binary files /dev/null and b/boilerplate/external/babel/localedata/so_ET.dat differ diff --git a/boilerplate/external/babel/localedata/so_KE.dat b/boilerplate/external/babel/localedata/so_KE.dat new file mode 100644 index 0000000..a456149 Binary files /dev/null and b/boilerplate/external/babel/localedata/so_KE.dat differ diff --git a/boilerplate/external/babel/localedata/so_SO.dat b/boilerplate/external/babel/localedata/so_SO.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/so_SO.dat differ diff --git a/boilerplate/external/babel/localedata/sq.dat b/boilerplate/external/babel/localedata/sq.dat new file mode 100644 index 0000000..1582ea0 Binary files /dev/null and b/boilerplate/external/babel/localedata/sq.dat differ diff --git a/boilerplate/external/babel/localedata/sq_AL.dat b/boilerplate/external/babel/localedata/sq_AL.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sq_AL.dat differ diff --git a/boilerplate/external/babel/localedata/sr.dat b/boilerplate/external/babel/localedata/sr.dat new file mode 100644 index 0000000..f37ec9a Binary files /dev/null and b/boilerplate/external/babel/localedata/sr.dat differ diff --git a/boilerplate/external/babel/localedata/sr_BA.dat b/boilerplate/external/babel/localedata/sr_BA.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_BA.dat differ diff --git a/boilerplate/external/babel/localedata/sr_CS.dat b/boilerplate/external/babel/localedata/sr_CS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_CS.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Cyrl.dat b/boilerplate/external/babel/localedata/sr_Cyrl.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Cyrl.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Cyrl_BA.dat b/boilerplate/external/babel/localedata/sr_Cyrl_BA.dat new file mode 100644 index 0000000..76f4ffa Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Cyrl_BA.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Cyrl_CS.dat b/boilerplate/external/babel/localedata/sr_Cyrl_CS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Cyrl_CS.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Cyrl_ME.dat b/boilerplate/external/babel/localedata/sr_Cyrl_ME.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Cyrl_ME.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Cyrl_RS.dat b/boilerplate/external/babel/localedata/sr_Cyrl_RS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Cyrl_RS.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Cyrl_YU.dat b/boilerplate/external/babel/localedata/sr_Cyrl_YU.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Cyrl_YU.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Latn.dat b/boilerplate/external/babel/localedata/sr_Latn.dat new file mode 100644 index 0000000..0ea9a4e Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Latn.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Latn_BA.dat b/boilerplate/external/babel/localedata/sr_Latn_BA.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Latn_BA.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Latn_CS.dat b/boilerplate/external/babel/localedata/sr_Latn_CS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Latn_CS.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Latn_ME.dat b/boilerplate/external/babel/localedata/sr_Latn_ME.dat new file mode 100644 index 0000000..5019868 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Latn_ME.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Latn_RS.dat b/boilerplate/external/babel/localedata/sr_Latn_RS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Latn_RS.dat differ diff --git a/boilerplate/external/babel/localedata/sr_Latn_YU.dat b/boilerplate/external/babel/localedata/sr_Latn_YU.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_Latn_YU.dat differ diff --git a/boilerplate/external/babel/localedata/sr_ME.dat b/boilerplate/external/babel/localedata/sr_ME.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_ME.dat differ diff --git a/boilerplate/external/babel/localedata/sr_RS.dat b/boilerplate/external/babel/localedata/sr_RS.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_RS.dat differ diff --git a/boilerplate/external/babel/localedata/sr_YU.dat b/boilerplate/external/babel/localedata/sr_YU.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sr_YU.dat differ diff --git a/boilerplate/external/babel/localedata/ss.dat b/boilerplate/external/babel/localedata/ss.dat new file mode 100644 index 0000000..989959f Binary files /dev/null and b/boilerplate/external/babel/localedata/ss.dat differ diff --git a/boilerplate/external/babel/localedata/ss_SZ.dat b/boilerplate/external/babel/localedata/ss_SZ.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ss_SZ.dat differ diff --git a/boilerplate/external/babel/localedata/ss_ZA.dat b/boilerplate/external/babel/localedata/ss_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ss_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/st.dat b/boilerplate/external/babel/localedata/st.dat new file mode 100644 index 0000000..1be0ba9 Binary files /dev/null and b/boilerplate/external/babel/localedata/st.dat differ diff --git a/boilerplate/external/babel/localedata/st_LS.dat b/boilerplate/external/babel/localedata/st_LS.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/st_LS.dat differ diff --git a/boilerplate/external/babel/localedata/st_ZA.dat b/boilerplate/external/babel/localedata/st_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/st_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/sv.dat b/boilerplate/external/babel/localedata/sv.dat new file mode 100644 index 0000000..315daaf Binary files /dev/null and b/boilerplate/external/babel/localedata/sv.dat differ diff --git a/boilerplate/external/babel/localedata/sv_FI.dat b/boilerplate/external/babel/localedata/sv_FI.dat new file mode 100644 index 0000000..81ec61e Binary files /dev/null and b/boilerplate/external/babel/localedata/sv_FI.dat differ diff --git a/boilerplate/external/babel/localedata/sv_SE.dat b/boilerplate/external/babel/localedata/sv_SE.dat new file mode 100644 index 0000000..d4e5e45 Binary files /dev/null and b/boilerplate/external/babel/localedata/sv_SE.dat differ diff --git a/boilerplate/external/babel/localedata/sw.dat b/boilerplate/external/babel/localedata/sw.dat new file mode 100644 index 0000000..0043cf2 Binary files /dev/null and b/boilerplate/external/babel/localedata/sw.dat differ diff --git a/boilerplate/external/babel/localedata/sw_KE.dat b/boilerplate/external/babel/localedata/sw_KE.dat new file mode 100644 index 0000000..c03dd35 Binary files /dev/null and b/boilerplate/external/babel/localedata/sw_KE.dat differ diff --git a/boilerplate/external/babel/localedata/sw_TZ.dat b/boilerplate/external/babel/localedata/sw_TZ.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/sw_TZ.dat differ diff --git a/boilerplate/external/babel/localedata/syr.dat b/boilerplate/external/babel/localedata/syr.dat new file mode 100644 index 0000000..9e19a60 Binary files /dev/null and b/boilerplate/external/babel/localedata/syr.dat differ diff --git a/boilerplate/external/babel/localedata/syr_SY.dat b/boilerplate/external/babel/localedata/syr_SY.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/syr_SY.dat differ diff --git a/boilerplate/external/babel/localedata/ta.dat b/boilerplate/external/babel/localedata/ta.dat new file mode 100644 index 0000000..097bbd3 Binary files /dev/null and b/boilerplate/external/babel/localedata/ta.dat differ diff --git a/boilerplate/external/babel/localedata/ta_IN.dat b/boilerplate/external/babel/localedata/ta_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ta_IN.dat differ diff --git a/boilerplate/external/babel/localedata/te.dat b/boilerplate/external/babel/localedata/te.dat new file mode 100644 index 0000000..ed76bd7 Binary files /dev/null and b/boilerplate/external/babel/localedata/te.dat differ diff --git a/boilerplate/external/babel/localedata/te_IN.dat b/boilerplate/external/babel/localedata/te_IN.dat new file mode 100644 index 0000000..1080cd8 Binary files /dev/null and b/boilerplate/external/babel/localedata/te_IN.dat differ diff --git a/boilerplate/external/babel/localedata/tg.dat b/boilerplate/external/babel/localedata/tg.dat new file mode 100644 index 0000000..6f96c3e Binary files /dev/null and b/boilerplate/external/babel/localedata/tg.dat differ diff --git a/boilerplate/external/babel/localedata/tg_Cyrl.dat b/boilerplate/external/babel/localedata/tg_Cyrl.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/tg_Cyrl.dat differ diff --git a/boilerplate/external/babel/localedata/tg_Cyrl_TJ.dat b/boilerplate/external/babel/localedata/tg_Cyrl_TJ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/tg_Cyrl_TJ.dat differ diff --git a/boilerplate/external/babel/localedata/tg_TJ.dat b/boilerplate/external/babel/localedata/tg_TJ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/tg_TJ.dat differ diff --git a/boilerplate/external/babel/localedata/th.dat b/boilerplate/external/babel/localedata/th.dat new file mode 100644 index 0000000..cc4ceec Binary files /dev/null and b/boilerplate/external/babel/localedata/th.dat differ diff --git a/boilerplate/external/babel/localedata/th_TH.dat b/boilerplate/external/babel/localedata/th_TH.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/th_TH.dat differ diff --git a/boilerplate/external/babel/localedata/ti.dat b/boilerplate/external/babel/localedata/ti.dat new file mode 100644 index 0000000..7b6bea6 Binary files /dev/null and b/boilerplate/external/babel/localedata/ti.dat differ diff --git a/boilerplate/external/babel/localedata/ti_ER.dat b/boilerplate/external/babel/localedata/ti_ER.dat new file mode 100644 index 0000000..6e47e30 Binary files /dev/null and b/boilerplate/external/babel/localedata/ti_ER.dat differ diff --git a/boilerplate/external/babel/localedata/ti_ET.dat b/boilerplate/external/babel/localedata/ti_ET.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/ti_ET.dat differ diff --git a/boilerplate/external/babel/localedata/tig.dat b/boilerplate/external/babel/localedata/tig.dat new file mode 100644 index 0000000..8aff772 Binary files /dev/null and b/boilerplate/external/babel/localedata/tig.dat differ diff --git a/boilerplate/external/babel/localedata/tig_ER.dat b/boilerplate/external/babel/localedata/tig_ER.dat new file mode 100644 index 0000000..aa34f4b Binary files /dev/null and b/boilerplate/external/babel/localedata/tig_ER.dat differ diff --git a/boilerplate/external/babel/localedata/tl.dat b/boilerplate/external/babel/localedata/tl.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/tl.dat differ diff --git a/boilerplate/external/babel/localedata/tn.dat b/boilerplate/external/babel/localedata/tn.dat new file mode 100644 index 0000000..5798123 Binary files /dev/null and b/boilerplate/external/babel/localedata/tn.dat differ diff --git a/boilerplate/external/babel/localedata/tn_ZA.dat b/boilerplate/external/babel/localedata/tn_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/tn_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/to.dat b/boilerplate/external/babel/localedata/to.dat new file mode 100644 index 0000000..9365ec8 Binary files /dev/null and b/boilerplate/external/babel/localedata/to.dat differ diff --git a/boilerplate/external/babel/localedata/to_TO.dat b/boilerplate/external/babel/localedata/to_TO.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/to_TO.dat differ diff --git a/boilerplate/external/babel/localedata/tr.dat b/boilerplate/external/babel/localedata/tr.dat new file mode 100644 index 0000000..eb3fcab Binary files /dev/null and b/boilerplate/external/babel/localedata/tr.dat differ diff --git a/boilerplate/external/babel/localedata/tr_TR.dat b/boilerplate/external/babel/localedata/tr_TR.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/tr_TR.dat differ diff --git a/boilerplate/external/babel/localedata/trv.dat b/boilerplate/external/babel/localedata/trv.dat new file mode 100644 index 0000000..bdbbb33 Binary files /dev/null and b/boilerplate/external/babel/localedata/trv.dat differ diff --git a/boilerplate/external/babel/localedata/ts.dat b/boilerplate/external/babel/localedata/ts.dat new file mode 100644 index 0000000..5b30431 Binary files /dev/null and b/boilerplate/external/babel/localedata/ts.dat differ diff --git a/boilerplate/external/babel/localedata/ts_ZA.dat b/boilerplate/external/babel/localedata/ts_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ts_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/tt.dat b/boilerplate/external/babel/localedata/tt.dat new file mode 100644 index 0000000..1440d03 Binary files /dev/null and b/boilerplate/external/babel/localedata/tt.dat differ diff --git a/boilerplate/external/babel/localedata/tt_RU.dat b/boilerplate/external/babel/localedata/tt_RU.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/tt_RU.dat differ diff --git a/boilerplate/external/babel/localedata/ug.dat b/boilerplate/external/babel/localedata/ug.dat new file mode 100644 index 0000000..d211140 Binary files /dev/null and b/boilerplate/external/babel/localedata/ug.dat differ diff --git a/boilerplate/external/babel/localedata/ug_Arab.dat b/boilerplate/external/babel/localedata/ug_Arab.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ug_Arab.dat differ diff --git a/boilerplate/external/babel/localedata/ug_Arab_CN.dat b/boilerplate/external/babel/localedata/ug_Arab_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ug_Arab_CN.dat differ diff --git a/boilerplate/external/babel/localedata/ug_CN.dat b/boilerplate/external/babel/localedata/ug_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ug_CN.dat differ diff --git a/boilerplate/external/babel/localedata/uk.dat b/boilerplate/external/babel/localedata/uk.dat new file mode 100644 index 0000000..955e94b Binary files /dev/null and b/boilerplate/external/babel/localedata/uk.dat differ diff --git a/boilerplate/external/babel/localedata/uk_UA.dat b/boilerplate/external/babel/localedata/uk_UA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/uk_UA.dat differ diff --git a/boilerplate/external/babel/localedata/ur.dat b/boilerplate/external/babel/localedata/ur.dat new file mode 100644 index 0000000..8a9a10b Binary files /dev/null and b/boilerplate/external/babel/localedata/ur.dat differ diff --git a/boilerplate/external/babel/localedata/ur_IN.dat b/boilerplate/external/babel/localedata/ur_IN.dat new file mode 100644 index 0000000..c5dfd3d Binary files /dev/null and b/boilerplate/external/babel/localedata/ur_IN.dat differ diff --git a/boilerplate/external/babel/localedata/ur_PK.dat b/boilerplate/external/babel/localedata/ur_PK.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/ur_PK.dat differ diff --git a/boilerplate/external/babel/localedata/uz.dat b/boilerplate/external/babel/localedata/uz.dat new file mode 100644 index 0000000..18833b2 Binary files /dev/null and b/boilerplate/external/babel/localedata/uz.dat differ diff --git a/boilerplate/external/babel/localedata/uz_AF.dat b/boilerplate/external/babel/localedata/uz_AF.dat new file mode 100644 index 0000000..c33cab6 Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_AF.dat differ diff --git a/boilerplate/external/babel/localedata/uz_Arab.dat b/boilerplate/external/babel/localedata/uz_Arab.dat new file mode 100644 index 0000000..eeed9b4 Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_Arab.dat differ diff --git a/boilerplate/external/babel/localedata/uz_Arab_AF.dat b/boilerplate/external/babel/localedata/uz_Arab_AF.dat new file mode 100644 index 0000000..c33cab6 Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_Arab_AF.dat differ diff --git a/boilerplate/external/babel/localedata/uz_Cyrl.dat b/boilerplate/external/babel/localedata/uz_Cyrl.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_Cyrl.dat differ diff --git a/boilerplate/external/babel/localedata/uz_Cyrl_UZ.dat b/boilerplate/external/babel/localedata/uz_Cyrl_UZ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_Cyrl_UZ.dat differ diff --git a/boilerplate/external/babel/localedata/uz_Latn.dat b/boilerplate/external/babel/localedata/uz_Latn.dat new file mode 100644 index 0000000..055d6c7 Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_Latn.dat differ diff --git a/boilerplate/external/babel/localedata/uz_Latn_UZ.dat b/boilerplate/external/babel/localedata/uz_Latn_UZ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_Latn_UZ.dat differ diff --git a/boilerplate/external/babel/localedata/uz_UZ.dat b/boilerplate/external/babel/localedata/uz_UZ.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/uz_UZ.dat differ diff --git a/boilerplate/external/babel/localedata/ve.dat b/boilerplate/external/babel/localedata/ve.dat new file mode 100644 index 0000000..870c451 Binary files /dev/null and b/boilerplate/external/babel/localedata/ve.dat differ diff --git a/boilerplate/external/babel/localedata/ve_ZA.dat b/boilerplate/external/babel/localedata/ve_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/ve_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/vi.dat b/boilerplate/external/babel/localedata/vi.dat new file mode 100644 index 0000000..cf74ba4 Binary files /dev/null and b/boilerplate/external/babel/localedata/vi.dat differ diff --git a/boilerplate/external/babel/localedata/vi_VN.dat b/boilerplate/external/babel/localedata/vi_VN.dat new file mode 100644 index 0000000..6c52e35 Binary files /dev/null and b/boilerplate/external/babel/localedata/vi_VN.dat differ diff --git a/boilerplate/external/babel/localedata/wal.dat b/boilerplate/external/babel/localedata/wal.dat new file mode 100644 index 0000000..a06545e Binary files /dev/null and b/boilerplate/external/babel/localedata/wal.dat differ diff --git a/boilerplate/external/babel/localedata/wal_ET.dat b/boilerplate/external/babel/localedata/wal_ET.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/wal_ET.dat differ diff --git a/boilerplate/external/babel/localedata/wo.dat b/boilerplate/external/babel/localedata/wo.dat new file mode 100644 index 0000000..fb2dfc5 Binary files /dev/null and b/boilerplate/external/babel/localedata/wo.dat differ diff --git a/boilerplate/external/babel/localedata/wo_Latn.dat b/boilerplate/external/babel/localedata/wo_Latn.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/wo_Latn.dat differ diff --git a/boilerplate/external/babel/localedata/wo_Latn_SN.dat b/boilerplate/external/babel/localedata/wo_Latn_SN.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/wo_Latn_SN.dat differ diff --git a/boilerplate/external/babel/localedata/wo_SN.dat b/boilerplate/external/babel/localedata/wo_SN.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/wo_SN.dat differ diff --git a/boilerplate/external/babel/localedata/xh.dat b/boilerplate/external/babel/localedata/xh.dat new file mode 100644 index 0000000..7172dd2 Binary files /dev/null and b/boilerplate/external/babel/localedata/xh.dat differ diff --git a/boilerplate/external/babel/localedata/xh_ZA.dat b/boilerplate/external/babel/localedata/xh_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/xh_ZA.dat differ diff --git a/boilerplate/external/babel/localedata/yo.dat b/boilerplate/external/babel/localedata/yo.dat new file mode 100644 index 0000000..e5f1c40 Binary files /dev/null and b/boilerplate/external/babel/localedata/yo.dat differ diff --git a/boilerplate/external/babel/localedata/yo_NG.dat b/boilerplate/external/babel/localedata/yo_NG.dat new file mode 100644 index 0000000..1967e9e Binary files /dev/null and b/boilerplate/external/babel/localedata/yo_NG.dat differ diff --git a/boilerplate/external/babel/localedata/zh.dat b/boilerplate/external/babel/localedata/zh.dat new file mode 100644 index 0000000..16b6978 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh.dat differ diff --git a/boilerplate/external/babel/localedata/zh_CN.dat b/boilerplate/external/babel/localedata/zh_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_CN.dat differ diff --git a/boilerplate/external/babel/localedata/zh_HK.dat b/boilerplate/external/babel/localedata/zh_HK.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_HK.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hans.dat b/boilerplate/external/babel/localedata/zh_Hans.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hans.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hans_CN.dat b/boilerplate/external/babel/localedata/zh_Hans_CN.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hans_CN.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hans_HK.dat b/boilerplate/external/babel/localedata/zh_Hans_HK.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hans_HK.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hans_MO.dat b/boilerplate/external/babel/localedata/zh_Hans_MO.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hans_MO.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hans_SG.dat b/boilerplate/external/babel/localedata/zh_Hans_SG.dat new file mode 100644 index 0000000..3376b52 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hans_SG.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hant.dat b/boilerplate/external/babel/localedata/zh_Hant.dat new file mode 100644 index 0000000..ec9ff48 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hant.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hant_HK.dat b/boilerplate/external/babel/localedata/zh_Hant_HK.dat new file mode 100644 index 0000000..60834ca Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hant_HK.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hant_MO.dat b/boilerplate/external/babel/localedata/zh_Hant_MO.dat new file mode 100644 index 0000000..fc05b3a Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hant_MO.dat differ diff --git a/boilerplate/external/babel/localedata/zh_Hant_TW.dat b/boilerplate/external/babel/localedata/zh_Hant_TW.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_Hant_TW.dat differ diff --git a/boilerplate/external/babel/localedata/zh_MO.dat b/boilerplate/external/babel/localedata/zh_MO.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_MO.dat differ diff --git a/boilerplate/external/babel/localedata/zh_SG.dat b/boilerplate/external/babel/localedata/zh_SG.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_SG.dat differ diff --git a/boilerplate/external/babel/localedata/zh_TW.dat b/boilerplate/external/babel/localedata/zh_TW.dat new file mode 100644 index 0000000..ea229b8 Binary files /dev/null and b/boilerplate/external/babel/localedata/zh_TW.dat differ diff --git a/boilerplate/external/babel/localedata/zu.dat b/boilerplate/external/babel/localedata/zu.dat new file mode 100644 index 0000000..f087079 Binary files /dev/null and b/boilerplate/external/babel/localedata/zu.dat differ diff --git a/boilerplate/external/babel/localedata/zu_ZA.dat b/boilerplate/external/babel/localedata/zu_ZA.dat new file mode 100644 index 0000000..25a701c Binary files /dev/null and b/boilerplate/external/babel/localedata/zu_ZA.dat differ diff --git a/boilerplate/external/babel/messages/__init__.py b/boilerplate/external/babel/messages/__init__.py new file mode 100644 index 0000000..da2e38d --- /dev/null +++ b/boilerplate/external/babel/messages/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Support for ``gettext`` message catalogs.""" + +from babel.messages.catalog import * diff --git a/boilerplate/external/babel/messages/catalog.py b/boilerplate/external/babel/messages/catalog.py new file mode 100644 index 0000000..ca50d8b --- /dev/null +++ b/boilerplate/external/babel/messages/catalog.py @@ -0,0 +1,768 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Data structures for message catalogs.""" + +from cgi import parse_header +from datetime import datetime +from difflib import get_close_matches +from email import message_from_string +from copy import copy +import re +import time + +from babel import __version__ as VERSION +from babel.core import Locale +from babel.dates import format_datetime +from babel.messages.plurals import get_plural +from babel.util import odict, distinct, set, LOCALTZ, UTC, FixedOffsetTimezone + +__all__ = ['Message', 'Catalog', 'TranslationError'] +__docformat__ = 'restructuredtext en' + + +PYTHON_FORMAT = re.compile(r'''(?x) + \% + (?:\(([\w]*)\))? + ( + [-#0\ +]?(?:\*|[\d]+)? + (?:\.(?:\*|[\d]+))? + [hlL]? + ) + ([diouxXeEfFgGcrs%]) +''') + + +class Message(object): + """Representation of a single message in a catalog.""" + + def __init__(self, id, string=u'', locations=(), flags=(), auto_comments=(), + user_comments=(), previous_id=(), lineno=None): + """Create the message object. + + :param id: the message ID, or a ``(singular, plural)`` tuple for + pluralizable messages + :param string: the translated message string, or a + ``(singular, plural)`` tuple for pluralizable messages + :param locations: a sequence of ``(filenname, lineno)`` tuples + :param flags: a set or sequence of flags + :param auto_comments: a sequence of automatic comments for the message + :param user_comments: a sequence of user comments for the message + :param previous_id: the previous message ID, or a ``(singular, plural)`` + tuple for pluralizable messages + :param lineno: the line number on which the msgid line was found in the + PO file, if any + """ + self.id = id #: The message ID + if not string and self.pluralizable: + string = (u'', u'') + self.string = string #: The message translation + self.locations = list(distinct(locations)) + self.flags = set(flags) + if id and self.python_format: + self.flags.add('python-format') + else: + self.flags.discard('python-format') + self.auto_comments = list(distinct(auto_comments)) + self.user_comments = list(distinct(user_comments)) + if isinstance(previous_id, basestring): + self.previous_id = [previous_id] + else: + self.previous_id = list(previous_id) + self.lineno = lineno + + def __repr__(self): + return '<%s %r (flags: %r)>' % (type(self).__name__, self.id, + list(self.flags)) + + def __cmp__(self, obj): + """Compare Messages, taking into account plural ids""" + if isinstance(obj, Message): + plural = self.pluralizable + obj_plural = obj.pluralizable + if plural and obj_plural: + return cmp(self.id[0], obj.id[0]) + elif plural: + return cmp(self.id[0], obj.id) + elif obj_plural: + return cmp(self.id, obj.id[0]) + return cmp(self.id, obj.id) + + def clone(self): + return Message(*map(copy, (self.id, self.string, self.locations, + self.flags, self.auto_comments, + self.user_comments, self.previous_id, + self.lineno))) + + def check(self, catalog=None): + """Run various validation checks on the message. Some validations + are only performed if the catalog is provided. This method returns + a sequence of `TranslationError` objects. + + :rtype: ``iterator`` + :param catalog: A catalog instance that is passed to the checkers + :see: `Catalog.check` for a way to perform checks for all messages + in a catalog. + """ + from babel.messages.checkers import checkers + errors = [] + for checker in checkers: + try: + checker(catalog, self) + except TranslationError, e: + errors.append(e) + return errors + + def fuzzy(self): + return 'fuzzy' in self.flags + fuzzy = property(fuzzy, doc="""\ + Whether the translation is fuzzy. + + >>> Message('foo').fuzzy + False + >>> msg = Message('foo', 'foo', flags=['fuzzy']) + >>> msg.fuzzy + True + >>> msg + + + :type: `bool` + """) + + def pluralizable(self): + return isinstance(self.id, (list, tuple)) + pluralizable = property(pluralizable, doc="""\ + Whether the message is plurizable. + + >>> Message('foo').pluralizable + False + >>> Message(('foo', 'bar')).pluralizable + True + + :type: `bool` + """) + + def python_format(self): + ids = self.id + if not isinstance(ids, (list, tuple)): + ids = [ids] + return bool(filter(None, [PYTHON_FORMAT.search(id) for id in ids])) + python_format = property(python_format, doc="""\ + Whether the message contains Python-style parameters. + + >>> Message('foo %(name)s bar').python_format + True + >>> Message(('foo %(name)s', 'foo %(name)s')).python_format + True + + :type: `bool` + """) + + +class TranslationError(Exception): + """Exception thrown by translation checkers when invalid message + translations are encountered.""" + + +DEFAULT_HEADER = u"""\ +# Translations template for PROJECT. +# Copyright (C) YEAR ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , YEAR. +#""" + + +class Catalog(object): + """Representation of a message catalog.""" + + def __init__(self, locale=None, domain=None, header_comment=DEFAULT_HEADER, + project=None, version=None, copyright_holder=None, + msgid_bugs_address=None, creation_date=None, + revision_date=None, last_translator=None, language_team=None, + charset='utf-8', fuzzy=True): + """Initialize the catalog object. + + :param locale: the locale identifier or `Locale` object, or `None` + if the catalog is not bound to a locale (which basically + means it's a template) + :param domain: the message domain + :param header_comment: the header comment as string, or `None` for the + default header + :param project: the project's name + :param version: the project's version + :param copyright_holder: the copyright holder of the catalog + :param msgid_bugs_address: the email address or URL to submit bug + reports to + :param creation_date: the date the catalog was created + :param revision_date: the date the catalog was revised + :param last_translator: the name and email of the last translator + :param language_team: the name and email of the language team + :param charset: the encoding to use in the output + :param fuzzy: the fuzzy bit on the catalog header + """ + self.domain = domain #: The message domain + if locale: + locale = Locale.parse(locale) + self.locale = locale #: The locale or `None` + self._header_comment = header_comment + self._messages = odict() + + self.project = project or 'PROJECT' #: The project name + self.version = version or 'VERSION' #: The project version + self.copyright_holder = copyright_holder or 'ORGANIZATION' + self.msgid_bugs_address = msgid_bugs_address or 'EMAIL@ADDRESS' + + self.last_translator = last_translator or 'FULL NAME ' + """Name and email address of the last translator.""" + self.language_team = language_team or 'LANGUAGE ' + """Name and email address of the language team.""" + + self.charset = charset or 'utf-8' + + if creation_date is None: + creation_date = datetime.now(LOCALTZ) + elif isinstance(creation_date, datetime) and not creation_date.tzinfo: + creation_date = creation_date.replace(tzinfo=LOCALTZ) + self.creation_date = creation_date #: Creation date of the template + if revision_date is None: + revision_date = datetime.now(LOCALTZ) + elif isinstance(revision_date, datetime) and not revision_date.tzinfo: + revision_date = revision_date.replace(tzinfo=LOCALTZ) + self.revision_date = revision_date #: Last revision date of the catalog + self.fuzzy = fuzzy #: Catalog header fuzzy bit (`True` or `False`) + + self.obsolete = odict() #: Dictionary of obsolete messages + self._num_plurals = None + self._plural_expr = None + + def _get_header_comment(self): + comment = self._header_comment + comment = comment.replace('PROJECT', self.project) \ + .replace('VERSION', self.version) \ + .replace('YEAR', self.revision_date.strftime('%Y')) \ + .replace('ORGANIZATION', self.copyright_holder) + if self.locale: + comment = comment.replace('Translations template', '%s translations' + % self.locale.english_name) + return comment + + def _set_header_comment(self, string): + self._header_comment = string + + header_comment = property(_get_header_comment, _set_header_comment, doc="""\ + The header comment for the catalog. + + >>> catalog = Catalog(project='Foobar', version='1.0', + ... copyright_holder='Foo Company') + >>> print catalog.header_comment #doctest: +ELLIPSIS + # Translations template for Foobar. + # Copyright (C) ... Foo Company + # This file is distributed under the same license as the Foobar project. + # FIRST AUTHOR , .... + # + + The header can also be set from a string. Any known upper-case variables + will be replaced when the header is retrieved again: + + >>> catalog = Catalog(project='Foobar', version='1.0', + ... copyright_holder='Foo Company') + >>> catalog.header_comment = '''\\ + ... # The POT for my really cool PROJECT project. + ... # Copyright (C) 1990-2003 ORGANIZATION + ... # This file is distributed under the same license as the PROJECT + ... # project. + ... #''' + >>> print catalog.header_comment + # The POT for my really cool Foobar project. + # Copyright (C) 1990-2003 Foo Company + # This file is distributed under the same license as the Foobar + # project. + # + + :type: `unicode` + """) + + def _get_mime_headers(self): + headers = [] + headers.append(('Project-Id-Version', + '%s %s' % (self.project, self.version))) + headers.append(('Report-Msgid-Bugs-To', self.msgid_bugs_address)) + headers.append(('POT-Creation-Date', + format_datetime(self.creation_date, 'yyyy-MM-dd HH:mmZ', + locale='en'))) + if self.locale is None: + headers.append(('PO-Revision-Date', 'YEAR-MO-DA HO:MI+ZONE')) + headers.append(('Last-Translator', 'FULL NAME ')) + headers.append(('Language-Team', 'LANGUAGE ')) + else: + headers.append(('PO-Revision-Date', + format_datetime(self.revision_date, + 'yyyy-MM-dd HH:mmZ', locale='en'))) + headers.append(('Last-Translator', self.last_translator)) + headers.append(('Language-Team', + self.language_team.replace('LANGUAGE', + str(self.locale)))) + headers.append(('Plural-Forms', self.plural_forms)) + headers.append(('MIME-Version', '1.0')) + headers.append(('Content-Type', + 'text/plain; charset=%s' % self.charset)) + headers.append(('Content-Transfer-Encoding', '8bit')) + headers.append(('Generated-By', 'Babel %s\n' % VERSION)) + return headers + + def _set_mime_headers(self, headers): + for name, value in headers: + if name.lower() == 'content-type': + mimetype, params = parse_header(value) + if 'charset' in params: + self.charset = params['charset'].lower() + break + for name, value in headers: + name = name.lower().decode(self.charset) + value = value.decode(self.charset) + if name == 'project-id-version': + parts = value.split(' ') + self.project = u' '.join(parts[:-1]) + self.version = parts[-1] + elif name == 'report-msgid-bugs-to': + self.msgid_bugs_address = value + elif name == 'last-translator': + self.last_translator = value + elif name == 'language-team': + self.language_team = value + elif name == 'plural-forms': + _, params = parse_header(' ;' + value) + self._num_plurals = int(params.get('nplurals', 2)) + self._plural_expr = params.get('plural', '(n != 1)') + elif name == 'pot-creation-date': + # FIXME: this should use dates.parse_datetime as soon as that + # is ready + value, tzoffset, _ = re.split('([+-]\d{4})$', value, 1) + + tt = time.strptime(value, '%Y-%m-%d %H:%M') + ts = time.mktime(tt) + + # Separate the offset into a sign component, hours, and minutes + plus_minus_s, rest = tzoffset[0], tzoffset[1:] + hours_offset_s, mins_offset_s = rest[:2], rest[2:] + + # Make them all integers + plus_minus = int(plus_minus_s + '1') + hours_offset = int(hours_offset_s) + mins_offset = int(mins_offset_s) + + # Calculate net offset + net_mins_offset = hours_offset * 60 + net_mins_offset += mins_offset + net_mins_offset *= plus_minus + + # Create an offset object + tzoffset = FixedOffsetTimezone(net_mins_offset) + + # Store the offset in a datetime object + dt = datetime.fromtimestamp(ts) + self.creation_date = dt.replace(tzinfo=tzoffset) + elif name == 'po-revision-date': + # Keep the value if it's not the default one + if 'YEAR' not in value: + # FIXME: this should use dates.parse_datetime as soon as + # that is ready + value, tzoffset, _ = re.split('([+-]\d{4})$', value, 1) + tt = time.strptime(value, '%Y-%m-%d %H:%M') + ts = time.mktime(tt) + + # Separate the offset into a sign component, hours, and + # minutes + plus_minus_s, rest = tzoffset[0], tzoffset[1:] + hours_offset_s, mins_offset_s = rest[:2], rest[2:] + + # Make them all integers + plus_minus = int(plus_minus_s + '1') + hours_offset = int(hours_offset_s) + mins_offset = int(mins_offset_s) + + # Calculate net offset + net_mins_offset = hours_offset * 60 + net_mins_offset += mins_offset + net_mins_offset *= plus_minus + + # Create an offset object + tzoffset = FixedOffsetTimezone(net_mins_offset) + + # Store the offset in a datetime object + dt = datetime.fromtimestamp(ts) + self.revision_date = dt.replace(tzinfo=tzoffset) + + mime_headers = property(_get_mime_headers, _set_mime_headers, doc="""\ + The MIME headers of the catalog, used for the special ``msgid ""`` entry. + + The behavior of this property changes slightly depending on whether a locale + is set or not, the latter indicating that the catalog is actually a template + for actual translations. + + Here's an example of the output for such a catalog template: + + >>> created = datetime(1990, 4, 1, 15, 30, tzinfo=UTC) + >>> catalog = Catalog(project='Foobar', version='1.0', + ... creation_date=created) + >>> for name, value in catalog.mime_headers: + ... print '%s: %s' % (name, value) + Project-Id-Version: Foobar 1.0 + Report-Msgid-Bugs-To: EMAIL@ADDRESS + POT-Creation-Date: 1990-04-01 15:30+0000 + PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE + Last-Translator: FULL NAME + Language-Team: LANGUAGE + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: 8bit + Generated-By: Babel ... + + And here's an example of the output when the locale is set: + + >>> revised = datetime(1990, 8, 3, 12, 0, tzinfo=UTC) + >>> catalog = Catalog(locale='de_DE', project='Foobar', version='1.0', + ... creation_date=created, revision_date=revised, + ... last_translator='John Doe ', + ... language_team='de_DE ') + >>> for name, value in catalog.mime_headers: + ... print '%s: %s' % (name, value) + Project-Id-Version: Foobar 1.0 + Report-Msgid-Bugs-To: EMAIL@ADDRESS + POT-Creation-Date: 1990-04-01 15:30+0000 + PO-Revision-Date: 1990-08-03 12:00+0000 + Last-Translator: John Doe + Language-Team: de_DE + Plural-Forms: nplurals=2; plural=(n != 1) + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: 8bit + Generated-By: Babel ... + + :type: `list` + """) + + def num_plurals(self): + if self._num_plurals is None: + num = 2 + if self.locale: + num = get_plural(self.locale)[0] + self._num_plurals = num + return self._num_plurals + num_plurals = property(num_plurals, doc="""\ + The number of plurals used by the catalog or locale. + + >>> Catalog(locale='en').num_plurals + 2 + >>> Catalog(locale='ga').num_plurals + 3 + + :type: `int` + """) + + def plural_expr(self): + if self._plural_expr is None: + expr = '(n != 1)' + if self.locale: + expr = get_plural(self.locale)[1] + self._plural_expr = expr + return self._plural_expr + plural_expr = property(plural_expr, doc="""\ + The plural expression used by the catalog or locale. + + >>> Catalog(locale='en').plural_expr + '(n != 1)' + >>> Catalog(locale='ga').plural_expr + '(n==1 ? 0 : n==2 ? 1 : 2)' + + :type: `basestring` + """) + + def plural_forms(self): + return 'nplurals=%s; plural=%s' % (self.num_plurals, self.plural_expr) + plural_forms = property(plural_forms, doc="""\ + Return the plural forms declaration for the locale. + + >>> Catalog(locale='en').plural_forms + 'nplurals=2; plural=(n != 1)' + >>> Catalog(locale='pt_BR').plural_forms + 'nplurals=2; plural=(n > 1)' + + :type: `str` + """) + + def __contains__(self, id): + """Return whether the catalog has a message with the specified ID.""" + return self._key_for(id) in self._messages + + def __len__(self): + """The number of messages in the catalog. + + This does not include the special ``msgid ""`` entry. + """ + return len(self._messages) + + def __iter__(self): + """Iterates through all the entries in the catalog, in the order they + were added, yielding a `Message` object for every entry. + + :rtype: ``iterator`` + """ + buf = [] + for name, value in self.mime_headers: + buf.append('%s: %s' % (name, value)) + flags = set() + if self.fuzzy: + flags |= set(['fuzzy']) + yield Message(u'', '\n'.join(buf), flags=flags) + for key in self._messages: + yield self._messages[key] + + def __repr__(self): + locale = '' + if self.locale: + locale = ' %s' % self.locale + return '<%s %r%s>' % (type(self).__name__, self.domain, locale) + + def __delitem__(self, id): + """Delete the message with the specified ID.""" + key = self._key_for(id) + if key in self._messages: + del self._messages[key] + + def __getitem__(self, id): + """Return the message with the specified ID. + + :param id: the message ID + :return: the message with the specified ID, or `None` if no such message + is in the catalog + :rtype: `Message` + """ + return self._messages.get(self._key_for(id)) + + def __setitem__(self, id, message): + """Add or update the message with the specified ID. + + >>> catalog = Catalog() + >>> catalog[u'foo'] = Message(u'foo') + >>> catalog[u'foo'] + + + If a message with that ID is already in the catalog, it is updated + to include the locations and flags of the new message. + + >>> catalog = Catalog() + >>> catalog[u'foo'] = Message(u'foo', locations=[('main.py', 1)]) + >>> catalog[u'foo'].locations + [('main.py', 1)] + >>> catalog[u'foo'] = Message(u'foo', locations=[('utils.py', 5)]) + >>> catalog[u'foo'].locations + [('main.py', 1), ('utils.py', 5)] + + :param id: the message ID + :param message: the `Message` object + """ + assert isinstance(message, Message), 'expected a Message object' + key = self._key_for(id) + current = self._messages.get(key) + if current: + if message.pluralizable and not current.pluralizable: + # The new message adds pluralization + current.id = message.id + current.string = message.string + current.locations = list(distinct(current.locations + + message.locations)) + current.auto_comments = list(distinct(current.auto_comments + + message.auto_comments)) + current.user_comments = list(distinct(current.user_comments + + message.user_comments)) + current.flags |= message.flags + message = current + elif id == '': + # special treatment for the header message + headers = message_from_string(message.string.encode(self.charset)) + self.mime_headers = headers.items() + self.header_comment = '\n'.join(['# %s' % comment for comment + in message.user_comments]) + self.fuzzy = message.fuzzy + else: + if isinstance(id, (list, tuple)): + assert isinstance(message.string, (list, tuple)), \ + 'Expected sequence but got %s' % type(message.string) + self._messages[key] = message + + def add(self, id, string=None, locations=(), flags=(), auto_comments=(), + user_comments=(), previous_id=(), lineno=None): + """Add or update the message with the specified ID. + + >>> catalog = Catalog() + >>> catalog.add(u'foo') + >>> catalog[u'foo'] + + + This method simply constructs a `Message` object with the given + arguments and invokes `__setitem__` with that object. + + :param id: the message ID, or a ``(singular, plural)`` tuple for + pluralizable messages + :param string: the translated message string, or a + ``(singular, plural)`` tuple for pluralizable messages + :param locations: a sequence of ``(filenname, lineno)`` tuples + :param flags: a set or sequence of flags + :param auto_comments: a sequence of automatic comments + :param user_comments: a sequence of user comments + :param previous_id: the previous message ID, or a ``(singular, plural)`` + tuple for pluralizable messages + :param lineno: the line number on which the msgid line was found in the + PO file, if any + """ + self[id] = Message(id, string, list(locations), flags, auto_comments, + user_comments, previous_id, lineno=lineno) + + def check(self): + """Run various validation checks on the translations in the catalog. + + For every message which fails validation, this method yield a + ``(message, errors)`` tuple, where ``message`` is the `Message` object + and ``errors`` is a sequence of `TranslationError` objects. + + :rtype: ``iterator`` + """ + for message in self._messages.values(): + errors = message.check(catalog=self) + if errors: + yield message, errors + + def update(self, template, no_fuzzy_matching=False): + """Update the catalog based on the given template catalog. + + >>> from babel.messages import Catalog + >>> template = Catalog() + >>> template.add('green', locations=[('main.py', 99)]) + >>> template.add('blue', locations=[('main.py', 100)]) + >>> template.add(('salad', 'salads'), locations=[('util.py', 42)]) + >>> catalog = Catalog(locale='de_DE') + >>> catalog.add('blue', u'blau', locations=[('main.py', 98)]) + >>> catalog.add('head', u'Kopf', locations=[('util.py', 33)]) + >>> catalog.add(('salad', 'salads'), (u'Salat', u'Salate'), + ... locations=[('util.py', 38)]) + + >>> catalog.update(template) + >>> len(catalog) + 3 + + >>> msg1 = catalog['green'] + >>> msg1.string + >>> msg1.locations + [('main.py', 99)] + + >>> msg2 = catalog['blue'] + >>> msg2.string + u'blau' + >>> msg2.locations + [('main.py', 100)] + + >>> msg3 = catalog['salad'] + >>> msg3.string + (u'Salat', u'Salate') + >>> msg3.locations + [('util.py', 42)] + + Messages that are in the catalog but not in the template are removed + from the main collection, but can still be accessed via the `obsolete` + member: + + >>> 'head' in catalog + False + >>> catalog.obsolete.values() + [] + + :param template: the reference catalog, usually read from a POT file + :param no_fuzzy_matching: whether to use fuzzy matching of message IDs + """ + messages = self._messages + remaining = messages.copy() + self._messages = odict() + + # Prepare for fuzzy matching + fuzzy_candidates = [] + if not no_fuzzy_matching: + fuzzy_candidates = [ + self._key_for(msgid) for msgid in messages + if msgid and messages[msgid].string + ] + fuzzy_matches = set() + + def _merge(message, oldkey, newkey): + message = message.clone() + fuzzy = False + if oldkey != newkey: + fuzzy = True + fuzzy_matches.add(oldkey) + oldmsg = messages.get(oldkey) + if isinstance(oldmsg.id, basestring): + message.previous_id = [oldmsg.id] + else: + message.previous_id = list(oldmsg.id) + else: + oldmsg = remaining.pop(oldkey, None) + message.string = oldmsg.string + if isinstance(message.id, (list, tuple)): + if not isinstance(message.string, (list, tuple)): + fuzzy = True + message.string = tuple( + [message.string] + ([u''] * (len(message.id) - 1)) + ) + elif len(message.string) != self.num_plurals: + fuzzy = True + message.string = tuple(message.string[:len(oldmsg.string)]) + elif isinstance(message.string, (list, tuple)): + fuzzy = True + message.string = message.string[0] + message.flags |= oldmsg.flags + if fuzzy: + message.flags |= set([u'fuzzy']) + self[message.id] = message + + for message in template: + if message.id: + key = self._key_for(message.id) + if key in messages: + _merge(message, key, key) + else: + if no_fuzzy_matching is False: + # do some fuzzy matching with difflib + matches = get_close_matches(key.lower().strip(), + fuzzy_candidates, 1) + if matches: + _merge(message, matches[0], key) + continue + + self[message.id] = message + + self.obsolete = odict() + for msgid in remaining: + if no_fuzzy_matching or msgid not in fuzzy_matches: + self.obsolete[msgid] = remaining[msgid] + # Make updated catalog's POT-Creation-Date equal to the template + # used to update the catalog + self.creation_date = template.creation_date + + def _key_for(self, id): + """The key for a message is just the singular ID even for pluralizable + messages. + """ + key = id + if isinstance(key, (list, tuple)): + key = id[0] + return key diff --git a/boilerplate/external/babel/messages/checkers.py b/boilerplate/external/babel/messages/checkers.py new file mode 100644 index 0000000..82c0189 --- /dev/null +++ b/boilerplate/external/babel/messages/checkers.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Various routines that help with validation of translations. + +:since: version 0.9 +""" + +from itertools import izip +from babel.messages.catalog import TranslationError, PYTHON_FORMAT +from babel.util import set + +#: list of format chars that are compatible to each other +_string_format_compatibilities = [ + set(['i', 'd', 'u']), + set(['x', 'X']), + set(['f', 'F', 'g', 'G']) +] + + +def num_plurals(catalog, message): + """Verify the number of plurals in the translation.""" + if not message.pluralizable: + if not isinstance(message.string, basestring): + raise TranslationError("Found plural forms for non-pluralizable " + "message") + return + + # skip further test if no catalog is provided. + elif catalog is None: + return + + msgstrs = message.string + if not isinstance(msgstrs, (list, tuple)): + msgstrs = (msgstrs,) + if len(msgstrs) != catalog.num_plurals: + raise TranslationError("Wrong number of plural forms (expected %d)" % + catalog.num_plurals) + + +def python_format(catalog, message): + """Verify the format string placeholders in the translation.""" + if 'python-format' not in message.flags: + return + msgids = message.id + if not isinstance(msgids, (list, tuple)): + msgids = (msgids,) + msgstrs = message.string + if not isinstance(msgstrs, (list, tuple)): + msgstrs = (msgstrs,) + + for msgid, msgstr in izip(msgids, msgstrs): + if msgstr: + _validate_format(msgid, msgstr) + + +def _validate_format(format, alternative): + """Test format string `alternative` against `format`. `format` can be the + msgid of a message and `alternative` one of the `msgstr`\s. The two + arguments are not interchangeable as `alternative` may contain less + placeholders if `format` uses named placeholders. + + The behavior of this function is undefined if the string does not use + string formattings. + + If the string formatting of `alternative` is compatible to `format` the + function returns `None`, otherwise a `TranslationError` is raised. + + Examples for compatible format strings: + + >>> _validate_format('Hello %s!', 'Hallo %s!') + >>> _validate_format('Hello %i!', 'Hallo %d!') + + Example for an incompatible format strings: + + >>> _validate_format('Hello %(name)s!', 'Hallo %s!') + Traceback (most recent call last): + ... + TranslationError: the format strings are of different kinds + + This function is used by the `python_format` checker. + + :param format: The original format string + :param alternative: The alternative format string that should be checked + against format + :return: None on success + :raises TranslationError: on formatting errors + """ + + def _parse(string): + result = [] + for match in PYTHON_FORMAT.finditer(string): + name, format, typechar = match.groups() + if typechar == '%' and name is None: + continue + result.append((name, str(typechar))) + return result + + def _compatible(a, b): + if a == b: + return True + for set in _string_format_compatibilities: + if a in set and b in set: + return True + return False + + def _check_positional(results): + positional = None + for name, char in results: + if positional is None: + positional = name is None + else: + if (name is None) != positional: + raise TranslationError('format string mixes positional ' + 'and named placeholders') + return bool(positional) + + a, b = map(_parse, (format, alternative)) + + # now check if both strings are positional or named + a_positional, b_positional = map(_check_positional, (a, b)) + if a_positional and not b_positional and not b: + raise TranslationError('placeholders are incompatible') + elif a_positional != b_positional: + raise TranslationError('the format strings are of different kinds') + + # if we are operating on positional strings both must have the + # same number of format chars and those must be compatible + if a_positional: + if len(a) != len(b): + raise TranslationError('positional format placeholders are ' + 'unbalanced') + for idx, ((_, first), (_, second)) in enumerate(izip(a, b)): + if not _compatible(first, second): + raise TranslationError('incompatible format for placeholder ' + '%d: %r and %r are not compatible' % + (idx + 1, first, second)) + + # otherwise the second string must not have names the first one + # doesn't have and the types of those included must be compatible + else: + type_map = dict(a) + for name, typechar in b: + if name not in type_map: + raise TranslationError('unknown named placeholder %r' % name) + elif not _compatible(typechar, type_map[name]): + raise TranslationError('incompatible format for ' + 'placeholder %r: ' + '%r and %r are not compatible' % + (name, typechar, type_map[name])) + + +def _find_checkers(): + try: + from pkg_resources import working_set + except ImportError: + return [num_plurals, python_format] + checkers = [] + for entry_point in working_set.iter_entry_points('babel.checkers'): + checkers.append(entry_point.load()) + return checkers + + +checkers = _find_checkers() diff --git a/boilerplate/external/babel/messages/extract.py b/boilerplate/external/babel/messages/extract.py new file mode 100644 index 0000000..e7cf696 --- /dev/null +++ b/boilerplate/external/babel/messages/extract.py @@ -0,0 +1,554 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Basic infrastructure for extracting localizable messages from source files. + +This module defines an extensible system for collecting localizable message +strings from a variety of sources. A native extractor for Python source files +is builtin, extractors for other sources can be added using very simple plugins. + +The main entry points into the extraction functionality are the functions +`extract_from_dir` and `extract_from_file`. +""" + +import os +try: + set +except NameError: + from sets import Set as set +import sys +from tokenize import generate_tokens, COMMENT, NAME, OP, STRING + +from babel.util import parse_encoding, pathmatch, relpath +from textwrap import dedent + +__all__ = ['extract', 'extract_from_dir', 'extract_from_file'] +__docformat__ = 'restructuredtext en' + +GROUP_NAME = 'babel.extractors' + +DEFAULT_KEYWORDS = { + '_': None, + 'gettext': None, + 'ngettext': (1, 2), + 'ugettext': None, + 'ungettext': (1, 2), + 'dgettext': (2,), + 'dngettext': (2, 3), + 'N_': None +} + +DEFAULT_MAPPING = [('**.py', 'python')] + +empty_msgid_warning = ( +'%s: warning: Empty msgid. It is reserved by GNU gettext: gettext("") ' +'returns the header entry with meta information, not the empty string.') + + +def _strip_comment_tags(comments, tags): + """Helper function for `extract` that strips comment tags from strings + in a list of comment lines. This functions operates in-place. + """ + def _strip(line): + for tag in tags: + if line.startswith(tag): + return line[len(tag):].strip() + return line + comments[:] = map(_strip, comments) + + +def extract_from_dir(dirname=os.getcwd(), method_map=DEFAULT_MAPPING, + options_map=None, keywords=DEFAULT_KEYWORDS, + comment_tags=(), callback=None, strip_comment_tags=False): + """Extract messages from any source files found in the given directory. + + This function generates tuples of the form: + + ``(filename, lineno, message, comments)`` + + Which extraction method is used per file is determined by the `method_map` + parameter, which maps extended glob patterns to extraction method names. + For example, the following is the default mapping: + + >>> method_map = [ + ... ('**.py', 'python') + ... ] + + This basically says that files with the filename extension ".py" at any + level inside the directory should be processed by the "python" extraction + method. Files that don't match any of the mapping patterns are ignored. See + the documentation of the `pathmatch` function for details on the pattern + syntax. + + The following extended mapping would also use the "genshi" extraction + method on any file in "templates" subdirectory: + + >>> method_map = [ + ... ('**/templates/**.*', 'genshi'), + ... ('**.py', 'python') + ... ] + + The dictionary provided by the optional `options_map` parameter augments + these mappings. It uses extended glob patterns as keys, and the values are + dictionaries mapping options names to option values (both strings). + + The glob patterns of the `options_map` do not necessarily need to be the + same as those used in the method mapping. For example, while all files in + the ``templates`` folders in an application may be Genshi applications, the + options for those files may differ based on extension: + + >>> options_map = { + ... '**/templates/**.txt': { + ... 'template_class': 'genshi.template:TextTemplate', + ... 'encoding': 'latin-1' + ... }, + ... '**/templates/**.html': { + ... 'include_attrs': '' + ... } + ... } + + :param dirname: the path to the directory to extract messages from + :param method_map: a list of ``(pattern, method)`` tuples that maps of + extraction method names to extended glob patterns + :param options_map: a dictionary of additional options (optional) + :param keywords: a dictionary mapping keywords (i.e. names of functions + that should be recognized as translation functions) to + tuples that specify which of their arguments contain + localizable strings + :param comment_tags: a list of tags of translator comments to search for + and include in the results + :param callback: a function that is called for every file that message are + extracted from, just before the extraction itself is + performed; the function is passed the filename, the name + of the extraction method and and the options dictionary as + positional arguments, in that order + :param strip_comment_tags: a flag that if set to `True` causes all comment + tags to be removed from the collected comments. + :return: an iterator over ``(filename, lineno, funcname, message)`` tuples + :rtype: ``iterator`` + :see: `pathmatch` + """ + if options_map is None: + options_map = {} + + absname = os.path.abspath(dirname) + for root, dirnames, filenames in os.walk(absname): + for subdir in dirnames: + if subdir.startswith('.') or subdir.startswith('_'): + dirnames.remove(subdir) + dirnames.sort() + filenames.sort() + for filename in filenames: + filename = relpath( + os.path.join(root, filename).replace(os.sep, '/'), + dirname + ) + for pattern, method in method_map: + if pathmatch(pattern, filename): + filepath = os.path.join(absname, filename) + options = {} + for opattern, odict in options_map.items(): + if pathmatch(opattern, filename): + options = odict + if callback: + callback(filename, method, options) + for lineno, message, comments in \ + extract_from_file(method, filepath, + keywords=keywords, + comment_tags=comment_tags, + options=options, + strip_comment_tags= + strip_comment_tags): + yield filename, lineno, message, comments + break + + +def extract_from_file(method, filename, keywords=DEFAULT_KEYWORDS, + comment_tags=(), options=None, strip_comment_tags=False): + """Extract messages from a specific file. + + This function returns a list of tuples of the form: + + ``(lineno, funcname, message)`` + + :param filename: the path to the file to extract messages from + :param method: a string specifying the extraction method (.e.g. "python") + :param keywords: a dictionary mapping keywords (i.e. names of functions + that should be recognized as translation functions) to + tuples that specify which of their arguments contain + localizable strings + :param comment_tags: a list of translator tags to search for and include + in the results + :param strip_comment_tags: a flag that if set to `True` causes all comment + tags to be removed from the collected comments. + :param options: a dictionary of additional options (optional) + :return: the list of extracted messages + :rtype: `list` + """ + fileobj = open(filename, 'U') + try: + return list(extract(method, fileobj, keywords, comment_tags, options, + strip_comment_tags)) + finally: + fileobj.close() + + +def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(), + options=None, strip_comment_tags=False): + """Extract messages from the given file-like object using the specified + extraction method. + + This function returns a list of tuples of the form: + + ``(lineno, message, comments)`` + + The implementation dispatches the actual extraction to plugins, based on the + value of the ``method`` parameter. + + >>> source = '''# foo module + ... def run(argv): + ... print _('Hello, world!') + ... ''' + + >>> from StringIO import StringIO + >>> for message in extract('python', StringIO(source)): + ... print message + (3, u'Hello, world!', []) + + :param method: a string specifying the extraction method (.e.g. "python"); + if this is a simple name, the extraction function will be + looked up by entry point; if it is an explicit reference + to a function (of the form ``package.module:funcname`` or + ``package.module.funcname``), the corresponding function + will be imported and used + :param fileobj: the file-like object the messages should be extracted from + :param keywords: a dictionary mapping keywords (i.e. names of functions + that should be recognized as translation functions) to + tuples that specify which of their arguments contain + localizable strings + :param comment_tags: a list of translator tags to search for and include + in the results + :param options: a dictionary of additional options (optional) + :param strip_comment_tags: a flag that if set to `True` causes all comment + tags to be removed from the collected comments. + :return: the list of extracted messages + :rtype: `list` + :raise ValueError: if the extraction method is not registered + """ + func = None + if ':' in method or '.' in method: + if ':' not in method: + lastdot = method.rfind('.') + module, attrname = method[:lastdot], method[lastdot + 1:] + else: + module, attrname = method.split(':', 1) + func = getattr(__import__(module, {}, {}, [attrname]), attrname) + else: + try: + from pkg_resources import working_set + except ImportError: + # pkg_resources is not available, so we resort to looking up the + # builtin extractors directly + builtin = {'ignore': extract_nothing, 'python': extract_python} + func = builtin.get(method) + else: + for entry_point in working_set.iter_entry_points(GROUP_NAME, + method): + func = entry_point.load(require=True) + break + if func is None: + raise ValueError('Unknown extraction method %r' % method) + + results = func(fileobj, keywords.keys(), comment_tags, + options=options or {}) + + for lineno, funcname, messages, comments in results: + if funcname: + spec = keywords[funcname] or (1,) + else: + spec = (1,) + if not isinstance(messages, (list, tuple)): + messages = [messages] + if not messages: + continue + + # Validate the messages against the keyword's specification + msgs = [] + invalid = False + # last_index is 1 based like the keyword spec + last_index = len(messages) + for index in spec: + if last_index < index: + # Not enough arguments + invalid = True + break + message = messages[index - 1] + if message is None: + invalid = True + break + msgs.append(message) + if invalid: + continue + + first_msg_index = spec[0] - 1 + if not messages[first_msg_index]: + # An empty string msgid isn't valid, emit a warning + where = '%s:%i' % (hasattr(fileobj, 'name') and \ + fileobj.name or '(unknown)', lineno) + print >> sys.stderr, empty_msgid_warning % where + continue + + messages = tuple(msgs) + if len(messages) == 1: + messages = messages[0] + + if strip_comment_tags: + _strip_comment_tags(comments, comment_tags) + yield lineno, messages, comments + + +def extract_nothing(fileobj, keywords, comment_tags, options): + """Pseudo extractor that does not actually extract anything, but simply + returns an empty list. + """ + return [] + + +def extract_python(fileobj, keywords, comment_tags, options): + """Extract messages from Python source code. + + :param fileobj: the seekable, file-like object the messages should be + extracted from + :param keywords: a list of keywords (i.e. function names) that should be + recognized as translation functions + :param comment_tags: a list of translator tags to search for and include + in the results + :param options: a dictionary of additional options (optional) + :return: an iterator over ``(lineno, funcname, message, comments)`` tuples + :rtype: ``iterator`` + """ + funcname = lineno = message_lineno = None + call_stack = -1 + buf = [] + messages = [] + translator_comments = [] + in_def = in_translator_comments = False + comment_tag = None + + encoding = parse_encoding(fileobj) or options.get('encoding', 'iso-8859-1') + + tokens = generate_tokens(fileobj.readline) + for tok, value, (lineno, _), _, _ in tokens: + if call_stack == -1 and tok == NAME and value in ('def', 'class'): + in_def = True + elif tok == OP and value == '(': + if in_def: + # Avoid false positives for declarations such as: + # def gettext(arg='message'): + in_def = False + continue + if funcname: + message_lineno = lineno + call_stack += 1 + elif in_def and tok == OP and value == ':': + # End of a class definition without parens + in_def = False + continue + elif call_stack == -1 and tok == COMMENT: + # Strip the comment token from the line + value = value.decode(encoding)[1:].strip() + if in_translator_comments and \ + translator_comments[-1][0] == lineno - 1: + # We're already inside a translator comment, continue appending + translator_comments.append((lineno, value)) + continue + # If execution reaches this point, let's see if comment line + # starts with one of the comment tags + for comment_tag in comment_tags: + if value.startswith(comment_tag): + in_translator_comments = True + translator_comments.append((lineno, value)) + break + elif funcname and call_stack == 0: + if tok == OP and value == ')': + if buf: + messages.append(''.join(buf)) + del buf[:] + else: + messages.append(None) + + if len(messages) > 1: + messages = tuple(messages) + else: + messages = messages[0] + # Comments don't apply unless they immediately preceed the + # message + if translator_comments and \ + translator_comments[-1][0] < message_lineno - 1: + translator_comments = [] + + yield (message_lineno, funcname, messages, + [comment[1] for comment in translator_comments]) + + funcname = lineno = message_lineno = None + call_stack = -1 + messages = [] + translator_comments = [] + in_translator_comments = False + elif tok == STRING: + # Unwrap quotes in a safe manner, maintaining the string's + # encoding + # https://sourceforge.net/tracker/?func=detail&atid=355470& + # aid=617979&group_id=5470 + value = eval('# coding=%s\n%s' % (encoding, value), + {'__builtins__':{}}, {}) + if isinstance(value, str): + value = value.decode(encoding) + buf.append(value) + elif tok == OP and value == ',': + if buf: + messages.append(''.join(buf)) + del buf[:] + else: + messages.append(None) + if translator_comments: + # We have translator comments, and since we're on a + # comma(,) user is allowed to break into a new line + # Let's increase the last comment's lineno in order + # for the comment to still be a valid one + old_lineno, old_comment = translator_comments.pop() + translator_comments.append((old_lineno+1, old_comment)) + elif call_stack > 0 and tok == OP and value == ')': + call_stack -= 1 + elif funcname and call_stack == -1: + funcname = None + elif tok == NAME and value in keywords: + funcname = value + + +def extract_javascript(fileobj, keywords, comment_tags, options): + """Extract messages from JavaScript source code. + + :param fileobj: the seekable, file-like object the messages should be + extracted from + :param keywords: a list of keywords (i.e. function names) that should be + recognized as translation functions + :param comment_tags: a list of translator tags to search for and include + in the results + :param options: a dictionary of additional options (optional) + :return: an iterator over ``(lineno, funcname, message, comments)`` tuples + :rtype: ``iterator`` + """ + from babel.messages.jslexer import tokenize, unquote_string + funcname = message_lineno = None + messages = [] + last_argument = None + translator_comments = [] + concatenate_next = False + encoding = options.get('encoding', 'utf-8') + last_token = None + call_stack = -1 + + for token in tokenize(fileobj.read().decode(encoding)): + if token.type == 'operator' and token.value == '(': + if funcname: + message_lineno = token.lineno + call_stack += 1 + + elif call_stack == -1 and token.type == 'linecomment': + value = token.value[2:].strip() + if translator_comments and \ + translator_comments[-1][0] == token.lineno - 1: + translator_comments.append((token.lineno, value)) + continue + + for comment_tag in comment_tags: + if value.startswith(comment_tag): + translator_comments.append((token.lineno, value.strip())) + break + + elif token.type == 'multilinecomment': + # only one multi-line comment may preceed a translation + translator_comments = [] + value = token.value[2:-2].strip() + for comment_tag in comment_tags: + if value.startswith(comment_tag): + lines = value.splitlines() + if lines: + lines[0] = lines[0].strip() + lines[1:] = dedent('\n'.join(lines[1:])).splitlines() + for offset, line in enumerate(lines): + translator_comments.append((token.lineno + offset, + line)) + break + + elif funcname and call_stack == 0: + if token.type == 'operator' and token.value == ')': + if last_argument is not None: + messages.append(last_argument) + if len(messages) > 1: + messages = tuple(messages) + elif messages: + messages = messages[0] + else: + messages = None + + # Comments don't apply unless they immediately precede the + # message + if translator_comments and \ + translator_comments[-1][0] < message_lineno - 1: + translator_comments = [] + + if messages is not None: + yield (message_lineno, funcname, messages, + [comment[1] for comment in translator_comments]) + + funcname = message_lineno = last_argument = None + concatenate_next = False + translator_comments = [] + messages = [] + call_stack = -1 + + elif token.type == 'string': + new_value = unquote_string(token.value) + if concatenate_next: + last_argument = (last_argument or '') + new_value + concatenate_next = False + else: + last_argument = new_value + + elif token.type == 'operator': + if token.value == ',': + if last_argument is not None: + messages.append(last_argument) + last_argument = None + else: + messages.append(None) + concatenate_next = False + elif token.value == '+': + concatenate_next = True + + elif call_stack > 0 and token.type == 'operator' \ + and token.value == ')': + call_stack -= 1 + + elif funcname and call_stack == -1: + funcname = None + + elif call_stack == -1 and token.type == 'name' and \ + token.value in keywords and \ + (last_token is None or last_token.type != 'name' or + last_token.value != 'function'): + funcname = token.value + + last_token = token diff --git a/boilerplate/external/babel/messages/frontend.py b/boilerplate/external/babel/messages/frontend.py new file mode 100644 index 0000000..2328aae --- /dev/null +++ b/boilerplate/external/babel/messages/frontend.py @@ -0,0 +1,1208 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""Frontends for the message extraction functionality.""" + +from ConfigParser import RawConfigParser +from datetime import datetime +from distutils import log +from distutils.cmd import Command +from distutils.errors import DistutilsOptionError, DistutilsSetupError +from locale import getpreferredencoding +import logging +from optparse import OptionParser +import os +import re +import shutil +from StringIO import StringIO +import sys +import tempfile + +from babel import __version__ as VERSION +from babel import Locale, localedata +from babel.core import UnknownLocaleError +from babel.messages.catalog import Catalog +from babel.messages.extract import extract_from_dir, DEFAULT_KEYWORDS, \ + DEFAULT_MAPPING +from babel.messages.mofile import write_mo +from babel.messages.pofile import read_po, write_po +from babel.messages.plurals import PLURALS +from babel.util import odict, LOCALTZ + +__all__ = ['CommandLineInterface', 'compile_catalog', 'extract_messages', + 'init_catalog', 'check_message_extractors', 'update_catalog'] +__docformat__ = 'restructuredtext en' + + +class compile_catalog(Command): + """Catalog compilation command for use in ``setup.py`` scripts. + + If correctly installed, this command is available to Setuptools-using + setup scripts automatically. For projects using plain old ``distutils``, + the command needs to be registered explicitly in ``setup.py``:: + + from babel.messages.frontend import compile_catalog + + setup( + ... + cmdclass = {'compile_catalog': compile_catalog} + ) + + :since: version 0.9 + :see: `Integrating new distutils commands `_ + :see: `setuptools `_ + """ + + description = 'compile message catalogs to binary MO files' + user_options = [ + ('domain=', 'D', + "domain of PO file (default 'messages')"), + ('directory=', 'd', + 'path to base directory containing the catalogs'), + ('input-file=', 'i', + 'name of the input file'), + ('output-file=', 'o', + "name of the output file (default " + "'//LC_MESSAGES/.po')"), + ('locale=', 'l', + 'locale of the catalog to compile'), + ('use-fuzzy', 'f', + 'also include fuzzy translations'), + ('statistics', None, + 'print statistics about translations') + ] + boolean_options = ['use-fuzzy', 'statistics'] + + def initialize_options(self): + self.domain = 'messages' + self.directory = None + self.input_file = None + self.output_file = None + self.locale = None + self.use_fuzzy = False + self.statistics = False + + def finalize_options(self): + if not self.input_file and not self.directory: + raise DistutilsOptionError('you must specify either the input file ' + 'or the base directory') + if not self.output_file and not self.directory: + raise DistutilsOptionError('you must specify either the input file ' + 'or the base directory') + + def run(self): + po_files = [] + mo_files = [] + + if not self.input_file: + if self.locale: + po_files.append((self.locale, + os.path.join(self.directory, self.locale, + 'LC_MESSAGES', + self.domain + '.po'))) + mo_files.append(os.path.join(self.directory, self.locale, + 'LC_MESSAGES', + self.domain + '.mo')) + else: + for locale in os.listdir(self.directory): + po_file = os.path.join(self.directory, locale, + 'LC_MESSAGES', self.domain + '.po') + if os.path.exists(po_file): + po_files.append((locale, po_file)) + mo_files.append(os.path.join(self.directory, locale, + 'LC_MESSAGES', + self.domain + '.mo')) + else: + po_files.append((self.locale, self.input_file)) + if self.output_file: + mo_files.append(self.output_file) + else: + mo_files.append(os.path.join(self.directory, self.locale, + 'LC_MESSAGES', + self.domain + '.mo')) + + if not po_files: + raise DistutilsOptionError('no message catalogs found') + + for idx, (locale, po_file) in enumerate(po_files): + mo_file = mo_files[idx] + infile = open(po_file, 'r') + try: + catalog = read_po(infile, locale) + finally: + infile.close() + + if self.statistics: + translated = 0 + for message in list(catalog)[1:]: + if message.string: + translated +=1 + percentage = 0 + if len(catalog): + percentage = translated * 100 // len(catalog) + log.info('%d of %d messages (%d%%) translated in %r', + translated, len(catalog), percentage, po_file) + + if catalog.fuzzy and not self.use_fuzzy: + log.warn('catalog %r is marked as fuzzy, skipping', po_file) + continue + + for message, errors in catalog.check(): + for error in errors: + log.error('error: %s:%d: %s', po_file, message.lineno, + error) + + log.info('compiling catalog %r to %r', po_file, mo_file) + + outfile = open(mo_file, 'wb') + try: + write_mo(outfile, catalog, use_fuzzy=self.use_fuzzy) + finally: + outfile.close() + + +class extract_messages(Command): + """Message extraction command for use in ``setup.py`` scripts. + + If correctly installed, this command is available to Setuptools-using + setup scripts automatically. For projects using plain old ``distutils``, + the command needs to be registered explicitly in ``setup.py``:: + + from babel.messages.frontend import extract_messages + + setup( + ... + cmdclass = {'extract_messages': extract_messages} + ) + + :see: `Integrating new distutils commands `_ + :see: `setuptools `_ + """ + + description = 'extract localizable strings from the project code' + user_options = [ + ('charset=', None, + 'charset to use in the output file'), + ('keywords=', 'k', + 'space-separated list of keywords to look for in addition to the ' + 'defaults'), + ('no-default-keywords', None, + 'do not include the default keywords'), + ('mapping-file=', 'F', + 'path to the mapping configuration file'), + ('no-location', None, + 'do not include location comments with filename and line number'), + ('omit-header', None, + 'do not include msgid "" entry in header'), + ('output-file=', 'o', + 'name of the output file'), + ('width=', 'w', + 'set output line width (default 76)'), + ('no-wrap', None, + 'do not break long message lines, longer than the output line width, ' + 'into several lines'), + ('sort-output', None, + 'generate sorted output (default False)'), + ('sort-by-file', None, + 'sort output by file location (default False)'), + ('msgid-bugs-address=', None, + 'set report address for msgid'), + ('copyright-holder=', None, + 'set copyright holder in output'), + ('add-comments=', 'c', + 'place comment block with TAG (or those preceding keyword lines) in ' + 'output file. Seperate multiple TAGs with commas(,)'), + ('strip-comments', None, + 'strip the comment TAGs from the comments.'), + ('input-dirs=', None, + 'directories that should be scanned for messages'), + ] + boolean_options = [ + 'no-default-keywords', 'no-location', 'omit-header', 'no-wrap', + 'sort-output', 'sort-by-file', 'strip-comments' + ] + + def initialize_options(self): + self.charset = 'utf-8' + self.keywords = '' + self._keywords = DEFAULT_KEYWORDS.copy() + self.no_default_keywords = False + self.mapping_file = None + self.no_location = False + self.omit_header = False + self.output_file = None + self.input_dirs = None + self.width = None + self.no_wrap = False + self.sort_output = False + self.sort_by_file = False + self.msgid_bugs_address = None + self.copyright_holder = None + self.add_comments = None + self._add_comments = [] + self.strip_comments = False + + def finalize_options(self): + if self.no_default_keywords and not self.keywords: + raise DistutilsOptionError('you must specify new keywords if you ' + 'disable the default ones') + if self.no_default_keywords: + self._keywords = {} + if self.keywords: + self._keywords.update(parse_keywords(self.keywords.split())) + + if not self.output_file: + raise DistutilsOptionError('no output file specified') + if self.no_wrap and self.width: + raise DistutilsOptionError("'--no-wrap' and '--width' are mutually " + "exclusive") + if not self.no_wrap and not self.width: + self.width = 76 + elif self.width is not None: + self.width = int(self.width) + + if self.sort_output and self.sort_by_file: + raise DistutilsOptionError("'--sort-output' and '--sort-by-file' " + "are mutually exclusive") + + if not self.input_dirs: + self.input_dirs = dict.fromkeys([k.split('.',1)[0] + for k in self.distribution.packages + ]).keys() + + if self.add_comments: + self._add_comments = self.add_comments.split(',') + + def run(self): + mappings = self._get_mappings() + outfile = open(self.output_file, 'w') + try: + catalog = Catalog(project=self.distribution.get_name(), + version=self.distribution.get_version(), + msgid_bugs_address=self.msgid_bugs_address, + copyright_holder=self.copyright_holder, + charset=self.charset) + + for dirname, (method_map, options_map) in mappings.items(): + def callback(filename, method, options): + if method == 'ignore': + return + filepath = os.path.normpath(os.path.join(dirname, filename)) + optstr = '' + if options: + optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for + k, v in options.items()]) + log.info('extracting messages from %s%s', filepath, optstr) + + extracted = extract_from_dir(dirname, method_map, options_map, + keywords=self._keywords, + comment_tags=self._add_comments, + callback=callback, + strip_comment_tags= + self.strip_comments) + for filename, lineno, message, comments in extracted: + filepath = os.path.normpath(os.path.join(dirname, filename)) + catalog.add(message, None, [(filepath, lineno)], + auto_comments=comments) + + log.info('writing PO template file to %s' % self.output_file) + write_po(outfile, catalog, width=self.width, + no_location=self.no_location, + omit_header=self.omit_header, + sort_output=self.sort_output, + sort_by_file=self.sort_by_file) + finally: + outfile.close() + + def _get_mappings(self): + mappings = {} + + if self.mapping_file: + fileobj = open(self.mapping_file, 'U') + try: + method_map, options_map = parse_mapping(fileobj) + for dirname in self.input_dirs: + mappings[dirname] = method_map, options_map + finally: + fileobj.close() + + elif getattr(self.distribution, 'message_extractors', None): + message_extractors = self.distribution.message_extractors + for dirname, mapping in message_extractors.items(): + if isinstance(mapping, basestring): + method_map, options_map = parse_mapping(StringIO(mapping)) + else: + method_map, options_map = [], {} + for pattern, method, options in mapping: + method_map.append((pattern, method)) + options_map[pattern] = options or {} + mappings[dirname] = method_map, options_map + + else: + for dirname in self.input_dirs: + mappings[dirname] = DEFAULT_MAPPING, {} + + return mappings + + +def check_message_extractors(dist, name, value): + """Validate the ``message_extractors`` keyword argument to ``setup()``. + + :param dist: the distutils/setuptools ``Distribution`` object + :param name: the name of the keyword argument (should always be + "message_extractors") + :param value: the value of the keyword argument + :raise `DistutilsSetupError`: if the value is not valid + :see: `Adding setup() arguments + `_ + """ + assert name == 'message_extractors' + if not isinstance(value, dict): + raise DistutilsSetupError('the value of the "message_extractors" ' + 'parameter must be a dictionary') + + +class init_catalog(Command): + """New catalog initialization command for use in ``setup.py`` scripts. + + If correctly installed, this command is available to Setuptools-using + setup scripts automatically. For projects using plain old ``distutils``, + the command needs to be registered explicitly in ``setup.py``:: + + from babel.messages.frontend import init_catalog + + setup( + ... + cmdclass = {'init_catalog': init_catalog} + ) + + :see: `Integrating new distutils commands `_ + :see: `setuptools `_ + """ + + description = 'create a new catalog based on a POT file' + user_options = [ + ('domain=', 'D', + "domain of PO file (default 'messages')"), + ('input-file=', 'i', + 'name of the input file'), + ('output-dir=', 'd', + 'path to output directory'), + ('output-file=', 'o', + "name of the output file (default " + "'//LC_MESSAGES/.po')"), + ('locale=', 'l', + 'locale for the new localized catalog'), + ] + + def initialize_options(self): + self.output_dir = None + self.output_file = None + self.input_file = None + self.locale = None + self.domain = 'messages' + + def finalize_options(self): + if not self.input_file: + raise DistutilsOptionError('you must specify the input file') + + if not self.locale: + raise DistutilsOptionError('you must provide a locale for the ' + 'new catalog') + try: + self._locale = Locale.parse(self.locale) + except UnknownLocaleError, e: + raise DistutilsOptionError(e) + + if not self.output_file and not self.output_dir: + raise DistutilsOptionError('you must specify the output directory') + if not self.output_file: + self.output_file = os.path.join(self.output_dir, self.locale, + 'LC_MESSAGES', self.domain + '.po') + + if not os.path.exists(os.path.dirname(self.output_file)): + os.makedirs(os.path.dirname(self.output_file)) + + def run(self): + log.info('creating catalog %r based on %r', self.output_file, + self.input_file) + + infile = open(self.input_file, 'r') + try: + # Although reading from the catalog template, read_po must be fed + # the locale in order to correcly calculate plurals + catalog = read_po(infile, locale=self.locale) + finally: + infile.close() + + catalog.locale = self._locale + catalog.fuzzy = False + + outfile = open(self.output_file, 'w') + try: + write_po(outfile, catalog) + finally: + outfile.close() + + +class update_catalog(Command): + """Catalog merging command for use in ``setup.py`` scripts. + + If correctly installed, this command is available to Setuptools-using + setup scripts automatically. For projects using plain old ``distutils``, + the command needs to be registered explicitly in ``setup.py``:: + + from babel.messages.frontend import update_catalog + + setup( + ... + cmdclass = {'update_catalog': update_catalog} + ) + + :since: version 0.9 + :see: `Integrating new distutils commands `_ + :see: `setuptools `_ + """ + + description = 'update message catalogs from a POT file' + user_options = [ + ('domain=', 'D', + "domain of PO file (default 'messages')"), + ('input-file=', 'i', + 'name of the input file'), + ('output-dir=', 'd', + 'path to base directory containing the catalogs'), + ('output-file=', 'o', + "name of the output file (default " + "'//LC_MESSAGES/.po')"), + ('locale=', 'l', + 'locale of the catalog to compile'), + ('ignore-obsolete=', None, + 'whether to omit obsolete messages from the output'), + ('no-fuzzy-matching', 'N', + 'do not use fuzzy matching'), + ('previous', None, + 'keep previous msgids of translated messages') + ] + boolean_options = ['ignore_obsolete', 'no_fuzzy_matching', 'previous'] + + def initialize_options(self): + self.domain = 'messages' + self.input_file = None + self.output_dir = None + self.output_file = None + self.locale = None + self.ignore_obsolete = False + self.no_fuzzy_matching = False + self.previous = False + + def finalize_options(self): + if not self.input_file: + raise DistutilsOptionError('you must specify the input file') + if not self.output_file and not self.output_dir: + raise DistutilsOptionError('you must specify the output file or ' + 'directory') + if self.output_file and not self.locale: + raise DistutilsOptionError('you must specify the locale') + if self.no_fuzzy_matching and self.previous: + self.previous = False + + def run(self): + po_files = [] + if not self.output_file: + if self.locale: + po_files.append((self.locale, + os.path.join(self.output_dir, self.locale, + 'LC_MESSAGES', + self.domain + '.po'))) + else: + for locale in os.listdir(self.output_dir): + po_file = os.path.join(self.output_dir, locale, + 'LC_MESSAGES', + self.domain + '.po') + if os.path.exists(po_file): + po_files.append((locale, po_file)) + else: + po_files.append((self.locale, self.output_file)) + + domain = self.domain + if not domain: + domain = os.path.splitext(os.path.basename(self.input_file))[0] + + infile = open(self.input_file, 'U') + try: + template = read_po(infile) + finally: + infile.close() + + if not po_files: + raise DistutilsOptionError('no message catalogs found') + + for locale, filename in po_files: + log.info('updating catalog %r based on %r', filename, + self.input_file) + infile = open(filename, 'U') + try: + catalog = read_po(infile, locale=locale, domain=domain) + finally: + infile.close() + + catalog.update(template, self.no_fuzzy_matching) + + tmpname = os.path.join(os.path.dirname(filename), + tempfile.gettempprefix() + + os.path.basename(filename)) + tmpfile = open(tmpname, 'w') + try: + try: + write_po(tmpfile, catalog, + ignore_obsolete=self.ignore_obsolete, + include_previous=self.previous) + finally: + tmpfile.close() + except: + os.remove(tmpname) + raise + + try: + os.rename(tmpname, filename) + except OSError: + # We're probably on Windows, which doesn't support atomic + # renames, at least not through Python + # If the error is in fact due to a permissions problem, that + # same error is going to be raised from one of the following + # operations + os.remove(filename) + shutil.copy(tmpname, filename) + os.remove(tmpname) + + +class CommandLineInterface(object): + """Command-line interface. + + This class provides a simple command-line interface to the message + extraction and PO file generation functionality. + """ + + usage = '%%prog %s [options] %s' + version = '%%prog %s' % VERSION + commands = { + 'compile': 'compile message catalogs to MO files', + 'extract': 'extract messages from source files and generate a POT file', + 'init': 'create new message catalogs from a POT file', + 'update': 'update existing message catalogs from a POT file' + } + + def run(self, argv=sys.argv): + """Main entry point of the command-line interface. + + :param argv: list of arguments passed on the command-line + """ + self.parser = OptionParser(usage=self.usage % ('command', '[args]'), + version=self.version) + self.parser.disable_interspersed_args() + self.parser.print_help = self._help + self.parser.add_option('--list-locales', dest='list_locales', + action='store_true', + help="print all known locales and exit") + self.parser.add_option('-v', '--verbose', action='store_const', + dest='loglevel', const=logging.DEBUG, + help='print as much as possible') + self.parser.add_option('-q', '--quiet', action='store_const', + dest='loglevel', const=logging.ERROR, + help='print as little as possible') + self.parser.set_defaults(list_locales=False, loglevel=logging.INFO) + + options, args = self.parser.parse_args(argv[1:]) + + self._configure_logging(options.loglevel) + if options.list_locales: + identifiers = localedata.list() + longest = max([len(identifier) for identifier in identifiers]) + identifiers.sort() + format = u'%%-%ds %%s' % (longest + 1) + for identifier in identifiers: + locale = Locale.parse(identifier) + output = format % (identifier, locale.english_name) + print output.encode(sys.stdout.encoding or + getpreferredencoding() or + 'ascii', 'replace') + return 0 + + if not args: + self.parser.error('no valid command or option passed. ' + 'Try the -h/--help option for more information.') + + cmdname = args[0] + if cmdname not in self.commands: + self.parser.error('unknown command "%s"' % cmdname) + + return getattr(self, cmdname)(args[1:]) + + def _configure_logging(self, loglevel): + self.log = logging.getLogger('babel') + self.log.setLevel(loglevel) + # Don't add a new handler for every instance initialization (#227), this + # would cause duplicated output when the CommandLineInterface as an + # normal Python class. + if self.log.handlers: + handler = self.log.handlers[0] + else: + handler = logging.StreamHandler() + self.log.addHandler(handler) + handler.setLevel(loglevel) + formatter = logging.Formatter('%(message)s') + handler.setFormatter(formatter) + + def _help(self): + print self.parser.format_help() + print "commands:" + longest = max([len(command) for command in self.commands]) + format = " %%-%ds %%s" % max(8, longest + 1) + commands = self.commands.items() + commands.sort() + for name, description in commands: + print format % (name, description) + + def compile(self, argv): + """Subcommand for compiling a message catalog to a MO file. + + :param argv: the command arguments + :since: version 0.9 + """ + parser = OptionParser(usage=self.usage % ('compile', ''), + description=self.commands['compile']) + parser.add_option('--domain', '-D', dest='domain', + help="domain of MO and PO files (default '%default')") + parser.add_option('--directory', '-d', dest='directory', + metavar='DIR', help='base directory of catalog files') + parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE', + help='locale of the catalog') + parser.add_option('--input-file', '-i', dest='input_file', + metavar='FILE', help='name of the input file') + parser.add_option('--output-file', '-o', dest='output_file', + metavar='FILE', + help="name of the output file (default " + "'//LC_MESSAGES/" + ".mo')") + parser.add_option('--use-fuzzy', '-f', dest='use_fuzzy', + action='store_true', + help='also include fuzzy translations (default ' + '%default)') + parser.add_option('--statistics', dest='statistics', + action='store_true', + help='print statistics about translations') + + parser.set_defaults(domain='messages', use_fuzzy=False, + compile_all=False, statistics=False) + options, args = parser.parse_args(argv) + + po_files = [] + mo_files = [] + if not options.input_file: + if not options.directory: + parser.error('you must specify either the input file or the ' + 'base directory') + if options.locale: + po_files.append((options.locale, + os.path.join(options.directory, + options.locale, 'LC_MESSAGES', + options.domain + '.po'))) + mo_files.append(os.path.join(options.directory, options.locale, + 'LC_MESSAGES', + options.domain + '.mo')) + else: + for locale in os.listdir(options.directory): + po_file = os.path.join(options.directory, locale, + 'LC_MESSAGES', options.domain + '.po') + if os.path.exists(po_file): + po_files.append((locale, po_file)) + mo_files.append(os.path.join(options.directory, locale, + 'LC_MESSAGES', + options.domain + '.mo')) + else: + po_files.append((options.locale, options.input_file)) + if options.output_file: + mo_files.append(options.output_file) + else: + if not options.directory: + parser.error('you must specify either the input file or ' + 'the base directory') + mo_files.append(os.path.join(options.directory, options.locale, + 'LC_MESSAGES', + options.domain + '.mo')) + if not po_files: + parser.error('no message catalogs found') + + for idx, (locale, po_file) in enumerate(po_files): + mo_file = mo_files[idx] + infile = open(po_file, 'r') + try: + catalog = read_po(infile, locale) + finally: + infile.close() + + if options.statistics: + translated = 0 + for message in list(catalog)[1:]: + if message.string: + translated +=1 + percentage = 0 + if len(catalog): + percentage = translated * 100 // len(catalog) + self.log.info("%d of %d messages (%d%%) translated in %r", + translated, len(catalog), percentage, po_file) + + if catalog.fuzzy and not options.use_fuzzy: + self.log.warn('catalog %r is marked as fuzzy, skipping', + po_file) + continue + + for message, errors in catalog.check(): + for error in errors: + self.log.error('error: %s:%d: %s', po_file, message.lineno, + error) + + self.log.info('compiling catalog %r to %r', po_file, mo_file) + + outfile = open(mo_file, 'wb') + try: + write_mo(outfile, catalog, use_fuzzy=options.use_fuzzy) + finally: + outfile.close() + + def extract(self, argv): + """Subcommand for extracting messages from source files and generating + a POT file. + + :param argv: the command arguments + """ + parser = OptionParser(usage=self.usage % ('extract', 'dir1 ...'), + description=self.commands['extract']) + parser.add_option('--charset', dest='charset', + help='charset to use in the output (default ' + '"%default")') + parser.add_option('-k', '--keyword', dest='keywords', action='append', + help='keywords to look for in addition to the ' + 'defaults. You can specify multiple -k flags on ' + 'the command line.') + parser.add_option('--no-default-keywords', dest='no_default_keywords', + action='store_true', + help="do not include the default keywords") + parser.add_option('--mapping', '-F', dest='mapping_file', + help='path to the extraction mapping file') + parser.add_option('--no-location', dest='no_location', + action='store_true', + help='do not include location comments with filename ' + 'and line number') + parser.add_option('--omit-header', dest='omit_header', + action='store_true', + help='do not include msgid "" entry in header') + parser.add_option('-o', '--output', dest='output', + help='path to the output POT file') + parser.add_option('-w', '--width', dest='width', type='int', + help="set output line width (default 76)") + parser.add_option('--no-wrap', dest='no_wrap', action = 'store_true', + help='do not break long message lines, longer than ' + 'the output line width, into several lines') + parser.add_option('--sort-output', dest='sort_output', + action='store_true', + help='generate sorted output (default False)') + parser.add_option('--sort-by-file', dest='sort_by_file', + action='store_true', + help='sort output by file location (default False)') + parser.add_option('--msgid-bugs-address', dest='msgid_bugs_address', + metavar='EMAIL@ADDRESS', + help='set report address for msgid') + parser.add_option('--copyright-holder', dest='copyright_holder', + help='set copyright holder in output') + parser.add_option('--project', dest='project', + help='set project name in output') + parser.add_option('--version', dest='version', + help='set project version in output') + parser.add_option('--add-comments', '-c', dest='comment_tags', + metavar='TAG', action='append', + help='place comment block with TAG (or those ' + 'preceding keyword lines) in output file. One ' + 'TAG per argument call') + parser.add_option('--strip-comment-tags', '-s', + dest='strip_comment_tags', action='store_true', + help='Strip the comment tags from the comments.') + + parser.set_defaults(charset='utf-8', keywords=[], + no_default_keywords=False, no_location=False, + omit_header = False, width=None, no_wrap=False, + sort_output=False, sort_by_file=False, + comment_tags=[], strip_comment_tags=False) + options, args = parser.parse_args(argv) + if not args: + parser.error('incorrect number of arguments') + + if options.output not in (None, '-'): + outfile = open(options.output, 'w') + else: + outfile = sys.stdout + + keywords = DEFAULT_KEYWORDS.copy() + if options.no_default_keywords: + if not options.keywords: + parser.error('you must specify new keywords if you disable the ' + 'default ones') + keywords = {} + if options.keywords: + keywords.update(parse_keywords(options.keywords)) + + if options.mapping_file: + fileobj = open(options.mapping_file, 'U') + try: + method_map, options_map = parse_mapping(fileobj) + finally: + fileobj.close() + else: + method_map = DEFAULT_MAPPING + options_map = {} + + if options.width and options.no_wrap: + parser.error("'--no-wrap' and '--width' are mutually exclusive.") + elif not options.width and not options.no_wrap: + options.width = 76 + + if options.sort_output and options.sort_by_file: + parser.error("'--sort-output' and '--sort-by-file' are mutually " + "exclusive") + + try: + catalog = Catalog(project=options.project, + version=options.version, + msgid_bugs_address=options.msgid_bugs_address, + copyright_holder=options.copyright_holder, + charset=options.charset) + + for dirname in args: + if not os.path.isdir(dirname): + parser.error('%r is not a directory' % dirname) + + def callback(filename, method, options): + if method == 'ignore': + return + filepath = os.path.normpath(os.path.join(dirname, filename)) + optstr = '' + if options: + optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for + k, v in options.items()]) + self.log.info('extracting messages from %s%s', filepath, + optstr) + + extracted = extract_from_dir(dirname, method_map, options_map, + keywords, options.comment_tags, + callback=callback, + strip_comment_tags= + options.strip_comment_tags) + for filename, lineno, message, comments in extracted: + filepath = os.path.normpath(os.path.join(dirname, filename)) + catalog.add(message, None, [(filepath, lineno)], + auto_comments=comments) + + if options.output not in (None, '-'): + self.log.info('writing PO template file to %s' % options.output) + write_po(outfile, catalog, width=options.width, + no_location=options.no_location, + omit_header=options.omit_header, + sort_output=options.sort_output, + sort_by_file=options.sort_by_file) + finally: + if options.output: + outfile.close() + + def init(self, argv): + """Subcommand for creating new message catalogs from a template. + + :param argv: the command arguments + """ + parser = OptionParser(usage=self.usage % ('init', ''), + description=self.commands['init']) + parser.add_option('--domain', '-D', dest='domain', + help="domain of PO file (default '%default')") + parser.add_option('--input-file', '-i', dest='input_file', + metavar='FILE', help='name of the input file') + parser.add_option('--output-dir', '-d', dest='output_dir', + metavar='DIR', help='path to output directory') + parser.add_option('--output-file', '-o', dest='output_file', + metavar='FILE', + help="name of the output file (default " + "'//LC_MESSAGES/" + ".po')") + parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE', + help='locale for the new localized catalog') + + parser.set_defaults(domain='messages') + options, args = parser.parse_args(argv) + + if not options.locale: + parser.error('you must provide a locale for the new catalog') + try: + locale = Locale.parse(options.locale) + except UnknownLocaleError, e: + parser.error(e) + + if not options.input_file: + parser.error('you must specify the input file') + + if not options.output_file and not options.output_dir: + parser.error('you must specify the output file or directory') + + if not options.output_file: + options.output_file = os.path.join(options.output_dir, + options.locale, 'LC_MESSAGES', + options.domain + '.po') + if not os.path.exists(os.path.dirname(options.output_file)): + os.makedirs(os.path.dirname(options.output_file)) + + infile = open(options.input_file, 'r') + try: + # Although reading from the catalog template, read_po must be fed + # the locale in order to correcly calculate plurals + catalog = read_po(infile, locale=options.locale) + finally: + infile.close() + + catalog.locale = locale + catalog.revision_date = datetime.now(LOCALTZ) + + self.log.info('creating catalog %r based on %r', options.output_file, + options.input_file) + + outfile = open(options.output_file, 'w') + try: + write_po(outfile, catalog) + finally: + outfile.close() + + def update(self, argv): + """Subcommand for updating existing message catalogs from a template. + + :param argv: the command arguments + :since: version 0.9 + """ + parser = OptionParser(usage=self.usage % ('update', ''), + description=self.commands['update']) + parser.add_option('--domain', '-D', dest='domain', + help="domain of PO file (default '%default')") + parser.add_option('--input-file', '-i', dest='input_file', + metavar='FILE', help='name of the input file') + parser.add_option('--output-dir', '-d', dest='output_dir', + metavar='DIR', help='path to output directory') + parser.add_option('--output-file', '-o', dest='output_file', + metavar='FILE', + help="name of the output file (default " + "'//LC_MESSAGES/" + ".po')") + parser.add_option('--locale', '-l', dest='locale', metavar='LOCALE', + help='locale of the translations catalog') + parser.add_option('--ignore-obsolete', dest='ignore_obsolete', + action='store_true', + help='do not include obsolete messages in the output ' + '(default %default)') + parser.add_option('--no-fuzzy-matching', '-N', dest='no_fuzzy_matching', + action='store_true', + help='do not use fuzzy matching (default %default)') + parser.add_option('--previous', dest='previous', action='store_true', + help='keep previous msgids of translated messages ' + '(default %default)') + + parser.set_defaults(domain='messages', ignore_obsolete=False, + no_fuzzy_matching=False, previous=False) + options, args = parser.parse_args(argv) + + if not options.input_file: + parser.error('you must specify the input file') + if not options.output_file and not options.output_dir: + parser.error('you must specify the output file or directory') + if options.output_file and not options.locale: + parser.error('you must specify the locale') + if options.no_fuzzy_matching and options.previous: + options.previous = False + + po_files = [] + if not options.output_file: + if options.locale: + po_files.append((options.locale, + os.path.join(options.output_dir, + options.locale, 'LC_MESSAGES', + options.domain + '.po'))) + else: + for locale in os.listdir(options.output_dir): + po_file = os.path.join(options.output_dir, locale, + 'LC_MESSAGES', + options.domain + '.po') + if os.path.exists(po_file): + po_files.append((locale, po_file)) + else: + po_files.append((options.locale, options.output_file)) + + domain = options.domain + if not domain: + domain = os.path.splitext(os.path.basename(options.input_file))[0] + + infile = open(options.input_file, 'U') + try: + template = read_po(infile) + finally: + infile.close() + + if not po_files: + parser.error('no message catalogs found') + + for locale, filename in po_files: + self.log.info('updating catalog %r based on %r', filename, + options.input_file) + infile = open(filename, 'U') + try: + catalog = read_po(infile, locale=locale, domain=domain) + finally: + infile.close() + + catalog.update(template, options.no_fuzzy_matching) + + tmpname = os.path.join(os.path.dirname(filename), + tempfile.gettempprefix() + + os.path.basename(filename)) + tmpfile = open(tmpname, 'w') + try: + try: + write_po(tmpfile, catalog, + ignore_obsolete=options.ignore_obsolete, + include_previous=options.previous) + finally: + tmpfile.close() + except: + os.remove(tmpname) + raise + + try: + os.rename(tmpname, filename) + except OSError: + # We're probably on Windows, which doesn't support atomic + # renames, at least not through Python + # If the error is in fact due to a permissions problem, that + # same error is going to be raised from one of the following + # operations + os.remove(filename) + shutil.copy(tmpname, filename) + os.remove(tmpname) + + +def main(): + return CommandLineInterface().run(sys.argv) + +def parse_mapping(fileobj, filename=None): + """Parse an extraction method mapping from a file-like object. + + >>> buf = StringIO(''' + ... [extractors] + ... custom = mypackage.module:myfunc + ... + ... # Python source files + ... [python: **.py] + ... + ... # Genshi templates + ... [genshi: **/templates/**.html] + ... include_attrs = + ... [genshi: **/templates/**.txt] + ... template_class = genshi.template:TextTemplate + ... encoding = latin-1 + ... + ... # Some custom extractor + ... [custom: **/custom/*.*] + ... ''') + + >>> method_map, options_map = parse_mapping(buf) + >>> len(method_map) + 4 + + >>> method_map[0] + ('**.py', 'python') + >>> options_map['**.py'] + {} + >>> method_map[1] + ('**/templates/**.html', 'genshi') + >>> options_map['**/templates/**.html']['include_attrs'] + '' + >>> method_map[2] + ('**/templates/**.txt', 'genshi') + >>> options_map['**/templates/**.txt']['template_class'] + 'genshi.template:TextTemplate' + >>> options_map['**/templates/**.txt']['encoding'] + 'latin-1' + + >>> method_map[3] + ('**/custom/*.*', 'mypackage.module:myfunc') + >>> options_map['**/custom/*.*'] + {} + + :param fileobj: a readable file-like object containing the configuration + text to parse + :return: a `(method_map, options_map)` tuple + :rtype: `tuple` + :see: `extract_from_directory` + """ + extractors = {} + method_map = [] + options_map = {} + + parser = RawConfigParser() + parser._sections = odict(parser._sections) # We need ordered sections + parser.readfp(fileobj, filename) + for section in parser.sections(): + if section == 'extractors': + extractors = dict(parser.items(section)) + else: + method, pattern = [part.strip() for part in section.split(':', 1)] + method_map.append((pattern, method)) + options_map[pattern] = dict(parser.items(section)) + + if extractors: + for idx, (pattern, method) in enumerate(method_map): + if method in extractors: + method = extractors[method] + method_map[idx] = (pattern, method) + + return (method_map, options_map) + +def parse_keywords(strings=[]): + """Parse keywords specifications from the given list of strings. + + >>> kw = parse_keywords(['_', 'dgettext:2', 'dngettext:2,3']).items() + >>> kw.sort() + >>> for keyword, indices in kw: + ... print (keyword, indices) + ('_', None) + ('dgettext', (2,)) + ('dngettext', (2, 3)) + """ + keywords = {} + for string in strings: + if ':' in string: + funcname, indices = string.split(':') + else: + funcname, indices = string, None + if funcname not in keywords: + if indices: + indices = tuple([(int(x)) for x in indices.split(',')]) + keywords[funcname] = indices + return keywords + + +if __name__ == '__main__': + main() diff --git a/boilerplate/external/babel/messages/jslexer.py b/boilerplate/external/babel/messages/jslexer.py new file mode 100644 index 0000000..c794252 --- /dev/null +++ b/boilerplate/external/babel/messages/jslexer.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://babel.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://babel.edgewall.org/log/. + +"""A simple JavaScript 1.5 lexer which is used for the JavaScript +extractor. +""" + +import re + +from babel.util import itemgetter + + +operators = [ + '+', '-', '*', '%', '!=', '==', '<', '>', '<=', '>=', '=', + '+=', '-=', '*=', '%=', '<<', '>>', '>>>', '<<=', '>>=', + '>>>=', '&', '&=', '|', '|=', '&&', '||', '^', '^=', '(', ')', + '[', ']', '{', '}', '!', '--', '++', '~', ',', ';', '.', ':' +] +operators.sort(lambda a, b: cmp(-len(a), -len(b))) + +escapes = {'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t'} + +rules = [ + (None, re.compile(r'\s+(?u)')), + (None, re.compile(r' + + + + + + + {% block title %}{{ app_name }}{% endblock %} + + + + + + + + + {% if is_mobile %} + + {% endif %} + + + {% block mediaCSS %}{% endblock %} + + + + + + + + +
+ {% block navbar %} + + {% endblock %} + +
+ {% block header %} + + {% endblock %} + + {% if messages|safe %} + {% for message in messages %} +

+ × + {{ message[0]|safe }} +

+ {% endfor %} + {% endif %} + {% block content %}{% endblock %} + + {% block footer %} +
+ {% trans %}Google App Engine Boilerplate is released under the{% endtrans %} LGPL +
+ {% endblock %} +
+
+ + + + + + + + + + + + {% if locale_language_id != "en" %} + {% if locale_iso.language and locale_iso.territory %} + + {% else %} + + {% endif %} + {% endif %} + {% block mediaJS %}{% endblock %} + + + diff --git a/boilerplate/templates/contact.html b/boilerplate/templates/contact.html new file mode 100644 index 0000000..3221cae --- /dev/null +++ b/boilerplate/templates/contact.html @@ -0,0 +1,37 @@ +{% extends base_layout %} + +{% block title %} + {% trans %}Contact{% endtrans %} - {{app_name}} +{% endblock %} +{% block header_title %} + {% trans %}Contact{% endtrans %} +{% endblock %} + +{% block content %} +
+
+ + + {{ macros.field(form.name, label=_("Name"), placeholder=_("Enter your")+" "+_("Name"), class="input-xlarge focused required") }} + {{ macros.field(form.email, label=_("Email"), placeholder=_("Enter your")+" "+_("Email"), class="input-xlarge focused required email", type="email") }} + {{ macros.field(form.message, label=_("Message"), class="input-xlarge required", cols="40", rows="8") }} +
+ +
+
+
+{% endblock %} + +{% block mediaJS %} + +{% endblock %} + diff --git a/boilerplate/templates/edit_email.html b/boilerplate/templates/edit_email.html new file mode 100644 index 0000000..cb7af42 --- /dev/null +++ b/boilerplate/templates/edit_email.html @@ -0,0 +1,42 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Change your email{% endtrans %} +{% endblock %} + +{% block content %} + +
+
+ +
+ +
+

{{ current_email }}

+
+
+ {{ macros.field(form.new_email, label=_("New Email"), placeholder=_("Enter a new email address"), class="input-xlarge focused required email", type="email") }} + {{ macros.field(form.password, label=_("Current Password"), placeholder=_("Enter your")+" "+_("Password"), class="input-xlarge focused required", type="password") }} +
+ +
+
+
+ +{% endblock %} + +{% block mediaJS %} + +{% endblock %} \ No newline at end of file diff --git a/boilerplate/templates/edit_password.html b/boilerplate/templates/edit_password.html new file mode 100644 index 0000000..f6c872f --- /dev/null +++ b/boilerplate/templates/edit_password.html @@ -0,0 +1,57 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Change your password{% endtrans %} +{% endblock %} + +{% block content %} + +
+
+ + {{ macros.field(form.current_password, label=_("Current Password"), placeholder=_("Enter your")+" "+_("Current Password"), class="input-xlarge focused required", type="password") }} + + {{ macros.field(form.password, label=_("New Password"), placeholder=_("Enter your")+" "+_("New Password"), class="input-xlarge focused required", type="password") }} + {% if not is_mobile %} + {{ macros.field(form.c_password, label=_("Confirm Password"), placeholder=_("Confirm Password"), class="input-xlarge focused required", type="password") }} + {% endif %} +
+ +
+
+
+ +{% endblock %} + +{% block mediaJS %} + +{% endblock %} \ No newline at end of file diff --git a/boilerplate/templates/edit_profile.html b/boilerplate/templates/edit_profile.html new file mode 100644 index 0000000..ce71a45 --- /dev/null +++ b/boilerplate/templates/edit_profile.html @@ -0,0 +1,78 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Edit Profile{% endtrans %} +{% endblock %} + +{% block content %} + +
+
+ + {{ macros.field(form.username, label=_("Username"), placeholder=_("Enter your")+" "+_("Username"), class="input-xlarge focused required") }} + {{ macros.field(form.name, label=_("Name"), placeholder=_("Enter your")+" "+_("Name"), class="input-xlarge focused") }} + {{ macros.field(form.last_name, label=_("Last Name"), placeholder=_("Enter your")+" "+_("Last Name"), class="input-xlarge focused") }} + {{ macros.field(form.country, label=_("Country")) }} +
+ + +
+ + + {% if enable_federated_login %} + {% if used_providers %} +
+

{% trans %}Existing social association{% endtrans %}:

+ + {% for provider in used_providers %} + + + {% if used_providers|length > 1 or local_account %} + + {% endif %} + + {% endfor %} + +
+ {% endif %} + {% if unused_providers %} +
+

{% trans %}Associate account with{% endtrans %}:

+ +
+ {% endif %} + {% endif %} +
+
+ +{% endblock %} + +{% block mediaJS %} + +{% endblock %} + diff --git a/boilerplate/templates/emails/account_activation.txt b/boilerplate/templates/emails/account_activation.txt new file mode 100644 index 0000000..51ecc6e --- /dev/null +++ b/boilerplate/templates/emails/account_activation.txt @@ -0,0 +1,16 @@ +

Hello {{username}},

+ +

+You just have registered in {{ app_name }} as @{{username}}. In order to activate your account, +please verify your email by clicking this link: +

+ +{{confirmation_url}} + +

+If you have any question, please do not hesitate to contact us at:
+{{support_url}} +

+ +

Sincerely,
+{{ app_name }} Team \ No newline at end of file diff --git a/boilerplate/templates/emails/contact.txt b/boilerplate/templates/emails/contact.txt new file mode 100644 index 0000000..d6e7d58 --- /dev/null +++ b/boilerplate/templates/emails/contact.txt @@ -0,0 +1,11 @@ +

Name: {{name}}

+

Email: {{email}}

+ +

Web Browser: {{browser}} {{browser_version}} +

Operating System: {{operating_system}} +

IP Address: {{ip}} + +

+Message:
+{{message}} +

\ No newline at end of file diff --git a/boilerplate/templates/emails/email_changed_notification_new.txt b/boilerplate/templates/emails/email_changed_notification_new.txt new file mode 100644 index 0000000..01f3fba --- /dev/null +++ b/boilerplate/templates/emails/email_changed_notification_new.txt @@ -0,0 +1,12 @@ +

Hello {{first_name}},

+ +

You've changed the email address associated with your {{ app_name }} account (@{{username}}).

+ +

Please follow the link to confirm {{new_email}} as your new e-mail contact:

+ +{{confirmation_url}} + +

Once confirmed, all {{ app_name }} email will be sent to this address.

+ +

Sincerely,
+{{ app_name }} Team \ No newline at end of file diff --git a/boilerplate/templates/emails/email_changed_notification_old.txt b/boilerplate/templates/emails/email_changed_notification_old.txt new file mode 100644 index 0000000..135da92 --- /dev/null +++ b/boilerplate/templates/emails/email_changed_notification_old.txt @@ -0,0 +1,11 @@ +

Hello {{ first_name }},

+ +

Recently you've changed the email address associated with your {{ app_name }} account (@{{username}}). +To confirm your new e-mail contact, please follow the link in the confirmation message sent to that address.

+ +

If you do not request this change and believe that your {{ app_name }} account has been compromised, +contact {{ app_name }} support by clicking this link:

+{{support_url}} + +

Sincerely,
+{{ app_name }} Team \ No newline at end of file diff --git a/boilerplate/templates/emails/error.txt b/boilerplate/templates/emails/error.txt new file mode 100644 index 0000000..eae0d83 --- /dev/null +++ b/boilerplate/templates/emails/error.txt @@ -0,0 +1,6 @@ +

App Name: {{app_name}}

+ +

+Message:
+{{message}} +

\ No newline at end of file diff --git a/boilerplate/templates/emails/password_changed.txt b/boilerplate/templates/emails/password_changed.txt new file mode 100644 index 0000000..aff4338 --- /dev/null +++ b/boilerplate/templates/emails/password_changed.txt @@ -0,0 +1,20 @@ +

Hello {{ first_name }},

+ +

The password for your {{ app_name }} account (@{{username}}), associated with your email {{email}}, +was recently changed. As a security precaution, this notification has been sent to you.

+ +

If you made this change, you don't need to do anything more.

+ +

If you didn't change your password, your account might have been hijacked. +Please follow the link to regain control over your account:

+{{reset_password_url}} + +

Important account security tips:

+

+1. Never give out your password or personal information by email or on social networks. +We will never contact you to ask you for your password.
+2. Never use the same password for this site and other websites. +

+ +

Sincerely,
+{{ app_name }} Team \ No newline at end of file diff --git a/boilerplate/templates/emails/reset_password.txt b/boilerplate/templates/emails/reset_password.txt new file mode 100644 index 0000000..6e22f1e --- /dev/null +++ b/boilerplate/templates/emails/reset_password.txt @@ -0,0 +1,19 @@ +

Hello {{username}},

+ +

To initiate the password reset process for your {{ app_name }} Account ({{email}}), click the link below:

+ +{{reset_password_url}} + +

If clicking the link above doesn't work, please copy and paste the URL in a new browser window instead.

+ +

If you've received this mail in error, it's likely that another user entered +your email address by mistake while trying to reset a password.

+ +

If you didn't initiate the request, you don't need to take any further action and can safely disregard this email.

+ +

If you have any question, please do not hesitate to contact us at:
+{{support_url}} +

+ +

Sincerely,
+{{ app_name }} Team \ No newline at end of file diff --git a/boilerplate/templates/errors/default_error.html b/boilerplate/templates/errors/default_error.html new file mode 100644 index 0000000..a98feca --- /dev/null +++ b/boilerplate/templates/errors/default_error.html @@ -0,0 +1,60 @@ + + + + + + + + Server Error | {{ exception }} + + + + + + + + + + + + + +

+

We’re sorry — something has gone wrong on our end.

+ +
+

Error: {{ exception }}

+

What could have caused this?

+
    +
  • Well, something technical went wrong on our site.
  • +
  • We might have removed the page when we redesigned our website.
  • +
  • Or the link you clicked might be old and does not work anymore.
  • +
  • Or you might have accidentally typed the wrong URL in the address bar.
  • +
+ +

What you can do?

+
    +
  • You might try retyping the URL and trying again.
  • +
  • Or we could take you back to the home page.
  • +
+ +
+ One more thing:
+ If you want to help us fix this issue, we are here to help. + Please contact us and let us know what went wrong. +
+
+
+ + + diff --git a/boilerplate/templates/errors/dos_api_denial.html b/boilerplate/templates/errors/dos_api_denial.html new file mode 100644 index 0000000..ea69dcb --- /dev/null +++ b/boilerplate/templates/errors/dos_api_denial.html @@ -0,0 +1,50 @@ + + + + + + + + Server Error | DoS Denial + + + + + + + + + + + + + +
+

We’re sorry — something has gone wrong on your end.

+ +
+

What could have caused this?

+
    +
  • Well, something technical went wrong on our site.
  • +
  • Or you were trying to access this website like a bot.
  • +
+ +

What you can do?

+
    +
  • You might try again later.
  • +
+
+
+ + + diff --git a/boilerplate/templates/errors/forbidden_access.html b/boilerplate/templates/errors/forbidden_access.html new file mode 100644 index 0000000..e7aca97 --- /dev/null +++ b/boilerplate/templates/errors/forbidden_access.html @@ -0,0 +1,59 @@ + + + + + + + + Server Error | Forbidden Access + + + + + + + + + + + + + +
+

Not Authorized

+ +
+

Error: Forbidden Access

+

What could have caused this?

+
    +
  • You don't have permission to access this URL.
  • +
  • We might have removed the page when we redesigned our website.
  • +
  • Or the link you clicked might be old and does not work anymore.
  • +
  • Or you might have accidentally typed the wrong URL in the address bar.
  • +
+ +

What you can do?

+
    +
  • We could take you back to the home page.
  • +
+ +
+ One more thing:
+ If you want to help us fix this issue, we are here to help. + Please contact us and let us know what went wrong. +
+
+
+ + + diff --git a/boilerplate/templates/errors/over_quota.html b/boilerplate/templates/errors/over_quota.html new file mode 100644 index 0000000..3777e17 --- /dev/null +++ b/boilerplate/templates/errors/over_quota.html @@ -0,0 +1,55 @@ + + + + + + + + Server Error | Over Quota + + + + + + + + + + + + + +
+

We’re sorry — something has gone wrong on our end.

+ +
+

What could have caused this?

+
    +
  • Well, something technical went wrong on our site.
  • +
+ +

What you can do?

+
    +
  • You might try again later.
  • +
+ +
+ One more thing:
+ If you want to help us fix this issue, we are here to help. + Please contact us and let us know what went wrong. +
+
+
+ + + diff --git a/boilerplate/templates/errors/timeout.html b/boilerplate/templates/errors/timeout.html new file mode 100644 index 0000000..7fca809 --- /dev/null +++ b/boilerplate/templates/errors/timeout.html @@ -0,0 +1,59 @@ + + + + + + + + Server Error | Timeout + + + + + + + + + + + + + +
+

We’re sorry — something has gone wrong on our end.

+ +
+

What could have caused this?

+
    +
  • Well, something technical went wrong on our site.
  • +
  • We might have removed the page when we redesigned our website.
  • +
  • Or the link you clicked might be old and does not work anymore.
  • +
  • Or you might have accidentally typed the wrong URL in the address bar.
  • +
+ +

What you can do?

+
    +
  • You might try again.
  • +
  • Or we could take you back to the home page.
  • +
+ +
+ One more thing:
+ If you want to help us fix this issue, we are here to help. + Please contact us and let us know what went wrong. +
+
+
+ + + diff --git a/boilerplate/templates/home.html b/boilerplate/templates/home.html new file mode 100644 index 0000000..fa89fa3 --- /dev/null +++ b/boilerplate/templates/home.html @@ -0,0 +1,141 @@ +{% extends base_layout %} + +{% block header_title_lead %}{% endblock %} + +{% block content %} + + {% if user_id %} +
+

+ Google App Engine Boilerplate +

+

+ {% trans %}Congratulations on your Google App Engine Boilerplate powered page.{% endtrans %} +
+ {% trans %}Learn why this Boilerplate Rocks{% endtrans %} {% trans %}or just{% endtrans %} + {% trans %}download the Source Code{% endtrans %} + {% trans %}to help you to create your application.{% endtrans %} +

+
+ {% else %} +
+
+
+

+ Google App Engine Boilerplate +

+

+ {% trans %}Congratulations on your Google App Engine Boilerplate powered page.{% endtrans %} +
+ {% trans %}Learn why this Boilerplate Rocks{% endtrans %} {% trans %}or just{% endtrans %} + {% trans %}download the Source Code{% endtrans %} + {% trans %}to help you to create your application.{% endtrans %} +

+
+
+
+
+
+ +
+ +
+
+
+ + +
+
+
+ +
+ {{ macros.federated_login(provider_info, provider_uris, enable_federated_login) }} +
+
+ +

{% trans %}Sign Up{% endtrans %}

+
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ {% endif %} + +{% endblock %} + +{% block mediaJS %} + +{% endblock %} + diff --git a/boilerplate/templates/login.html b/boilerplate/templates/login.html new file mode 100644 index 0000000..d251e14 --- /dev/null +++ b/boilerplate/templates/login.html @@ -0,0 +1,48 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Login{% endtrans %} +{% endblock %} + +{% block content %} +
+
+ + {{ macros.field(form.username, label=_("Username or Email"), placeholder=_("Enter your")+" "+_("Username or Email"), class="input-xlarge focused required") }} + {{ macros.field(form.password, label=_("Password"), placeholder=_("Enter your")+" "+_("Password"), class="input-xlarge focused required", type="password") }} +
+
+ + +
+
+ + {{ macros.federated_login(provider_info, provider_uris, enable_federated_login) }} +
+

{% trans %}Don't have an account?{% endtrans %}

+ +
+
+
+{% endblock %} + +{% block mediaJS %} + +{% endblock %} \ No newline at end of file diff --git a/boilerplate/templates/macros.html b/boilerplate/templates/macros.html new file mode 100644 index 0000000..57cd9b3 --- /dev/null +++ b/boilerplate/templates/macros.html @@ -0,0 +1,33 @@ + +{% macro field(field, label='') -%} +
+ {% set text = label or field.label.text %} + {% if field.flags.required %} + {{ field.label(text=text + " *", class="control-label") }} + {% else %} + {{ field.label(text=text + " ", class="control-label") }} + {% endif %} +
+ {{ field(**kwargs) }} + {% if field.errors %} + {% for error in field.errors %}{% endfor %} + {% endif %} +
+
+{%- endmacro %} + + +{% macro federated_login(provider_info, provider_uris, enable_federated_login) -%} +{% if enable_federated_login %} +
+

{% trans %}Or Sign in using:{% endtrans %}

+ +
+{% endif %} +{%- endmacro %} \ No newline at end of file diff --git a/boilerplate/templates/password_reset.html b/boilerplate/templates/password_reset.html new file mode 100644 index 0000000..a4c42f7 --- /dev/null +++ b/boilerplate/templates/password_reset.html @@ -0,0 +1,49 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Forgot your password?{% endtrans %} +{% endblock %} + +{% block content %} + + +
+
+ +
+ +
+ +
+
+
+
+ {{captchahtml | safe}} +
+
+
+ +
+
+
+{% endblock %} + +{% block mediaJS %} + +{% endblock %} \ No newline at end of file diff --git a/boilerplate/templates/password_reset_complete.html b/boilerplate/templates/password_reset_complete.html new file mode 100644 index 0000000..f0d7c52 --- /dev/null +++ b/boilerplate/templates/password_reset_complete.html @@ -0,0 +1,38 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Reset password{% endtrans %} +{% endblock %} + +{% block content %} + +
+
+ + {{ macros.field(form.password, label=_("New Password"), placeholder=_("Enter your")+" "+_("New Password"), class="input-xlarge focused required", type="password") }} + {% if not is_mobile %} + {{ macros.field(form.c_password, label=_("Confirm Password"), placeholder=_("Confirm Password"), class="input-xlarge focused required", type="password") }} + {% endif %} +
+ +
+
+
+ +{% endblock %} + +{% block mediaJS %} + +{% endblock %} \ No newline at end of file diff --git a/boilerplate/templates/register.html b/boilerplate/templates/register.html new file mode 100644 index 0000000..f461aef --- /dev/null +++ b/boilerplate/templates/register.html @@ -0,0 +1,58 @@ +{% extends base_layout %} + +{% block header_title %} + {% trans %}Sign Up{% endtrans %} +{% endblock %} + +{% block content %} + +
+
+ + {{ macros.field(form.username, label=_("Username"), placeholder=_("Enter your")+" "+_("Username"), class="input-xlarge focused required") }} + {{ macros.field(form.name, label=_("Name"), placeholder=_("Enter your")+" "+_("Name"), class="input-xlarge focused") }} + {{ macros.field(form.last_name, label=_("Last Name"), placeholder=_("Enter your")+" "+_("Last Name"), class="input-xlarge focused") }} + {{ macros.field(form.email, label=_("Email"), placeholder=_("Enter your")+" "+_("Email"), class="input-xlarge focused required email", type="email") }} + {{ macros.field(form.password, label=_("Password"), placeholder=_("Enter your")+" "+_("Password"), class="input-xlarge focused required", type="password") }} + {% if not is_mobile %} + {{ macros.field(form.c_password, label=_("Confirm Password"), placeholder=_("Confirm Password"), class="input-xlarge focused required", type="password") }} + {% endif %} + {{ macros.field(form.country, label=_("Country")) }} +
+ +
+
+
+ +{% endblock %} + +{% block mediaJS %} + +{% endblock %} + diff --git a/boilerplate/tests.py b/boilerplate/tests.py new file mode 100644 index 0000000..a3c7d9f --- /dev/null +++ b/boilerplate/tests.py @@ -0,0 +1,425 @@ +''' +Run the tests using testrunner.py script in the project root directory. + +Usage: testrunner.py SDK_PATH TEST_PATH +Run unit tests for App Engine apps. + +SDK_PATH Path to the SDK installation +TEST_PATH Path to package containing test modules + +Options: + -h, --help show this help message and exit + +''' +import unittest +import webapp2 +import os +import webtest +from google.appengine.ext import testbed + +from mock import Mock +from mock import patch + +import boilerplate +from boilerplate import models +from boilerplate import routes +from boilerplate import routes as boilerplate_routes +from boilerplate import config as boilerplate_config +from boilerplate.lib import utils +from boilerplate.lib import captcha +from boilerplate.lib import i18n +from boilerplate.lib import test_helpers + +# setting HTTP_HOST in extra_environ parameter for TestApp is not enough for taskqueue stub +os.environ['HTTP_HOST'] = 'localhost' + +# globals +network = False + +# mock Internet calls +if not network: + i18n.get_territory_from_ip = Mock(return_value=None) + + +class AppTest(unittest.TestCase, test_helpers.HandlerHelpers): + def setUp(self): + + webapp2_config = boilerplate_config.config + + # create a WSGI application. + self.app = webapp2.WSGIApplication(config=webapp2_config) + routes.add_routes(self.app) + boilerplate_routes.add_routes(self.app) + self.testapp = webtest.TestApp(self.app, extra_environ={'REMOTE_ADDR' : '127.0.0.1'}) + + # activate GAE stubs + self.testbed = testbed.Testbed() + self.testbed.activate() + self.testbed.init_datastore_v3_stub() + self.testbed.init_memcache_stub() + self.testbed.init_urlfetch_stub() + self.testbed.init_taskqueue_stub() + self.testbed.init_mail_stub() + self.mail_stub = self.testbed.get_stub(testbed.MAIL_SERVICE_NAME) + self.taskqueue_stub = self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME) + self.testbed.init_user_stub() + + self.headers = {'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) Version/6.0 Safari/536.25', + 'Accept-Language' : 'en_US'} + + # fix configuration if this is still a raw boilerplate code - required by test with mails + if not utils.is_email_valid(self.app.config.get('contact_sender')): + self.app.config['contact_sender'] = "noreply-testapp@example.com" + if not utils.is_email_valid(self.app.config.get('contact_recipient')): + self.app.config['contact_recipient'] = "support-testapp@example.com" + + def tearDown(self): + self.testbed.deactivate() + + def test_homepage(self): + response = self.get('/') + self.assertIn('Congratulations on your Google App Engine Boilerplate powered page.', response) + + def test_homepage_has_no_calls_create_login_url(self): + with patch('google.appengine.api.users.create_login_url') as create_login_url: + self.get('/') + self.assertEqual(0, create_login_url.call_count) + + def test_csrf_protection(self): + self.register_activate_testuser() + self.post('/login/', + dict(username='testuser', password='password'), status=403) + + def test_login_from_homepage(self): + self.register_activate_testuser() + + form = self.get_form('/', 'form_login_user') + form['username'] = 'testuser' + form['password'] = '123456' + self.submit(form) + self.assert_user_logged_in() + + def test_login_invalid_password(self): + self.register_activate_testuser() + + form = self.get_form('/', 'form_login_user') + form['username'] = 'testuser' + form['password'] = 'wrongpassword' + self.submit(form, expect_error=True, error_message='Your username or password is incorrect') + self.assert_user_not_logged_in() + + def test_login_not_activated(self): + self.register_testuser() + + form = self.get_form('/', 'form_login_user') + form['username'] = 'testuser' + form['password'] = '123456' + self.submit(form, expect_error=True, error_message='Please check your email to activate it') + self.assert_user_not_logged_in() + + def _login_openid(self, provider, uid, email=None): + openid_user = Mock() + openid_user.federated_identity.return_value = uid + openid_user.email.return_value = email + with patch('google.appengine.api.users.get_current_user', return_value=openid_user): + response = self.get('/social_login/{}/complete'.format(provider), status=302) + response = response.follow(status=200, headers=self.headers) + return response + + def test_login_openid_add_association(self): + response = self._login_openid('google', 'http://www.google.com/accounts/123') + self.assert_success_message_in_response(response, 'Welcome! You have been registered as a new user') + self.assert_user_logged_in() + + def test_login_openid_with_email_add_association(self): + response = self._login_openid('google', 'http://www.google.com/accounts/123', 'testuser@example.com') + self.assert_success_message_in_response(response, 'Welcome! You have been registered as a new user') + self.assert_user_logged_in() + user = models.User.query().get() + self.assertEqual('testuser@example.com', user.email) + + def test_login_openid(self): + user = self.register_activate_testuser() + models.SocialUser(user=user.key, provider='google', uid='http://www.google.com/accounts/123').put() + self._login_openid('google', uid='http://www.google.com/accounts/123') + self.assert_user_logged_in(user_id=user.get_id()) + + def test_login_twitter_no_association(self): + response = self._test_login_twitter() + self.assert_success_message_in_response(response, "Welcome! You have been registered as a new user") + self.assert_user_logged_in() + + def test_login_twitter_add_association(self): + self.register_activate_login_testuser() + response = self._test_login_twitter() + self.assert_success_message_in_response(response, 'Twitter association added.') + + def test_login_twitter(self): + user = self.register_activate_testuser() + models.SocialUser(user=user.key, provider='twitter', uid='7588892').put() + self._test_login_twitter() + self.assert_user_logged_in() + + def _test_login_twitter(self): + oauth_token = 'NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0' + oauth_token_secret = 'veNRnAWe6inFuo8o2u8SLLZLjolYDmDP7SzL0YfYI' + oauth_callback_confirmed = 'true' + oauth_verifier = 'uw7NjWHT6OJ1MpJOXsHfNxoAhPKpgI8BlYDhxEjIBY' + user_id = '7588892' + access_token = '{}-kagSNqWge8gB1WwE3plnFsJHAZVfxWD7Vb57p0b4'.format(user_id) + oauth_token_secret2 = 'PbKfYqSryyeKDWz4ebtY3o5ogNLG11WJuZBc9fQrQo' + screen_name = 'testuser' + + class Response: + def __init__(self, content): + self.content = content + def readlines(self): + return self.content.split('\n') + + urlopen = Mock(side_effect=[Response('oauth_token={}&oauth_token_secret={}&oauth_callback_confirmed=true'. + format(oauth_token, oauth_token_secret, oauth_callback_confirmed)), + Response('oauth_token={}&oauth_token_secret={}&user_id={}&screen_name={}'. + format(access_token, oauth_token_secret2, user_id, screen_name)), + Response('{"id":%s}' % user_id)]) + with patch('urllib2.urlopen', urlopen): + response = self.get('/social_login/twitter', status=302) + self.assertTrue(response.headers['Location'].startswith('http://api.twitter.com/oauth/authenticate?')) + + self.assertEquals(urlopen.call_count, 1) + self.assertTrue(urlopen.call_args_list[0][0][0]. + startswith('https://api.twitter.com/oauth/request_token?')) + + response = self.get('/social_login/twitter/complete?oauth_token={}&oauth_verifier={}'. + format(oauth_token, oauth_verifier), status=302) + self.assertEquals(urlopen.call_count, 2) + self.assertTrue(urlopen.call_args_list[1][0][0]. + startswith('https://api.twitter.com/oauth/access_token?')) + + response = response.follow(status=200, headers=self.headers) + return response + + def test_resend_activation_mail(self): + self.register_testuser() + + # login with valid credentials + form = self.get_form('/', 'form_login_user') + form['username'] = 'testuser' + form['password'] = '123456' + # account is not activated + response = self.submit(form, expect_error=True, error_message='Please check your email to activate it') + self.assert_user_not_logged_in() + # "lose" activation mail + self.get_sent_messages(to='testuser@example.com')[0] + + # resend the activation mail + response2 = response.click(description='click here').follow(status=200, headers=self.headers) + self.assert_success_message_in_response(response2, + "The verification email has been resent to testuser@example.com.") + + # click again should fail + response = response.click(description='click here').follow(status=200, headers=self.headers) + self.assert_error_message_in_response(response, 'The link is invalid.') + + message = self.get_sent_messages(to='testuser@example.com')[0] + url = self.get_url_from_message(message, 'activation') + response = self.get(url, status=302).follow(status=200, headers=self.headers) + self.assert_success_message_in_response(response, + message='Congratulations, Your account testuser has been successfully activated.') + + def test_request_with_no_user_agent_header(self): + self.get('/', headers={'Accept-Language' : 'en_US'}) + + def test_request_with_no_accept_language_header(self): + self.get('/', headers={'User-Agent' : 'Safari'}) + + def test_request_with_no_headers(self): + self.get('/', headers=None) + + def test_edit_profile(self): + self.get('/settings/profile', status=302) # not for anonymous + user = self.register_activate_login_testuser() + + form = self.get_form('/settings/profile', 'form_edit_profile') + self.assertEqual(form['username'].value, 'testuser') + self.assertEqual(form['name'].value, '') + self.assertEqual(form['last_name'].value, '') + self.assertEqual(form['country'].value, '') + self.submit(form, success_message='your settings have been saved.') + + form['username'] = 'testuser2' + form['name'] = 'Test' + form['last_name'] = 'User' + form['country'] = 'US' + self.submit(form, success_message='your settings have been saved.') + self.assertEqual(user.username, 'testuser2') + self.assertEqual(user.name, 'Test') + self.assertEqual(user.last_name, 'User') + self.assertEqual(user.country, 'US') + + self.testapp.reset() + self.login_user('testuser2', '123456') + + def test_logout(self): + self.register_activate_login_testuser() + self.get('/logout/', status=302) + self.assert_user_not_logged_in() + + def test_edit_email(self): + user = self.register_activate_login_testuser() + + form = self.get_form('/settings/email', 'form_edit_email', expect_fields=['new_email', 'password']) + form['new_email'] = 'invalid_email-example.com' + form['password'] = '123456' + self.submit(form, expect_error=True, error_message='Invalid email address.') + form['new_email'] = 'tu@example.com' + form['password'] = '123' + self.submit(form, expect_error=True, error_message='Incorrect password!') + form['password'] = '123456' + self.submit(form, success_message='Please check your new email for confirmation') + + message_old_address = self.get_sent_messages(to='testuser@example.com', reset_mail_stub=False)[0] + message_new_address = self.get_sent_messages(to='tu@example.com')[0] + self.assertEqual(message_old_address.sender, self.app.config.get('contact_sender')) + self.assertEqual(message_new_address.sender, self.app.config.get('contact_sender')) + self.assertIn("Recently you've changed the email address", message_old_address.html.payload) + self.assertIn("You've changed the email address", message_new_address.html.payload) + + self.assertEqual(user.email, 'testuser@example.com') + + # click confirmation link + url = self.get_url_from_message(message_new_address, 'change-email') + self.get(url, status=302) + + self.assertEqual(user.email, 'tu@example.com') + + def test_password_reset(self): + self.register_activate_testuser() + + form = self.get_form('/password-reset/', 'form_reset_password', + expect_fields=['email_or_username', 'recaptcha_challenge_field', 'recaptcha_response_field']) + form['email_or_username'] = 'testuser' + with patch('boilerplate.lib.captcha.submit', return_value=captcha.RecaptchaResponse(is_valid=False)): + self.submit(form, expect_error=True, error_message='Wrong image verification code.') + with patch('boilerplate.lib.captcha.submit', return_value=captcha.RecaptchaResponse(is_valid=True)): + response1 = self.submit(form, warning_message="you will receive an e-mail from us with instructions for resetting your password.") + form['email_or_username'] = 'user_does_not_exists' + response2 = self.submit(form, warning_message="you will receive an e-mail from us with instructions for resetting your password.") + page1 = response1.body, response1.request.url + page2 = response2.body.replace('user_does_not_exists', 'testuser'), response2.request.url + self.assertEqual(page1, page2, "for security reasons application should respond with the same page here") + + message = self.get_sent_messages(to='testuser@example.com')[0] + self.assertEqual(message.sender, self.app.config.get('contact_sender')) + self.assertIn('click the link below:', message.html.payload) + + # click password reset link and submit new password + url = self.get_url_from_message(message, 'password-reset') + self.get(url) + form = self.get_form(url, 'form_new_password', expect_fields=['password', 'c_password']) + self.assert_user_not_logged_in() + form['password'] = form['c_password'] = '456456' + self.submit(form, success_message='Password changed successfully') + + self.testapp.reset() + self.login_user('testuser', '456456') + + def test_edit_password(self): + self.register_activate_login_testuser() + form = self.get_form('/settings/password', 'form_edit_password', + expect_fields=['current_password', 'password', 'c_password']) + form['current_password'] = '123456' + form['password'] = '789789' + form['c_password'] = '789' + self.submit(form, expect_error=True, error_message='Passwords must match.') + form['c_password'] = '789789' + self.submit(form) + + self.testapp.reset() + self.login_user('testuser', '789789') + + def test_register(self): + self._test_register('/register/', + expect_fields=['username', 'name', 'last_name', 'email', 'password', 'c_password', 'country']) + + def test_register_from_home_page(self): + self._test_register('/', + expect_fields=['username', 'email', 'country', 'password', 'c_password']) + + def _test_register(self, url, form_id='form_register', expect_fields=None): + form = self.get_form(url, form_id, expect_fields=expect_fields) + + # TODO: check mutliple validation errors on the form + self.submit(form, expect_error=True, error_message='This field is required.', error_field='username') + form['username'] = 'Reguser' + form['email'] = 'reguser@example.com' + form['password'] = form['c_password'] = '456456' + self.submit(form, success_message='You were successfully registered. Please check your email to activate your account') + + message = self.get_sent_messages(to='reguser@example.com')[0] + url = self.get_url_from_message(message, 'activation') + + # try to activate account with invalid token + response = self.get(url+'qwe', status=302).follow(status=200, headers=self.headers) + self.assert_error_message_in_response(response, 'The link is invalid.') + + response = self.get(url, status=302).follow(status=200, headers=self.headers) + self.assert_success_message_in_response(response, + message='Congratulations, Your account reguser has been successfully activated.') + + # activation token has already been used + response = self.get(url, status=302).follow(status=200, headers=self.headers) + self.assert_error_message_in_response(response, 'The link is invalid.') + + # activated user should be auto-logged in + self.assert_user_logged_in() + + def test_contact(self): + form = self.get_form('/contact/', 'form_contact', + expect_fields=['exception', 'name', 'email', 'message']) + form['name'] = 'Anton' + form['email'] = 'anton@example.com' + form['message'] = 'Hi there...' + self.submit(form) + message = self.get_sent_messages(to=self.app.config.get('contact_recipient'))[0] + self.assertEqual(message.sender, self.app.config.get('contact_sender')) + self.assertIn('Hi there...', message.html.payload) + + self.register_activate_login_testuser() + form = self.get_form('/contact/', 'form_contact') + self.assertEqual(form['name'].value, '') + self.assertEqual(form['email'].value, 'testuser@example.com') + self.assertEqual(form['message'].value, '') + form['message'] = 'help' + self.submit(form, expect_error=True, error_field='name') + form['name'].value = 'Antonioni' + self.submit(form, expect_error=False) + message = self.get_sent_messages(to=self.app.config.get('contact_recipient'))[0] + self.assertIn('help', message.html.payload) + + +class ModelTest(unittest.TestCase): + def setUp(self): + + # activate GAE stubs + self.testbed = testbed.Testbed() + self.testbed.activate() + self.testbed.init_datastore_v3_stub() + self.testbed.init_memcache_stub() + + def tearDown(self): + self.testbed.deactivate() + + def test_user_token(self): + user = models.User(name="tester", email="tester@example.com") + user.put() + user2 = models.User(name="tester2", email="tester2@example.com") + user2.put() + + token = models.User.create_signup_token(user.get_id()) + self.assertTrue(models.User.validate_signup_token(user.get_id(), token)) + self.assertFalse(models.User.validate_resend_token(user.get_id(), token)) + self.assertFalse(models.User.validate_signup_token(user2.get_id(), token)) + + +if __name__ == "__main__": + unittest.main() diff --git a/bulkloader.md b/bulkloader.md new file mode 100644 index 0000000..18dddbc --- /dev/null +++ b/bulkloader.md @@ -0,0 +1,19 @@ +Downloading to csv +------------------ +appcfg.py download_data --config_file=bulkloader.yaml --filename=.csv --kind= --url=http://.appspot.com/_ah/remote_api + +Uploading to dev server +----------------------- +login: admin +password: +appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/.csv --kind= --application="dev~uplusprofiles" --url=http://localhost:8080/_ah/remote_api + +Uploading to app engine +----------------------- +appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/.csv --kind= --url=http://localhost:8080/_ah/remote_api + +Examples: + +python2x appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/courses.csv --kind=Course --url=http://devn-uplusprofiles.appspot.com/_ah/remote_api + +python2x appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/sources.csv --kind=Source --url=http://devn-uplusprofiles.appspot.com/_ah/remote_api \ No newline at end of file diff --git a/uplusprofiles/bulkloader.yaml b/bulkloader.yaml similarity index 96% rename from uplusprofiles/bulkloader.yaml rename to bulkloader.yaml index f535120..9855a08 100644 --- a/uplusprofiles/bulkloader.yaml +++ b/bulkloader.yaml @@ -23,7 +23,7 @@ transformers: - property: url external_name: url - + - kind: Course connector: csv @@ -53,6 +53,9 @@ transformers: - property: short_desc external_name: description + - property: url + external_name: url + - property: source external_name: source import_transform: transform.create_foreign_key('Source') diff --git a/bulkup.sh b/bulkup.sh new file mode 100755 index 0000000..fb14a3f --- /dev/null +++ b/bulkup.sh @@ -0,0 +1,3 @@ +~/google_appengine/appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/courses.csv --kind=Course --application="dev~socialmooc" --url=http://localhost:8080/_ah/remote_api . +~/google_appengine/appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/sources.csv --kind=Source --application="dev~socialmooc" --url=http://localhost:8080/_ah/remote_api . + diff --git a/bulkup_remote.sh b/bulkup_remote.sh new file mode 100755 index 0000000..99bb53f --- /dev/null +++ b/bulkup_remote.sh @@ -0,0 +1,2 @@ +~/google_appengine/appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/courses.csv --kind=Course --url=http://socialmooc.appspot.com/_ah/remote_api +~/google_appengine/appcfg.py --config_file=bulkloader.yaml upload_data --filename=data/sources.csv --kind=Source --url=http://socialmooc.appspot.com/_ah/remote_api \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..dacaffe --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,16 @@ +import os + +""" +This configuration file loads environment's specific config settings for the application. +It takes precedence over the config located in the boilerplate package. +""" + +if "SERVER_SOFTWARE" in os.environ: + if os.environ['SERVER_SOFTWARE'].startswith('Dev'): + from config.localhost import config + + elif os.environ['SERVER_SOFTWARE'].startswith('Google'): + from config.production import config +else: + from config.testing import config + diff --git a/config/localhost.py b/config/localhost.py new file mode 100644 index 0000000..9d0e766 --- /dev/null +++ b/config/localhost.py @@ -0,0 +1,114 @@ +""" +This is the boilerplate default configuration file. +Changes and additions to settings should be done in the config module +located in the application root rather than this config. +""" + + +config = { + 'environment': 'localhost', + + # webapp2 sessions + 'webapp2_extras.sessions' : {'secret_key': '_PUT_KEY_HERE_YOUR_SECRET_KEY_'}, + + # webapp2 authentication + 'webapp2_extras.auth' : {'user_model': 'web.models.User.User', + 'cookie_name': 'session_name'}, + + # jinja2 templates + 'webapp2_extras.jinja2' : {'template_path': ['templates','boilerplate/templates', 'admin/templates'], + 'environment_args': { + 'extensions': ['jinja2.ext.i18n'] + }, + + 'globals': { + 'site_name': 'Udacity+', + 'meetups_url': 'http://www.meetup.com/Udacity/', + } + }, + + # application name + 'app_name' : "Udacity+", + + # the default language code for the application. + # should match whatever language the site uses when i18n is disabled + 'app_lang' : 'en', + + # Locale code = _ (ie 'en_US') + # to pick locale codes see http://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code + # also see http://www.sil.org/iso639-3/codes.asp + # Language codes defined under iso 639-1 http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + # Territory codes defined under iso 3166-1 alpha-2 http://en.wikipedia.org/wiki/ISO_3166-1 + # disable i18n if locales array is empty or None + 'locales' : ['en_US', 'es_ES', 'it_IT', 'zh_CN', 'id_ID', 'fr_FR', 'de_DE', 'ru_RU', 'pt_BR'], + + # contact page email settings + 'contact_sender' : "PUT_SENDER_EMAIL_HERE", + 'contact_recipient' : "PUT_RECIPIENT_EMAIL_HERE", + + # Password AES Encryption Parameters + 'aes_key' : "12_24_32_BYTES_KEY_FOR_PASSWORDS", + 'salt' : "_PUT_SALT_HERE_TO_SHA512_PASSWORDS_", + + # get your own consumer key and consumer secret by registering at https://dev.twitter.com/apps + # callback url must be: http://[YOUR DOMAIN]/login/twitter/complete + 'twitter_consumer_key' : 'PUT_YOUR_TWITTER_CONSUMER_KEY_HERE', + 'twitter_consumer_secret' : 'PUT_YOUR_TWITTER_CONSUMER_SECRET_HERE', + + #Facebook Login + # get your own consumer key and consumer secret by registering at https://developers.facebook.com/apps + #Very Important: set the site_url= your domain in the application settings in the facebook app settings page + # callback url must be: http://[YOUR DOMAIN]/login/facebook/complete + 'fb_api_key' : 'PUT_YOUR_FACEBOOK_PUBLIC_KEY_HERE', + 'fb_secret' : 'PUT_YOUR_FACEBOOK_PUBLIC_KEY_HERE', + + #Linkedin Login + #Get you own api key and secret from https://www.linkedin.com/secure/developer + 'linkedin_api' : 'PUT_YOUR_LINKEDIN_PUBLIC_KEY_HERE', + 'linkedin_secret' : 'PUT_YOUR_LINKEDIN_PUBLIC_KEY_HERE', + + # Github login + # Register apps here: https://github.com/settings/applications/new + 'github_server' : 'github.com', + 'github_redirect_uri' : 'http://www.example.com/social_login/github/complete', + 'github_client_id' : 'PUT_YOUR_GITHUB_CLIENT_ID_HERE', + 'github_client_secret' : 'PUT_YOUR_GITHUB_CLIENT_SECRET_HERE', + + # get your own recaptcha keys by registering at http://www.google.com/recaptcha/ + 'captcha_public_key' : "PUT_YOUR_RECAPCHA_PUBLIC_KEY_HERE", + 'captcha_private_key' : "PUT_YOUR_RECAPCHA_PRIVATE_KEY_HERE", + + # Leave blank "google_analytics_domain" if you only want Analytics code + 'google_analytics_domain' : "YOUR_PRIMARY_DOMAIN (e.g. google.com)", + 'google_analytics_code' : "UA-XXXXX-X", + + # add status codes and templates used to catch and display errors + # if a status code is not listed here it will use the default app engine + # stacktrace error page or browser error page + 'error_templates' : { + 403: 'errors/default_error.html', + 404: 'errors/default_error.html', + 500: 'errors/default_error.html', + }, + + # Enable Federated login (OpenID and OAuth) + # Google App Engine Settings must be set to Authentication Options: Federated Login + 'enable_federated_login' : True, + # jinja2 base layout template + + 'base_layout' : 'base.html', + + # send error emails to developers + 'send_mail_developer' : False, + + # fellas' list + 'developers' : ( + ('Santa Klauss', 'snowypal@northpole.com'), + ), + + # site title + 'gravatar_base_url': "http://www.gravatar.com/avatar/", + 'MSG_NOTIFY_EMAIL': 'uplusumessaging@uplus.com', + 'MSG_NOTIFY_EMAIL_DISP': "Udacity+ ", + +} # end config diff --git a/config/production.py b/config/production.py new file mode 100644 index 0000000..f937310 --- /dev/null +++ b/config/production.py @@ -0,0 +1,114 @@ +""" +This is the boilerplate default configuration file. +Changes and additions to settings should be done in the config module +located in the application root rather than this config. +""" + + +config = { + 'environment': 'production', + + # webapp2 sessions + 'webapp2_extras.sessions' : {'secret_key': 'C2wuhu6abubRuphaDuwAneruswupU5adeth$qutrayedraQekeVUjujArAyUthAq'}, + + # webapp2 authentication + 'webapp2_extras.auth' : {'user_model': 'boilerplate.models.User', + 'cookie_name': 'session_name'}, + + # jinja2 templates + 'webapp2_extras.jinja2' : {'template_path': ['templates','boilerplate/templates', 'admin/templates'], + 'environment_args': { + 'extensions': ['jinja2.ext.i18n'] + }, + + 'globals': { + 'site_name': 'Udacity+', + 'meetups_url': 'http://www.meetup.com/Udacity/', + } + }, + + # application name + 'app_name' : "Udacity+", + + # the default language code for the application. + # should match whatever language the site uses when i18n is disabled + 'app_lang' : 'en', + + # Locale code = _ (ie 'en_US') + # to pick locale codes see http://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code + # also see http://www.sil.org/iso639-3/codes.asp + # Language codes defined under iso 639-1 http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + # Territory codes defined under iso 3166-1 alpha-2 http://en.wikipedia.org/wiki/ISO_3166-1 + # disable i18n if locales array is empty or None + 'locales' : ['en_US', 'es_ES', 'it_IT', 'zh_CN', 'id_ID', 'fr_FR', 'de_DE', 'ru_RU', 'pt_BR'], + + # contact page email settings + 'contact_sender' : "PUT_SENDER_EMAIL_HERE", + 'contact_recipient' : "PUT_RECIPIENT_EMAIL_HERE", + + # Password AES Encryption Parameters + 'aes_key' : "12_24_32_BYTES_KEY_FOR_PASSWORDS", + 'salt' : "_PUT_SALT_HERE_TO_SHA512_PASSWORDS_", + + # get your own consumer key and consumer secret by registering at https://dev.twitter.com/apps + # callback url must be: http://[YOUR DOMAIN]/login/twitter/complete + 'twitter_consumer_key' : 'PUT_YOUR_TWITTER_CONSUMER_KEY_HERE', + 'twitter_consumer_secret' : 'PUT_YOUR_TWITTER_CONSUMER_SECRET_HERE', + + #Facebook Login + # get your own consumer key and consumer secret by registering at https://developers.facebook.com/apps + #Very Important: set the site_url= your domain in the application settings in the facebook app settings page + # callback url must be: http://[YOUR DOMAIN]/login/facebook/complete + 'fb_api_key' : 'PUT_YOUR_FACEBOOK_PUBLIC_KEY_HERE', + 'fb_secret' : 'PUT_YOUR_FACEBOOK_PUBLIC_KEY_HERE', + + #Linkedin Login + #Get you own api key and secret from https://www.linkedin.com/secure/developer + 'linkedin_api' : 'PUT_YOUR_LINKEDIN_PUBLIC_KEY_HERE', + 'linkedin_secret' : 'PUT_YOUR_LINKEDIN_PUBLIC_KEY_HERE', + + # Github login + # Register apps here: https://github.com/settings/applications/new + 'github_server' : 'github.com', + 'github_redirect_uri' : 'http://www.example.com/social_login/github/complete', + 'github_client_id' : 'PUT_YOUR_GITHUB_CLIENT_ID_HERE', + 'github_client_secret' : 'PUT_YOUR_GITHUB_CLIENT_SECRET_HERE', + + # get your own recaptcha keys by registering at http://www.google.com/recaptcha/ + 'captcha_public_key' : "PUT_YOUR_RECAPCHA_PUBLIC_KEY_HERE", + 'captcha_private_key' : "PUT_YOUR_RECAPCHA_PRIVATE_KEY_HERE", + + # Leave blank "google_analytics_domain" if you only want Analytics code + 'google_analytics_domain' : "YOUR_PRIMARY_DOMAIN (e.g. google.com)", + 'google_analytics_code' : "UA-XXXXX-X", + + # add status codes and templates used to catch and display errors + # if a status code is not listed here it will use the default app engine + # stacktrace error page or browser error page + 'error_templates' : { + 403: 'errors/default_error.html', + 404: 'errors/default_error.html', + 500: 'errors/default_error.html', + }, + + # Enable Federated login (OpenID and OAuth) + # Google App Engine Settings must be set to Authentication Options: Federated Login + 'enable_federated_login' : True, + # jinja2 base layout template + + 'base_layout' : 'base.html', + + # send error emails to developers + 'send_mail_developer' : False, + + # fellas' list + 'developers' : ( + ('Santa Klauss', 'snowypal@northpole.com'), + ), + + # site title + 'gravatar_base_url': "http://www.gravatar.com/avatar/", + 'MSG_NOTIFY_EMAIL': 'uplusumessaging@uplus.com', + 'MSG_NOTIFY_EMAIL_DISP': "Udacity+ ", + + } # end config diff --git a/config/testing.py b/config/testing.py new file mode 100644 index 0000000..e64be8a --- /dev/null +++ b/config/testing.py @@ -0,0 +1,8 @@ +config = { + +# environment this app is running on: localhost, testing, production +'environment': "testing", + +# ----> ADD MORE CONFIGURATION OPTIONS HERE <---- + +} \ No newline at end of file diff --git a/uplusprofiles/data/courses.csv b/data/courses.csv similarity index 50% rename from uplusprofiles/data/courses.csv rename to data/courses.csv index 6ba898e..ca0787e 100644 --- a/uplusprofiles/data/courses.csv +++ b/data/courses.csv @@ -1,19 +1,19 @@ -name,level,cid,number,dept,source,key,description -Introduction to Computer Science,Beginning,CS101,101,CS,Udacity,udacitycs101,Building a Search Engine (CS101) -Design of Computer Programs,Advanced,CS212,212,CS,Udacity,udacitycs212,Programming Principles (CS212) -Algorithms,Intermediate,CS215,215,CS,Udacity,udacitycs215,Crunching Social Networks (CS215) -Differential Equations in Action,Intermediate,CS222,222,CS,Udacity,udacitycs222,Making Math Matter (CS222) -Web Development,Intermediate,CS253,253,CS,Udacity,udacitycs253,How to Build a Blog (CS253) -Software Testing,Intermediate,CS258,258,CS,Udacity,udacitycs258,How to Make Software Fail (CS258) -Software Debugging,Intermediate,CS259,259,CS,Udacity,udacitycs259,Automating the Boring Tasks (CS259) -Programming Languages,Intermediate,CS262,262,CS,Udacity,udacitycs262,Building a Web Browser (CS262) -Introduction to Theoretical Computer Science,Intermediate,CS313,313,CS,Udacity,udacitycs313,Dealing with Challenging Problems (CS313) -Artificial Intelligence,Advanced,CS373,373,CS,Udacity,udacitycs373,Programming A Robotic Car (CS373) -Applied Cryptography,Advanced,CS387,387,CS,Udacity,udacitycs387,Science of Secrets (CS387) -How to Build a Startup,Intermediate,EP245,245,EP,Udacity,udacityep245,The Lean LaunchPad (EP245) -Introduction to Physics,Beginning,PH100,100,PH,Udacity,udacityph100,Landmarks in Physics (PH100) -Introduction to Statistics,Beginning,ST101,101,ST,Udacity,udacityst101,Making Decisions Based on Data (ST101) -Interactive Rendering,Intermediate,CS291,291,CS,Udacity,udacitycs291,Introduction to 3D Computer Graphics (CS291) -Introduction to Parallel Programming,Advanced,CS344,344,CS,Udacity,udacitycs344,Using CUDA to Harness the Power of GPUs (CS 344) -Functional Hardware Verification,Advanced,CS348,348,CS,Udacity,udacitycs348,How to Verify Chips and Eliminate Bugs (CS348) -HTML5 Game Development,Intermediate,CS255,255,CS,Udacity,udacitycs255,Building High Performance Web Applications (CS255) \ No newline at end of file +name,level,cid,number,dept,source,key,description,url +Introduction to Computer Science,Beginning,CS101,101,CS,Udacity,udacitycs101,Building a Search Engine (CS101),http://udacity.com/course/cs101 +Design of Computer Programs,Advanced,CS212,212,CS,Udacity,udacitycs212,Programming Principles (CS212),http://udacity.com/course/cs212 +Algorithms,Intermediate,CS215,215,CS,Udacity,udacitycs215,Crunching Social Networks (CS215),http://udacity.com/course/cs215 +Differential Equations in Action,Intermediate,CS222,222,CS,Udacity,udacitycs222,Making Math Matter (CS222),http://udacity.com/course/cs222 +Web Development,Intermediate,CS253,253,CS,Udacity,udacitycs253,How to Build a Blog (CS253),http://udacity.com/course/cs253 +Software Testing,Intermediate,CS258,258,CS,Udacity,udacitycs258,How to Make Software Fail (CS258),http://udacity.com/course/cs258 +Software Debugging,Intermediate,CS259,259,CS,Udacity,udacitycs259,Automating the Boring Tasks (CS259),http://udacity.com/course/cs259 +Programming Languages,Intermediate,CS262,262,CS,Udacity,udacitycs262,Building a Web Browser (CS262),http://udacity.com/course/cs262 +Introduction to Theoretical Computer Science,Intermediate,CS313,313,CS,Udacity,udacitycs313,Dealing with Challenging Problems (CS313),http://udacity.com/course/cs313 +Artificial Intelligence,Advanced,CS373,373,CS,Udacity,udacitycs373,Programming A Robotic Car (CS373),http://udacity.com/course/cs317 +Applied Cryptography,Advanced,CS387,387,CS,Udacity,udacitycs387,Science of Secrets (CS387),http://udacity.com/course/cs387 +How to Build a Startup,Intermediate,EP245,245,EP,Udacity,udacityep245,The Lean LaunchPad (EP245),http://udacity.com/course/ep245 +Introduction to Physics,Beginning,PH100,100,PH,Udacity,udacityph100,Landmarks in Physics (PH100),http://udacity.com/course/ph100 +Introduction to Statistics,Beginning,ST101,101,ST,Udacity,udacityst101,Making Decisions Based on Data (ST101),http://udacity.com/course/st101 +Interactive Rendering,Intermediate,CS291,291,CS,Udacity,udacitycs291,Introduction to 3D Computer Graphics (CS291),http://udacity.com/course/cs291 +Introduction to Parallel Programming,Advanced,CS344,344,CS,Udacity,udacitycs344,Using CUDA to Harness the Power of GPUs (CS 344),http://udacity.com/course/cs344 +Functional Hardware Verification,Advanced,CS348,348,CS,Udacity,udacitycs348,How to Verify Chips and Eliminate Bugs (CS348),http://udacity.com/course/cs348 +HTML5 Game Development,Intermediate,CS255,255,CS,Udacity,udacitycs255,Building High Performance Web Applications (CS255),http://udacity.com/course/cs255 \ No newline at end of file diff --git a/uplusprofiles/data/sources.csv b/data/sources.csv similarity index 100% rename from uplusprofiles/data/sources.csv rename to data/sources.csv diff --git a/uplusprofiles/doc/errors.md b/doc/errors.md similarity index 100% rename from uplusprofiles/doc/errors.md rename to doc/errors.md diff --git a/udacitychat/doc/udacityChat.mm b/doc/udacityChat.mm similarity index 100% rename from udacitychat/doc/udacityChat.mm rename to doc/udacityChat.mm diff --git a/index.yaml b/index.yaml new file mode 100644 index 0000000..3e7b736 --- /dev/null +++ b/index.yaml @@ -0,0 +1,92 @@ +indexes: + +# AUTOGENERATED + +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. + +- kind: Conversation + properties: + - name: receivers_list_norm + - name: modified + direction: desc + +- kind: User + properties: + - name: __key__ + direction: desc + +- kind: User + properties: + - name: email + - name: __key__ + direction: desc + +- kind: User + properties: + - name: email + - name: friends + +- kind: User + properties: + - name: last_name + - name: __key__ + direction: desc + +- kind: User + properties: + - name: username + - name: __key__ + direction: desc + +- kind: User + properties: + - name: username + - name: email + +- kind: User + properties: + - name: username + - name: email + - name: friends + +- kind: User + properties: + - name: username + - name: email + - name: name + +- kind: User + properties: + - name: username + - name: email, friends + +- kind: User + properties: + - name: username + - name: email, password + +- kind: User + properties: + - name: username + - name: friends + +- kind: User + properties: + - name: username + - name: real_name + +- kind: User + properties: + - name: username + - name: username, email + +- kind: User + properties: + - name: username + - name: username, password diff --git a/locale/babel.cfg b/locale/babel.cfg new file mode 100644 index 0000000..844eed7 --- /dev/null +++ b/locale/babel.cfg @@ -0,0 +1,10 @@ +# Extraction from html templates +[jinja2: **/templates/**.html] +encoding = utf-8 +# Extraction from Python source files +[python: **/web/**.py] +[python: **/lib/**.py] +[python: **/models/**.py] +# Extraction from JavaScript files +[javascript: **.js] +extract_messages = $._, jQuery._ \ No newline at end of file diff --git a/locale/de_DE/LC_MESSAGES/messages.mo b/locale/de_DE/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..38d4e25 Binary files /dev/null and b/locale/de_DE/LC_MESSAGES/messages.mo differ diff --git a/locale/de_DE/LC_MESSAGES/messages.po b/locale/de_DE/LC_MESSAGES/messages.po new file mode 100644 index 0000000..55bbd21 --- /dev/null +++ b/locale/de_DE/LC_MESSAGES/messages.po @@ -0,0 +1,340 @@ +# German (Germany) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-08-13 08:13+0100\n" +"Last-Translator: Siegfried Hirsch \n" +"Language-Team: de_DE \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "%s Benutzer Verfikation" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "%s E-Mail-Änderungs Benachrichtigung" + +#, python-format +msgid "%s Password Assistance" +msgstr "%s Passwort Hilfe" + +#, python-format +msgid "%s association successfully added." +msgstr "%s Verbindung erfolgreich hinzugefügt." + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "%s Authentication ist noch nicht implementiert" + +#, python-format +msgid "%s successfully disassociated." +msgstr "%s erfolgreich los gelöst!" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" +"Upgraden Sie zu einem anderen Browser\n" +" \tor \n" +" \tinstalliere Google Chrome Frame um diese Seite zu geniesen." + +msgid "Associate account with" +msgstr "Verbinde Konto mit" + +msgid "Change password" +msgstr "Ändern Passwort" + +msgid "Change your email" +msgstr "Ändern Sie Ihre E-Mail" + +msgid "Change your password" +msgstr "Ändern Sie Ihr Passwort" + +msgid "Confirm Password" +msgstr "Bestätigen Sie das Passwort" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "Herzlichen Glückwunsch zu Ihrer Google App Engine Boilerplate Seite." + +#, python-format +msgid "Congratulations, Your account %s has been successfully activated.{0:>s}" +msgstr "Glückwunsch, Ihr Konto %s wurde erfolgreich aktiviert.{0:>s}" + +msgid "Contact" +msgstr "Kontakt" + +msgid "Country" +msgstr "Land" + +msgid "Current Password" +msgstr "Aktuelles Passwort" + +msgid "Edit Profile" +msgstr "Bearbeite Profil" + +msgid "Email" +msgstr "E-Mail" + +msgid "Enter a new email address" +msgstr "Geben Sie eine neue E-Mail-Adresse an" + +msgid "Enter your" +msgstr "Eingabe" + +msgid "Error sending the message. Please try again later." +msgstr "Fehler beim Senden der Nachricht. Bitte versuchen Sie es später." + +msgid "Existing social association" +msgstr "Bestehende Social-Verbindungen" + +msgid "Federated login is disabled." +msgstr "Verbundauthentifizierung ist deaktiviert." + +msgid "Forgot your password?" +msgstr "Passwort vergessen?" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "Google App Engine Boilerplate veröffentlicht unter der" + +msgid "If the e-mail address you entered" +msgstr "Wenn die E-Mail-Adresse, die Sich eingegeben haben" + +msgid "If the username you entered" +msgstr "Wenn der Benutzername, den Sie eingegeben haben" + +msgid "Incorrect password! Please enter your current password to change your account settings." +msgstr "Ungültiges Passwort! Bitte geben Sie Ihr aktuelles Passwort ein um Ihre Kontoeinstellungen zu ändern." + +msgid "Invalid email address." +msgstr "Ungültige E-Mail-Adresse." + +msgid "Language" +msgstr "Sprache" + +msgid "Last Name" +msgstr "Nachname" + +msgid "Learn why this Boilerplate Rocks" +msgstr "Lernen Sie warum dieses Boilerplate rockt" + +msgid "Login" +msgstr "Anmelden" + +msgid "Logout" +msgstr "Abmelden" + +msgid "Message" +msgstr "Nachricht" + +msgid "Name" +msgstr "Name" + +msgid "New Email" +msgstr "Neue E-Mail" + +msgid "New Password" +msgstr "Neues Passwort" + +#, python-format +msgid "No user authentication information received from %s. Please ensure you are logging in from an authorized OpenID Provider (OP)." +msgstr "Keine Benutzerauthenfizierunginformation erhalten von %s. Bitte stellen Sie sicher, dass Sie sich von einem autorisierten OpenId Provider (OP) einloggen." + +msgid "Or Sign in using:" +msgstr "Oder Anmelden via" + +msgid "Password" +msgstr "Passwort" + +msgid "Password changed successfully." +msgstr "Passwort erfolgreich geändert." + +msgid "Passwords must match." +msgstr "Passwörter müssen gleich sein" + +msgid "Please check your new email for confirmation. Your email will be updated after confirmation." +msgstr "Bitte prüfen Sie Ihre neue E-Mail auf Bestätigung. Ihre E-Mail wird nach der Bestätigung upgedatet." + +msgid "Remember me?" +msgstr "Eingaben speichern?" + +msgid "Remove" +msgstr "Entfernen" + +msgid "Reset password" +msgstr "Passwort zurücksetzen" + +msgid "Secure Page" +msgstr "Sichere Seite" + +msgid "Send Message" +msgstr "Sende Nachricht" + +msgid "Sign Up" +msgstr "Anmelden" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "Social Konto bei %s für diesen Benutzer nicht gefunden." + +msgid "Sorry you are not logged in." +msgstr "Entschuldigen Sie, Sie sind ncht angemeldet!" + +msgid "Sorry, Some error occurred." +msgstr "Entschuldigen Sie, ein Fehler ist passiert." + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "Entschuldigen Sie, die E-Mail %s ist bereits registriert.{0:>s}" + +msgid "Sorry, The user is already registered." +msgstr "Entschuldigen Sie, der Benutzer ist bereits registriert." + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "Entschuldigen Sie, der Benutzername %s ist bereits registriert.{0:>s}" + +msgid "Source Code" +msgstr "Quellcode" + +msgid "Thanks, your settings have been saved." +msgstr "Danke, Ihre Einstellungen sind gespeichert worden." + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "Die URL die Sie verwendet haben ist entweder nicht korrekt oder nicht länger gültig." + +msgid "The URL you tried to use is either incorrect or no longer valid. Enter your details again below to get a new one." +msgstr "Die URL die Sie verwendet haben ist entweder nicht korrekt oder nicht länger gültig. Geben Sie bitte Ihre Details erneut unten ein um eine neue zu erhalten." + +#, python-format +msgid "The account %s is already in use." +msgstr "Das Konto %s ist bereits in Benutzung." + +#, python-format +msgid "The email %s is already registered." +msgstr "Die E-Mail %s ist bereits registriert." + +msgid "The two passwords must match." +msgstr "Passwörter müssen gleich sein." + +#, python-format +msgid "The username %s is already taken. Please choose another.{0:>s}" +msgstr "Der Benutzername %s ist bereits vergeben. Bitte verwenden Sie einen anderen.{0:>s}" + +#, python-format +msgid "The verification email has been resent to %s. Please check your email to activate your account." +msgstr "Die E-Mail-Überprüfung wurde nochmals an %s gesendet. Bitte prüfen Sie Ihre E-Mail um Ihr Konto zu aktivieren" + +#, python-format +msgid "This %s account is already in use." +msgstr "Dieses %s Konto ist bereits in Benutzung." + +msgid "This Twitter account is already in use." +msgstr "Dieses Twitter Konto ist bereits in Benutzung." + +#, python-format +msgid "This Twitter account is not associated with any local account. If you already have a %s Account, you have sign in here or create an account." +msgstr "Dieses Twitter Konto ist mit keinem lokalen Konto verbunden. Wenn Sie bereits ein %s Konto haben, melden Sie sich hier oder legen Sie einen neuen Account an." + +msgid "This authentication method is not yet implemented." +msgstr "Diese Authentifizierungsmethode ist noch nicht implementiert." + +msgid "Twitter association added." +msgstr "Twitter Verbindung hinzugefügt." + +msgid "Unable to update profile. Please try again later." +msgstr "Profil kann nicht aktualisiert werden. Bitte versuchen Sie es später nochmal." + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "Unerwarteter Fehler beim Anlegen des Benutzers %s{0:>s}." + +msgid "Update Profile" +msgstr "Profil aktualisieren" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "Bneutzer ist ausgeloggt, aber es gab einen Fehler bei der Umleitung." + +msgid "Username" +msgstr "Benutzername" + +msgid "Username invalid. Use only letters and numbers." +msgstr "Benutzername ungültig. Bitte nur Buchstaben und Zahlen nutzen." + +msgid "Username or Email" +msgstr "Benutzername oder E-Mail" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "Willkommen %s, Sie sind jetzt eingeloggt in.{0:>s}" + +msgid "Wrong image verification code. Please try again." +msgstr "Falscher Bildprüfungscode. Bitte erneut versuchen." + +msgid "You didn't change your email." +msgstr "Sie haben Ihre E-Mail nicht geändert." + +msgid "You were successfully registered. Please check your email to activate your account." +msgstr "Sie wurden erfolgreich registriert. Bitte prüfen Sie Ihre E-Mail um Ihr Konto zu aktivieren" + +msgid "You've signed out successfully. Warning: Please clear all cookies and logout of OpenId providers too if you logged in on a public computer." +msgstr "Sie haben sich erfolgreich abgemeldet. Hinweis: Bitte löschen Sie alle Cookies und melden Sie sich bei Ihrem OpenID Provider ab, falls Sie an einem öffentlichen Computer angemeldet waren." + +msgid "Your account has been activated. Please sign in to your account." +msgstr "Ihr Konto wurd aktiviert. Bitte melden Sie sich in Ihrem Konto an." + +msgid "Your account has not yet been activated. Please check your email to activate it or" +msgstr "Ihr Konto wurde noch nicht aktiviert. Bitte prüfen Sie Ihre E-Mail um Ihr Konto zu aktivieren oder" + +msgid "Your browser is ancient!" +msgstr "Ihr Browser ist antik!" + +msgid "Your email has been successfully updated." +msgstr "Ihre E-Mail wurde erfolgreich aktualisiert." + +msgid "Your message was sent successfully." +msgstr "Ihre Nachricht wurde erfolgreich versendet." + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "Ihr neuer Benutzername ist %s{0:>s}" + +msgid "Your username or password is incorrect. Please try again (make sure your caps lock is off)" +msgstr "Ihr Benutzername oder Passwort ist ungültig. Bitte versuchen Sie es erneut (stellen Sie sicher, dass Ihre Großschreibtaste aus ist.)" + +msgid "click here" +msgstr "klicken Sie hier" + +msgid "contact us" +msgstr "kontaktieren Sie uns" + +msgid "download the Source Code" +msgstr "den Quellcode herunterladen" + +msgid "for further assistance." +msgstr "für weitere Unterstützung." + +#, fuzzy +msgid "is associated with an account in our records, you will receive an e-mail from us with instructions for resetting your password.
If you don't receive instructions within a minute or two, check your email's spam and junk filters, or " +msgstr "ist mit einem gespeicherten Konto verbunden, Sie werden eine E-Mail von uns erhalten mit den Anweisungen Ihr Passwort zurückzusetzen.
Falls Sie keine E-Mail erhalten, prüfen Sie bitte Ihren Spamordner oder" + +msgid "or just" +msgstr "oder jetzt" + +msgid "to help you to create your application." +msgstr "um Ihnen zu helfen eine Anwendung zu erzeugen" + +msgid "to resend the email." +msgstr "um die E-Mail erneut zu senden." + diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..d519a62 Binary files /dev/null and b/locale/en_US/LC_MESSAGES/messages.mo differ diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po new file mode 100644 index 0000000..760a7b5 --- /dev/null +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -0,0 +1,368 @@ +# English (United States) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-06-21 01:50-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: en_US \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "" + +#, python-format +msgid "%s Password Assistance" +msgstr "" + +#, python-format +msgid "%s association successfully added." +msgstr "" + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "" + +#, python-format +msgid "%s successfully disassociated." +msgstr "" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" + +msgid "Associate account with" +msgstr "" + +msgid "Change password" +msgstr "" + +msgid "Change your email" +msgstr "" + +msgid "Change your password" +msgstr "" + +msgid "Confirm Password" +msgstr "" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "" + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" + +msgid "Contact" +msgstr "" + +msgid "Country" +msgstr "" + +msgid "Current Password" +msgstr "" + +msgid "Edit Profile" +msgstr "" + +msgid "Email" +msgstr "" + +msgid "Enter a new email address" +msgstr "" + +msgid "Enter your" +msgstr "" + +msgid "Error sending the message. Please try again later." +msgstr "" + +msgid "Existing social association" +msgstr "" + +msgid "Federated login is disabled." +msgstr "" + +msgid "Forgot your password?" +msgstr "" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "" + +msgid "If the e-mail address you entered" +msgstr "" + +msgid "If the username you entered" +msgstr "" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" + +msgid "Invalid email address." +msgstr "" + +msgid "Language" +msgstr "" + +msgid "Last Name" +msgstr "" + +msgid "Learn why this Boilerplate Rocks" +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Logout" +msgstr "" + +msgid "Message" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "New Email" +msgstr "" + +msgid "New Password" +msgstr "" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" + +msgid "Or Sign in using:" +msgstr "" + +msgid "Password" +msgstr "" + +msgid "Password changed successfully." +msgstr "" + +msgid "Passwords must match." +msgstr "" + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" + +msgid "Remember me?" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Reset password" +msgstr "" + +msgid "Secure Page" +msgstr "" + +msgid "Send Message" +msgstr "" + +msgid "Sign Up" +msgstr "" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "" + +msgid "Sorry you are not logged in." +msgstr "" + +msgid "Sorry, Some error occurred." +msgstr "" + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "" + +msgid "Sorry, The user is already registered." +msgstr "" + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "" + +msgid "Source Code" +msgstr "" + +msgid "Thanks, your settings have been saved." +msgstr "" + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "" + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" + +#, python-format +msgid "The account %s is already in use." +msgstr "" + +#, python-format +msgid "The email %s is already registered." +msgstr "" + +msgid "The two passwords must match." +msgstr "" + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" + +#, python-format +msgid "This %s account is already in use." +msgstr "" + +msgid "This Twitter account is already in use." +msgstr "" + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" + +msgid "This authentication method is not yet implemented." +msgstr "" + +msgid "Twitter association added." +msgstr "" + +msgid "Unable to update profile. Please try again later." +msgstr "" + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "" + +msgid "Update Profile" +msgstr "" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "" + +msgid "Username" +msgstr "" + +msgid "Username invalid. Use only letters and numbers." +msgstr "" + +msgid "Username or Email" +msgstr "" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "" + +msgid "Wrong image verification code. Please try again." +msgstr "" + +msgid "You didn't change your email." +msgstr "" + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" + +msgid "Your browser is ancient!" +msgstr "" + +msgid "Your email has been successfully updated." +msgstr "" + +msgid "Your message was sent successfully." +msgstr "" + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" + +msgid "click here" +msgstr "" + +msgid "contact us" +msgstr "" + +msgid "download the Source Code" +msgstr "" + +msgid "for further assistance." +msgstr "" + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" + +msgid "or just" +msgstr "" + +msgid "to help you to create your application." +msgstr "" + +msgid "to resend the email." +msgstr "" + diff --git a/locale/es_ES/LC_MESSAGES/messages.mo b/locale/es_ES/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..665c08a Binary files /dev/null and b/locale/es_ES/LC_MESSAGES/messages.mo differ diff --git a/locale/es_ES/LC_MESSAGES/messages.po b/locale/es_ES/LC_MESSAGES/messages.po new file mode 100644 index 0000000..72015a0 --- /dev/null +++ b/locale/es_ES/LC_MESSAGES/messages.po @@ -0,0 +1,427 @@ +# Spanish (Spain) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-06-21 01:50-0400\n" +"Last-Translator: Coto Augosto \n" +"Language-Team: es_ES \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "%s Verificación de cuenta" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "%s Notificación de cambio de Email" + +#, python-format +msgid "%s Password Assistance" +msgstr "%s Asistencia de contraseña" + +#, fuzzy, python-format +msgid "%s association successfully added." +msgstr "%s asociación exitósamente agregada." + +#, fuzzy, python-format +msgid "%s authentication is not yet implemented." +msgstr "La autenticación %s aún no está implementada." + +#, fuzzy, python-format +msgid "%s successfully disassociated." +msgstr "%s exitósamente disociado." + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" +"Actualiza a un navegador " +"diferente\n" +" \to \n" +" \tinstala Google Chrome Frame para experimentar este sitio." + +#, fuzzy +msgid "Associate account with" +msgstr "Asociar cuenta con" + +#, fuzzy +msgid "Change password" +msgstr "Cambiar contraseña" + +msgid "Change your email" +msgstr "Cambiar correo electrónico" + +msgid "Change your password" +msgstr "Cambiar contraseña" + +msgid "Confirm Password" +msgstr "Confirmar contraseña" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "Felicitaciones por tu página potenciada con Google App Engine Boilerplate." + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" +"Felicitaciones, Tu cuenta %s ha sido exitósamente " +"activada.{0:>s}" + +msgid "Contact" +msgstr "Contacto" + +msgid "Country" +msgstr "País" + +msgid "Current Password" +msgstr "Contraseña actual" + +msgid "Edit Profile" +msgstr "Editar Perfil" + +msgid "Email" +msgstr "Email" + +msgid "Enter a new email address" +msgstr "Ingresa una nueva dirección de correo electrónico" + +msgid "Enter your" +msgstr "Ingresa tu" + +msgid "Error sending the message. Please try again later." +msgstr "Error al enviar el mensaje. Por favor, inténtalo de nuevo más tarde" + +#, fuzzy +msgid "Existing social association" +msgstr "Asociación a cuentas sociales existentes" + +msgid "Federated login is disabled." +msgstr "Federated login no está habilitado." + +msgid "Forgot your password?" +msgstr "Olvidaste tu contraseña?" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "Google App Engine Boilerplate es lanzado bajo la" + +msgid "If the e-mail address you entered" +msgstr "Si la dirección de correo electrónico ingresada" + +#, fuzzy +msgid "If the username you entered" +msgstr "Si el nombre de usuario ingresado" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" +"Contraseña incorrecta! Por favor, ingresa tu actual contraseña para " +"cambiar tu configuraciones de cuenta." + +msgid "Invalid email address." +msgstr "Dirección de correo electrónico no válida." + +msgid "Language" +msgstr "Idioma" + +msgid "Last Name" +msgstr "Apellido" + +msgid "Learn why this Boilerplate Rocks" +msgstr "Aprende porque este Boilerplate es asombroso" + +msgid "Login" +msgstr "Login" + +msgid "Logout" +msgstr "Cerrar sesión" + +msgid "Message" +msgstr "Mensaje" + +msgid "Name" +msgstr "Nombre" + +#, fuzzy +msgid "New Email" +msgstr "Nuevo Email" + +msgid "New Password" +msgstr "Nueva Contraseña" + +#, fuzzy, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" +"No se recibió información de autenticación de %s. Por favor asegurate de " +"haber iniciado sesión en un proveedor OpenID autorizado." + +msgid "Or Sign in using:" +msgstr "O inicia sesión usando:" + +msgid "Password" +msgstr "Contraseña" + +#, fuzzy +msgid "Password changed successfully." +msgstr "Contraseña cambiada exitósamente." + +msgid "Passwords must match." +msgstr "Las contraseñas deben coincidir." + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" +"Por favor revisa tu email para confirmar. Tu nuevo email será actializado" +" después de la confirmación." + +msgid "Remember me?" +msgstr "Recordarme?" + +msgid "Remove" +msgstr "Remover" + +msgid "Reset password" +msgstr "Reiniciar contraseña" + +msgid "Secure Page" +msgstr "Página segura" + +msgid "Send Message" +msgstr "Enviar Mensaje" + +msgid "Sign Up" +msgstr "Registrar" + +#, fuzzy, python-format +msgid "Social account on %s not found for this user." +msgstr "Cuenta de %s no encontrada para este usuario." + +#, fuzzy +msgid "Sorry you are not logged in." +msgstr "Lo sentimos, no has iniciado sessión" + +#, fuzzy +msgid "Sorry, Some error occurred." +msgstr "Lo sentimos, ha ocurrido un error." + +#, fuzzy, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "Lo sentimos, El email %s ya está registrado.{0:>s}" + +#, fuzzy +msgid "Sorry, The user is already registered." +msgstr "Lo sentimos, El usuario ya está registrado." + +#, fuzzy, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "Lo sentimos, El nombre de usuario %s ya está registrado.{0:>s}" + +msgid "Source Code" +msgstr "Código fuente" + +msgid "Thanks, your settings have been saved." +msgstr "Gracias, tus configuraciones han sido almacenadas." + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "La URL que has utlizado es incorrecta o ya no es válida." + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" +"La URL que has utlizado es incorrecta o ya no es válida. Ingrese " +"tus datos nuevamente a continuación para obtener otra." + +#, fuzzy, python-format +msgid "The account %s is already in use." +msgstr "Esta cuenta %s ya está en uso." + +#, fuzzy, python-format +msgid "The email %s is already registered." +msgstr "El email %s ya está registrado." + +#, fuzzy +msgid "The two passwords must match." +msgstr "Las contraseñas deben coincidir." + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" +"El nombre de usuarios %s ya está registrado. Por favor elige " +"otro.{0:>s}" + +#, fuzzy, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" +"El email de verificación ha sido reenviado a %s. Por favor, revisa tu email para " +"activar tu cuenta" + +#, fuzzy, python-format +msgid "This %s account is already in use." +msgstr "Esta cuenta %s ya está en uso." + +#, fuzzy +msgid "This Twitter account is already in use." +msgstr "Esta cuenta de Twitter ya está en uso." + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" +"Esta cuenta de Twitter no está asociada con ninguna cuanta local. Si " +"ya tienes una cuanta en %s, debes iniciar sesión aquí " +"o crear una cuenta." + + +#, fuzzy +msgid "This authentication method is not yet implemented." +msgstr "Este método de autenticación aún no está implementado." + +#, fuzzy +msgid "Twitter association added." +msgstr "Asociación Twitter agregada." + +#, fuzzy +msgid "Unable to update profile. Please try again later." +msgstr "No es posible actualizar el perfil de usuario. Por favor intenete más tarde." + +#, fuzzy, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "Error inesperado al crear el usuario %s{0:>s}." + +#, fuzzy +msgid "Update Profile" +msgstr "Actualizar perfil de usuario" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "El usuario ha sido desconectado, pero hubo un error en la redirección." + +msgid "Username" +msgstr "Nombre de usuario" + +msgid "Username invalid. Use only letters and numbers." +msgstr "Nombre de usuario no válido. Usa sólo letras y números." + +msgid "Username or Email" +msgstr "Nombre de Usuario o Email" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "Bienvenido %s. has iniciado sesión.{0:>s}" + +msgid "Wrong image verification code. Please try again." +msgstr "Código de verificación de imagen incorrecto. Inténtalo nuevamente." + +#, fuzzy +msgid "You didn't change your email." +msgstr "No cambiaste tu correo electrónico." + +#, fuzzy +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" +"Has sido exitósamente registrado. Por favor revisa tu email para activar tu cuenta." + +#, fuzzy +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" +"Has sido desconectado exitósamente. Advertencia: Por favor, además borra todas las cookies y " +"cierra la sesión los proveedores OpenID si iniciaste sesión en un computador público." + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" +"Tu cuenta ha sido activada, Por favor inicia sesión " +" en tu cuenta." + +#, fuzzy +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" +"Tu cuenta aún no ha sido activada. Por favor revisa tu email para " +"activarla or" + +msgid "Your browser is ancient!" +msgstr "Tu Navegador es demasiado antiguo!" + +#, fuzzy +msgid "Your email has been successfully updated." +msgstr "Correo electrónico exitósamente actualizado." + +#, fuzzy +msgid "Your message was sent successfully." +msgstr "Tu mensaje fue enviado exitósamente." + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "Tu nuevo nombre de usuario is %s{0:>s}" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" +"Tu nombre de usuario o contraseña es incorrecto. Por favor, intetenta nuevamanente " +"(asegúrate que las mayúsculas no están activadas)" + +msgid "click here" +msgstr "presiona aquí" + +#, fuzzy +msgid "contact us" +msgstr "contáctanos" + +msgid "download the Source Code" +msgstr "descarga el código fuente" + +msgid "for further assistance." +msgstr "para obtener más ayuda." + +#, fuzzy +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" +"está asociado a una cuenta en nuestros registros, recibirás un e-mail de " +"nosotros con instrucciones para restablecer tu contraseña.
Si no " +"recibes este e-mail dentro de uno o dos minutos, revisa tu carpeta de " +"correo basura, o " + +msgid "or just" +msgstr "o sólo" + +msgid "to help you to create your application." +msgstr "para ayudar a crear tu aplicación." + +#, fuzzy +msgid "to resend the email." +msgstr "para reeenviar el email." + diff --git a/locale/fr_FR/LC_MESSAGES/messages.mo b/locale/fr_FR/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..000fff9 Binary files /dev/null and b/locale/fr_FR/LC_MESSAGES/messages.mo differ diff --git a/locale/fr_FR/LC_MESSAGES/messages.po b/locale/fr_FR/LC_MESSAGES/messages.po new file mode 100644 index 0000000..ecb330b --- /dev/null +++ b/locale/fr_FR/LC_MESSAGES/messages.po @@ -0,0 +1,368 @@ +# French (France) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-07-19 12:53-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: fr_FR \n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "" + +#, python-format +msgid "%s Password Assistance" +msgstr "" + +#, python-format +msgid "%s association successfully added." +msgstr "" + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "" + +#, python-format +msgid "%s successfully disassociated." +msgstr "" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" + +msgid "Associate account with" +msgstr "" + +msgid "Change password" +msgstr "" + +msgid "Change your email" +msgstr "" + +msgid "Change your password" +msgstr "" + +msgid "Confirm Password" +msgstr "" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "" + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" + +msgid "Contact" +msgstr "" + +msgid "Country" +msgstr "" + +msgid "Current Password" +msgstr "" + +msgid "Edit Profile" +msgstr "" + +msgid "Email" +msgstr "" + +msgid "Enter a new email address" +msgstr "" + +msgid "Enter your" +msgstr "" + +msgid "Error sending the message. Please try again later." +msgstr "" + +msgid "Existing social association" +msgstr "" + +msgid "Federated login is disabled." +msgstr "" + +msgid "Forgot your password?" +msgstr "" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "" + +msgid "If the e-mail address you entered" +msgstr "" + +msgid "If the username you entered" +msgstr "" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" + +msgid "Invalid email address." +msgstr "" + +msgid "Language" +msgstr "" + +msgid "Last Name" +msgstr "" + +msgid "Learn why this Boilerplate Rocks" +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Logout" +msgstr "" + +msgid "Message" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "New Email" +msgstr "" + +msgid "New Password" +msgstr "" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" + +msgid "Or Sign in using:" +msgstr "" + +msgid "Password" +msgstr "" + +msgid "Password changed successfully." +msgstr "" + +msgid "Passwords must match." +msgstr "" + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" + +msgid "Remember me?" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Reset password" +msgstr "" + +msgid "Secure Page" +msgstr "" + +msgid "Send Message" +msgstr "" + +msgid "Sign Up" +msgstr "" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "" + +msgid "Sorry you are not logged in." +msgstr "" + +msgid "Sorry, Some error occurred." +msgstr "" + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "" + +msgid "Sorry, The user is already registered." +msgstr "" + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "" + +msgid "Source Code" +msgstr "" + +msgid "Thanks, your settings have been saved." +msgstr "" + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "" + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" + +#, python-format +msgid "The account %s is already in use." +msgstr "" + +#, python-format +msgid "The email %s is already registered." +msgstr "" + +msgid "The two passwords must match." +msgstr "" + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" + +#, python-format +msgid "This %s account is already in use." +msgstr "" + +msgid "This Twitter account is already in use." +msgstr "" + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" + +msgid "This authentication method is not yet implemented." +msgstr "" + +msgid "Twitter association added." +msgstr "" + +msgid "Unable to update profile. Please try again later." +msgstr "" + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "" + +msgid "Update Profile" +msgstr "" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "" + +msgid "Username" +msgstr "" + +msgid "Username invalid. Use only letters and numbers." +msgstr "" + +msgid "Username or Email" +msgstr "" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "" + +msgid "Wrong image verification code. Please try again." +msgstr "" + +msgid "You didn't change your email." +msgstr "" + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" + +msgid "Your browser is ancient!" +msgstr "" + +msgid "Your email has been successfully updated." +msgstr "" + +msgid "Your message was sent successfully." +msgstr "" + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" + +msgid "click here" +msgstr "" + +msgid "contact us" +msgstr "" + +msgid "download the Source Code" +msgstr "" + +msgid "for further assistance." +msgstr "" + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" + +msgid "or just" +msgstr "" + +msgid "to help you to create your application." +msgstr "" + +msgid "to resend the email." +msgstr "" + diff --git a/locale/id_ID/LC_MESSAGES/messages.mo b/locale/id_ID/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..cd7de1f Binary files /dev/null and b/locale/id_ID/LC_MESSAGES/messages.mo differ diff --git a/locale/id_ID/LC_MESSAGES/messages.po b/locale/id_ID/LC_MESSAGES/messages.po new file mode 100644 index 0000000..1d6e7f3 --- /dev/null +++ b/locale/id_ID/LC_MESSAGES/messages.po @@ -0,0 +1,373 @@ +# Indonesian (Indonesia) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-06-21 01:51-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: id_ID \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "" + +#, python-format +msgid "%s Password Assistance" +msgstr "" + +#, python-format +msgid "%s association successfully added." +msgstr "" + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "" + +#, python-format +msgid "%s successfully disassociated." +msgstr "" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" + +msgid "Associate account with" +msgstr "" + +#, fuzzy +msgid "Change password" +msgstr "Ubah password" + +msgid "Change your email" +msgstr "Ubah email" + +msgid "Change your password" +msgstr "Ubah password" + +msgid "Confirm Password" +msgstr "Ulangi password" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "Selamat atas halaman yang di-support oleh Google App Engine Boilerplate." + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" + +msgid "Contact" +msgstr "Kontak" + +msgid "Country" +msgstr "Negara" + +msgid "Current Password" +msgstr "Password saat ini" + +msgid "Edit Profile" +msgstr "Edit Profil" + +msgid "Email" +msgstr "Email" + +msgid "Enter a new email address" +msgstr "" + +msgid "Enter your" +msgstr "Masukkan" + +msgid "Error sending the message. Please try again later." +msgstr "" + +msgid "Existing social association" +msgstr "" + +msgid "Federated login is disabled." +msgstr "" + +msgid "Forgot your password?" +msgstr "Lupa password?" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "" + +msgid "If the e-mail address you entered" +msgstr "" + +msgid "If the username you entered" +msgstr "" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" + +msgid "Invalid email address." +msgstr "" + +msgid "Language" +msgstr "Bahasa" + +msgid "Last Name" +msgstr "Name Akhir" + +msgid "Learn why this Boilerplate Rocks" +msgstr "Cari tahu kenapa Boilerplate keren" + +msgid "Login" +msgstr "Masuk" + +msgid "Logout" +msgstr "Keluar" + +msgid "Message" +msgstr "Pesan" + +msgid "Name" +msgstr "Nama" + +#, fuzzy +msgid "New Email" +msgstr "Ubah email" + +msgid "New Password" +msgstr "Password baru" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" + +msgid "Or Sign in using:" +msgstr "" + +msgid "Password" +msgstr "Password" + +msgid "Password changed successfully." +msgstr "" + +msgid "Passwords must match." +msgstr "" + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" + +msgid "Remember me?" +msgstr "Ingin diingat?" + +msgid "Remove" +msgstr "" + +msgid "Reset password" +msgstr "Reset password" + +msgid "Secure Page" +msgstr "Halaman aman" + +msgid "Send Message" +msgstr "Kirim pesan" + +msgid "Sign Up" +msgstr "Daftar" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "" + +msgid "Sorry you are not logged in." +msgstr "" + +msgid "Sorry, Some error occurred." +msgstr "" + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "" + +msgid "Sorry, The user is already registered." +msgstr "" + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "" + +msgid "Source Code" +msgstr "Kode sumber" + +msgid "Thanks, your settings have been saved." +msgstr "" + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "" + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" + +#, python-format +msgid "The account %s is already in use." +msgstr "" + +#, python-format +msgid "The email %s is already registered." +msgstr "" + +msgid "The two passwords must match." +msgstr "" + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" + +#, python-format +msgid "This %s account is already in use." +msgstr "" + +msgid "This Twitter account is already in use." +msgstr "" + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" + +msgid "This authentication method is not yet implemented." +msgstr "" + +msgid "Twitter association added." +msgstr "" + +msgid "Unable to update profile. Please try again later." +msgstr "" + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "" + +#, fuzzy +msgid "Update Profile" +msgstr "Edit Profil" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "" + +msgid "Username" +msgstr "Nama Pengguna" + +msgid "Username invalid. Use only letters and numbers." +msgstr "" + +msgid "Username or Email" +msgstr "Nama pengguna atau email" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "" + +msgid "Wrong image verification code. Please try again." +msgstr "" + +#, fuzzy +msgid "You didn't change your email." +msgstr "Ubah email" + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" + +msgid "Your browser is ancient!" +msgstr "Browser kamu sudah ketinggalan zaman!" + +msgid "Your email has been successfully updated." +msgstr "" + +msgid "Your message was sent successfully." +msgstr "" + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" + +msgid "click here" +msgstr "" + +#, fuzzy +msgid "contact us" +msgstr "Kontak" + +msgid "download the Source Code" +msgstr "unduh kode sumber" + +msgid "for further assistance." +msgstr "" + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" + +msgid "or just" +msgstr "atau" + +msgid "to help you to create your application." +msgstr "saja untuk membuat aplikasi sendiri." + +msgid "to resend the email." +msgstr "" + diff --git a/locale/it_IT/LC_MESSAGES/messages.mo b/locale/it_IT/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..1e7b7e8 Binary files /dev/null and b/locale/it_IT/LC_MESSAGES/messages.mo differ diff --git a/locale/it_IT/LC_MESSAGES/messages.po b/locale/it_IT/LC_MESSAGES/messages.po new file mode 100644 index 0000000..c17301e --- /dev/null +++ b/locale/it_IT/LC_MESSAGES/messages.po @@ -0,0 +1,375 @@ +# Italian (Italy) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-06-21 01:51-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: it_IT \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "" + +#, python-format +msgid "%s Password Assistance" +msgstr "" + +#, python-format +msgid "%s association successfully added." +msgstr "" + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "" + +#, python-format +msgid "%s successfully disassociated." +msgstr "" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" + +msgid "Associate account with" +msgstr "" + +#, fuzzy +msgid "Change password" +msgstr "Cambia la tua password" + +msgid "Change your email" +msgstr "Cambia il tuo email" + +msgid "Change your password" +msgstr "Cambia la tua password" + +msgid "Confirm Password" +msgstr "Conferma la Password" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "" +"Congratulationi, hai creato questa pagina con Google App Engine " +"Boilerplate." + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" + +msgid "Contact" +msgstr "Contattare" + +msgid "Country" +msgstr "Stato" + +msgid "Current Password" +msgstr "Password attuale" + +msgid "Edit Profile" +msgstr "Modifica Profilo" + +msgid "Email" +msgstr "Email" + +msgid "Enter a new email address" +msgstr "" + +msgid "Enter your" +msgstr "Inserire" + +msgid "Error sending the message. Please try again later." +msgstr "" + +msgid "Existing social association" +msgstr "" + +msgid "Federated login is disabled." +msgstr "" + +msgid "Forgot your password?" +msgstr "Password dimenticata?" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "" + +msgid "If the e-mail address you entered" +msgstr "" + +msgid "If the username you entered" +msgstr "" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" + +msgid "Invalid email address." +msgstr "" + +msgid "Language" +msgstr "Lingua" + +msgid "Last Name" +msgstr "Cognome" + +msgid "Learn why this Boilerplate Rocks" +msgstr "Puoi sapere perché questo Boilerplate è sorprendente" + +msgid "Login" +msgstr "Entra" + +msgid "Logout" +msgstr "Esci" + +msgid "Message" +msgstr "Messaggio" + +msgid "Name" +msgstr "Nome" + +#, fuzzy +msgid "New Email" +msgstr "Cambia il tuo email" + +msgid "New Password" +msgstr "Nuova Password" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" + +msgid "Or Sign in using:" +msgstr "Oppure Accedi utilizzando:" + +msgid "Password" +msgstr "Password" + +msgid "Password changed successfully." +msgstr "" + +msgid "Passwords must match." +msgstr "" + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" + +msgid "Remember me?" +msgstr "Resta connesso" + +msgid "Remove" +msgstr "" + +msgid "Reset password" +msgstr "Reset password" + +msgid "Secure Page" +msgstr "Pagina d'accesso" + +msgid "Send Message" +msgstr "Inviare il messaggio" + +msgid "Sign Up" +msgstr "Crea Utente" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "" + +msgid "Sorry you are not logged in." +msgstr "" + +msgid "Sorry, Some error occurred." +msgstr "" + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "" + +msgid "Sorry, The user is already registered." +msgstr "" + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "" + +msgid "Source Code" +msgstr "Codice sorgente" + +msgid "Thanks, your settings have been saved." +msgstr "" + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "" + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" + +#, python-format +msgid "The account %s is already in use." +msgstr "" + +#, python-format +msgid "The email %s is already registered." +msgstr "" + +msgid "The two passwords must match." +msgstr "" + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" + +#, python-format +msgid "This %s account is already in use." +msgstr "" + +msgid "This Twitter account is already in use." +msgstr "" + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" + +msgid "This authentication method is not yet implemented." +msgstr "" + +msgid "Twitter association added." +msgstr "" + +msgid "Unable to update profile. Please try again later." +msgstr "" + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "" + +#, fuzzy +msgid "Update Profile" +msgstr "Modifica Profilo" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "" + +msgid "Username" +msgstr "Nome utente" + +msgid "Username invalid. Use only letters and numbers." +msgstr "" + +msgid "Username or Email" +msgstr "Username o Email" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "" + +msgid "Wrong image verification code. Please try again." +msgstr "" + +#, fuzzy +msgid "You didn't change your email." +msgstr "Cambia il tuo email" + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" + +msgid "Your browser is ancient!" +msgstr "Il tuo browser è obsoleto!" + +msgid "Your email has been successfully updated." +msgstr "" + +msgid "Your message was sent successfully." +msgstr "" + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" + +msgid "click here" +msgstr "" + +#, fuzzy +msgid "contact us" +msgstr "Contattare" + +msgid "download the Source Code" +msgstr "scaricare il codice sorgente" + +msgid "for further assistance." +msgstr "" + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" + +msgid "or just" +msgstr "o semplicemente" + +msgid "to help you to create your application." +msgstr "per iniziare a creare la tua applicazione." + +msgid "to resend the email." +msgstr "" + diff --git a/locale/messages.pot b/locale/messages.pot new file mode 100644 index 0000000..631dbbb --- /dev/null +++ b/locale/messages.pot @@ -0,0 +1,348 @@ +#, python-format +msgid "%s Account Verification" +msgstr "" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "" + +#, python-format +msgid "%s Password Assistance" +msgstr "" + +#, python-format +msgid "%s association successfully added." +msgstr "" + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "" + +#, python-format +msgid "%s successfully disassociated." +msgstr "" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" + +msgid "Associate account with" +msgstr "" + +msgid "Change password" +msgstr "" + +msgid "Change your email" +msgstr "" + +msgid "Change your password" +msgstr "" + +msgid "Confirm Password" +msgstr "" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "" + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" + +msgid "Contact" +msgstr "" + +msgid "Country" +msgstr "" + +msgid "Current Password" +msgstr "" + +msgid "Edit Profile" +msgstr "" + +msgid "Email" +msgstr "" + +msgid "Enter a new email address" +msgstr "" + +msgid "Enter your" +msgstr "" + +msgid "Error sending the message. Please try again later." +msgstr "" + +msgid "Existing social association" +msgstr "" + +msgid "Federated login is disabled." +msgstr "" + +msgid "Forgot your password?" +msgstr "" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "" + +msgid "If the e-mail address you entered" +msgstr "" + +msgid "If the username you entered" +msgstr "" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" + +msgid "Invalid email address." +msgstr "" + +msgid "Language" +msgstr "" + +msgid "Last Name" +msgstr "" + +msgid "Learn why this Boilerplate Rocks" +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Logout" +msgstr "" + +msgid "Message" +msgstr "" + +msgid "Name" +msgstr "" + +msgid "New Email" +msgstr "" + +msgid "New Password" +msgstr "" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" + +msgid "Or Sign in using:" +msgstr "" + +msgid "Password" +msgstr "" + +msgid "Password changed successfully." +msgstr "" + +msgid "Passwords must match." +msgstr "" + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" + +msgid "Remember me?" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Reset password" +msgstr "" + +msgid "Secure Page" +msgstr "" + +msgid "Send Message" +msgstr "" + +msgid "Sign Up" +msgstr "" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "" + +msgid "Sorry you are not logged in." +msgstr "" + +msgid "Sorry, Some error occurred." +msgstr "" + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "" + +msgid "Sorry, The user is already registered." +msgstr "" + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "" + +msgid "Source Code" +msgstr "" + +msgid "Thanks, your settings have been saved." +msgstr "" + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "" + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" + +#, python-format +msgid "The account %s is already in use." +msgstr "" + +#, python-format +msgid "The email %s is already registered." +msgstr "" + +msgid "The two passwords must match." +msgstr "" + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" + +#, python-format +msgid "This %s account is already in use." +msgstr "" + +msgid "This Twitter account is already in use." +msgstr "" + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" + +msgid "This authentication method is not yet implemented." +msgstr "" + +msgid "Twitter association added." +msgstr "" + +msgid "Unable to update profile. Please try again later." +msgstr "" + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "" + +msgid "Update Profile" +msgstr "" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "" + +msgid "Username" +msgstr "" + +msgid "Username invalid. Use only letters and numbers." +msgstr "" + +msgid "Username or Email" +msgstr "" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "" + +msgid "Wrong image verification code. Please try again." +msgstr "" + +msgid "You didn't change your email." +msgstr "" + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" + +msgid "Your browser is ancient!" +msgstr "" + +msgid "Your email has been successfully updated." +msgstr "" + +msgid "Your message was sent successfully." +msgstr "" + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" + +msgid "click here" +msgstr "" + +msgid "contact us" +msgstr "" + +msgid "download the Source Code" +msgstr "" + +msgid "for further assistance." +msgstr "" + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" + +msgid "or just" +msgstr "" + +msgid "to help you to create your application." +msgstr "" + +msgid "to resend the email." +msgstr "" + diff --git a/locale/pt_BR/LC_MESSAGES/messages.mo b/locale/pt_BR/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..d6e1222 Binary files /dev/null and b/locale/pt_BR/LC_MESSAGES/messages.mo differ diff --git a/locale/pt_BR/LC_MESSAGES/messages.po b/locale/pt_BR/LC_MESSAGES/messages.po new file mode 100644 index 0000000..4dea0e1 --- /dev/null +++ b/locale/pt_BR/LC_MESSAGES/messages.po @@ -0,0 +1,398 @@ +# Portugues (Brasil) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-06-21 01:50-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: en_US \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "%s Verificação de Conta" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "%s Notificação de Email Alterado" + +#, python-format +msgid "%s Password Assistance" +msgstr "%s Ajuda de Senha" + +#, python-format +msgid "%s association successfully added." +msgstr "%s associação adicionada com sucesso." + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "%s autenticação ainda não está implementada." + +#, python-format +msgid "%s successfully disassociated." +msgstr "%s desassociação com sucesso." + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" +"Atualize para um browser diferente\n" +" \tou \n" +" \tisntale Google Chrome Frame para usar este site." + +msgid "Associate account with" +msgstr "Associar conta com" + +msgid "Change password" +msgstr "Alterar senha" + +msgid "Change your email" +msgstr "Alterar seu email" + +msgid "Change your password" +msgstr "Alterar sua senha" + +msgid "Confirm Password" +msgstr "Confirme a Senha" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "Parabéns pela sua página movida a Google App Engine Boilerplate." + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "Parabéns, sua conta %s foi ativada com sucesso.{0:>s}" + +msgid "Contact" +msgstr "Contato" + +msgid "Country" +msgstr "País" + +msgid "Current Password" +msgstr "Senha Atual" + +msgid "Edit Profile" +msgstr "Editar perfil" + +msgid "Email" +msgstr "Email" + +msgid "Enter a new email address" +msgstr "Informe um novo endereço de email" + +msgid "Enter your" +msgstr "Etnre seu" + +msgid "Error sending the message. Please try again later." +msgstr "Erro ao enviar mensagem. Por favor, tente novamente mais tarde." + +msgid "Existing social association" +msgstr "Associação social existente" + +msgid "Federated login is disabled." +msgstr "Login federado está desabilitado." + +msgid "Forgot your password?" +msgstr "Esqueceu sua senha?" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "Googla App Engine Boilerplate foi lançado sob a" + +msgid "If the e-mail address you entered" +msgstr "Se o endereço de email informado" + +msgid "If the username you entered" +msgstr "Se o username informado" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" +"Senha inválida! Por favor informe sua senha atual para alterar as " +"preferências da conta." + +msgid "Invalid email address." +msgstr "Endereço de email inválido" + +msgid "Language" +msgstr "Idioma" + +msgid "Last Name" +msgstr "Sobrenome" + +msgid "Learn why this Boilerplate Rocks" +msgstr "Entenda por que esse Boilerplate detona" + +msgid "Login" +msgstr "Login" + +msgid "Logout" +msgstr "Logout" + +msgid "Message" +msgstr "Message" + +msgid "Name" +msgstr "Nome" + +msgid "New Email" +msgstr "Novo Email" + +msgid "New Password" +msgstr "Nova Senha" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" +"Nenhuma informação de autenticação de usuário recebida de %s. Por favor verifique " +"se você está se logando a partir de um provedor OpenID autorizado." + +msgid "Or Sign in using:" +msgstr "Ou entre usando:" + +msgid "Password" +msgstr "Senha" + +msgid "Password changed successfully." +msgstr "Senha atualizada com sucesso." + +msgid "Passwords must match." +msgstr "Senhas devem coincidir." + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" +"Por favor verifique seu novo email de confirmação. Seu email será atualizado " +"depois da confirmação." + +msgid "Remember me?" +msgstr "Lembra-se de mim?" + +msgid "Remove" +msgstr "Remover" + +msgid "Reset password" +msgstr "Resetar senha" + +msgid "Secure Page" +msgstr "Página Segura" + +msgid "Send Message" +msgstr "Enviar Mensagem" + +msgid "Sign Up" +msgstr "Se Cadastrar" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "Conta social em %s não encontrada para este usuário." + +msgid "Sorry you are not logged in." +msgstr "Desculpe mas você não está logado." + +msgid "Sorry, Some error occurred." +msgstr "Desculpe, aconteceu um erro." + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "Desculpe, o email %s já foi registrado.{0:>s}" + +msgid "Sorry, The user is already registered." +msgstr "Desculpe, este usuário já foi registrado." + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "Desculpe, o usuário %s já foi registrado.{0:>s}" + +msgid "Source Code" +msgstr "Código Fonte" + +msgid "Thanks, your settings have been saved." +msgstr "Obrigado, suas configurações foram salvas." + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "A URL que você tentou acessar está incorreta ou não é mais válida." + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" +"A URL que você tentou acessar está incorreta ou não é mais válida. Informe " +"seus detalhes abaixo novamente para conseguir uma nova." + +#, python-format +msgid "The account %s is already in use." +msgstr "A conta %s já está em uso." + +#, python-format +msgid "The email %s is already registered." +msgstr "O email %s já foi registrado." + +msgid "The two passwords must match." +msgstr "As duas senhas devem coincidir." + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" +"O username %s já foi usado. Por favor escolha outro.{0:>s}" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" +"O email de verificação foi reenviado %s. Por favor verifique seus emails para " +"ativar a sua conta." + +#, python-format +msgid "This %s account is already in use." +msgstr "Esta conta %s já está em uso." + +msgid "This Twitter account is already in use." +msgstr "Esta conta do Twitter já está em uso." + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" +"Esta conta do Twitter não está associada com nenhuma conta local. Se você " +"já tem a conta %s, você tem que entrar aqui " +"ou criar uma nova conta." + +msgid "This authentication method is not yet implemented." +msgstr "Este método de autenticação ainda não foi implementado." + +msgid "Twitter association added." +msgstr "Associação com Twitter adicionada." + +msgid "Unable to update profile. Please try again later." +msgstr "Impossível atualizar perfil. Por favor tente novamente mais tarde." + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "Erro inexperado ao criar usuário %s{0:>s}." + +msgid "Update Profile" +msgstr "Atualizar Perfil" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "Usuário não está logado, porém houve um erro de redirecionamento." + +msgid "Username" +msgstr "Usuário" + +msgid "Username invalid. Use only letters and numbers." +msgstr "Usuário inválido. Use somente letras e números." + +msgid "Username or Email" +msgstr "Usuário e Email" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "Bem vindo %s, você está logado.{0:>s}" + +msgid "Wrong image verification code. Please try again." +msgstr "Código de verificação de imagem inválido. Por favor tente novamente." + +msgid "You didn't change your email." +msgstr "Você não alterou seu email." + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" +"Você foi registrado com sucesso. Por favor check seu email para ativar " +"ativar sua conta." + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" +"Seu registro foi cancelado com sucesso. Aviso: Por favor limpe todos os cookies " +" e faça logout dos provedores OpenID também se estiver logado através de " +"um computador público." + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" +"Sua conta foi ativada. Por favor entre na sua conta." + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" +"Sua conta ainda não foi ativada. Por favor verifique seu email para " +"ativação ou" + +msgid "Your browser is ancient!" +msgstr "Seu browser é pré-histórico!" + +msgid "Your email has been successfully updated." +msgstr "Seu email foi atualizado com sucesso." + +msgid "Your message was sent successfully." +msgstr "Sua mensagem foi enviada com sucesso." + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "Seu usuário é %s{0:>s}" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" +"Seu usuário ou senha está incorreto. Por favor tente novamente (verifique se " +"o caps lock está habilitado)" + +msgid "click here" +msgstr "clique aqui" + +msgid "contact us" +msgstr "contate-nos" + +msgid "download the Source Code" +msgstr "baixar o Código Fonte" + +msgid "for further assistance." +msgstr "para mais detalhes." + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" +"está associado com uma conta em nossos registros, você receberá um e-mail " +"com instruções de como resetar sua senha.
Se não receber as instruções em " +"alguns minutos, verifique sua caixa de spam, ou " + +msgid "or just" +msgstr "ou simplesmente" + +msgid "to help you to create your application." +msgstr "para ajuda-lo a criar sua aplicação." + +msgid "to resend the email." +msgstr "para reenviar o email." + diff --git a/locale/ru_RU/LC_MESSAGES/messages.mo b/locale/ru_RU/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..c514b6b Binary files /dev/null and b/locale/ru_RU/LC_MESSAGES/messages.mo differ diff --git a/locale/ru_RU/LC_MESSAGES/messages.po b/locale/ru_RU/LC_MESSAGES/messages.po new file mode 100644 index 0000000..68cb300 --- /dev/null +++ b/locale/ru_RU/LC_MESSAGES/messages.po @@ -0,0 +1,168 @@ +# Translations template for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Alexander Mitkalev , 2012. +msgid "" +msgstr "" +"Project-Id-Version: Google App Engine Boilerplate\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-10-20 13:45+0400\n" +"PO-Revision-Date: 2012-10-20 22:18+0400\n" +"Last-Translator: iROOT \n" +"Language-Team: Russian (Russia) (http://www.transifex.com/projects/p/google-" +"app-engine-boilerplate/language/ru_RU/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ru_RU\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" +"Перейдите на другой браузер\\n\n" +" \\tили " +"\\n\n" +" \\tустановите Google Chrome Frame чтобы испытать этот сайт." + +msgid "Associate account with" +msgstr "Связать аккаунт с" + +msgid "Change password" +msgstr "Изменить пароль" + +msgid "Change your email" +msgstr "Изменить E-Mail" + +msgid "Change your password" +msgstr "Изменить пароль" + +msgid "Confirm Password" +msgstr "Подтвердите пароль" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "Поздравляем Вас с установкой Google App Engine Boilerplate." + +msgid "Contact" +msgstr "Обратная связь" + +msgid "Country" +msgstr "Страна" + +msgid "Current Password" +msgstr "Текущий пароль" + +msgid "Edit Profile" +msgstr "Редактировать профиль" + +msgid "Email" +msgstr "E-Mail" + +msgid "Enter a new email address" +msgstr "Введите новый адрес электронной почты" + +msgid "Enter your" +msgstr "Укажите" + +msgid "Existing social association" +msgstr "Существующие связанные социальные сервисы" + +msgid "Forgot your password?" +msgstr "Забыли пароль?" + +msgid "Good Password" +msgstr "Хороший пароль" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "Google App Engine Boilerplate выпущен под" + +msgid "Insecure Password" +msgstr "Небезопасный пароль" + +msgid "Language" +msgstr "Язык" + +msgid "Last Name" +msgstr "Фамилия" + +msgid "Learn why this Boilerplate Rocks" +msgstr "Узнайте о Boilerplate Rocks" + +msgid "Login" +msgstr "Войти" + +msgid "Logout" +msgstr "Выйти" + +msgid "Message" +msgstr "Сообщение" + +msgid "Name" +msgstr "Имя" + +msgid "New Email" +msgstr "Новый E-Mail" + +msgid "New Password" +msgstr "Новый пароль" + +msgid "Or Sign in using:" +msgstr "Или войти с помощью:" + +msgid "Password" +msgstr "Пароль" + +msgid "Remember me?" +msgstr "Запомнить меня?" + +msgid "Remove" +msgstr "Удалить" + +msgid "Reset password" +msgstr "Сбросить пароль" + +msgid "Secure Page" +msgstr "Безопасная страница" + +msgid "Secure Password" +msgstr "Безопасный пароль" + +msgid "Send Message" +msgstr "Отправить сообщение" + +msgid "Short Password" +msgstr "Короткий пароль" + +msgid "Sign Up" +msgstr "Регистрация" + +msgid "Source Code" +msgstr "Исходный код" + +msgid "Update Profile" +msgstr "Обновить профиль" + +msgid "Username" +msgstr "Имя пользователя" + +msgid "Username or Email" +msgstr "Имя пользователя или E-Mail" + +msgid "Your browser is ancient!" +msgstr "Ваш браузер устарел!" + +msgid "download the Source Code" +msgstr "скачайте исходный код" + +msgid "or just" +msgstr "или просто" + +msgid "to help you to create your application." +msgstr "чтобы помочь вам создать свое приложение." diff --git a/locale/zh_CN/LC_MESSAGES/messages.mo b/locale/zh_CN/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..de3db6d Binary files /dev/null and b/locale/zh_CN/LC_MESSAGES/messages.mo differ diff --git a/locale/zh_CN/LC_MESSAGES/messages.po b/locale/zh_CN/LC_MESSAGES/messages.po new file mode 100644 index 0000000..4c64d0f --- /dev/null +++ b/locale/zh_CN/LC_MESSAGES/messages.po @@ -0,0 +1,373 @@ +# Chinese (China) translations for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2012. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2012-08-12 16:24-0400\n" +"PO-Revision-Date: 2012-06-21 01:51-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#, python-format +msgid "%s Account Verification" +msgstr "" + +#, python-format +msgid "%s Email Changed Notification" +msgstr "" + +#, python-format +msgid "%s Password Assistance" +msgstr "" + +#, python-format +msgid "%s association successfully added." +msgstr "" + +#, python-format +msgid "%s authentication is not yet implemented." +msgstr "" + +#, python-format +msgid "%s successfully disassociated." +msgstr "" + +msgid "" +"Upgrade to a different browser\n" +" \tor \n" +" \tinstall Google Chrome Frame to experience this site." +msgstr "" + +msgid "Associate account with" +msgstr "" + +#, fuzzy +msgid "Change password" +msgstr "更改您的密码" + +msgid "Change your email" +msgstr "更改您的电子邮件" + +msgid "Change your password" +msgstr "更改您的密码" + +msgid "Confirm Password" +msgstr "确认密码" + +msgid "Congratulations on your Google App Engine Boilerplate powered page." +msgstr "恭喜您的“Google App Engine的样板供电页。" + +#, python-format +msgid "" +"Congratulations, Your account %s has been successfully " +"activated.{0:>s}" +msgstr "" + +msgid "Contact" +msgstr "联系" + +msgid "Country" +msgstr "国家" + +msgid "Current Password" +msgstr "新密码" + +msgid "Edit Profile" +msgstr "编辑个人资料" + +msgid "Email" +msgstr "电子邮件" + +msgid "Enter a new email address" +msgstr "" + +msgid "Enter your" +msgstr "输入您的" + +msgid "Error sending the message. Please try again later." +msgstr "" + +msgid "Existing social association" +msgstr "" + +msgid "Federated login is disabled." +msgstr "" + +msgid "Forgot your password?" +msgstr "忘记密码?" + +msgid "Google App Engine Boilerplate is released under the" +msgstr "" + +msgid "If the e-mail address you entered" +msgstr "" + +msgid "If the username you entered" +msgstr "" + +msgid "" +"Incorrect password! Please enter your current password to change your " +"account settings." +msgstr "" + +msgid "Invalid email address." +msgstr "" + +msgid "Language" +msgstr "语言" + +msgid "Last Name" +msgstr "姓" + +msgid "Learn why this Boilerplate Rocks" +msgstr "了解为什么这个样板是惊人的" + +msgid "Login" +msgstr "登录" + +msgid "Logout" +msgstr "登出" + +msgid "Message" +msgstr "讯息" + +msgid "Name" +msgstr "名称" + +#, fuzzy +msgid "New Email" +msgstr "更改您的电子邮件" + +msgid "New Password" +msgstr "新密码" + +#, python-format +msgid "" +"No user authentication information received from %s. Please ensure you " +"are logging in from an authorized OpenID Provider (OP)." +msgstr "" + +msgid "Or Sign in using:" +msgstr "或注册使用:" + +msgid "Password" +msgstr "密码" + +msgid "Password changed successfully." +msgstr "" + +msgid "Passwords must match." +msgstr "" + +msgid "" +"Please check your new email for confirmation. Your email will be updated " +"after confirmation." +msgstr "" + +msgid "Remember me?" +msgstr "记得我吗?" + +msgid "Remove" +msgstr "" + +msgid "Reset password" +msgstr "重设密码" + +msgid "Secure Page" +msgstr "安全页面" + +msgid "Send Message" +msgstr "发送消息" + +msgid "Sign Up" +msgstr "创建用户" + +#, python-format +msgid "Social account on %s not found for this user." +msgstr "" + +msgid "Sorry you are not logged in." +msgstr "" + +msgid "Sorry, Some error occurred." +msgstr "" + +#, python-format +msgid "Sorry, The email %s is already registered.{0:>s}" +msgstr "" + +msgid "Sorry, The user is already registered." +msgstr "" + +#, python-format +msgid "Sorry, The username %s is already registered.{0:>s}" +msgstr "" + +msgid "Source Code" +msgstr "源代码" + +msgid "Thanks, your settings have been saved." +msgstr "" + +msgid "The URL you tried to use is either incorrect or no longer valid." +msgstr "" + +msgid "" +"The URL you tried to use is either incorrect or no longer valid. Enter " +"your details again below to get a new one." +msgstr "" + +#, python-format +msgid "The account %s is already in use." +msgstr "" + +#, python-format +msgid "The email %s is already registered." +msgstr "" + +msgid "The two passwords must match." +msgstr "" + +#, python-format +msgid "" +"The username %s is already taken. Please choose " +"another.{0:>s}" +msgstr "" + +#, python-format +msgid "" +"The verification email has been resent to %s. Please check your email to " +"activate your account." +msgstr "" + +#, python-format +msgid "This %s account is already in use." +msgstr "" + +msgid "This Twitter account is already in use." +msgstr "" + +#, python-format +msgid "" +"This Twitter account is not associated with any local account. If you " +"already have a %s Account, you have sign in here " +"or create an account." +msgstr "" + +msgid "This authentication method is not yet implemented." +msgstr "" + +msgid "Twitter association added." +msgstr "" + +msgid "Unable to update profile. Please try again later." +msgstr "" + +#, python-format +msgid "Unexpected error creating the user %s{0:>s}." +msgstr "" + +#, fuzzy +msgid "Update Profile" +msgstr "编辑个人资料" + +msgid "User is logged out, but there was an error on the redirection." +msgstr "" + +msgid "Username" +msgstr "用户名" + +msgid "Username invalid. Use only letters and numbers." +msgstr "" + +msgid "Username or Email" +msgstr "用户名或电子邮件" + +#, python-format +msgid "Welcome %s, you are now logged in.{0:>s}" +msgstr "" + +msgid "Wrong image verification code. Please try again." +msgstr "" + +#, fuzzy +msgid "You didn't change your email." +msgstr "更改您的电子邮件" + +msgid "" +"You were successfully registered. Please check your email to activate " +"your account." +msgstr "" + +msgid "" +"You've signed out successfully. Warning: Please clear all cookies and " +"logout of OpenId providers too if you logged in on a public computer." +msgstr "" + +msgid "" +"Your account has been activated. Please sign in " +"to your account." +msgstr "" + +msgid "" +"Your account has not yet been activated. Please check your email to " +"activate it or" +msgstr "" + +msgid "Your browser is ancient!" +msgstr "您的浏览器 古!" + +msgid "Your email has been successfully updated." +msgstr "" + +msgid "Your message was sent successfully." +msgstr "" + +#, python-format +msgid "Your new username is %s{0:>s}" +msgstr "" + +msgid "" +"Your username or password is incorrect. Please try again (make sure your " +"caps lock is off)" +msgstr "" + +msgid "click here" +msgstr "" + +#, fuzzy +msgid "contact us" +msgstr "联系" + +msgid "download the Source Code" +msgstr "下载源代码" + +msgid "for further assistance." +msgstr "" + +msgid "" +"is associated with an account in our records, you will receive an e-mail " +"from us with instructions for resetting your password.
If you don't " +"receive instructions within a minute or two, check your email's spam and " +"junk filters, or " +msgstr "" + +msgid "or just" +msgstr "或只是" + +msgid "to help you to create your application." +msgstr "以帮助您创建您的应用程序。." + +msgid "to resend the email." +msgstr "" + diff --git a/main.py b/main.py new file mode 100644 index 0000000..5e428e8 --- /dev/null +++ b/main.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +## +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +__author__ = 'multiple' +__website__ = 'www.socialmooc.appspot.com' + +import os, sys +# Third party libraries path must be fixed before importing webapp2 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'boilerplate/external')) + +import webapp2 + +import routes +from boilerplate import routes as boilerplate_routes +from admin import routes as admin_routes +from boilerplate import config as boilerplate_config +import config +from boilerplate.lib.basehandler import handle_error + +webapp2_config = boilerplate_config.config +webapp2_config.update(config.config) + +app = webapp2.WSGIApplication(debug = os.environ['SERVER_SOFTWARE'].startswith('Dev'), config=webapp2_config) + +for status_int in app.config['error_templates']: + app.error_handlers[status_int] = handle_error + +routes.add_routes(app) +boilerplate_routes.add_routes(app) +admin_routes.add_routes(app) +# so we can go to domain.com/ +routes.add_routes_catchall(app) + diff --git a/routes.py b/routes.py new file mode 100644 index 0000000..aef4d6c --- /dev/null +++ b/routes.py @@ -0,0 +1,63 @@ +""" +Using redirect route instead of simple routes since it supports strict_slash +Simple route: http://webapp-improved.appspot.com/guide/routing.html#simple-routes +RedirectRoute: http://webapp-improved.appspot.com/api/webapp2_extras/routes.html#webapp2_extras.routes.RedirectRoute +""" + + +from webapp2_extras.routes import RedirectRoute +from web.controllers import home +from web.controllers import chat +from web.controllers import profile +from web.controllers import friends +from web.controllers import messages +from web.controllers import usersearch +from web.controllers import usersettings + + +secure_scheme = 'https' + +_routes = [ + RedirectRoute('/', home.UserHomePage, name='home', strict_slash=True), + RedirectRoute('/messages', messages.MessagePage, name="messages", strict_slash=True), + RedirectRoute('/messages//', messages.MessagePage, name="message", strict_slash=True), + RedirectRoute('/friends', friends.FriendsController, name='friends', strict_slash=True), + RedirectRoute('/chat/', chat.Chat, name='chat', strict_slash=True), + RedirectRoute('/search', usersearch.Search, name="search", strict_slash=True), + RedirectRoute('/communication', chat.Communication, name='chat-communication', strict_slash=True), + RedirectRoute('/tokenexpireHandler',chat.TokenexpireHandler, name='tokenexpire', strict_slash=True), + ('/_ah/channel/connected/?', chat.Connect), + ('/_ah/channel/disconnected/?', chat.Disconnect), + + RedirectRoute(r'/settings', usersettings.UserSettings, name="settings", strict_slash=True), + + RedirectRoute('//project/', profile.Projects, + name="project_view", strict_slash=True, handler_method='view_project', methods=('GET')), + + RedirectRoute('//project/add', profile.ProjectsUpload, + name='project-add', strict_slash=True, handler_method='add_project', methods=('POST')), + + RedirectRoute('//project/edit', profile.ProjectsUpload, + name='project-edit', strict_slash=True, handler_method='edit_project', methods=('POST')), + + RedirectRoute('//project/delete', profile.Projects, + name='project-delete', strict_slash=True, handler_method='delete_project', methods=('POST')), + + RedirectRoute('/', home.UserHomePage, name='home', strict_slash=True), + ] + +_routes_catchall = [ + RedirectRoute('/', profile.ProfilePage, name='profile', strict_slash=True, methods=('GET, POST')), +] + +def get_routes(): + return _routes + +def add_routes(app, routes=_routes): + if app.debug: + secure_scheme = 'http' + for r in routes: + app.router.add(r) + +def add_routes_catchall(app): + add_routes(app, _routes_catchall) \ No newline at end of file diff --git a/static/README.TXT b/static/README.TXT new file mode 100644 index 0000000..6450675 --- /dev/null +++ b/static/README.TXT @@ -0,0 +1,2 @@ + +Put in this folder your statics files (CSS, JavaScript, Images, etc) \ No newline at end of file diff --git a/static/apple-touch-icon-precomposed.png b/static/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..ba25a64 Binary files /dev/null and b/static/apple-touch-icon-precomposed.png differ diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png new file mode 100644 index 0000000..3d0a73b Binary files /dev/null and b/static/apple-touch-icon.png differ diff --git a/static/crossdomain.xml b/static/crossdomain.xml new file mode 100644 index 0000000..29a035d --- /dev/null +++ b/static/crossdomain.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/static/css/boilerplate.min.css b/static/css/boilerplate.min.css new file mode 100644 index 0000000..0e0c533 --- /dev/null +++ b/static/css/boilerplate.min.css @@ -0,0 +1,2 @@ +/* general elements */ +@import "bootstrap.min.css";@import "bootstrap-responsive.min.css";body{padding-top:50px}span.password_strength{display:block;width:271px;margin:4px 0 0}.well{background-color:#efefef;background-repeat:repeat-x;background-image:-moz-linear-gradient(top, #efefef 0, #eee 100%);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%, #fff),color-stop(50%, #e7e7e7));background-image:-webkit-linear-gradient(top, #efefef 0, #eee 100%);background-image:-ms-linear-gradient(top, #efefef 0, #eee 100%);background-image:-o-linear-gradient(top, #efefef 0, #eee 100%);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#e7e7e7', GradientType=0);background-image:linear-gradient(top, #efefef 0, #eee 1000%)}.well .form-actions{margin-bottom:0;padding-bottom:0;background-color:transparent}.auth-at-home button.btn-login{margin-left:13px}.auth-at-home span.password_strength{display:inline-block;width:auto;cursor:default;margin-bottom:6px}.auth-at-loginpage fieldset{width:25%;min-width:270px;margin:0 auto}.auth-at-loginpage .btn-login{margin-right:1em}.auth-at-loginpage .remember{display:inline}.auth-at-dropdown{width:230px}.auth-at-dropdown form{margin:0}.auth-at-dropdown a{padding:0;color:#08c;text-decoration:none}.auth-at-dropdown a:hover{color:#005580;text-decoration:underline}.auth-at-dropdown .control-group,.auth-at-home .control-group{margin-bottom:0}.auth-at-dropdown input,.auth-at-home input{width:96%}.auth-at-dropdown input[type=checkbox],.auth-at-home input[type=checkbox]{width:auto}.auth-at-dropdown .btn-login,.auth-at-home .btn-login{margin:0}#third_party_login{padding:0;background:transparent}@media (max-width: 980px){#third_party_login .nav-collapse .dropdown-menu #form_login_user a{ padding:0}}#third_party_login h4{padding:.5em 0}#register{padding:0;background:transparent}#register h4{padding:.5em 0}.social-login-icons a.social-btn{display:inline-block;margin:0 0 .5em 0;text-indent:-9000px;width:32px;height:32px;background-image:url('../img/social-login-icons.png');background-repeat:no-repeat}.social-btn-google{background-position:0 0}.social-btn-facebook{background-position:-32px 0}.social-btn-linkedin{background-position:-64px -32px}.social-btn-myopenid{background-position:0 -32px}.social-btn-twitter{background-position:-64px 0}.social-btn-yahoo{background-position:-32px -32px}.social-btn-github{background-position:-97px 0}.existing-association{padding:0;background:transparent;border-top:1px solid #e5e5e5;margin:18px 0 0 0;clear:both}.existing-association h4{padding-top:.5em}.existing-association div{margin-top:.5em}.existing-association div .btn-danger{float:right}.existing-association div div{width:250px}.navbar .brand{margin-left:0}.hasPlaceholder{color:#777}.subnav{width:100%;height:36px;background-color:#eee;background-repeat:repeat-x;background-image:-moz-linear-gradient(top, #fff, #f2f2f2);background-image:-webkit-gradient(linear,left top,left bottom,color-stop( #fff),color-stop( #f2f2f2));background-image:-webkit-linear-gradient(top, #fff, #f2f2f2);background-image:-ms-linear-gradient(top, #fff, #f2f2f2);background-image:-o-linear-gradient(top, #fff, #f2f2f2);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2', GradientType=0);background-image:linear-gradient(top, #fff, #f2f2f2);border:1px solid #e5e5e5;border-radius:4px;margin-bottom:18px}.subnav ul{margin:0;overflow:hidden}.subnav ul li{margin:0 -2px;line-height:36px;display:inline-block;padding:0 11px;border-left:1px solid #eee;color:#777}.subnav ul li:first-child{border-left:0}.subnav ul li:last-child{border-right:0}.subnav ul li:hover{color:#4183C4;background:#ff0;background-image:-moz-linear-gradient(top, #e7e7e7 0, #f1f1f1 70%);background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%, #e7e7e7),color-stop(70%, #f1f1f1));background-image:-webkit-linear-gradient(top, #e7e7e7 0, #f1f1f1 70%);background-image:-ms-linear-gradient(top, #e7e7e7 0, #f1f1f1 70%);background-image:-o-linear-gradient(top, #e7e7e7 0, #f1f1f1 70%);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#e7e7e7', endColorstr='#f1f1f1', GradientType=0);background-image:linear-gradient(top, #e7e7e7 0, #f1f1f1 70%)}.subnav ul li.active{background-color:#E5E5E5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,.125);box-shadow:inset 0 3px 8px rgba(0,0,0,.125)}.subnav ul li a{color:inherit;display:inline-block}.subnav ul li a:hover{text-decoration:none;color:inherit}footer{margin-bottom:20px}@media (max-width: 767px){body{ padding-right:0;padding-left:0}.container{padding-right:.5em;padding-left:.5em}.navbar-fixed-top{margin-bottom:0}.navbar-fixed-top,.navbar-fixed-bottom{margin-right:0;margin-left:0}.navbar .well{background-image:none;padding:20px 2px}.led{background-color:orange}.subnav{position:static;top:auto;z-index:auto;width:auto;height:auto;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;margin-top:10px}.subnav ul li{display:block}}@media (max-width: 480px){.container{ padding-left:0;padding-right:0}.navbar-fixed-top{margin-bottom:0}.navbar .well{background-image:none;padding:10px 2px}.navbar .brand{font-size:1.4em}.home_bp{background-size:60%;padding-top:18em}.led{background-color:red}}@media (min-width: 768px) and (max-width: 979px){.navbar .well{ background-image:none;padding:20px 2px}.led{background-color:#F4FA58}}@media (max-width: 979px){body{ padding-top:0}}@media (min-width: 980px){.led{ background-color:#58FA58}}@media (min-width: 1200px){.led{ background-color:#87cefa}.subnav-fixed .nav{width:1170px;margin:0 auto;padding:0 1px}}@media (max-width: 1200px){.auth-at-home .row-fluid .span8{ width:100%}.auth-at-home button.btn-login{margin-left:0}} \ No newline at end of file diff --git a/static/css/bootstrap-modal.css b/static/css/bootstrap-modal.css new file mode 100644 index 0000000..b1d2899 --- /dev/null +++ b/static/css/bootstrap-modal.css @@ -0,0 +1,215 @@ +/*! + * Bootstrap Modal + * + * Copyright Jordan Schroter + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ + +.modal-open { + position: relative; /* safari */ + overflow: hidden; +} + + +/* add a scroll bar to stop page from jerking around */ +.modal-open.page-overflow .page-container, +.modal-open.page-overflow .page-container .navbar-fixed-top, +.modal-open.page-overflow .page-container .navbar-fixed-bottom, +.modal-open.page-overflow .modal-scrollable { + overflow-y: scroll; +} + +@media (max-width: 979px) { + .modal-open.page-overflow .page-container .navbar-fixed-top, + .modal-open.page-overflow .page-container .navbar-fixed-bottom { + overflow-y: visible; + } +} + + +.modal-scrollable { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: auto; +} + +.modal { + outline: none; + position: absolute; + margin-top: 0; + top: 50%; + overflow: visible; /* allow content to popup out (i.e tooltips) */ +} + +.modal.fade { + top: -100%; + -webkit-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out, bottom 0.3s ease-out, margin-top 0.3s ease-out; +} + +.modal.fade.in { + top: 50%; +} + +.modal-body { + max-height: none; + overflow: visible; +} + +.modal.modal-absolute { + position: absolute; + z-index: 950; +} + +.modal .loading-mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #fff; + border-radius: 6px; +} + +.modal-backdrop.modal-absolute{ + position: absolute; + z-index: 940; +} + +.modal-backdrop, +.modal-backdrop.fade.in{ + opacity: 0.7; + filter: alpha(opacity=70); + background: #fff; +} + +.modal.container { + width: 940px; + margin-left: -470px; +} + +/* Modal Overflow */ + +.modal-overflow.modal { + top: 1%; +} + +.modal-overflow.modal.fade { + top: -100%; +} + +.modal-overflow.modal.fade.in { + top: 1%; +} + +.modal-overflow .modal-body { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +/* Responsive */ + +@media (min-width: 1200px) { + .modal.container { + width: 1170px; + margin-left: -585px; + } +} + +@media (max-width: 979px) { + .modal, + .modal.container, + .modal.modal-overflow { + top: 1%; + right: 1%; + left: 1%; + bottom: auto; + width: auto !important; + height: auto !important; + margin: 0 !important; + padding: 0 !important; + } + + .modal.fade.in, + .modal.container.fade.in, + .modal.modal-overflow.fade.in { + top: 1%; + bottom: auto; + } + + .modal-body, + .modal-overflow .modal-body { + position: static; + margin: 0; + height: auto !important; + max-height: none !important; + overflow: visible !important; + } + + .modal-footer, + .modal-overflow .modal-footer { + position: static; + } +} + +.loading-spinner { + position: absolute; + top: 50%; + left: 50%; + margin: -12px 0 0 -12px; +} + +/* +Animate.css - http://daneden.me/animate +Licensed under the ☺ license (http://licence.visualidiot.com/) + +Copyright (c) 2012 Dan Eden*/ + +.animated { + -webkit-animation-duration: 1s; + -moz-animation-duration: 1s; + -o-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; + -o-animation-fill-mode: both; + animation-fill-mode: both; +} + +@-webkit-keyframes shake { + 0%, 100% {-webkit-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-webkit-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-webkit-transform: translateX(10px);} +} + +@-moz-keyframes shake { + 0%, 100% {-moz-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-moz-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-moz-transform: translateX(10px);} +} + +@-o-keyframes shake { + 0%, 100% {-o-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-o-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-o-transform: translateX(10px);} +} + +@keyframes shake { + 0%, 100% {transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {transform: translateX(-10px);} + 20%, 40%, 60%, 80% {transform: translateX(10px);} +} + +.shake { + -webkit-animation-name: shake; + -moz-animation-name: shake; + -o-animation-name: shake; + animation-name: shake; +} diff --git a/uplusmessaging/stylesheets/bootstrap-responsive.css b/static/css/bootstrap-responsive.css similarity index 100% rename from uplusmessaging/stylesheets/bootstrap-responsive.css rename to static/css/bootstrap-responsive.css diff --git a/static/css/bootstrap-responsive.min.css b/static/css/bootstrap-responsive.min.css new file mode 100644 index 0000000..2269019 --- /dev/null +++ b/static/css/bootstrap-responsive.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap Responsive v2.2.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/uplusmessaging/stylesheets/bootstrap.min.css b/static/css/bootstrap.min.css similarity index 100% rename from uplusmessaging/stylesheets/bootstrap.min.css rename to static/css/bootstrap.min.css diff --git a/static/css/chat.css b/static/css/chat.css new file mode 100644 index 0000000..9b6d394 --- /dev/null +++ b/static/css/chat.css @@ -0,0 +1,73 @@ +/* Some clever person who knows bootstrap can fix this later */ +.nomargin { + margin-left: 0px; + margin-top: 0px; +} + +#whole-chatbox { + background-color: #fff; + margin: 0 -20px; /* negative indent the amount of the padding to maintain the grid system */ + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15); + box-shadow: 0 1px 2px rgba(0,0,0,.15); + width: 670px; + border: 1px solid; +} + +#chatclient { + width: 670px; +} + +#channelpanes { + margin-top: -20px; + height: 540px; +} + +#chatarea { + width: 514px; /* 75%-2*5px padding-1px border */ + height: 540px; + overflow-y: scroll; + padding: 5px; + border-right: 1px solid #DDDDDD; +} + +#userarea { + width: 135px; /* 25%-2*5px padding */ + height: 540px; + padding: 5px; +} + +div#inputs { + height: auto; +} + +div#textinput { + width: 500px; /* 75%-2*5px padding-1px border */ + padding: 5px; + border-top: 1px solid #DDDDDD; + border-right: 1px solid #DDDDDD; + height: auto; +} + +div#spare { + width: 135px; /* 25%-2*5px padding */ + border-top: 1px solid #DDDDDD; + padding: 5px; + height: auto; +} + +input#chatinput { + width: 459px; +} + +input.virgin-textinput { + color: #A0A0A0; +} + +li.highlighted a { + color: #FF1010; + background-color: #60FF60; +} diff --git a/uplusprofiles/stylesheets/chosen.css b/static/css/chosen.css similarity index 99% rename from uplusprofiles/stylesheets/chosen.css rename to static/css/chosen.css index f61fb7d..c80708e 100644 --- a/uplusprofiles/stylesheets/chosen.css +++ b/static/css/chosen.css @@ -387,7 +387,7 @@ .chzn-rtl .chzn-search input { background: #fff url('/img/chosen-sprite.png') no-repeat -38px -22px; background: url('/img/chosen-sprite.png') no-repeat -38px -22px, -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); - background: url('/img/chosen-sprite.png') no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: url('/img/chosen-sprite.png') no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background: url('/img/chosen-sprite.png') no-repeat -38px -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background: url('/img/chosen-sprite.png') no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background: url('/img/chosen-sprite.png') no-repeat -38px -22px, linear-gradient(#eeeeee 1%, #ffffff 15%); diff --git a/uplusprofiles/stylesheets/datepicker.css b/static/css/datepicker.css similarity index 100% rename from uplusprofiles/stylesheets/datepicker.css rename to static/css/datepicker.css diff --git a/static/css/jquery.dialog2.css b/static/css/jquery.dialog2.css new file mode 100644 index 0000000..5f9ce1d --- /dev/null +++ b/static/css/jquery.dialog2.css @@ -0,0 +1,16 @@ +.modal { + display: none; + top: 20%; + margin: 0 0 0 -280px; +} + +.modal.loading .modal-header .loader { + height: 36px; + width: 16px; + + margin-right: 10px; + + display: block; + float: left; + background: no-repeat scroll url('/img/ajax-loader.gif') center center; +} \ No newline at end of file diff --git a/uplusprofiles/stylesheets/multi-select.css b/static/css/multi-select.css similarity index 100% rename from uplusprofiles/stylesheets/multi-select.css rename to static/css/multi-select.css diff --git a/uplusprofiles/stylesheets/pagedown.css b/static/css/pagedown.css similarity index 100% rename from uplusprofiles/stylesheets/pagedown.css rename to static/css/pagedown.css diff --git a/uplusmessaging/stylesheets/select2.css b/static/css/select2.css similarity index 100% rename from uplusmessaging/stylesheets/select2.css rename to static/css/select2.css diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..f9677f9 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,38 @@ +dl { + margin-bottom: 10px; +} + +dt { + font-weight: normal; +} + +dt,dd { + line-height: 25px; + padding-top: 5px; + margin-bottom: 10px;; +} +.home_bp{background:transparent url(/img/logo.png) top center no-repeat;padding-top:20em;text-align:center}.home_bp h1{font-size:1.6em}.home_bp p{font-size:1.2em} + +.psearch-result td {vertical-align: top } +.psearch-avatar { width: 50px; } +.psearch-avatar img { height: 50px; width: 50px; } +.psearch-info { padding-left: 10px; vertical-align: top; } +.psearch-username { font-size: 1.2em; padding-bottom: 15px; } +.psearch-realname { font-size: .8em; color: #888; } +.select2-highlighted .psearch-realname{ font-size: .8em; color: #eee; } +.bigdrop.select2-container .select2-results {max-height: 300px;} +.bigdrop .select2-results {max-height: 300px;} + +.dropdown-menu .divider { + background-color: #E5E5E5; + border-bottom: 1px solid #FFFFFF; + height: 1px; + margin: 0px; + overflow: hidden; +} +.dropdown-menu .opt{ + margin-left: -50px; +} +.containchat { + padding-left:50px; +} \ No newline at end of file diff --git a/uplusmessaging/stylesheets/temp.css b/static/css/temp.css similarity index 100% rename from uplusmessaging/stylesheets/temp.css rename to static/css/temp.css diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100755 index 0000000..1a71ea7 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/humans.txt b/static/humans.txt new file mode 100644 index 0000000..5b037cf --- /dev/null +++ b/static/humans.txt @@ -0,0 +1,15 @@ +# humanstxt.org/ +# The humans responsible & technology colophon + +# TEAM + + -- -- + +# THANKS + + + +# TECHNOLOGY COLOPHON + + HTML5, CSS3 + jQuery, Modernizr diff --git a/static/img/ajax-loader.gif b/static/img/ajax-loader.gif new file mode 100644 index 0000000..4e651ed Binary files /dev/null and b/static/img/ajax-loader.gif differ diff --git a/uplusprofiles/img/chosen-sprite.png b/static/img/chosen-sprite.png similarity index 100% rename from uplusprofiles/img/chosen-sprite.png rename to static/img/chosen-sprite.png diff --git a/uplusmessaging/img/defaultavatar.png b/static/img/defaultavatar.png similarity index 100% rename from uplusmessaging/img/defaultavatar.png rename to static/img/defaultavatar.png diff --git a/uplusprofiles/img/favicon.png b/static/img/favicon.png similarity index 100% rename from uplusprofiles/img/favicon.png rename to static/img/favicon.png diff --git a/uplusprofiles/img/glyphicons-halflings-white.png b/static/img/glyphicons-halflings-white.png similarity index 100% rename from uplusprofiles/img/glyphicons-halflings-white.png rename to static/img/glyphicons-halflings-white.png diff --git a/uplusprofiles/img/glyphicons-halflings.png b/static/img/glyphicons-halflings.png similarity index 100% rename from uplusprofiles/img/glyphicons-halflings.png rename to static/img/glyphicons-halflings.png diff --git a/static/img/logo.png b/static/img/logo.png new file mode 100644 index 0000000..94fdc81 Binary files /dev/null and b/static/img/logo.png differ diff --git a/uplusmessaging/img/robot.png b/static/img/robot.png similarity index 100% rename from uplusmessaging/img/robot.png rename to static/img/robot.png diff --git a/uplusmessaging/img/select2/select2-spinner.gif b/static/img/select2/select2-spinner.gif similarity index 100% rename from uplusmessaging/img/select2/select2-spinner.gif rename to static/img/select2/select2-spinner.gif diff --git a/uplusmessaging/img/select2/select2.png b/static/img/select2/select2.png similarity index 100% rename from uplusmessaging/img/select2/select2.png rename to static/img/select2/select2.png diff --git a/uplusmessaging/img/select2/select2x2.png b/static/img/select2/select2x2.png similarity index 100% rename from uplusmessaging/img/select2/select2x2.png rename to static/img/select2/select2x2.png diff --git a/static/img/social-login-icons.png b/static/img/social-login-icons.png new file mode 100644 index 0000000..23239c1 Binary files /dev/null and b/static/img/social-login-icons.png differ diff --git a/uplusprofiles/img/switch.png b/static/img/switch.png similarity index 100% rename from uplusprofiles/img/switch.png rename to static/img/switch.png diff --git a/uplusprofiles/img/wmd-buttons.png b/static/img/wmd-buttons.png similarity index 100% rename from uplusprofiles/img/wmd-buttons.png rename to static/img/wmd-buttons.png diff --git a/uplusmessaging/js/bootstrap-carousel.js b/static/js/bootstrap-carousel.js similarity index 100% rename from uplusmessaging/js/bootstrap-carousel.js rename to static/js/bootstrap-carousel.js diff --git a/uplusprofiles/js/bootstrap-datepicker.js b/static/js/bootstrap-datepicker.js similarity index 100% rename from uplusprofiles/js/bootstrap-datepicker.js rename to static/js/bootstrap-datepicker.js diff --git a/uplusmessaging/js/bootstrap-modal.js b/static/js/bootstrap-modal.js similarity index 100% rename from uplusmessaging/js/bootstrap-modal.js rename to static/js/bootstrap-modal.js diff --git a/uplusmessaging/js/bootstrap-tab.js b/static/js/bootstrap-tab.js similarity index 100% rename from uplusmessaging/js/bootstrap-tab.js rename to static/js/bootstrap-tab.js diff --git a/uplusmessaging/js/bootstrap-transition.js b/static/js/bootstrap-transition.js similarity index 100% rename from uplusmessaging/js/bootstrap-transition.js rename to static/js/bootstrap-transition.js diff --git a/uplusmessaging/js/bootstrap.min.js b/static/js/bootstrap.min.js similarity index 100% rename from uplusmessaging/js/bootstrap.min.js rename to static/js/bootstrap.min.js diff --git a/static/js/chat.js b/static/js/chat.js new file mode 100644 index 0000000..4081806 --- /dev/null +++ b/static/js/chat.js @@ -0,0 +1,471 @@ +$(document).ready(function() { + // Clear the chattextbox if it is clicked into and default text is in it + $("#chatinput").focus(function(event) { + if ($(this).hasClass("virgin-textinput")) { + $(this).removeClass("virgin-textinput"); + $(this).val(""); + } + }); + + function chatScrollBottom() { + var height = $("div#chatarea").outerHeight(); // Height of the div + var totalheight = $("div#chatarea")[0].scrollHeight; // Total height of content + var scrolltop = $("div#chatarea").scrollTop(); // Top of the scrolled part (varies by scrolling) + /* When at the bottom, totalheight = height+scrolltop */ + return totalheight <= height+scrolltop; + //return scrollheight == height; + } + + + var channels = { }; // Channels the user is in + // Format: {"channelname": { + // "text": text, "user": [user1, user2], "tab": jQueryTab + // } } + var users = { }; // Users the user is in communication with + // Format: {"username": text} + var activeEntity = server; // Could be #channel or @user + +// Suite of test code: + /* + username = "Spez"; + server = "!Server"; + addChannel(server); + onMessage({data:"PRIVMSG Jimmy Hi mate"}); + onMessage({data:"CHANNELMSG #randomChannel TestSender You've been randomly added to this channel to see if it works"}); + addChannel("#testChannel"); + onMessage({data:"USERS #testChannel Billy Jimmy ThisUserShouldBeGone Tommy"}); + onMessage({data:"CHANNELMSG #randomChannel TestSender You've been randomly added to this channel to see if it works"}); + onMessage({data:"CHANNELMSG #testChannel Jimmy Oh look, it's "+username}); + onMessage({data:"NOTICE Server had something to say to you"}); + onMessage({data:"JOINED NewUser #randomChannel"}); + onMessage({data:"JOINED Quitter #randomChannel"}); + onMessage({data:"JOINED Quitter #testChannel"}); + onMessage({data:"PRIVMSG Quitter I am going now, lol"}); + onMessage({data:"LEFT ThisUserShouldBeGone #testChannel"}); + onMessage({data:"PRIVMSG Paulo I AM AWESOME"}); + onMessage({data:"QUIT Quitter"});*/ +// End test code + + // Opens a Google API Channel to receive messages from the server + function openChannel() { + //channel seems ambiguous here.maybe change a name? + var channel = new goog.appengine.Channel(token); + var handler = { + "onopen": onOpen, + "onmessage": onMessage, + "onerror": onError, + "onclose": onClose + }; + var socket = channel.open(handler); + socket.onopen = onOpen; + socket.onmessage = onMessage; + socket.onerror = onError; + socket.onclose = onClose; + //onMessage({data:"NOTICE token in openChannel "+token}) + } + setTimeout(openChannel, 100); + + function onOpen() { + if (!(server in channels)){ + addChannel(server); + } + onMessage({data: "NOTICE Connected to server"}); + } + + window.onbeforeunload = onExit; + + function onExit() { + // If user leaves page, try to send a quit message + // Just in case the /_ah/channel/disconnected/ page isn't hit + sendMessage("QUIT"); + } + + function onMessage(message) { + message = message.data; + // Received a message from the server + var command = message.split(' ')[0]; + var arg = message.substring(message.indexOf(' ')+1); + switch(command) { + case "PRIVMSG": + // Message from another @user;recipient side + // Format: PRIVMSG SENDER message text + var sender = arg.split(' ')[0]; + // Retrieve the message + arg = arg.substring(arg.indexOf(' ')+1); + var content = createTimestamp()+" <"+sender+"> "+arg; + addUserContent(sender, content); + break; + case "SENTMSG": + // A message sent by this user (server confirmation or error message);sender side + // Format: SENTMSG RECIPIENT message text + var recipient = arg.split(' ')[0]; + arg = arg.substring(arg.indexOf(' ')+1); + var content = createTimestamp()+" <"+username+"> "+arg; + addUserContent(recipient, content); + break; + case "CHANNELMSG": + // Message sent to a #channel which user is in + // Format: CHANNELMSG CHANNEL SENDER message text + var channel = arg.split(' ')[0]; + var sender = arg.split(' ')[1]; + // Retrieve the message + arg = arg.substring(arg.indexOf(' ', + arg.indexOf(' ')+1)+1); + var content = createTimestamp()+" <"+sender+"> "+arg; + addChannelContent(channel, content); + break; + case "NOTICEINDENT": + // Not sure if we want to support this, seems gimicky + addChannelContent(server, createTimestamp()+"     "+arg, true); + break; + case "NOTICE": + // Message from server + // Format: NOTICE notice text + addChannelContent(server, createTimestamp()+" "+arg); + break; + case "USERS": + // Format: USERS channel username1 username2 username3 + var channel = arg.split(' ')[0]; + if (!(channel in channels)) { + addChannel(channel); + } + var usersInChannel = arg.substring(arg.indexOf(' ')+1).split(' ') + for (var u in usersInChannel) { + addUsernameToChannel(usersInChannel[u], channel); + } + break; + case "JOINED": + // Format: JOINED username channel + var u = arg.split(' ')[0]; + var channel = arg.split(' ')[1]; + addUsernameToChannel(u, channel); + addChannelContent(channel, createTimestamp()+" "+u+" joined "+channel); + break; + case "LEAVE": + // leave on local end + // Format: LEAVE username channel + var u = arg.split(' ')[0]; + var channel = arg.split(' ')[1]; + removeTab(channel); + break; + case "LEFT": + // Format: LEFT username channel + var u = arg.split(' ')[0]; + var channel = arg.split(' ')[1]; + removeUsernameFromChannel(u, channel); + addChannelContent(channel, createTimestamp()+" "+u+" left"); + break; + case "QUIT": + // User QUIT chat, so remove from all channels + // Format: QUIT username + var u = arg.split(' ')[0]; + for (var c in channels) { + if (removeUsernameFromChannel(u, c)) { + addChannelContent(c, createTimestamp()+" "+u+" quit"); + } + } + if (u in users) { + // Was talking to this user + addUserContent(u, createTimestamp()+" "+u+" quit"); + } + break; + case "ALIAS": + // Format: ALIAS oldname newname + // Not included during launch + break; + case "PING": + // Format: PING response text + sendMessage("PONG "+arg); + break; + } + } + function onError(error) { + // Could be caused by a timeout after 120 minutes, try reconnecting + onMessage({data: "NOTICE An error occurred with the server connection"}); + //when token expire + if(error.code===401 && error.description==='Token+timed+out.'){ + $.post("/tokenexpireHandler?username="+username,function(data){ + token=data; + openChannel(); + }); + } + } + function onClose() { + // Could be caused by a timeout after 120 minutes, try reconnecting + onMessage({data: "NOTICE Disconnected from server"}); + } + + // Sends messages to the server via XHR + // Could potentially replace with jQuery's .ajax() + function sendMessage(message) { + message = encodeURIComponent(message); // Escape URI characters + var path = "/communication?username="+username+"&identifier="+identifier+"&message="+message; + var xhr = new XMLHttpRequest(); + xhr.open("POST", path, true); + xhr.send(); + }; + + // Return a string of the timestamp "YYYY/MM/DD HH:mm" - local time! + function createTimestamp() { + var d = new Date(); + var year = d.getFullYear().toString(); + var month = d.getMonth()+1; + month = month < 10 ? "0"+month.toString() : month.toString(); + var day = d.getDate(); + day = day < 10 ? "0"+day.toString() : day.toString(); + var hour = d.getHours(); + hour = hour < 10 ? "0"+hour.toString() : hour.toString(); + var minute = d.getMinutes(); + minute = minute < 10? "0"+minute.toString() : minute.toString(); + return year+"/"+month+"/"+day+" "+hour+":"+minute; + } + + // Adds a username to a channel that is being listened to + function addUsernameToChannel(newUsername, channel) { + if (!("users" in channels[channel])) { + channels[channel]["users"] = [ ]; + } + if (channels[channel]["users"].indexOf(newUsername) === -1) { + channels[channel]["users"].push(newUsername); + updateEntity(channel); + return true; + } else { + return false; + } + } + + function removeUsernameFromChannel(removedUsername, channel) { + if (channels[channel]["users"].indexOf(removedUsername) !== -1) { + //may be someting get wrong in below statement + channels[channel]["users"].splice(channels[channel]["users"].indexOf(removedUsername), 1); + updateEntity(channel); + return true; + } else { return false; } // User is not in the channel + } + + // Escape, markup and otherwise edit content + // Customise this at will + function markupContent(content, escape) { + if (typeof(escape) === 'undefined' || escape === false) { + // Escape HTML, by creating a jQuery element, inserting text, then pulling out HTML + content = $("
").text(content).html(); + } + // Add newline at the end + content += "
"; + return content; + } + + // Adds content to a channel tab + function addChannelContent(channel, content, escape) { + if (typeof(escape) === 'undefined') + escape = false; + content = markupContent(content, escape); + if (!(channel in channels)) { + // New Channel (that the user presumably joined) + addChannel(channel) + } + channels[channel]["text"] += content; + updateEntity(channel); + } + + // Adds content to a user's tab + function addUserContent(user, content) { + content = markupContent(content); + if (!(user in users)) { + // New conversation with this user + addUser(user); + } + users[user] += content; + updateEntity(user); + } + + // Add a channel to be listened to, adds tab as well + function addChannel(channel) { + // NB: Wipes history on leave/rejoin + channels[channel] = {"text": "", "users": [username], "tab": addTab(channel)}; + } + + // Add a user for private conversation + function addUser(newUsername) { + /* users = {"username": "chattext"} // No need for an array of users */ + users[newUsername] = ""; + addTab(newUsername); + } + + // Add a tab + function addTab(tabName) { + var identifer = (tabName in users) ? + tabName : tabName.substring(1)+".c"; // Trim leading # or ! and append trailing . + var newTab = jQuery('
  • '+ + ''+tabName+'
  • '); + $("ul#chattabs").append(newTab); + // Only go to tab if it is not a user message + if (!(tabName in users)) { + $("li", $("ul#chattabs")).removeClass("active"); + newTab.addClass("active"); + activeEntity = tabName; + updateEntity(tabName); + } + // Handle click events for newtab + newTab.click(function(event) { + var clickedTab = $(this); + event.preventDefault(); + // Inactivate currently active tab + $("li", $("ul#chattabs")).removeClass("active"); + clickedTab.removeClass("highlighted"); + clickedTab.addClass("active"); + activeEntity = $("a", clickedTab).html(); + updateEntity(activeEntity); + }); + return newTab; + } + + //remove a Tab when user leave.? + function removeTab(tabName){ + var identifer = (tabName in users) ? + tabName : tabName.substring(1)+".c"; + //maybe this statement is better + //$("ul#chattabs").remove('li[id="'+identifer+'"]') + removedTab=$('li[id="'+identifer+'"]'); + previousTab=$($("li").get($("li").index(removedTab)-1)); + removedTab.remove(); + //go to previous Tab. + previousTab.addClass("active"); + activeEntity=$("a",previousTab).html(); + updateEntity(activeEntity); + } + + function updateEntity(updatedEntity) { + //updatedEntity is a channelname or a username + if (updatedEntity === activeEntity) { + // Active entity was updated so update chat and users + /* Technically, depending on message, only chat or users + needs to be updated, but this is easier */ + // Scroll state + var scrolledToBottom = chatScrollBottom(); + if (activeEntity in channels) { + // activeEntity is a #channel + $("div#chatarea").html(channels[activeEntity]["text"]); + var userList = "Users in "+activeEntity+":"; + for (user in channels[activeEntity]["users"].sort()) { + userList += "
    "+channels[activeEntity]["users"][user]; + } + $("div#userarea").html(userList); + } else { + // activeEntity is a @user + $("div#chatarea").html(users[activeEntity]); + var userlist = [activeEntity, username].sort().join("
    "); + $("div#userarea").html(userlist); + } + // Implement scrolling + // If previously scrolled to bottom, then scroll to new content + if (scrolledToBottom) { + // Scroll to bottom of the div when getting + $("div#chatarea").scrollTop($("div#chatarea")[0].scrollHeight); + $("#spare").html(helpMessages()); + } else { + // Don't scroll to allow user to continue reading + $("#spare").html("Scroll down to read new messages."); + } + } else { + // Inactive entity was updated, so make that tab flash/highlight + if (updatedEntity in users) { + // If it is a user, just add the class + $("#"+updatedEntity).addClass("highlighted"); + } else { + // If it is server or channel, it will be preceeded by ! or # + // Luckily we can reference the tab directly + $(channels[updatedEntity]["tab"]).addClass("highlighted"); + } + } + } + + // Client stuff + $("#chatinput").keyup(function(event) { + if(event.keyCode == 13 || event.which == 13){ // Enter is pressed + var enteredText = $(this).val(); + $(this).val(""); // Clear the box + if (enteredText.length <= 0) { + // Do nothing + } else if (enteredText.charAt(0) == "/") { + // User entered a /COMMAND + var command = enteredText.split(" ")[0].substring(1); + var arg = enteredText.indexOf(" ") < 0 ? "": + enteredText.substring(enteredText.indexOf(" ")+1); + slashCommand(command, arg); + } else { + if (activeEntity === server) { + // What do we do if someone talks to the server? + + } else if (activeEntity in channels) { + // Talk in channel + sendMessage("CHANNELMSG "+activeEntity+" "+enteredText); + } else { + // Talking to a user + sendMessage("PRIVMSG "+activeEntity+" "+enteredText); + } + } + } + }); + // Have to use keydown for TAB, otherwise textbox loses focus + $("#chatinput").keydown(function(event) { + if(event.keyCode == 9 || event.which == 9){ + // Tab completion + } + }); + + function slashCommand(command, arg){ + switch(command.toUpperCase()) { + case "HELP": + onMessage({data: "NOTICE The following commands are supported:"}); + onMessage({data: "NOTICE optional arguments are in square brackets: [channelname]"}); + onMessage({data: "NOTICE only arguments ending in ... can contain spaces: message..."}); + onMessage({data: "NOTICEINDENT /join channelname -- Joins the indicated channel"}); + onMessage({data: "NOTICEINDENT /msg recipient message... -- Sends a private message to the recipient, can also use /w"}); + onMessage({data: "NOTICEINDENT /leave [channelname] -- Leaves the indicated channel, or the current channel if channelname is blank"}); + onMessage({data: "NOTICEINDENT /quit -- End your chat session"}); + break; + case "W": + case "MSG": + if (arg.length <= 0 || arg.indexOf(" ") < 0) { + // Either recipient or message is missing, or not properly space separated + onMessage({data: "NOTICE Correct format: /msg recipient message"}); + } else { + var recipient = arg.split(" ")[0]; + var message = arg.substring(arg.indexOf(" ")+1); + sendMessage("PRIVMSG "+recipient+" "+message); + } + break; + case "JOIN": + if (arg.length <= 0) { + onMessage({data: "NOTICE Correct format: /join channelname"}); + } else { + sendMessage("JOIN "+arg.split(' ')[0]); + } + break; + case "LEAVE": + arg = arg.length <= 0 ? activeEntity : arg.split(" ")[0]; + if (arg in channels && arg !== server) { + sendMessage("LEAVE "+arg); + // Handle leave on local end! + onMessage({data : "NOTICE " + "you left " + arg}); + //remove tab. + onMessage({data:"LEAVE "+username+" "+arg}); + delete channels.arg; + } else { + onMessage({data: "NOTICE "+arg+" is not a channel you can leave"}); + } + break; + case "QUIT": + sendMessage("QUIT"); + break; + default: + onMessage({data: "NOTICE /"+command+" is not supported."}); + } + } + + function helpMessages() { + var messages = ["Try entering /help.", "Automatically scrolling to new messages."]; + return messages[Math.floor(Math.random() * messages.length)]; + } +}); \ No newline at end of file diff --git a/uplusprofiles/js/chosen.jquery.min.js b/static/js/chosen.jquery.min.js similarity index 100% rename from uplusprofiles/js/chosen.jquery.min.js rename to static/js/chosen.jquery.min.js diff --git a/static/js/jquery.dialog2.helpers.js b/static/js/jquery.dialog2.helpers.js new file mode 100644 index 0000000..d279314 --- /dev/null +++ b/static/js/jquery.dialog2.helpers.js @@ -0,0 +1,192 @@ +/* + * Dialog2: Yet another dialog plugin for jQuery. + * + * This time based on bootstrap styles with some nice ajax control features, + * zero dependencies to jQuery.UI and basic options to control it. + * + * Licensed under the MIT license + * http://www.opensource.org/licenses/mit-license.php + * + * @version: 2.0.0 (22/03/2012) + * + * @requires jQuery >= 1.4 + * + * @requires jQuery.form plugin (http://jquery.malsup.com/form/) >= 2.8 for ajax form submit + * @requires jQuery.controls plugin (https://github.com/Nikku/jquery-controls) >= 0.9 for ajax link binding support + * + * @requires bootstrap styles (twitter.github.com/bootstrap) in version 2.x to look nice + * + * @author nico.rehwaldt + */ + +/** + * This script file extends the plugin to provide helper functions + * alert(), confirm() and prompt() + * + * Thanks to ulrichsg for the contribution + */ +(function($) { + var localizedButton = $.fn.dialog2.localization.localizedButton; + + var __helpers = { + + /** + * Creates an alert displaying the given message. + * Will call options.close on close (if specified). + * + * $.fn.dialog2.alert("This dialog is non intrusive", { + * close: function() { + * alert("This one is!"); + * } + * }); + * + * @param message to be displayed as the dialog body + * @param options (optional) to be used when creating the dialog + */ + alert: function(message, options) { + options = $.extend({}, options); + var labels = $.extend({}, $.fn.dialog2.helpers.defaults.alert, options); + + var dialog = $("
    "); + + var closeCallback = options.close; + delete options.close; + + var buttons = localizedButton(labels.buttonLabelOk, __closeAndCall(closeCallback, dialog)); + + return __open(dialog, message, labels.title, buttons, options); + }, + + /** + * Creates an confirm dialog displaying the given message. + * + * Will call options.confirm on confirm (if specified). + * Will call options.decline on decline (if specified). + * + * $.fn.dialog2.confirm("Is this dialog non intrusive?", { + * confirm: function() { alert("You said yes? Well... no"); }, + * decline: function() { alert("You said no? Right choice!") } + * }); + * + * @param message to be displayed as the dialog body + * @param options (optional) to be used when creating the dialog + */ + confirm: function(message, options) { + options = $.extend({}, options); + var labels = $.extend({}, $.fn.dialog2.helpers.defaults.confirm, options); + + var dialog = $("
    "); + + var confirmCallback = options.confirm; + delete options.confirm; + + var declineCallback = options.decline; + delete options.decline; + + var buttons = {}; + localizedButton(labels.buttonLabelYes, __closeAndCall(confirmCallback, dialog), buttons); + localizedButton(labels.buttonLabelNo, __closeAndCall(declineCallback, dialog), buttons); + + return __open(dialog, message, labels.title, buttons, options); + }, + + /** + * Creates an prompt dialog displaying the given message together with + * an element to input text in. + * + * Will call options.ok on ok (if specified). + * Will call options.cancel on cancel (if specified). + * + * $.fn.dialog2.prompt("What is your age?", { + * ok: function(event, value) { alert("Your age is: " + value); }, + * cancel: function() { alert("Better tell me!"); } + * }); + * + * @param message to be displayed as the dialog body + * @param options (optional) to be used when creating the dialog + */ + prompt: function(message, options) { + // Special: Dialog has to be closed on escape or multiple inputs + // with the same id will be added to the DOM! + options = $.extend({}, options, {closeOnEscape: true}); + var labels = $.extend({}, $.fn.dialog2.helpers.defaults.prompt, options); + + var inputId = 'dialog2.helpers.prompt.input.id'; + var input = $("") + .attr("id", inputId) + .val(options.defaultValue || ""); + + var html = $("
    "); + html.append($("