Просмотр исходного кода

Started working on localization support #16

sh4nks 10 лет назад
Родитель
Сommit
ee5ffbed58

+ 3 - 0
babel.cfg

@@ -0,0 +1,3 @@
+[python: **.py]
+[jinja2: **/templates/**.html]
+extensions=jinja2.ext.autoescape,jinja2.ext.with_

+ 1 - 1
flaskbb/_compat.py

@@ -26,4 +26,4 @@ else:           # pragma: no cover
     iterkeys = lambda d: d.iterkeys()
     iterkeys = lambda d: d.iterkeys()
     itervalues = lambda d: d.itervalues()
     itervalues = lambda d: d.itervalues()
     iteritems = lambda d: d.iteritems()
     iteritems = lambda d: d.iteritems()
-    max_integer = sys.maxint
+    max_integer = sys.maxint

+ 12 - 1
flaskbb/app.py

@@ -31,7 +31,7 @@ from flaskbb.forum.views import forum
 from flaskbb.forum.models import Post, Topic, Category, Forum
 from flaskbb.forum.models import Post, Topic, Category, Forum
 # extensions
 # extensions
 from flaskbb.extensions import db, login_manager, mail, cache, redis_store, \
 from flaskbb.extensions import db, login_manager, mail, cache, redis_store, \
-    debugtoolbar, migrate, themes, plugin_manager
+    debugtoolbar, migrate, themes, plugin_manager, babel
 from flask.ext.whooshalchemy import whoosh_index
 from flask.ext.whooshalchemy import whoosh_index
 # various helpers
 # various helpers
 from flaskbb.utils.helpers import format_date, time_since, crop_title, \
 from flaskbb.utils.helpers import format_date, time_since, crop_title, \
@@ -139,6 +139,17 @@ def configure_extensions(app):
 
 
     login_manager.init_app(app)
     login_manager.init_app(app)
 
 
+    # Flask-Babel
+    babel.init_app(app)
+
+    @babel.localeselector
+    def get_locale():
+        # if a user is logged in, use the locale from the user settings
+        if current_user.is_authenticated() and current_user.language:
+            return current_user.language
+        # otherwise we will just fallback to the default language
+        return flaskbb_config["DEFAULT_LANGUAGE"]
+
 
 
 def configure_template_filters(app):
 def configure_template_filters(app):
     """
     """

+ 4 - 1
flaskbb/extensions.py

@@ -17,7 +17,7 @@ from flask.ext.redis import Redis
 from flask.ext.migrate import Migrate
 from flask.ext.migrate import Migrate
 from flask.ext.themes2 import Themes
 from flask.ext.themes2 import Themes
 from flask.ext.plugins import PluginManager
 from flask.ext.plugins import PluginManager
-
+from flask.ext.babel import Babel
 
 
 # Database
 # Database
 db = SQLAlchemy()
 db = SQLAlchemy()
@@ -45,3 +45,6 @@ themes = Themes()
 
 
 # PluginManager
 # PluginManager
 plugin_manager = PluginManager()
 plugin_manager = PluginManager()
+
+# Babel
+babel = Babel()

+ 18 - 4
flaskbb/fixtures/settings.py

@@ -10,6 +10,8 @@
 """
 """
 from flask.ext.themes2 import get_themes_list
 from flask.ext.themes2 import get_themes_list
 
 
+from flaskbb.extensions import babel
+
 
 
 def available_themes():
 def available_themes():
     return [(theme.identifier, theme.name) for theme in get_themes_list()]
     return [(theme.identifier, theme.name) for theme in get_themes_list()]
@@ -19,6 +21,11 @@ def available_markups():
     return [('bbcode', 'BBCode'), ('markdown', 'Markdown')]
     return [('bbcode', 'BBCode'), ('markdown', 'Markdown')]
 
 
 
 
+def available_languages():
+    return [(locale.language, locale.display_name)
+            for locale in babel.list_translations()]
+
+
 fixture = (
 fixture = (
     # Settings Group
     # Settings Group
     ('general', {
     ('general', {
@@ -94,17 +101,24 @@ fixture = (
             })
             })
         ),
         ),
     }),
     }),
-    ('themes', {
-        'name': "Theme Settings",
-        "description": "Change the appearance for your forum.",
+    ('theme_language', {
+        'name': "Theme and Language Settings",
+        "description": "Change the appearance and language for your forum.",
         "settings": (
         "settings": (
             ('default_theme', {
             ('default_theme', {
                 'value':        "bootstrap3",
                 'value':        "bootstrap3",
                 'value_type':   "select",
                 'value_type':   "select",
                 'extra':        {'choices': available_themes},
                 'extra':        {'choices': available_themes},
-                'name':         "Default theme",
+                'name':         "Default Theme",
                 'description':  "Change the default theme for your forum."
                 'description':  "Change the default theme for your forum."
             }),
             }),
+            ('default_language', {
+                'value':        "en",
+                'value_type':   "select",
+                'extra':        {'choices': available_languages},
+                'name':         "Default Language",
+                'description':  "Change the default language for your forum."
+            }),
         ),
         ),
     }),
     }),
 )
 )

+ 25 - 18
flaskbb/forum/views.py

@@ -14,6 +14,7 @@ import datetime
 from flask import (Blueprint, redirect, url_for, current_app,
 from flask import (Blueprint, redirect, url_for, current_app,
                    request, flash)
                    request, flash)
 from flask.ext.login import login_required, current_user
 from flask.ext.login import login_required, current_user
+from flask.ext.babel import gettext as _
 
 
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
@@ -156,7 +157,7 @@ def new_topic(forum_id, slug=None):
     forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
     forum_instance = Forum.query.filter_by(id=forum_id).first_or_404()
 
 
     if not can_post_topic(user=current_user, forum=forum):
     if not can_post_topic(user=current_user, forum=forum):
-        flash("You do not have the permissions to create a new topic.",
+        flash(_("You do not have the permissions to create a new topic."),
               "danger")
               "danger")
         return redirect(forum.url)
         return redirect(forum.url)
 
 
@@ -185,7 +186,8 @@ def delete_topic(topic_id, slug=None):
 
 
     if not can_delete_topic(user=current_user, topic=topic):
     if not can_delete_topic(user=current_user, topic=topic):
 
 
-        flash("You do not have the permissions to delete the topic", "danger")
+        flash(_("You do not have the permissions to delete the topic"),
+              "danger")
         return redirect(topic.forum.url)
         return redirect(topic.forum.url)
 
 
     involved_users = User.query.filter(Post.topic_id == topic.id,
     involved_users = User.query.filter(Post.topic_id == topic.id,
@@ -203,7 +205,7 @@ def lock_topic(topic_id, slug=None):
     # TODO: Bulk lock
     # TODO: Bulk lock
 
 
     if not can_moderate(user=current_user, forum=topic.forum):
     if not can_moderate(user=current_user, forum=topic.forum):
-        flash("You do not have the permissions to lock this topic", "danger")
+        flash(_("You do not have the permissions to lock this topic"), "danger")
         return redirect(topic.url)
         return redirect(topic.url)
 
 
     topic.locked = True
     topic.locked = True
@@ -221,7 +223,8 @@ def unlock_topic(topic_id, slug=None):
 
 
     # Unlock is basically the same as lock
     # Unlock is basically the same as lock
     if not can_moderate(user=current_user, forum=topic.forum):
     if not can_moderate(user=current_user, forum=topic.forum):
-        flash("Yo do not have the permissions to unlock this topic", "danger")
+        flash(_("You do not have the permissions to unlock this topic"),
+              "danger")
         return redirect(topic.url)
         return redirect(topic.url)
 
 
     topic.locked = False
     topic.locked = False
@@ -236,7 +239,8 @@ def highlight_topic(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     if not can_moderate(user=current_user, forum=topic.forum):
     if not can_moderate(user=current_user, forum=topic.forum):
-        flash("You do not have the permissions to highlight this topic", "danger")
+        flash(_("You do not have the permissions to highlight this topic"),
+              "danger")
         return redirect(topic.url)
         return redirect(topic.url)
 
 
     topic.important = True
     topic.important = True
@@ -252,7 +256,8 @@ def trivialize_topic(topic_id, slug=None):
 
 
     # Unlock is basically the same as lock
     # Unlock is basically the same as lock
     if not can_moderate(user=current_user, forum=topic.forum):
     if not can_moderate(user=current_user, forum=topic.forum):
-        flash("Yo do not have the permissions to trivialize this topic", "danger")
+        flash(_("You do not have the permissions to trivialize this topic"),
+              "danger")
         return redirect(topic.url)
         return redirect(topic.url)
 
 
     topic.important = False
     topic.important = False
@@ -272,17 +277,17 @@ def move_topic(topic_id, forum_id, topic_slug=None, forum_slug=None):
     # TODO: Bulk move
     # TODO: Bulk move
 
 
     if not can_moderate(user=current_user, forum=topic.forum):
     if not can_moderate(user=current_user, forum=topic.forum):
-        flash("Yo do not have the permissions to move this topic", "danger")
+        flash(_("You do not have the permissions to move this topic"), "danger")
         return redirect(forum_instance.url)
         return redirect(forum_instance.url)
 
 
     if not topic.move(forum_instance):
     if not topic.move(forum_instance):
-        flash(
-            "Could not move the topic to forum %s" % forum_instance.title,
+        flash(_(
+            "Could not move the topic to forum %(forum_instance.title)s"),
             "danger"
             "danger"
         )
         )
         return redirect(topic.url)
         return redirect(topic.url)
 
 
-    flash("Topic was moved to forum %s" % forum_instance.title, "success")
+    flash(_("Topic was moved to forum %(forum_instance.title)s"), "success")
     return redirect(topic.url)
     return redirect(topic.url)
 
 
 
 
@@ -297,14 +302,15 @@ def merge_topic(old_id, new_id, old_slug=None, new_slug=None):
 
 
     # Looks to me that the user should have permissions on both forums, right?
     # Looks to me that the user should have permissions on both forums, right?
     if not can_moderate(user=current_user, forum=_old_topic.forum):
     if not can_moderate(user=current_user, forum=_old_topic.forum):
-        flash("Yo do not have the permissions to merge this topic", "danger")
+        flash(_("You do not have the permissions to merge this topic"),
+              "danger")
         return redirect(_old_topic.url)
         return redirect(_old_topic.url)
 
 
     if not _old_topic.merge(_new_topic):
     if not _old_topic.merge(_new_topic):
-        flash("Could not merge the topic.", "danger")
+        flash(_("Could not merge the topic."), "danger")
         return redirect(_old_topic.url)
         return redirect(_old_topic.url)
 
 
-    flash("Topic succesfully merged.", "success")
+    flash(_("Topic succesfully merged."), "success")
     return redirect(_new_topic.url)
     return redirect(_new_topic.url)
 
 
 
 
@@ -315,7 +321,7 @@ def new_post(topic_id, slug=None):
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
     topic = Topic.query.filter_by(id=topic_id).first_or_404()
 
 
     if not can_post_reply(user=current_user, topic=topic):
     if not can_post_reply(user=current_user, topic=topic):
-        flash("You do not have the permissions to post here", "danger")
+        flash(_("You do not have the permissions to post here"), "danger")
         return redirect(topic.forum.url)
         return redirect(topic.forum.url)
 
 
     form = ReplyForm()
     form = ReplyForm()
@@ -341,7 +347,8 @@ def reply_post(topic_id, post_id):
     post = Post.query.filter_by(id=post_id).first_or_404()
     post = Post.query.filter_by(id=post_id).first_or_404()
 
 
     if not can_post_reply(user=current_user, topic=topic):
     if not can_post_reply(user=current_user, topic=topic):
-        flash("You do not have the permissions to post in this topic", "danger")
+        flash(_("You do not have the permissions to post in this topic"),
+              "danger")
         return redirect(topic.forum.url)
         return redirect(topic.forum.url)
 
 
     form = ReplyForm()
     form = ReplyForm()
@@ -366,7 +373,7 @@ def edit_post(post_id):
     post = Post.query.filter_by(id=post_id).first_or_404()
     post = Post.query.filter_by(id=post_id).first_or_404()
 
 
     if not can_edit_post(user=current_user, post=post):
     if not can_edit_post(user=current_user, post=post):
-        flash("You do not have the permissions to edit this post", "danger")
+        flash(_("You do not have the permissions to edit this post"), "danger")
         return redirect(post.topic.url)
         return redirect(post.topic.url)
 
 
     form = ReplyForm()
     form = ReplyForm()
@@ -396,7 +403,7 @@ def delete_post(post_id, slug=None):
     # TODO: Bulk delete
     # TODO: Bulk delete
 
 
     if not can_delete_post(user=current_user, post=post):
     if not can_delete_post(user=current_user, post=post):
-        flash("You do not have the permissions to edit this post", "danger")
+        flash(_("You do not have the permissions to edit this post"), "danger")
         return redirect(post.topic.url)
         return redirect(post.topic.url)
 
 
     first_post = post.first_post
     first_post = post.first_post
@@ -419,7 +426,7 @@ def report_post(post_id):
     form = ReportForm()
     form = ReportForm()
     if form.validate_on_submit():
     if form.validate_on_submit():
         form.save(current_user, post)
         form.save(current_user, post)
-        flash("Thanks for reporting!", "success")
+        flash(_("Thanks for reporting!"), "success")
 
 
     return render_template("forum/report_post.html", form=form)
     return render_template("forum/report_post.html", form=form)
 
 

+ 1 - 0
flaskbb/templates/user/general_settings.html

@@ -6,6 +6,7 @@
     <legend class="">General Settings</legend>
     <legend class="">General Settings</legend>
     {{ form.hidden_tag() }}
     {{ form.hidden_tag() }}
     {{ horizontal_field(form.theme) }}
     {{ horizontal_field(form.theme) }}
+    {{ horizontal_field(form.language) }}
     <div class="form-group row">
     <div class="form-group row">
         <div class="col-sm-offset-3 col-sm-9">
         <div class="col-sm-offset-3 col-sm-9">
             <button type="submit" class="btn btn-success">Save</button>
             <button type="submit" class="btn btn-success">Save</button>

+ 83 - 0
flaskbb/translations/de/LC_MESSAGES/messages.po

@@ -0,0 +1,83 @@
+# German translations for FlaskBB.
+# Copyright (C) 2015 FlaskBB
+# This file is distributed under the same license as the FlaskBB project.
+# Peter Justin <sh4nks7@gmail.com>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version:  0.1-dev\n"
+"Report-Msgid-Bugs-To: http://github.com/flaskbb/issues\n"
+"POT-Creation-Date: 2015-01-03 13:47+0100\n"
+"PO-Revision-Date: 2015-01-03 13:45+0100\n"
+"Last-Translator: Peter Justin <sh4nks7@gmail.com>\n"
+"Language-Team: FlaskBB Team\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 1.3\n"
+
+#: flaskbb/forum/views.py:160
+msgid "You do not have the permissions to create a new topic."
+msgstr ""
+
+#: flaskbb/forum/views.py:189
+msgid "You do not have the permissions to delete the topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:208
+msgid "You do not have the permissions to lock this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:226
+msgid "You do not have the permissions to unlock this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:242
+msgid "You do not have the permissions to highlight this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:259
+msgid "You do not have the permissions to trivialize this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:280
+msgid "You do not have the permissions to move this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:284
+msgid "Could not move the topic to forum %(forum_instance.title)s"
+msgstr ""
+
+#: flaskbb/forum/views.py:290
+msgid "Topic was moved to forum %(forum_instance.title)s"
+msgstr ""
+
+#: flaskbb/forum/views.py:305
+msgid "You do not have the permissions to merge this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:310
+msgid "Could not merge the topic."
+msgstr ""
+
+#: flaskbb/forum/views.py:313
+msgid "Topic succesfully merged."
+msgstr ""
+
+#: flaskbb/forum/views.py:324
+msgid "You do not have the permissions to post here"
+msgstr ""
+
+#: flaskbb/forum/views.py:350
+msgid "You do not have the permissions to post in this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:376 flaskbb/forum/views.py:406
+msgid "You do not have the permissions to edit this post"
+msgstr ""
+
+#: flaskbb/forum/views.py:429
+msgid "Thanks for reporting!"
+msgstr ""
+

+ 83 - 0
flaskbb/translations/en/LC_MESSAGES/messages.po

@@ -0,0 +1,83 @@
+# German translations for FlaskBB.
+# Copyright (C) 2015 FlaskBB
+# This file is distributed under the same license as the FlaskBB project.
+# Peter Justin <sh4nks7@gmail.com>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version:  0.1-dev\n"
+"Report-Msgid-Bugs-To: http://github.com/flaskbb/issues\n"
+"POT-Creation-Date: 2015-01-03 13:47+0100\n"
+"PO-Revision-Date: 2015-01-03 13:45+0100\n"
+"Last-Translator: Peter Justin <sh4nks7@gmail.com>\n"
+"Language-Team: FlaskBB Team\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 1.3\n"
+
+#: flaskbb/forum/views.py:160
+msgid "You do not have the permissions to create a new topic."
+msgstr ""
+
+#: flaskbb/forum/views.py:189
+msgid "You do not have the permissions to delete the topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:208
+msgid "You do not have the permissions to lock this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:226
+msgid "You do not have the permissions to unlock this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:242
+msgid "You do not have the permissions to highlight this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:259
+msgid "You do not have the permissions to trivialize this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:280
+msgid "You do not have the permissions to move this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:284
+msgid "Could not move the topic to forum %(forum_instance.title)s"
+msgstr ""
+
+#: flaskbb/forum/views.py:290
+msgid "Topic was moved to forum %(forum_instance.title)s"
+msgstr ""
+
+#: flaskbb/forum/views.py:305
+msgid "You do not have the permissions to merge this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:310
+msgid "Could not merge the topic."
+msgstr ""
+
+#: flaskbb/forum/views.py:313
+msgid "Topic succesfully merged."
+msgstr ""
+
+#: flaskbb/forum/views.py:324
+msgid "You do not have the permissions to post here"
+msgstr ""
+
+#: flaskbb/forum/views.py:350
+msgid "You do not have the permissions to post in this topic"
+msgstr ""
+
+#: flaskbb/forum/views.py:376 flaskbb/forum/views.py:406
+msgid "You do not have the permissions to edit this post"
+msgstr ""
+
+#: flaskbb/forum/views.py:429
+msgid "Thanks for reporting!"
+msgstr ""
+

+ 1 - 1
flaskbb/user/forms.py

@@ -29,7 +29,7 @@ is_image = regexp(IMG_RE,
 class GeneralSettingsForm(Form):
 class GeneralSettingsForm(Form):
     # The choices for those fields will be generated in the user view
     # The choices for those fields will be generated in the user view
     # because we cannot access the current_app outside of the context
     # because we cannot access the current_app outside of the context
-    #language = SelectField("Language")
+    language = SelectField("Language")
     theme = SelectField("Theme")
     theme = SelectField("Theme")
 
 
 
 

+ 1 - 0
flaskbb/user/models.py

@@ -92,6 +92,7 @@ class User(db.Model, UserMixin):
     notes = db.Column(db.Text)
     notes = db.Column(db.Text)
 
 
     theme = db.Column(db.String(15))
     theme = db.Column(db.String(15))
+    language = db.Column(db.String(15), default="en")
 
 
     posts = db.relationship("Post", backref="user", lazy="dynamic")
     posts = db.relationship("Post", backref="user", lazy="dynamic")
     topics = db.relationship("Topic", backref="user", lazy="dynamic")
     topics = db.relationship("Topic", backref="user", lazy="dynamic")

+ 6 - 1
flaskbb/user/views.py

@@ -15,7 +15,7 @@ from flask import Blueprint, flash, request, redirect, url_for
 from flask.ext.login import login_required, current_user
 from flask.ext.login import login_required, current_user
 from flask.ext.themes2 import get_themes_list
 from flask.ext.themes2 import get_themes_list
 
 
-from flaskbb.extensions import db
+from flaskbb.extensions import db, babel
 from flaskbb.utils.helpers import render_template
 from flaskbb.utils.helpers import render_template
 from flaskbb.user.models import User, PrivateMessage
 from flaskbb.user.models import User, PrivateMessage
 from flaskbb.user.forms import (ChangePasswordForm, ChangeEmailForm,
 from flaskbb.user.forms import (ChangePasswordForm, ChangeEmailForm,
@@ -57,13 +57,18 @@ def settings():
     form.theme.choices = [(theme.identifier, theme.name)
     form.theme.choices = [(theme.identifier, theme.name)
                           for theme in get_themes_list()]
                           for theme in get_themes_list()]
 
 
+    form.language.choices = [(locale.language, locale.display_name)
+                             for locale in babel.list_translations()]
+
     if form.validate_on_submit():
     if form.validate_on_submit():
         current_user.theme = form.theme.data
         current_user.theme = form.theme.data
+        current_user.language = form.language.data
         current_user.save()
         current_user.save()
 
 
         flash("Your settings have been updated!", "success")
         flash("Your settings have been updated!", "success")
     else:
     else:
         form.theme.data = current_user.theme
         form.theme.data = current_user.theme
+        form.theme.data = current_user.language
 
 
     return render_template("user/general_settings.html", form=form)
     return render_template("user/general_settings.html", form=form)
 
 

+ 29 - 0
manage.py

@@ -11,6 +11,7 @@
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
 import sys
 import sys
+import os
 
 
 from flask import current_app
 from flask import current_app
 from werkzeug.utils import import_string
 from werkzeug.utils import import_string
@@ -175,5 +176,33 @@ def insertmassdata():
     insert_mass_data()
     insert_mass_data()
 
 
 
 
+@manager.command
+def update_translations():
+    """
+    Updates the translations
+    """
+    os.system("pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .")
+    os.system("pybabel update -i messages.pot -d flaskbb/translations")
+    os.unlink("messages.pot")
+
+
+@manager.command
+def init_translations(translation):
+    """
+    Adds a new language to the translations
+    """
+    os.system("pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .")
+    os.system("pybabel init -i messages.pot -d flaskbb/translations -l " + translation)
+    os.unlink('messages.pot')
+
+
+@manager.command
+def compile_translations():
+    """
+    Compile the translations.
+    """
+    os.system("pybabel compile -d flaskbb/translations")
+
+
 if __name__ == "__main__":
 if __name__ == "__main__":
     manager.run()
     manager.run()

+ 7 - 3
requirements.txt

@@ -1,10 +1,13 @@
+Babel==1.3
 Flask==0.10.1
 Flask==0.10.1
+Flask-Babel==0.9
 Flask-Cache==0.13.1
 Flask-Cache==0.13.1
 Flask-DebugToolbar==0.9.0
 Flask-DebugToolbar==0.9.0
 Flask-Login==0.2.11
 Flask-Login==0.2.11
 Flask-Mail==0.9.1
 Flask-Mail==0.9.1
 Flask-Migrate==1.2.0
 Flask-Migrate==1.2.0
 Flask-Plugins==1.5
 Flask-Plugins==1.5
+Flask-Redis==0.0.6
 Flask-SQLAlchemy==2.0
 Flask-SQLAlchemy==2.0
 Flask-Script==2.0.5
 Flask-Script==2.0.5
 Flask-Themes2==0.1.3
 Flask-Themes2==0.1.3
@@ -14,6 +17,7 @@ Mako==1.0.0
 MarkupSafe==0.23
 MarkupSafe==0.23
 Pygments==1.6
 Pygments==1.6
 SQLAlchemy==0.9.8
 SQLAlchemy==0.9.8
+Unidecode==0.04.16
 WTForms==2.0.1
 WTForms==2.0.1
 Werkzeug==0.9.6
 Werkzeug==0.9.6
 Whoosh==2.6.0
 Whoosh==2.6.0
@@ -22,14 +26,14 @@ blinker==1.3
 cov-core==1.14.0
 cov-core==1.14.0
 coverage==3.7.1
 coverage==3.7.1
 itsdangerous==0.24
 itsdangerous==0.24
+markdown2==2.3.0
 py==1.4.25
 py==1.4.25
 pytest==2.6.3
 pytest==2.6.3
 pytest-cov==1.8.0
 pytest-cov==1.8.0
 pytest-random==0.02
 pytest-random==0.02
+pytz==2014.10
 redis==2.10.3
 redis==2.10.3
 simplejson==3.6.4
 simplejson==3.6.4
-flask-redis==0.0.6
-unidecode==0.04.16
-markdown2==2.3.0
+speaklater==1.3
 https://github.com/frol/postmarkup/tarball/master#egg=postmarkup
 https://github.com/frol/postmarkup/tarball/master#egg=postmarkup
 https://github.com/jshipley/Flask-WhooshAlchemy/archive/master.zip#egg=Flask-Whooshalchemy
 https://github.com/jshipley/Flask-WhooshAlchemy/archive/master.zip#egg=Flask-Whooshalchemy