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

Merge branch 'master' into bulk-actions

sh4nks 10 лет назад
Родитель
Сommit
1998d1e65c
35 измененных файлов с 6182 добавлено и 457 удалено
  1. 1 1
      flaskbb/auth/views.py
  2. 66 17
      flaskbb/forum/models.py
  3. 3 3
      flaskbb/forum/views.py
  4. 44 14
      flaskbb/management/forms.py
  5. 21 14
      flaskbb/management/views.py
  6. 3 2
      flaskbb/message/forms.py
  7. 2 3
      flaskbb/message/models.py
  8. 7 0
      flaskbb/plugins/portal/__init__.py
  9. 2 1
      flaskbb/plugins/portal/views.py
  10. 4 0
      flaskbb/static/css/flaskbb.css
  11. 114 0
      flaskbb/static/css/megalist-multiselect.css
  12. BIN
      flaskbb/static/img/megalist-icons.png
  13. 1174 0
      flaskbb/static/js/megalist-multiselect.js
  14. 828 0
      flaskbb/static/js/modernizr.custom.js
  15. 125 0
      flaskbb/static/less/megalist-multiselect.less
  16. 1 1
      flaskbb/templates/forum/search_result.html
  17. 1 1
      flaskbb/templates/layout.html
  18. 5 0
      flaskbb/templates/macros.html
  19. 20 0
      flaskbb/templates/management/forum_form.html
  20. 9 1
      flaskbb/templates/management/management_layout.html
  21. 1 1
      flaskbb/themes/bootstrap2/templates/layout.html
  22. 3 1
      flaskbb/themes/bootstrap3/templates/layout.html
  23. 375 381
      flaskbb/translations/messages.pot
  24. 1608 0
      flaskbb/translations/zh_CN/LC_MESSAGES/messages.po
  25. 1615 0
      flaskbb/translations/zh_TW/LC_MESSAGES/messages.po
  26. 21 0
      flaskbb/user/models.py
  27. 48 0
      flaskbb/utils/decorators.py
  28. 5 1
      flaskbb/utils/markup.py
  29. 6 4
      flaskbb/utils/populate.py
  30. 31 0
      flaskbb/utils/widgets.py
  31. 4 5
      manage.py
  32. 31 0
      migrations/versions/127be3fb000_added_m2m_forumgroups_table.py
  33. 1 1
      migrations/versions/8ad96e49dc6_init.py
  34. 2 1
      tests/fixtures/forum.py
  35. 1 4
      tests/unit/test_forum_models.py

+ 1 - 1
flaskbb/auth/views.py

@@ -80,7 +80,7 @@ def register():
     """
     """
 
 
     if current_user is not None and current_user.is_authenticated():
     if current_user is not None and current_user.is_authenticated():
-        return redirect(url_for("user.profile"))
+        return redirect(url_for("user.profile", username=current_user.username))
 
 
     if current_app.config["RECAPTCHA_ENABLED"]:
     if current_app.config["RECAPTCHA_ENABLED"]:
         from flaskbb.auth.forms import RegisterRecaptchaForm
         from flaskbb.auth.forms import RegisterRecaptchaForm

+ 66 - 17
flaskbb/forum/models.py

@@ -11,8 +11,10 @@
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
 
 
 from flask import url_for, abort
 from flask import url_for, abort
+from sqlalchemy.orm import aliased
 
 
 from flaskbb.extensions import db
 from flaskbb.extensions import db
+from flaskbb.utils.decorators import can_access_forum, can_access_topic
 from flaskbb.utils.helpers import slugify, get_categories_and_forums, \
 from flaskbb.utils.helpers import slugify, get_categories_and_forums, \
     get_forums
     get_forums
 from flaskbb.utils.database import CRUDMixin
 from flaskbb.utils.database import CRUDMixin
@@ -38,6 +40,24 @@ topictracker = db.Table(
               nullable=False))
               nullable=False))
 
 
 
 
+# m2m table for group-forum permission mapping
+forumgroups = db.Table(
+    'forumgroups',
+    db.Column(
+        'group_id',
+        db.Integer(),
+        db.ForeignKey('groups.id'),
+        nullable=False
+    ),
+    db.Column(
+        'forum_id',
+        db.Integer(),
+        db.ForeignKey('forums.id', use_alter=True, name="fk_forum_id"),
+        nullable=False
+    )
+)
+
+
 class TopicsRead(db.Model, CRUDMixin):
 class TopicsRead(db.Model, CRUDMixin):
     __tablename__ = "topicsread"
     __tablename__ = "topicsread"
 
 
@@ -302,6 +322,12 @@ class Topic(db.Model, CRUDMixin):
         """
         """
         return "<{} {}>".format(self.__class__.__name__, self.id)
         return "<{} {}>".format(self.__class__.__name__, self.id)
 
 
+    @classmethod
+    @can_access_topic
+    def get_topic(cls, topic_id, user):
+        topic = Topic.query.filter_by(id=topic_id).first_or_404()
+        return topic
+
     def tracker_needs_update(self, forumsread, topicsread):
     def tracker_needs_update(self, forumsread, topicsread):
         """Returns True if the topicsread tracker needs an update.
         """Returns True if the topicsread tracker needs an update.
         Also, if the ``TRACKER_LENGTH`` is configured, it will just recognize
         Also, if the ``TRACKER_LENGTH`` is configured, it will just recognize
@@ -548,15 +574,28 @@ class Forum(db.Model, CRUDMixin):
     last_post_created = db.Column(db.DateTime, default=datetime.utcnow())
     last_post_created = db.Column(db.DateTime, default=datetime.utcnow())
 
 
     # One-to-many
     # One-to-many
-    topics = db.relationship("Topic", backref="forum", lazy="dynamic",
-                             cascade="all, delete-orphan")
+    topics = db.relationship(
+        "Topic",
+        backref="forum",
+        lazy="dynamic",
+        cascade="all, delete-orphan"
+    )
 
 
     # Many-to-many
     # Many-to-many
-    moderators = \
-        db.relationship("User", secondary=moderators,
-                        primaryjoin=(moderators.c.forum_id == id),
-                        backref=db.backref("forummoderator", lazy="dynamic"),
-                        lazy="joined")
+    moderators = db.relationship(
+        "User",
+        secondary=moderators,
+        primaryjoin=(moderators.c.forum_id == id),
+        backref=db.backref("forummoderator", lazy="dynamic"),
+        lazy="joined"
+    )
+    groups = db.relationship(
+        "Group",
+        secondary=forumgroups,
+        primaryjoin=(forumgroups.c.forum_id == id),
+        backref="forumgroups",
+        lazy="joined",
+    )
 
 
     # Properties
     # Properties
     @property
     @property
@@ -671,22 +710,31 @@ class Forum(db.Model, CRUDMixin):
         # topicsread
         # topicsread
         return False
         return False
 
 
-    def save(self, moderators=None):
+    def save(self, moderators=None, groups=None):
         """Saves a forum
         """Saves a forum
 
 
         :param moderators: If given, it will update the moderators in this
         :param moderators: If given, it will update the moderators in this
                            forum with the given iterable of user objects.
                            forum with the given iterable of user objects.
-       """
-        if moderators is not None:
-            for moderator in self.moderators:
-                self.moderators.remove(moderator)
-            db.session.commit()
+	:param groups: A list with group objects."""
+        """
+        if self.id:
+            db.session.merge(self)
+        else:
+            if groups is None:
+                # importing here because of circular dependencies
+                from flaskbb.user.models import Group
+                self.groups = Group.query.order_by(Group.name.asc()).all()
+            db.session.add(self)
 
 
-            for moderator in moderators:
-                if moderator:
-                    self.moderators.append(moderator)
+	    if moderators is not None:
+	        for moderator in self.moderators:
+	            self.moderators.remove(moderator)
+	        db.session.commit()
+
+	        for moderator in moderators:
+	            if moderator:
+	                self.moderators.append(moderator)
 
 
-        db.session.add(self)
         db.session.commit()
         db.session.commit()
         return self
         return self
 
 
@@ -729,6 +777,7 @@ class Forum(db.Model, CRUDMixin):
 
 
     # Classmethods
     # Classmethods
     @classmethod
     @classmethod
+    @can_access_forum
     def get_forum(cls, forum_id, user):
     def get_forum(cls, forum_id, user):
         """Returns the forum and forumsread object as a tuple for the user.
         """Returns the forum and forumsread object as a tuple for the user.
 
 

+ 3 - 3
flaskbb/forum/views.py

@@ -101,7 +101,7 @@ def view_topic(topic_id, slug=None):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
     # Fetch some information about the topic
     # Fetch some information about the topic
-    topic = Topic.query.filter_by(id=topic_id).first()
+    topic = Topic.get_topic(topic_id=topic_id, user=current_user)
 
 
     # Count the topic views
     # Count the topic views
     topic.views += 1
     topic.views += 1
@@ -155,7 +155,7 @@ def view_post(post_id):
 def new_topic(forum_id, slug=None):
 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_instance):
         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)
@@ -402,7 +402,7 @@ def reply_post(topic_id, post_id):
             form.save(current_user, topic)
             form.save(current_user, topic)
             return redirect(post.topic.url)
             return redirect(post.topic.url)
     else:
     else:
-        form.content.data = format_quote(post)
+        form.content.data = format_quote(post.username, post.content)
 
 
     return render_template("forum/new_post.html", topic=post.topic, form=form)
     return render_template("forum/new_post.html", topic=post.topic, form=form)
 
 

+ 44 - 14
flaskbb/management/forms.py

@@ -10,15 +10,17 @@
 """
 """
 from flask_wtf import Form
 from flask_wtf import Form
 from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
 from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
-                     BooleanField, SelectField, SubmitField)
+                     BooleanField, SelectField, SubmitField,
+		     HiddenField)
 from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
 from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
                                 URL, ValidationError)
                                 URL, ValidationError)
 from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
 from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
                                            QuerySelectMultipleField)
                                            QuerySelectMultipleField)
+from sqlalchemy.orm.session import make_transient, make_transient_to_detached
 from flask_babelex import lazy_gettext as _
 from flask_babelex import lazy_gettext as _
 
 
 from flaskbb.utils.fields import BirthdayField
 from flaskbb.utils.fields import BirthdayField
-from flaskbb.utils.widgets import SelectBirthdayWidget
+from flaskbb.utils.widgets import SelectBirthdayWidget, MultiSelect
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.forum.models import Forum, Category
 from flaskbb.forum.models import Forum, Category
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
@@ -36,6 +38,10 @@ def selectable_categories():
     return Category.query.order_by(Category.position)
     return Category.query.order_by(Category.position)
 
 
 
 
+def selectable_groups():
+    return Group.query.order_by(Group.id.asc()).all()
+
+
 def select_primary_group():
 def select_primary_group():
     return Group.query.filter(Group.guest != True).order_by(Group.id)
     return Group.query.filter(Group.guest != True).order_by(Group.id)
 
 
@@ -310,6 +316,14 @@ class ForumForm(Form):
         description=_("Disable new posts and topics in this forum.")
         description=_("Disable new posts and topics in this forum.")
     )
     )
 
 
+    groups = QuerySelectMultipleField(
+        _("Group Access to Forum"),
+        query_factory=selectable_groups,
+        get_label="name",
+        widget=MultiSelect(),
+        description=_("Select user groups that can access this forum.")
+    )
+
     submit = SubmitField(_("Save"))
     submit = SubmitField(_("Save"))
 
 
     def validate_external(self, field):
     def validate_external(self, field):
@@ -354,29 +368,45 @@ class ForumForm(Form):
         else:
         else:
             field.data = approved_moderators
             field.data = approved_moderators
 
 
-    def save(self):
-        forum = Forum(title=self.title.data,
-                      description=self.description.data,
-                      position=self.position.data,
-                      external=self.external.data,
-                      show_moderators=self.show_moderators.data,
-                      locked=self.locked.data)
-
-        if self.moderators.data:
-            # is already validated
-            forum.moderators = self.moderators.data
+    def validate_groups(self, field):
 
 
-        forum.category_id = self.category.data.id
+        if field.data:
+            pass
+        elif field.raw_data:
+            ids = field.raw_data.pop().split(",")
+            groups = Group.query.filter(Group.id.in_(ids)).all()
+            field.data = groups
+        else:
+            field.data = []
 
 
+    def save(self):
+        data = self.data
+        # remove the button
+        data.pop('submit', None)
+        forum = Forum(**data)
         return forum.save()
         return forum.save()
 
 
 
 
 class EditForumForm(ForumForm):
 class EditForumForm(ForumForm):
+
+    id = HiddenField()
+
     def __init__(self, forum, *args, **kwargs):
     def __init__(self, forum, *args, **kwargs):
         self.forum = forum
         self.forum = forum
         kwargs['obj'] = self.forum
         kwargs['obj'] = self.forum
         ForumForm.__init__(self, *args, **kwargs)
         ForumForm.__init__(self, *args, **kwargs)
 
 
+    def save(self):
+        data = self.data
+        # remove the button
+        data.pop('submit', None)
+        forum = Forum(**data)
+        # flush SQLA info from created instance so that it can be merged
+        make_transient(forum)
+        make_transient_to_detached(forum)
+
+        return forum.save()
+
 
 
 class AddForumForm(ForumForm):
 class AddForumForm(ForumForm):
     pass
     pass

+ 21 - 14
flaskbb/management/views.py

@@ -512,9 +512,7 @@ def edit_forum(forum_id):
 
 
     form = EditForumForm(forum)
     form = EditForumForm(forum)
     if form.validate_on_submit():
     if form.validate_on_submit():
-        form.populate_obj(forum)
-        forum.save(moderators=form.moderators.data)
-
+        form.save()
         flash(_("Forum successfully updated."), "success")
         flash(_("Forum successfully updated."), "success")
         return redirect(url_for("management.edit_forum", forum_id=forum.id))
         return redirect(url_for("management.edit_forum", forum_id=forum.id))
     else:
     else:
@@ -554,6 +552,7 @@ def add_forum(category_id=None):
         flash(_("Forum successfully added."), "success")
         flash(_("Forum successfully added."), "success")
         return redirect(url_for("management.forums"))
         return redirect(url_for("management.forums"))
     else:
     else:
+        form.groups.data = Group.query.order_by(Group.id.asc()).all()
         if category_id:
         if category_id:
             category = Category.query.filter_by(id=category_id).first()
             category = Category.query.filter_by(id=category_id).first()
             form.category.data = category
             form.category.data = category
@@ -626,13 +625,20 @@ def enable_plugin(plugin):
 
 
         disabled_file = os.path.join(plugin_dir, "DISABLED")
         disabled_file = os.path.join(plugin_dir, "DISABLED")
 
 
-        os.remove(disabled_file)
+        try:
+            if os.path.exists(disabled_file):
+                os.remove(disabled_file)
+                flash(_("Plugin is enabled. Please reload your app."),
+                      "success")
+            else:
+                flash(_("Plugin is already enabled. Please reload  your app."),
+                      "warning")
 
 
-        flash(_("Plugin is enabled. Please reload your app."), "success")
+        except OSError:
+            flash(_("If you are using a host which doesn't support writting "
+                    "on the disk, this won't work - than you need to delete "
+                    "the 'DISABLED' file by yourself."), "danger")
 
 
-        flash(_("If you are using a host which doesn't support writting on the "
-                "disk, this won't work - than you need to delete the "
-                "'DISABLED' file by yourself."), "info")
     else:
     else:
         flash(_("Couldn't enable Plugin."), "danger")
         flash(_("Couldn't enable Plugin."), "danger")
 
 
@@ -655,13 +661,14 @@ def disable_plugin(plugin):
 
 
     disabled_file = os.path.join(plugin_dir, "DISABLED")
     disabled_file = os.path.join(plugin_dir, "DISABLED")
 
 
-    open(disabled_file, "a").close()
-
-    flash(_("Plugin is disabled. Please reload your app."), "success")
+    try:
+        open(disabled_file, "a").close()
+        flash(_("Plugin is disabled. Please reload your app."), "success")
 
 
-    flash(_("If you are using a host which doesn't "
-            "support writting on the disk, this won't work - than you need to "
-            "create a 'DISABLED' file by yourself."), "info")
+    except OSError:
+        flash(_("If you are using a host which doesn't "
+                "support writting on the disk, this won't work - than you "
+                "need to create a 'DISABLED' file by yourself."), "info")
 
 
     return redirect(url_for("management.plugins"))
     return redirect(url_for("management.plugins"))
 
 

+ 3 - 2
flaskbb/message/forms.py

@@ -47,9 +47,10 @@ class ConversationForm(Form):
             shared_id=shared_id,
             shared_id=shared_id,
             from_user_id=from_user,
             from_user_id=from_user,
             to_user_id=to_user,
             to_user_id=to_user,
-            user_id=user_id
+            user_id=user_id,
+            unread=unread
         )
         )
-        message = Message(message=self.message.data)
+        message = Message(message=self.message.data, user_id=from_user)
         return conversation.save(message=message)
         return conversation.save(message=message)
 
 
 
 

+ 2 - 3
flaskbb/message/models.py

@@ -91,12 +91,11 @@ class Message(db.Model, CRUDMixin):
     def save(self, conversation=None):
     def save(self, conversation=None):
         """Saves a private message.
         """Saves a private message.
 
 
-        :param conversation_id: The id of the conversation to which the message
-                                belongs to.
+        :param conversation: The  conversation to which the message
+                             belongs to.
         """
         """
         if conversation is not None:
         if conversation is not None:
             self.conversation_id = conversation.id
             self.conversation_id = conversation.id
-            self.user_id = conversation.from_user_id
             self.date_created = datetime.utcnow()
             self.date_created = datetime.utcnow()
 
 
         db.session.add(self)
         db.session.add(self)

+ 7 - 0
flaskbb/plugins/portal/__init__.py

@@ -27,6 +27,13 @@ fixture = (
                 'description':  "The forum ids from which forums the posts should be displayed on the portal.",
                 'description':  "The forum ids from which forums the posts should be displayed on the portal.",
                 'extra': {"choices": available_forums, "coerce": int}
                 'extra': {"choices": available_forums, "coerce": int}
             }),
             }),
+            ('plugin_portal_recent_topics', {
+                'value':        10,
+                'value_type':   "integer",
+                'name':         "Number of Recent Topics",
+                'description':  "The number of topics in Recent Topics portlet.",
+                'extra': {"min": 1},
+            }),
         ),
         ),
     }),
     }),
 )
 )

+ 2 - 1
flaskbb/plugins/portal/views.py

@@ -30,7 +30,8 @@ def index():
         order_by(Topic.id.desc()).\
         order_by(Topic.id.desc()).\
         paginate(page, flaskbb_config["TOPICS_PER_PAGE"], True)
         paginate(page, flaskbb_config["TOPICS_PER_PAGE"], True)
 
 
-    recent_topics = Topic.query.order_by(Topic.last_updated.desc()).limit(5)
+    recent_topics = Topic.query.order_by(Topic.last_updated.desc()).limit(
+                            flaskbb_config.get("PLUGIN_PORTAL_RECENT_TOPICS", 10))
 
 
     user_count = User.query.count()
     user_count = User.query.count()
     topic_count = Topic.query.count()
     topic_count = Topic.query.count()

+ 4 - 0
flaskbb/static/css/flaskbb.css

@@ -391,3 +391,7 @@ margin-bottom: 0px;
 .conversation-list .conversation-date {
 .conversation-list .conversation-date {
     color: #999999;
     color: #999999;
 }
 }
+
+.spacer-megalist{
+    padding-top: 20px;
+}

+ 114 - 0
flaskbb/static/css/megalist-multiselect.css

@@ -0,0 +1,114 @@
+.megalist-mutliselect {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  overflow: hidden;
+}
+.megalist-mutliselect .megalist {
+  float: left;
+  width: 285px;
+}
+.megalist-mutliselect .megalist input[type=text] {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  -ms-box-sizing: border-box;
+  box-sizing: border-box;
+  width: 100% !important;
+  padding: 5px 4px;
+  margin-bottom: 5px;
+}
+.megalist-mutliselect .megalist .megalist-inner {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  -ms-box-sizing: border-box;
+  box-sizing: border-box;
+  position: relative;
+  height: 205px;
+  width: 100%;
+  overflow: hidden;
+  border: 1px solid silver;
+}
+.megalist-mutliselect .megalist .megalist-inner ul {
+  position: absolute;
+  padding: 0;
+  display: block;
+  margin-top: 0px;
+  width: 100%;
+  top: 0px;
+}
+.megalist-mutliselect .megalist .megalist-inner ul li {
+  margin: 0;
+  border: none;
+  white-space: nowrap;
+  overflow: hidden;
+  padding: 6px 0px 6px 10px !important;
+  display: block;
+  position: absolute;
+  width: 100%;
+  border-top: 1px solid #EDEDED;
+  line-height: 1em !important;
+  cursor: pointer;
+  color: #555;
+}
+.megalist-mutliselect .megalist .megalist-inner ul li:hover {
+  background-color: #08c;
+}
+.megalist-mutliselect .megalist .scrollbar {
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  position: absolute;
+  right: 1px;
+  width: 11px;
+  height: 25px;
+  background-color: #bebebe;
+  z-index: 2;
+}
+.megalist-mutliselect .megalist .scrollbar:hover {
+  background-color: #afafaf;
+}
+.megalist-mutliselect .megalist .scrollbar-background {
+  position: absolute;
+  top: 0px;
+  right: 0px;
+  width: 14px;
+  height: 100%;
+  z-index: 1;
+  background-color: #ececec;
+}
+.megalist-mutliselect .move-buttons {
+  margin: 90px 0px;
+  float: left;
+}
+.megalist-mutliselect .move-buttons .move-button {
+  height: 30px;
+  padding: 0px 50px;
+  margin-bottom: 10px;
+  cursor: pointer;
+}
+.megalist-mutliselect .move-buttons .move-button svg {
+  fill: #0374bb;
+}
+.megalist-mutliselect .move-buttons .move-button:hover svg {
+  fill: #024570;
+}
+.megalist-mutliselect .move-buttons .move-button:hover.arrow-left.no-svg .svg {
+  background-position: 0% 100%;
+}
+.megalist-mutliselect .move-buttons .move-button:hover.arrow-right.no-svg .svg {
+  background-position: 100% 100%;
+}
+.megalist-mutliselect .move-buttons .move-button.no-svg .svg {
+  background-repeat: no-repeat;
+  background-position: center;
+  background: url('../icons/megalist-icons.png');
+  width: 32px;
+  height: 32px;
+}
+.megalist-mutliselect .move-buttons .move-button.arrow-left.no-svg .svg {
+  background-position: 0% 0%;
+}
+.megalist-mutliselect .move-buttons .move-button.arrow-right.no-svg .svg {
+  background-position: 100% 0%;
+}

BIN
flaskbb/static/img/megalist-icons.png


+ 1174 - 0
flaskbb/static/js/megalist-multiselect.js

@@ -0,0 +1,1174 @@
+!function($){
+  'use strict';
+
+ /* LIST CLASS DEFINITION
+  * ========================= */
+
+  var Megalist = function(element, $parent) {
+    var html;
+
+    if ($parent === undefined){
+    //if there's no $parent then we are creating one
+        this.$el = element;
+        this.setOptions(this.$el.options);
+        // build HTML
+        html = this.buildParentDOM();
+        //source list - data to choose from
+        this.$el.sourceList = new Megalist(html.srcElement, this.$el);
+        //destination list - data chosen by user
+        this.$el.destinationList = new Megalist(html.dstElement, this.$el);
+
+    } else {
+    //else just init one of the megalistSide children
+        this.init(element, $parent);
+    }
+    return this;
+  };
+
+  Megalist.prototype = {
+
+    constructor: Megalist,
+
+    /**
+     * megalistSide constructor - initializes one side of megalist
+     *
+     * @param {object} element - jQuery object on witch megalist is initialized
+     * @param {object} $parent - optional jQuery object with parent for
+     *                           megalistSide initialization only
+     * @return {object} - returns self
+     */
+    init: function(element, $parent) {
+        this.$el = element;
+        this.$parent = $parent;
+
+        //defaults
+        this.processedItems = {};
+        this.totalItems = [];
+        this.itemHeight = -1;
+        this.listItems = $();
+        this.suffix = undefined;
+        this.yPosition = 0;
+        this.filteredData = [];
+        this.pageHeight = 0;
+        this.scrollingActive = false;
+
+        //init widget
+        this.setOptions(this.$parent.options);
+        this.getSuffix();
+        this.buildDOM();
+        this.bindEvents();
+        this.bindData();
+        this.updateLayout();
+        this.generatePOST(this.conf.BUILD_FULL_POST);
+
+        return this;
+    },
+
+    /**
+     * Sets default options and extends them if configuration was provided on
+     * megalist initialization
+     *
+     * @param {object} options - object containing options for megalist
+     */
+    setOptions: function(options){
+        var conf = {};
+
+        // mimimum scrollbar height in pixels
+        conf.SCROLLBAR_MIN_SIZE = 12;
+        // inertial delay for megalist ui update after resize event occurs
+        conf.RESIZE_TIMEOUT_DELAY = 100;
+        // minimum characters to trigger quicksearch filtering
+        conf.MINIMUM_SEARCH_QUERY_SIZE = 3;
+        // build full or simple (comma separated ids) post
+        conf.BUILD_FULL_POST = false;
+        // move action event name to trigger
+        conf.MOVE_ACTION_NAME = 'move';
+        //functional suffixes for multiselect: destination list suffix
+        conf.DESTINATION_SUFFIX = 'dst';
+        // functional suffixes for multiselect: source list suffix
+        conf.SOURCE_SUFFIX = 'src';
+        // text to display as search input placeholder
+        conf.PLACEHOLDER_TEXT = 'Search';
+        // time to wait for first continous scrolling
+        conf.CONTINOUS_SCROLLING_FIRST_INTERVAL = 500;
+        // time to wait for every next continous scrolling
+        conf.CONTINOUS_SCROLLING_INTERVAL = 60;
+
+        if (typeof options === 'object'){
+            conf = $.extend(conf, options);
+        }
+        this.conf = conf;
+    },
+
+    /**
+     * Builds required html elements for both source and destination
+     * megalistSide and append them to parent element
+     */
+    buildParentDOM: function() {
+        var srcElement, dstElement;
+
+        this.$el.html('');
+        this.$el.addClass('megalist-mutliselect');
+
+        //create 2 containers for megalists and buttons between then append
+        srcElement = $( '<div/>', {
+            'id': this.$el.attr('id') + '_' + this.conf.SOURCE_SUFFIX,
+            'class': 'megalist-inner'
+        });
+        dstElement = $( '<div/>', {
+            'id': this.$el.attr('id') + '_' + this.conf.DESTINATION_SUFFIX,
+            'class': 'megalist-inner'
+        });
+        this.$el.$moveButtons = $( '<div/>', {
+            'class': 'move-buttons'
+        });
+
+        this.$el.append(srcElement, this.$el.$moveButtons, dstElement);
+
+        return {srcElement:srcElement, dstElement:dstElement};
+    },
+
+    /**
+     * Builds required html elements for megalistSide:
+     * searchbox, scrollbar, move button and hidden result input
+     */
+    buildDOM: function() {
+        var arrowIcon = 'arrow-left';
+
+        if (this.suffix === this.conf.SOURCE_SUFFIX) {
+            arrowIcon = 'arrow-right';
+        }
+
+        this.$el.wrap('<div class="megalist"></div>"');
+
+        this.$search = $('<input/>', {
+            'id': this.$el.attr('id') + '_search',
+            'placeholder': this.conf.PLACEHOLDER_TEXT,
+            'type': 'text'
+        });
+        this.$scrollbar = $('<div/>', {
+            'id': this.$el.attr('id') + '_scrollbar',
+            'class': 'scrollbar'
+        });
+        this.$scrollbarBackground = $('<div/>', {
+            'class': 'scrollbar-background'
+        });
+        this.$moveall = $('<div/>', {
+            'class': 'move-button ' + arrowIcon
+        });
+
+        if (Modernizr.svg) {
+            this.$moveall.append($(
+                '<svg width="32" height="32" viewBox="0 0 64 64">' +
+                '<use xlink:href="#' + arrowIcon + '"></svg>'
+            ));
+        } else {
+            this.$moveall.addClass('no-svg');
+            this.$moveall.append($('<div class="svg" />'));
+        }
+
+        //attach to container in parent
+        this.$parent.$moveButtons.append(this.$moveall);
+
+        this.$input = $('<input/>', {
+            'name': this.name,
+            'type': 'hidden'
+        });
+        this.$ul = $('<ul />');
+
+        this.$el.before(this.$search);
+
+        // Set tabindex, so the element can be in focus
+        this.$el.attr('tabindex', '-1');
+    },
+
+    /**
+     * Resolves suffix for megalistSide so that it know if it's source or
+     * destination side. Resolving is based on id of the container
+     */
+    getSuffix: function() {
+        var id_tokens, lastToken;
+
+        id_tokens = this.$el.attr('id').split('_');
+        lastToken = id_tokens[id_tokens.length - 1];
+        this.name = id_tokens.splice(id_tokens, id_tokens.length-1).join('_');
+
+        if (lastToken === this.conf.SOURCE_SUFFIX) {
+            this.suffix = this.conf.SOURCE_SUFFIX;
+        } else if (lastToken === this.conf.DESTINATION_SUFFIX) {
+            this.suffix = this.conf.DESTINATION_SUFFIX;
+        }
+    },
+
+    /**
+     * Returns targetList for current megalistSide. In not defined, gets proper
+     * one from $parent first and stores it for later use
+     *
+     * @return {object} - returns target list
+     */
+    getTargetList: function() {
+        if (!(this.targetList instanceof Object)){
+            if ( this.suffix === this.conf.SOURCE_SUFFIX) {
+                this.targetList = this.$parent.destinationList;
+            } else if ( this.suffix === this.conf.DESTINATION_SUFFIX) {
+                this.targetList = this.$parent.sourceList;
+            }
+        }
+        return this.targetList;
+    },
+
+    /**
+     * Binds all events need by the widget
+     */
+    bindEvents: function() {
+        var self = this,
+            filterEvent;
+
+       $(window).resize(function(event){
+            return self.onResize(event);
+        });
+
+        $(window).bind('keydown', function(event) {
+            return self.onKeydown(event);
+        });
+
+        this.$el.mousedown(function() {
+            setTimeout(function(){
+                this.focus();
+            }, 1);
+        });
+
+        this.$el.bind('mousewheel DOMMouseScroll', function(event) {
+            event.preventDefault();
+            return self.onMouseWheel(event);
+        });
+
+        this.$el.click(function(event) {
+            self.processListClick(event);
+        });
+
+        this.$scrollbar.bind('mousedown', function(event) {
+             self.onScrollbarStart(event);
+        });
+
+        this.$scrollbarBackground.mousedown(function(event) {
+            self.scrollingActive = true;
+            self.scrollDir = undefined;
+            self.onScrollbarBackgroundClick(event);
+        });
+
+        this.$scrollbarBackground.mouseleave(function(event) {
+            self.scrollingActive = false;
+        });
+
+        this.$scrollbarBackground.mouseup(function(event) {
+            self.scrollingActive = false;
+        });
+
+        this.$moveall.click(function(event) {
+            self.onMoveAll(event);
+        });
+
+        if (Modernizr.hasEvent('input', this.$search)) {
+            filterEvent = 'input';
+        } else {
+            filterEvent = 'keyup';
+        }
+
+        this.$search.on(filterEvent, function() {
+            self.yPosition = 0;
+            self.filterList();
+        });
+    },
+
+    /**
+     * Extracts the supplied data for megalistSide from data-provider-src or
+     * data-provider-dst attributes depending which side is being loaded.
+     * The attributes must be set on megalist container.
+     */
+    bindData: function() {
+        this.origData = this.$parent.attr('data-provider-' + this.suffix);
+        if (this.origData.length){
+            this.dataProviderOrig =  this.parseData(this.origData);
+            this.$parent.attr('data-provider-' + this.suffix, '');
+        } else {
+            this.dataProviderOrig = {};
+        }
+
+        this.dataProvider = this.dataProviderOrig;
+
+        this.clearSelectedIndex();
+
+        this.$ul.find('li').each(function() {
+            $(this).remove();
+        });
+
+        this.yPosition = 0;
+    },
+
+    /**
+     * Parses the data extracted from container attribues. Currently two
+     * formats are supported: JSON and passing old <select> element that
+     * is being replaced by this widget
+     *
+     * @param {string} origData - string extracted from attribute
+     *                            (JSON or old select html)
+     * @return {Array} parsed - parsed data array
+     */
+    parseData: function(origData){
+        var parsed = [], item = {};
+        var selected = ':not(:selected)';
+
+        //first see if it's JSON
+        try {
+          parsed = $.parseJSON(origData);
+        } catch(e) {
+          //not JSON
+        }
+        //ok, maybe it's being fed <option>s from an old select?
+        if (origData.substr(0, 7) == '<select'){
+          if (this.suffix === this.conf.DESTINATION_SUFFIX) {
+              selected = ':selected';
+          }
+           $.map($('option', origData).filter(selected), function(opt){
+               item.listValue = opt.value;
+               item.label = opt.text;
+               parsed.push(item);
+               item = {};
+           });
+        } else if ((origData.indexOf('<select') > -1)){
+            console.log('ERROR: the supplied string MUST start with <select');
+        }
+
+        return parsed;
+    },
+
+    /**
+     * Updates responsive mutliselect on window resize by recalculating new
+     * sizing and redrawing megalistSide widgets. Updating has some inertia
+     * added resizing only after RESIZE_TIMEOUT_DELAY is reached
+     */
+    onResize: function() {
+        clearTimeout(this.reizeTimeout);
+        var self = this,
+            totalHeight = this.dataProvider.length * this.itemHeight,
+            maxPosition = totalHeight - this.$el.height();
+
+        maxPosition = Math.max(0, maxPosition);
+        this.yPosition = Math.min(this.yPosition, maxPosition);
+        this.reizeTimeout = setTimeout(function() {
+            self.updateLayout();
+        }, this.conf.RESIZE_TIMEOUT_DELAY);
+    },
+
+    /**
+    * @TODO - @FIXME
+    * @param {event} event - user key press event
+    */
+    onKeydown: function (event) {
+        var delta = 0,
+            action = this.conf.MOVE_ACTION_NAME,
+            self = this,
+            oldindex = this.getSelectedIndex(),
+            index = oldindex + delta;
+
+        if (!this.$el.is(':focus')) {
+            return;
+        }
+
+        switch (event.which) {
+            case 33:  // Page up
+                delta = -1 * Math.floor(this.$el.height() / this.itemHeight);
+                break;
+
+            case 34:  // Page down
+                delta = Math.floor(this.$el.height() / this.itemHeight);
+                break;
+
+            case 38:  // Up
+                delta = -1;
+                break;
+
+            case 40:  // Down
+                delta = 1;
+                break;
+
+            default:
+                return;
+        }
+
+        if (index > this.dataProvider.length -1) {
+            index = this.dataProvider.length;
+        }
+        if (index < 0) {
+            index = 0;
+        }
+
+        if (index === oldindex) {
+            return false;
+        }
+
+        this.setSelectedIndex(index);
+
+        if (this.yPosition > (index * this.itemHeight)) {
+            this.yPosition = (index*this.itemHeight);
+        }
+        if (this.yPosition < ((index+1) * this.itemHeight) - this.$el.height()) {
+            this.yPosition = ((index+1)*this.itemHeight) - this.$el.height();
+        }
+
+        this.updateLayout();
+        this.cleanupTimeout = setTimeout(function() {
+            self.cleanupListItems();
+        }, 100);
+
+        var target = this.$ul.find('.megalistSelected');
+
+        setTimeout(function() {
+            var event = $.Event(action, data),
+                data = {
+                    selectedIndex: index,
+                    srcElement: $(target),
+                    item: self.dataProvider[index],
+                    destination: self.$el.attr('id')
+                };
+            self.$el.trigger(event);
+        }, 150);
+
+        return false;
+    },
+
+    /**
+     * Updates megalistSide widget on mouse scroll event
+     * only concerned about vertical scroll
+     *
+     * @param {event} event - mouse wheel event
+     */
+    onMouseWheel: function (event) {
+        clearTimeout(this.cleanupTimeout);
+
+        var self = this,
+            orgEvent = event.originalEvent,
+            delta = 0,
+            totalHeight = this.dataProvider.length * this.itemHeight,
+            maxPosition = totalHeight - this.$el.height();
+
+        // Old school scrollwheel delta
+        if (orgEvent.wheelDelta) {
+            delta = orgEvent.wheelDelta / 120;
+        }
+        if (orgEvent.detail) {
+            delta = -orgEvent.detail / 3;
+        }
+
+        // Webkit
+        if ( orgEvent.wheelDeltaY !== undefined ) {
+            delta = orgEvent.wheelDeltaY / 120;
+        }
+
+        this.yPosition -= delta * this.itemHeight;
+
+        //limit the mouse wheel scroll area
+        if (this.yPosition > maxPosition) {
+            this.yPosition = maxPosition;
+        }
+        if (this.yPosition < 0) {
+            this.yPosition = 0;
+        }
+
+        this.updateLayout();
+        this.cleanupTimeout = setTimeout(function() {
+            self.cleanupListItems();
+        }, 100);
+
+        return false;
+    },
+
+    /**
+     * Handles click event on megalist element
+     *
+     * @param {event} event - mouse wheel event
+     */
+    processListClick: function(event) {
+        var self = this,
+            target = event.target,
+            index = $(target).attr('list-index'),
+            out_data = this.dataProvider[index],
+            clicked_value = this.dataProvider[index];
+
+        while (target.parentNode !== null) {
+            if (target.nodeName === 'LI') {
+                break;
+            }
+            target = target.parentNode;
+        }
+
+        if (target.nodeName !== 'LI') {
+            return false;
+        }
+
+        if (index === this.selectedIndex) {
+            return false;
+        }
+
+        this.setSelectedIndex(index);
+
+        this.getTargetList().updateDataProvider(out_data);
+
+        self.clearSelectedIndex();
+
+        self.dataProviderOrig.splice(
+            self.dataProviderOrig.indexOf(clicked_value), 1
+        );
+
+        if (this.yPosition > this.getMaxPosition()) {
+            this.yPosition -= this.itemHeight;
+        }
+
+        self.filterList();
+        this.$parent.destinationList.generatePOST(this.conf.BUILD_FULL_POST);
+
+        return true;
+    },
+
+    /**
+     * Handles click on "move all" button, move all items from one
+     * megalistSide to the other, renders POST result into hidden input field
+     * after the action is performed
+     *
+     */
+    onMoveAll: function(){
+        var out_data = this.dataProvider,
+            i;
+
+        this.getTargetList().updateDataProvider(out_data);
+
+        this.clearSelectedIndex();
+        this.dataProvider = [];
+        if (this.filteredData.length > 0) {
+            for (i = this.filteredData.length - 1; i >= 0; i--) {
+                this.dataProviderOrig.splice(this.filteredData[i], 1);
+            }
+        } else if (!this.searchingIsActive()) {
+            this.dataProviderOrig = [];
+        }
+        this.$parent.destinationList.generatePOST(this.conf.BUILD_FULL_POST);
+        this.updateLayout();
+    },
+
+    /**
+     * Handles drag event on scrollbar - binds events appropriate to user
+     * action and delgates event to correct function
+     *
+     * @param {event} event - mouse event on scrollbar
+     */
+    onScrollbarStart: function(event) {
+        var self = this;
+
+        this.unbindScrollbarEvents();
+        this.scrollbarInputCoordinates = this.getInputCoordinates(event);
+
+        $(document).bind('mousemove', function(event) {
+             self.onScrollbarMove(event);
+        });
+
+        $(document).bind('mouseup', function() {
+             self.unbindScrollbarEvents();
+        });
+
+        event.preventDefault();
+        return false;
+    },
+
+    /**
+     * Handles drag event on scroll bar and recalculates what items should be
+     * rendered in the viewport
+     *
+     * @param {event} event - scrollbar drag event to get coordinates from
+     */
+    onScrollbarMove: function(event) {
+        var newCoordinates = this.getInputCoordinates(event),
+            height = this.$el.height(),
+            totalHeight = this.dataProvider.length * this.itemHeight,
+            scrollbarHeight = this.$scrollbar.height(),
+            yDelta = this.scrollbarInputCoordinates.y - newCoordinates.y,
+            yPosition = parseInt(this.$scrollbar.css('top'), 10),
+            usingMinSize = scrollbarHeight === this.conf.SCROLLBAR_MIN_SIZE,
+            heightOffset = usingMinSize ? scrollbarHeight : 0,
+            newYPosition;
+
+        // valid move occurs only when pressing left mouse button
+        if (event.which !== 1) {
+            this.unbindScrollbarEvents();
+            return;
+        }
+
+        yPosition -= yDelta;
+
+        yPosition = Math.max(yPosition, 0);
+        yPosition = Math.min(yPosition, height - scrollbarHeight);
+        yPosition = Math.min(yPosition, height);
+
+        this.$scrollbar.css('top', yPosition);
+        this.scrollbarInputCoordinates = newCoordinates;
+
+        newYPosition = (
+            yPosition / (height - heightOffset) *
+            (this.itemHeight * this.dataProvider.length - 1)
+        );
+        newYPosition = Math.max(0, newYPosition);
+        newYPosition = Math.min(
+            newYPosition, totalHeight - height
+        );
+
+        this.yPosition = newYPosition;
+        this.updateLayout(true);
+
+        event.preventDefault();
+        return false;
+    },
+
+    /**
+     * Utility function to remove events bound to the scrollbar
+     *
+     */
+    unbindScrollbarEvents: function() {
+        $(document).unbind('mousemove');
+        $(document).unbind('mouseup');
+    },
+
+    /**
+     * Handles click event on scrollbar background - a click on scrollbar
+     * background should cause pageUp/PageDown action on the viewport
+     *
+     * @param {event} event - scrollbar click event to get coordinates from
+     */
+    onScrollbarBackgroundClick: function(event, repeatTimeout) {
+        var self = this,
+            // firefox uses originalEvent.layerY instead of offsetY
+            yOffset = event.offsetY !== undefined ? event.offsetY : event.originalEvent.layerY,
+            scrollbarBackgroundHeight = $(event.target).height(),
+            clickPos = yOffset / scrollbarBackgroundHeight,
+            listTotalHeight = this.dataProvider.length * this.itemHeight,
+            scrollbarHeightFraction = this.$scrollbar.height() / scrollbarBackgroundHeight,
+            currentPos = this.yPosition / listTotalHeight,
+            offsetToMove = this.pageHeight,
+            shouldMoveUp = clickPos > currentPos + scrollbarHeightFraction,
+            shouldMoveDown = clickPos < currentPos;
+
+        if (!this.scrollingActive) {
+            return;
+        }
+
+        if (this.scrollDir == undefined) {
+            if (shouldMoveUp) {
+                this.scrollDir = 1;
+            } else if (shouldMoveDown) {
+                this.scrollDir = -1;
+            } else {
+                return;
+            }
+        }
+
+        if (shouldMoveUp && this.scrollDir === 1) {
+            this.yPosition += offsetToMove;
+        } else if (shouldMoveDown && this.scrollDir === -1) {
+            this.yPosition -= offsetToMove;
+        } else {
+            return;
+        }
+
+        if (this.yPosition > listTotalHeight - this.pageHeight) {
+            this.yPosition = listTotalHeight - this.pageHeight;
+        } else if (this.yPosition < 0) {
+            this.yPosition = 0;
+        }
+
+        this.updateLayout();
+
+        if (this.scrollingActive) {
+            if (repeatTimeout === undefined) {
+                repeatTimeout = this.conf.CONTINOUS_SCROLLING_FIRST_INTERVAL;
+            }
+            setTimeout(function() {
+                self.onScrollbarBackgroundClick(
+                    event, self.conf.CONTINOUS_SCROLLING_INTERVAL
+                );
+            }, repeatTimeout);
+        }
+    },
+
+    /**
+     * Removes items rendered in megalist that no longer fit into the viewport
+     * and removes them from processed items cache
+     */
+    cleanupListItems: function() {
+        //remove any remaining LI elements hanging out on the dom
+        var temp = [],
+            item, index, x;
+
+        for (x = 0; x < this.totalItems.length; x++ ) {
+            item = this.totalItems[x];
+            index = item.attr('list-index');
+            if (this.processedItems[index] === undefined) {
+                item.remove();
+            }
+        }
+        //cleanup processedItems array
+        if (this.processedItems) {
+            for (index in this.processedItems) {
+                temp.push(this.processedItems[index]);
+            }
+        }
+        this.totalItems = temp;
+    },
+
+    /**
+     * Extracts input coordinates from the event
+     *
+     * @param {event} event - event to get coordinates from
+     * @return {object} result - object containge x and y coordinates
+     */
+    getInputCoordinates: function (event) {
+        var targetEvent = event,
+            result = {
+                x: Math.round(targetEvent.pageX),
+                y: Math.round(targetEvent.pageY)
+            };
+        return result;
+    },
+
+    /**
+     * Main rendering function for megalist: redraws the list based on data
+     * fed to megalist and scrollbar position. Iterates over visible items
+     * and renders them then calls update on the scrollbar if ignoreScrollbar
+     * not set to true
+     *
+     * @param {boolean} ignoreScrollbar - a flag allowing scrollbar to not be
+     * redrawn if not necessary
+     */
+    updateLayout: function(ignoreScrollbar) {
+        var height = this.$el.height(),
+            i = -1,
+            startPosition = Math.ceil(this.yPosition / this.itemHeight),
+            maxHeight = 2 * (height + (2 * this.itemHeight)),
+            index, item, currentPosition, parentLength;
+
+        if (this.dataProvider.length > 0) {
+            this.$ul.detach();
+            this.processedItems = {};
+
+            while (i * this.itemHeight < maxHeight) {
+                index = Math.min(
+                    Math.max(startPosition + i, 0),
+                    this.dataProvider.length
+                );
+
+                item = this.getItemAtIndex(index);
+                this.totalItems.push(item);
+
+                this.processedItems[index.toString()] = item;
+                currentPosition = i * this.itemHeight;
+                this.setItemPosition(item, 0, currentPosition);
+
+                if (item.parent().length <= 0) {
+                    this.$ul.append(item);
+
+                    if (this.itemHeight <= 0) {
+                        this.prepareLayout(item);
+                        this.updateLayout();
+                        return;
+                    }
+                }
+                i++;
+            }
+
+            this.cleanupListItems();
+            if (ignoreScrollbar !== true) {
+                this.updateScrollBar();
+            }
+            if (this.$scrollbar.parent().length > 0){
+                this.$scrollbar.before(this.$ul);
+            } else {
+                 this.$el.append(this.$ul);
+            }
+        } else {
+            if (this.$ul.children().length > 0) {
+                this.$ul.empty();
+                this.cleanupListItems();
+                parentLength = this.$scrollbar.parent().length > 0;
+                if (ignoreScrollbar !== true && parentLength > 0) {
+                    this.updateScrollBar();
+                }
+            } else {
+                this.hideScrollbar();
+            }
+        }
+    },
+
+    /**
+     * Prepares layout by appending list to DOM, and calculating site of
+     * element and size of single page
+     *
+     * @param  {object} item    jQuery object representing single item
+     */
+    prepareLayout: function(item) {
+        var itemsPerPage;
+
+        // make sure item have proper height by filling it with content
+        item.html('&nsbp;');
+        this.$el.append(this.$ul);
+
+        // calculate height of item and height of single page
+        this.itemHeight = item.outerHeight();
+        itemsPerPage = Math.floor(
+            this.$ul.parent().height() / this.itemHeight
+        );
+        this.pageHeight = this.itemHeight * itemsPerPage;
+    },
+
+    /**
+     * Shows scrollbar
+     */
+    showScrollbar: function() {
+        this.$el.append(this.$scrollbar, this.$scrollbarBackground);
+    },
+
+    /**
+     * Hides scrollbar
+     */
+    hideScrollbar: function() {
+        this.$scrollbar.detach();
+        this.$scrollbarBackground.detach();
+    },
+
+    /**
+     * Renders the scrollbar as a part of UI update when list is scrolled or
+     * modified
+     */
+    updateScrollBar: function() {
+        var height = this.$el.height(),
+            maxScrollbarHeight = height,
+            maxItemsHeight = this.dataProvider.length * this.itemHeight,
+            targetHeight = maxScrollbarHeight * Math.min(
+                maxScrollbarHeight / maxItemsHeight, 1
+            ),
+            actualHeight = Math.floor(
+                Math.max(targetHeight, this.conf.SCROLLBAR_MIN_SIZE)
+            ),
+            scrollPosition = (
+                this.yPosition / (maxItemsHeight - height) *
+                (maxScrollbarHeight - actualHeight)
+            ),
+            parent = this.$scrollbar.parent();
+
+        if (scrollPosition < 0) {
+            actualHeight = Math.max(actualHeight + scrollPosition, 0);
+            scrollPosition = 0;
+        } else if (scrollPosition > (height - actualHeight)) {
+            actualHeight = Math.min(actualHeight, height - scrollPosition);
+        }
+
+        this.$scrollbar.height(actualHeight);
+
+        if ((this.dataProvider.length * this.itemHeight) <= height) {
+            if (parent.length > 0) {
+                this.hideScrollbar();
+            }
+        } else {
+            if (parent.length <= 0) {
+                this.showScrollbar();
+            }
+            this.$scrollbar.css('top', scrollPosition);
+        }
+    },
+
+    /**
+     * Utility function to set offset css on an item
+     *
+     * @param {object} item - megalist element
+     * @param {int} x - x offset in pixels
+     * @param {int} y - y offset in pixels
+     */
+    setItemPosition: function(item, x, y) {
+        item.css('left', x);
+        item.css('top', y);
+    },
+
+    /**
+     * Gets megalist item at given index. Parses it to <li> item if necessary
+     *
+     * @param {int} i - object index
+     * @return {object} - jQuery object containing selected <li> element
+     */
+    getItemAtIndex: function(i) {
+        var item, iString, data;
+        if (this.dataProvider === this.listItems) {
+            item = $(this.listItems[i]);
+        }
+        else if (i !== undefined){
+            iString = i.toString();
+
+            if (this.listItems[iString] === null ||
+                this.listItems[iString] === undefined
+            ) {
+                item = $('<li />');
+                this.listItems[iString] = item;
+            } else {
+                item = $(this.listItems[i]);
+            }
+
+            if (i >= 0 && i < this.dataProvider.length){
+                data = this.dataProvider[i];
+                item.html(data.label);
+                item.attr('list-value', data.listValue);
+            }
+        }
+        if (item !== null && item !== undefined) {
+            item.attr('list-index', i);
+        }
+        return item;
+    },
+
+    /**
+     * Returns index of currently selected item
+     *
+     * @return {int} - index of item that was selected
+     */
+    getSelectedIndex: function() {
+        return parseInt(this.selectedIndex, 10);
+    },
+
+    /**
+     * Sets item at given index as selected and adds appropriate styling to it
+     *
+     * @param {int} index = index of item that was selected
+     */
+    setSelectedIndex: function(index) {
+        var item = this.getItemAtIndex(this.selectedIndex);
+
+        if (item !== undefined) {
+            item.removeClass('megalistSelected');
+        }
+
+        this.selectedIndex = index;
+        this.getItemAtIndex(index).addClass('megalistSelected');
+    },
+
+    /**
+     * Clears currently selected object by removing styling and setting
+     * internal variable pointing to currently selected item to -1
+     *
+     */
+    clearSelectedIndex: function() {
+        var item = this.getItemAtIndex(this.selectedIndex);
+
+        if (item !== undefined) {
+            item.removeClass('megalistSelected');
+        }
+        this.selectedIndex = -1;
+    },
+
+    /**
+     * Sets initial data for megalist and updates layout with it
+     *
+     * @param {Array} dataProvider - object array to initially feed megalist
+     */
+    setDataProvider: function(dataProvider) {
+        this.clearSelectedIndex();
+        this.dataProviderOrig = dataProvider;
+        this.dataProvider = dataProvider;
+
+        this.$ul.find('li').each(function() {
+            $(this).remove();
+        });
+
+        this.yPosition = 0;
+        this.updateLayout();
+    },
+
+    /**
+     * Updates megalist with new data. Accepts either a single object or
+     * an Array of objects and updates layout with new data
+     *
+     * @param {object|Array} newElement - new object / array of objects
+     *                                    to be inserted into the list
+     */
+    updateDataProvider: function(newElement) {
+        this.clearSelectedIndex();
+
+        if ($.isArray(newElement)) {
+            $.merge(this.dataProviderOrig, newElement);
+        } else {
+            this.dataProviderOrig.push(newElement);
+        }
+        this.filterList();
+
+        this.$ul.find('li').each(function() {
+            $(this).remove();
+        });
+
+        this.yPosition = this.getMaxPosition();
+        this.updateLayout();
+    },
+
+    /**
+     * Returns current objects in megalist
+     *
+     * @return {Array} - list of objects in megalist
+     *
+     */
+    getDataProvider: function() {
+        return this.dataProvider;
+    },
+
+    /**
+     * Get maximum value of yPosition
+     *
+     * @return {int} maximum value of yPosition
+     */
+    getMaxPosition: function() {
+        var height = this.$el.height(),
+            totalHeight = this.dataProvider.length * this.itemHeight;
+
+        return totalHeight > height ? totalHeight - height : 0;
+    },
+
+    /**
+     * Checks if search input has minimal length and therefor searching  is
+     * active.
+     * @return {bool} true when searching is active, false otherwise
+     */
+    searchingIsActive: function() {
+        var querySize = $.trim(this.$search.val()).length;
+        return querySize >= this.conf.MINIMUM_SEARCH_QUERY_SIZE;
+    },
+
+    /**
+     * Parses search input and performs filtering of list. The algorithm
+     * splits the search query to tokens and seeks for all subsequent
+     * tokens in the data. If not all tokens are found in the data then this
+     * record is excluded from the results.
+     *
+     */
+    filterList: function() {
+        var self = this,
+            searchQuery = $.trim(this.$search.val().toLowerCase()),
+            searchTokens = searchQuery.split(' '),
+            i;
+
+        this.filteredData = [];
+
+        for (i = searchTokens.length - 1; i >= 0; i--) {
+            searchTokens[i] = $.trim(searchTokens[i]);
+        }
+
+        if (!this.searchingIsActive()) {
+            this.dataProvider = this.dataProviderOrig;
+
+        } else {
+            this.dataProvider = $.grep(
+                this.dataProviderOrig,
+                function(val, index) {
+                    return self.testListElement(val, searchTokens, index);
+                }
+            );
+        }
+
+        this.updateLayout();
+    },
+
+    /**
+     * Tests if element of list data meets the query search criterias.
+     *
+     * @param  {string} val          value of the element to test
+     * @param  {array}  searchTokens query search tokens
+     * @param  {int}    index        the index of element in original data
+     *
+     * @return {boolean}             whether element meets the criteria on not
+     */
+    testListElement: function(val, searchTokens, index) {
+        var tokenIndex = 0,
+            valI = 0,
+            tokenDetected = true,
+            i;
+        val = val.label.toLowerCase();
+        while (valI < val.length) {
+            if (val[valI++] === searchTokens[tokenIndex][0]) {
+                tokenDetected = true;
+                for (i = 1; i < searchTokens[tokenIndex].length; i++) {
+                    if (val[valI] === searchTokens[tokenIndex][i]) {
+                        valI++;
+                    } else {
+                        tokenDetected = false;
+                        break;
+                    }
+                }
+                if (tokenDetected && ++tokenIndex === searchTokens.length) {
+                    this.filteredData[this.filteredData.length] = index;
+                    return true;
+                }
+            }
+        }
+        return false;
+    },
+
+    /**
+     * Generates string result of what is currently selected and populates
+     * this.$input value, adding it to DOM id necessary. Only does it for
+     * destination list. Result can be in 2 formats: POST-like (full) or comma
+     * separated
+     *
+     * @param {boolean} full - wherever to generate full POST-like data
+     * @return {string} result - string result of what is currently selected
+     */
+    generatePOST: function(full) {
+      var i,
+          postData = [],
+          result = {},
+          name = this.name;
+
+      if (this.suffix === this.conf.DESTINATION_SUFFIX){
+          for (i = 0; i < this.dataProviderOrig.length; i++) {
+              postData[i] = this.dataProviderOrig[i].listValue;
+          }
+          if (full === true){
+              result[name] = postData;
+              result = decodeURIComponent($.param(result, true ));
+              //cut out first name so that the post will not contain repetition
+              result = result.slice(this.name.length + 1, result.length);
+              this.$input.val(result);
+          } else {
+              result = postData.join(',');
+              this.$input.val(result);
+          }
+
+          if (this.$el.has(this.$input).length < 1) {
+              this.$el.append(this.$input);
+          }
+          return result;
+      } else {
+          return '';
+      }
+    }
+
+  };
+
+  /* LIST PLUGIN DEFINITION
+   * ========================== */
+
+  $.fn.megalist = function (option, params) {
+    if (typeof option === 'object') { this.options = option;}
+    var multiselect = new Megalist(this);
+    if (typeof option === 'string') { this.result = multiselect[option](params); }
+    return this;
+  };
+
+  // injects svg arrow icons into dom
+  $(document).ready(function(){
+    $('body').append('<svg style="display: none" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"> <path id="arrow-left" d="M48 10.667q1.104 0 1.885 0.781t0.781 1.885-0.792 1.896l-16.771 16.771 16.771 16.771q0.792 0.792 0.792 1.896t-0.781 1.885-1.885 0.781q-1.125 0-1.896-0.771l-18.667-18.667q-0.771-0.771-0.771-1.896t0.771-1.896l18.667-18.667q0.771-0.771 1.896-0.771zM32 10.667q1.104 0 1.885 0.781t0.781 1.885-0.792 1.896l-16.771 16.771 16.771 16.771q0.792 0.792 0.792 1.896t-0.781 1.885-1.885 0.781q-1.125 0-1.896-0.771l-18.667-18.667q-0.771-0.771-0.771-1.896t0.771-1.896l18.667-18.667q0.771-0.771 1.896-0.771z"></path> <path id="arrow-right" d="M29.333 10.667q1.104 0 1.875 0.771l18.667 18.667q0.792 0.792 0.792 1.896t-0.792 1.896l-18.667 18.667q-0.771 0.771-1.875 0.771t-1.885-0.781-0.781-1.885q0-1.125 0.771-1.896l16.771-16.771-16.771-16.771q-0.771-0.771-0.771-1.896 0-1.146 0.76-1.906t1.906-0.76zM13.333 10.667q1.104 0 1.875 0.771l18.667 18.667q0.792 0.792 0.792 1.896t-0.792 1.896l-18.667 18.667q-0.771 0.771-1.875 0.771t-1.885-0.781-0.781-1.885q0-1.125 0.771-1.896l16.771-16.771-16.771-16.771q-0.771-0.771-0.771-1.896 0-1.146 0.76-1.906t1.906-0.76z"></path></svg>');});
+
+  //adds indexOf to arry prototype for ie8
+  if(!Array.prototype.indexOf){Array.prototype.indexOf=function(e){var t=this.length>>>0;var n=Number(arguments[1])||0;n=n<0?Math.ceil(n):Math.floor(n);if(n<0)n+=t;for(;n<t;n++){if(n in this&&this[n]===e)return n}return-1}}
+
+} (window.jQuery);

+ 828 - 0
flaskbb/static/js/modernizr.custom.js

@@ -0,0 +1,828 @@
+/* Modernizr 2.8.3 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
+ */
+;
+
+
+
+window.Modernizr = (function( window, document, undefined ) {
+
+    var version = '2.8.3',
+
+    Modernizr = {},
+
+    enableClasses = true,
+
+    docElement = document.documentElement,
+
+    mod = 'modernizr',
+    modElem = document.createElement(mod),
+    mStyle = modElem.style,
+
+    inputElem  = document.createElement('input')  ,
+
+    smile = ':)',
+
+    toString = {}.toString,
+
+    prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
+
+
+
+    omPrefixes = 'Webkit Moz O ms',
+
+    cssomPrefixes = omPrefixes.split(' '),
+
+    domPrefixes = omPrefixes.toLowerCase().split(' '),
+
+    ns = {'svg': 'http://www.w3.org/2000/svg'},
+
+    tests = {},
+    inputs = {},
+    attrs = {},
+
+    classes = [],
+
+    slice = classes.slice,
+
+    featureName, 
+
+
+    injectElementWithStyles = function( rule, callback, nodes, testnames ) {
+
+      var style, ret, node, docOverflow,
+          div = document.createElement('div'),
+                body = document.body,
+                fakeBody = body || document.createElement('body');
+
+      if ( parseInt(nodes, 10) ) {
+                      while ( nodes-- ) {
+              node = document.createElement('div');
+              node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
+              div.appendChild(node);
+          }
+      }
+
+                style = ['&#173;','<style id="s', mod, '">', rule, '</style>'].join('');
+      div.id = mod;
+          (body ? div : fakeBody).innerHTML += style;
+      fakeBody.appendChild(div);
+      if ( !body ) {
+                fakeBody.style.background = '';
+                fakeBody.style.overflow = 'hidden';
+          docOverflow = docElement.style.overflow;
+          docElement.style.overflow = 'hidden';
+          docElement.appendChild(fakeBody);
+      }
+
+      ret = callback(div, rule);
+        if ( !body ) {
+          fakeBody.parentNode.removeChild(fakeBody);
+          docElement.style.overflow = docOverflow;
+      } else {
+          div.parentNode.removeChild(div);
+      }
+
+      return !!ret;
+
+    },
+
+
+
+    isEventSupported = (function() {
+
+      var TAGNAMES = {
+        'select': 'input', 'change': 'input',
+        'submit': 'form', 'reset': 'form',
+        'error': 'img', 'load': 'img', 'abort': 'img'
+      };
+
+      function isEventSupported( eventName, element ) {
+
+        element = element || document.createElement(TAGNAMES[eventName] || 'div');
+        eventName = 'on' + eventName;
+
+            var isSupported = eventName in element;
+
+        if ( !isSupported ) {
+                if ( !element.setAttribute ) {
+            element = document.createElement('div');
+          }
+          if ( element.setAttribute && element.removeAttribute ) {
+            element.setAttribute(eventName, '');
+            isSupported = is(element[eventName], 'function');
+
+                    if ( !is(element[eventName], 'undefined') ) {
+              element[eventName] = undefined;
+            }
+            element.removeAttribute(eventName);
+          }
+        }
+
+        element = null;
+        return isSupported;
+      }
+      return isEventSupported;
+    })(),
+
+
+    _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
+
+    if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
+      hasOwnProp = function (object, property) {
+        return _hasOwnProperty.call(object, property);
+      };
+    }
+    else {
+      hasOwnProp = function (object, property) { 
+        return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
+      };
+    }
+
+
+    if (!Function.prototype.bind) {
+      Function.prototype.bind = function bind(that) {
+
+        var target = this;
+
+        if (typeof target != "function") {
+            throw new TypeError();
+        }
+
+        var args = slice.call(arguments, 1),
+            bound = function () {
+
+            if (this instanceof bound) {
+
+              var F = function(){};
+              F.prototype = target.prototype;
+              var self = new F();
+
+              var result = target.apply(
+                  self,
+                  args.concat(slice.call(arguments))
+              );
+              if (Object(result) === result) {
+                  return result;
+              }
+              return self;
+
+            } else {
+
+              return target.apply(
+                  that,
+                  args.concat(slice.call(arguments))
+              );
+
+            }
+
+        };
+
+        return bound;
+      };
+    }
+
+    function setCss( str ) {
+        mStyle.cssText = str;
+    }
+
+    function setCssAll( str1, str2 ) {
+        return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
+    }
+
+    function is( obj, type ) {
+        return typeof obj === type;
+    }
+
+    function contains( str, substr ) {
+        return !!~('' + str).indexOf(substr);
+    }
+
+    function testProps( props, prefixed ) {
+        for ( var i in props ) {
+            var prop = props[i];
+            if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
+                return prefixed == 'pfx' ? prop : true;
+            }
+        }
+        return false;
+    }
+
+    function testDOMProps( props, obj, elem ) {
+        for ( var i in props ) {
+            var item = obj[props[i]];
+            if ( item !== undefined) {
+
+                            if (elem === false) return props[i];
+
+                            if (is(item, 'function')){
+                                return item.bind(elem || obj);
+                }
+
+                            return item;
+            }
+        }
+        return false;
+    }
+
+    function testPropsAll( prop, prefixed, elem ) {
+
+        var ucProp  = prop.charAt(0).toUpperCase() + prop.slice(1),
+            props   = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
+
+            if(is(prefixed, "string") || is(prefixed, "undefined")) {
+          return testProps(props, prefixed);
+
+            } else {
+          props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
+          return testDOMProps(props, prefixed, elem);
+        }
+    }    tests['flexbox'] = function() {
+      return testPropsAll('flexWrap');
+    };    tests['canvas'] = function() {
+        var elem = document.createElement('canvas');
+        return !!(elem.getContext && elem.getContext('2d'));
+    };
+
+    tests['canvastext'] = function() {
+        return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
+    };
+
+
+
+    tests['webgl'] = function() {
+        return !!window.WebGLRenderingContext;
+    };
+
+
+    tests['touch'] = function() {
+        var bool;
+
+        if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
+          bool = true;
+        } else {
+          injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
+            bool = node.offsetTop === 9;
+          });
+        }
+
+        return bool;
+    };
+
+
+
+    tests['geolocation'] = function() {
+        return 'geolocation' in navigator;
+    };
+
+
+    tests['postmessage'] = function() {
+      return !!window.postMessage;
+    };
+
+
+    tests['websqldatabase'] = function() {
+      return !!window.openDatabase;
+    };
+
+    tests['indexedDB'] = function() {
+      return !!testPropsAll("indexedDB", window);
+    };
+
+    tests['hashchange'] = function() {
+      return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
+    };
+
+    tests['history'] = function() {
+      return !!(window.history && history.pushState);
+    };
+
+    tests['draganddrop'] = function() {
+        var div = document.createElement('div');
+        return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
+    };
+
+    tests['websockets'] = function() {
+        return 'WebSocket' in window || 'MozWebSocket' in window;
+    };
+
+
+    tests['rgba'] = function() {
+        setCss('background-color:rgba(150,255,150,.5)');
+
+        return contains(mStyle.backgroundColor, 'rgba');
+    };
+
+    tests['hsla'] = function() {
+            setCss('background-color:hsla(120,40%,100%,.5)');
+
+        return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
+    };
+
+    tests['multiplebgs'] = function() {
+                setCss('background:url(https://),url(https://),red url(https://)');
+
+            return (/(url\s*\(.*?){3}/).test(mStyle.background);
+    };    tests['backgroundsize'] = function() {
+        return testPropsAll('backgroundSize');
+    };
+
+    tests['borderimage'] = function() {
+        return testPropsAll('borderImage');
+    };
+
+
+
+    tests['borderradius'] = function() {
+        return testPropsAll('borderRadius');
+    };
+
+    tests['boxshadow'] = function() {
+        return testPropsAll('boxShadow');
+    };
+
+    tests['textshadow'] = function() {
+        return document.createElement('div').style.textShadow === '';
+    };
+
+
+    tests['opacity'] = function() {
+                setCssAll('opacity:.55');
+
+                    return (/^0.55$/).test(mStyle.opacity);
+    };
+
+
+    tests['cssanimations'] = function() {
+        return testPropsAll('animationName');
+    };
+
+
+    tests['csscolumns'] = function() {
+        return testPropsAll('columnCount');
+    };
+
+
+    tests['cssgradients'] = function() {
+        var str1 = 'background-image:',
+            str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
+            str3 = 'linear-gradient(left top,#9f9, white);';
+
+        setCss(
+                       (str1 + '-webkit- '.split(' ').join(str2 + str1) +
+                       prefixes.join(str3 + str1)).slice(0, -str1.length)
+        );
+
+        return contains(mStyle.backgroundImage, 'gradient');
+    };
+
+
+    tests['cssreflections'] = function() {
+        return testPropsAll('boxReflect');
+    };
+
+
+    tests['csstransforms'] = function() {
+        return !!testPropsAll('transform');
+    };
+
+
+    tests['csstransforms3d'] = function() {
+
+        var ret = !!testPropsAll('perspective');
+
+                        if ( ret && 'webkitPerspective' in docElement.style ) {
+
+                      injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
+            ret = node.offsetLeft === 9 && node.offsetHeight === 3;
+          });
+        }
+        return ret;
+    };
+
+
+    tests['csstransitions'] = function() {
+        return testPropsAll('transition');
+    };
+
+
+
+    tests['fontface'] = function() {
+        var bool;
+
+        injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
+          var style = document.getElementById('smodernizr'),
+              sheet = style.sheet || style.styleSheet,
+              cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
+
+          bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
+        });
+
+        return bool;
+    };
+
+    tests['generatedcontent'] = function() {
+        var bool;
+
+        injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
+          bool = node.offsetHeight >= 3;
+        });
+
+        return bool;
+    };
+    tests['video'] = function() {
+        var elem = document.createElement('video'),
+            bool = false;
+
+            try {
+            if ( bool = !!elem.canPlayType ) {
+                bool      = new Boolean(bool);
+                bool.ogg  = elem.canPlayType('video/ogg; codecs="theora"')      .replace(/^no$/,'');
+
+                            bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
+
+                bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
+            }
+
+        } catch(e) { }
+
+        return bool;
+    };
+
+    tests['audio'] = function() {
+        var elem = document.createElement('audio'),
+            bool = false;
+
+        try {
+            if ( bool = !!elem.canPlayType ) {
+                bool      = new Boolean(bool);
+                bool.ogg  = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
+                bool.mp3  = elem.canPlayType('audio/mpeg;')               .replace(/^no$/,'');
+
+                                                    bool.wav  = elem.canPlayType('audio/wav; codecs="1"')     .replace(/^no$/,'');
+                bool.m4a  = ( elem.canPlayType('audio/x-m4a;')            ||
+                              elem.canPlayType('audio/aac;'))             .replace(/^no$/,'');
+            }
+        } catch(e) { }
+
+        return bool;
+    };
+
+
+    tests['localstorage'] = function() {
+        try {
+            localStorage.setItem(mod, mod);
+            localStorage.removeItem(mod);
+            return true;
+        } catch(e) {
+            return false;
+        }
+    };
+
+    tests['sessionstorage'] = function() {
+        try {
+            sessionStorage.setItem(mod, mod);
+            sessionStorage.removeItem(mod);
+            return true;
+        } catch(e) {
+            return false;
+        }
+    };
+
+
+    tests['webworkers'] = function() {
+        return !!window.Worker;
+    };
+
+
+    tests['applicationcache'] = function() {
+        return !!window.applicationCache;
+    };
+
+
+    tests['svg'] = function() {
+        return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
+    };
+
+    tests['inlinesvg'] = function() {
+      var div = document.createElement('div');
+      div.innerHTML = '<svg/>';
+      return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
+    };
+
+    tests['smil'] = function() {
+        return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
+    };
+
+
+    tests['svgclippaths'] = function() {
+        return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
+    };
+
+    function webforms() {
+                                            Modernizr['input'] = (function( props ) {
+            for ( var i = 0, len = props.length; i < len; i++ ) {
+                attrs[ props[i] ] = !!(props[i] in inputElem);
+            }
+            if (attrs.list){
+                                  attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
+            }
+            return attrs;
+        })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
+                            Modernizr['inputtypes'] = (function(props) {
+
+            for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
+
+                inputElem.setAttribute('type', inputElemType = props[i]);
+                bool = inputElem.type !== 'text';
+
+                                                    if ( bool ) {
+
+                    inputElem.value         = smile;
+                    inputElem.style.cssText = 'position:absolute;visibility:hidden;';
+
+                    if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
+
+                      docElement.appendChild(inputElem);
+                      defaultView = document.defaultView;
+
+                                        bool =  defaultView.getComputedStyle &&
+                              defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
+                                                                                  (inputElem.offsetHeight !== 0);
+
+                      docElement.removeChild(inputElem);
+
+                    } else if ( /^(search|tel)$/.test(inputElemType) ){
+                                                                                    } else if ( /^(url|email)$/.test(inputElemType) ) {
+                                        bool = inputElem.checkValidity && inputElem.checkValidity() === false;
+
+                    } else {
+                                        bool = inputElem.value != smile;
+                    }
+                }
+
+                inputs[ props[i] ] = !!bool;
+            }
+            return inputs;
+        })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
+        }
+    for ( var feature in tests ) {
+        if ( hasOwnProp(tests, feature) ) {
+                                    featureName  = feature.toLowerCase();
+            Modernizr[featureName] = tests[feature]();
+
+            classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
+        }
+    }
+
+    Modernizr.input || webforms();
+
+
+     Modernizr.addTest = function ( feature, test ) {
+       if ( typeof feature == 'object' ) {
+         for ( var key in feature ) {
+           if ( hasOwnProp( feature, key ) ) {
+             Modernizr.addTest( key, feature[ key ] );
+           }
+         }
+       } else {
+
+         feature = feature.toLowerCase();
+
+         if ( Modernizr[feature] !== undefined ) {
+                                              return Modernizr;
+         }
+
+         test = typeof test == 'function' ? test() : test;
+
+         if (typeof enableClasses !== "undefined" && enableClasses) {
+           docElement.className += ' ' + (test ? '' : 'no-') + feature;
+         }
+         Modernizr[feature] = test;
+
+       }
+
+       return Modernizr; 
+     };
+
+
+    setCss('');
+    modElem = inputElem = null;
+
+    ;(function(window, document) {
+                var version = '3.7.0';
+
+            var options = window.html5 || {};
+
+            var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+            var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
+
+            var supportsHtml5Styles;
+
+            var expando = '_html5shiv';
+
+            var expanID = 0;
+
+            var expandoData = {};
+
+            var supportsUnknownElements;
+
+        (function() {
+          try {
+            var a = document.createElement('a');
+            a.innerHTML = '<xyz></xyz>';
+                    supportsHtml5Styles = ('hidden' in a);
+
+            supportsUnknownElements = a.childNodes.length == 1 || (function() {
+                        (document.createElement)('a');
+              var frag = document.createDocumentFragment();
+              return (
+                typeof frag.cloneNode == 'undefined' ||
+                typeof frag.createDocumentFragment == 'undefined' ||
+                typeof frag.createElement == 'undefined'
+              );
+            }());
+          } catch(e) {
+                    supportsHtml5Styles = true;
+            supportsUnknownElements = true;
+          }
+
+        }());
+
+            function addStyleSheet(ownerDocument, cssText) {
+          var p = ownerDocument.createElement('p'),
+          parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+          p.innerHTML = 'x<style>' + cssText + '</style>';
+          return parent.insertBefore(p.lastChild, parent.firstChild);
+        }
+
+            function getElements() {
+          var elements = html5.elements;
+          return typeof elements == 'string' ? elements.split(' ') : elements;
+        }
+
+            function getExpandoData(ownerDocument) {
+          var data = expandoData[ownerDocument[expando]];
+          if (!data) {
+            data = {};
+            expanID++;
+            ownerDocument[expando] = expanID;
+            expandoData[expanID] = data;
+          }
+          return data;
+        }
+
+            function createElement(nodeName, ownerDocument, data){
+          if (!ownerDocument) {
+            ownerDocument = document;
+          }
+          if(supportsUnknownElements){
+            return ownerDocument.createElement(nodeName);
+          }
+          if (!data) {
+            data = getExpandoData(ownerDocument);
+          }
+          var node;
+
+          if (data.cache[nodeName]) {
+            node = data.cache[nodeName].cloneNode();
+          } else if (saveClones.test(nodeName)) {
+            node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+          } else {
+            node = data.createElem(nodeName);
+          }
+
+                                                    return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
+        }
+
+            function createDocumentFragment(ownerDocument, data){
+          if (!ownerDocument) {
+            ownerDocument = document;
+          }
+          if(supportsUnknownElements){
+            return ownerDocument.createDocumentFragment();
+          }
+          data = data || getExpandoData(ownerDocument);
+          var clone = data.frag.cloneNode(),
+          i = 0,
+          elems = getElements(),
+          l = elems.length;
+          for(;i<l;i++){
+            clone.createElement(elems[i]);
+          }
+          return clone;
+        }
+
+            function shivMethods(ownerDocument, data) {
+          if (!data.cache) {
+            data.cache = {};
+            data.createElem = ownerDocument.createElement;
+            data.createFrag = ownerDocument.createDocumentFragment;
+            data.frag = data.createFrag();
+          }
+
+
+          ownerDocument.createElement = function(nodeName) {
+                    if (!html5.shivMethods) {
+              return data.createElem(nodeName);
+            }
+            return createElement(nodeName, ownerDocument, data);
+          };
+
+          ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+                                                          'var n=f.cloneNode(),c=n.createElement;' +
+                                                          'h.shivMethods&&(' +
+                                                                                                                getElements().join().replace(/[\w\-]+/g, function(nodeName) {
+            data.createElem(nodeName);
+            data.frag.createElement(nodeName);
+            return 'c("' + nodeName + '")';
+          }) +
+            ');return n}'
+                                                         )(html5, data.frag);
+        }
+
+            function shivDocument(ownerDocument) {
+          if (!ownerDocument) {
+            ownerDocument = document;
+          }
+          var data = getExpandoData(ownerDocument);
+
+          if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
+            data.hasCSS = !!addStyleSheet(ownerDocument,
+                                                                                'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
+                                                                                    'mark{background:#FF0;color:#000}' +
+                                                                                    'template{display:none}'
+                                         );
+          }
+          if (!supportsUnknownElements) {
+            shivMethods(ownerDocument, data);
+          }
+          return ownerDocument;
+        }
+
+            var html5 = {
+
+                'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video',
+
+                'version': version,
+
+                'shivCSS': (options.shivCSS !== false),
+
+                'supportsUnknownElements': supportsUnknownElements,
+
+                'shivMethods': (options.shivMethods !== false),
+
+                'type': 'default',
+
+                'shivDocument': shivDocument,
+
+                createElement: createElement,
+
+                createDocumentFragment: createDocumentFragment
+        };
+
+            window.html5 = html5;
+
+            shivDocument(document);
+
+    }(this, document));
+
+    Modernizr._version      = version;
+
+    Modernizr._prefixes     = prefixes;
+    Modernizr._domPrefixes  = domPrefixes;
+    Modernizr._cssomPrefixes  = cssomPrefixes;
+
+
+    Modernizr.hasEvent      = isEventSupported;
+
+    Modernizr.testProp      = function(prop){
+        return testProps([prop]);
+    };
+
+    Modernizr.testAllProps  = testPropsAll;
+
+
+    Modernizr.testStyles    = injectElementWithStyles;
+    Modernizr.prefixed      = function(prop, obj, elem){
+      if(!obj) {
+        return testPropsAll(prop, 'pfx');
+      } else {
+            return testPropsAll(prop, obj, elem);
+      }
+    };
+
+
+    docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
+
+                                                    (enableClasses ? ' js ' + classes.join(' ') : '');
+
+    return Modernizr;
+
+})(this, this.document);
+/*yepnope1.5.4|WTFPL*/
+(function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}})(this,document);
+Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0));};
+;

+ 125 - 0
flaskbb/static/less/megalist-multiselect.less

@@ -0,0 +1,125 @@
+.box-sizing(@boxmodel) {
+  -webkit-box-sizing: @boxmodel;
+     -moz-box-sizing: @boxmodel;
+      -ms-box-sizing: @boxmodel;
+          box-sizing: @boxmodel;
+}
+.border-radius(@radius) {
+    -webkit-border-radius: @radius;
+       -moz-border-radius: @radius;
+            border-radius: @radius;
+}
+.user-select(@select) {
+    -webkit-user-select: @select;
+       -moz-user-select: @select;
+        -ms-user-select: @select;
+            user-select: @select;
+}
+
+@icon-move-color: rgb(3, 116, 187);
+.megalist-mutliselect {
+    .user-select(none);
+    overflow: hidden;
+    .megalist {
+        float: left;
+        width: 285px;
+        input[type=text] {
+            .box-sizing(border-box);
+            width: 100% !important;
+            padding: 5px 4px;
+            margin-bottom: 5px;
+        }
+        .megalist-inner {
+            .box-sizing(border-box);
+            overflow: hidden;
+            position: relative;
+            height: 205px;
+            width: 100%;
+            overflow: hidden;
+            border: 1px solid silver;
+            ul {
+                position: absolute;
+                padding: 0;
+                display: block;
+                margin-top: 0px;
+                width: 100%;
+                top: 0px;
+                li {
+                    margin:0;
+                    border:none;
+                    white-space: nowrap;
+                    overflow: hidden;
+                    padding: 6px 0px 6px 10px !important;
+                    display: block;
+                    position: absolute;
+                    width: 100%;
+                    border-top: 1px solid #EDEDED;
+                    line-height: 1em !important;
+                    cursor: pointer;
+                    color: #555;
+                    &:hover {
+                        background-color: #08c;
+                    }
+                }
+            }
+        }
+        .scrollbar {
+            .border-radius(3px);
+            position: absolute;
+            right: 1px;
+            width: 11px;
+            height: 25px;
+            background-color: rgb(190, 190, 190);
+            z-index: 2;
+            &:hover {
+                background-color: rgb(175, 175, 175);
+            }
+        }
+        .scrollbar-background {
+            position: absolute;
+            top: 0px;
+            right: 0px;
+            width: 14px;
+            height: 100%;
+            z-index: 1;
+            background-color: rgb(236, 236, 236);
+        }
+    }
+    .move-buttons {
+        margin: 90px 0px;
+        float: left;
+        .move-button {
+            height: 30px;
+            padding: 0px 50px;
+            margin-bottom: 10px;
+            cursor: pointer;
+            svg {
+                fill: @icon-move-color;
+            }
+            &:hover {
+                svg {
+                    fill: darken(@icon-move-color, 15%);
+                }
+                &.arrow-left.no-svg .svg {
+                    background-position: 0% 100%;
+                }
+                &.arrow-right.no-svg .svg {
+                    background-position: 100% 100%;
+                }
+            }
+            &.no-svg .svg {
+                background-repeat: no-repeat;
+                background-position: center;
+                background: url('../icons/megalist-icons.png');
+                width: 32px;
+                height: 32px;
+            }
+            &.arrow-left.no-svg .svg {
+                background-position: 0% 0%;
+            }
+            &.arrow-right.no-svg .svg {
+                background-position: 100% 0%;
+            }
+        }
+    }
+}

+ 1 - 1
flaskbb/templates/forum/search_result.html

@@ -169,7 +169,7 @@
                 </tr>
                 </tr>
             {% else %}
             {% else %}
                 <tr>
                 <tr>
-                    <td colspan="5">{% trans %}No topics found matching your search criteria.{% trans %}</td>
+                    <td colspan="5">{% trans %}No topics found matching your search criteria.{% endtrans %}</td>
                 </tr>
                 </tr>
             {% endfor %}
             {% endfor %}
             </tbody>
             </tbody>

+ 1 - 1
flaskbb/templates/layout.html

@@ -126,7 +126,7 @@
         <div id="footer">
         <div id="footer">
             <div class="container">
             <div class="container">
                 <p class="text-muted credit pull-left">powered by <a href="http://flask.pocoo.org">Flask</a></p>
                 <p class="text-muted credit pull-left">powered by <a href="http://flask.pocoo.org">Flask</a></p>
-                <p class="text-muted credit pull-right">&copy; 2013 - <a href="http://flaskbb.org">FlaskBB.org</a></p>
+                <p class="text-muted credit pull-right">&copy; 2013 - 2015 - <a href="http://flaskbb.org">FlaskBB.org</a></p>
             </div>
             </div>
         </div>
         </div>
         {% endblock %}
         {% endblock %}

+ 5 - 0
flaskbb/templates/macros.html

@@ -307,6 +307,11 @@
 </li>
 </li>
 {% endmacro %}
 {% endmacro %}
 
 
+{% macro tablink_href(endpoint, name, active=False) %}
+<li {% if endpoint == request.endpoint or active %}class="active"{% endif %} >
+    <a href={{ endpoint }} role="tab" data-toggle="tab">{{ name }}</a>
+</li>
+{% endmacro %}
 
 
 {% macro render_pagination(page_obj, url, ul_class='') %}
 {% macro render_pagination(page_obj, url, ul_class='') %}
 <ul class='{%- if ul_class -%}{{ ul_class }}{%- else -%}pagination{%- endif -%}'>
 <ul class='{%- if ul_class -%}{{ ul_class }}{%- else -%}pagination{%- endif -%}'>

+ 20 - 0
flaskbb/templates/management/forum_form.html

@@ -32,6 +32,11 @@
                         {{ render_boolean_field(form.show_moderators) }}
                         {{ render_boolean_field(form.show_moderators) }}
                         {{ render_boolean_field(form.locked) }}
                         {{ render_boolean_field(form.locked) }}
 
 
+			<div id="perms">
+			    <h4>{{ _("Group Access to the forum") }}</h4>
+			    {{ form.groups }}
+			</div>
+
                         <div class="row">
                         <div class="row">
                             {{ render_submit_field(form.submit, div_class="col-lg-offset-0 col-lg-9") }}
                             {{ render_submit_field(form.submit, div_class="col-lg-offset-0 col-lg-9") }}
                         </div>
                         </div>
@@ -42,3 +47,18 @@
     </div>
     </div>
 </div>
 </div>
 {% endblock %}
 {% endblock %}
+
+{% block scripts %}
+{{ super() }}
+<script type="application/javascript" >
+    $(document).ready(function() {
+        var options = {
+            PLACEHOLDER_TEXT: "Search Groups",
+            BUILD_FULL_POST: false
+        };
+
+        $('#groups').megalist(options);
+        $('#groups').attr('inited', true);
+    });
+</script>
+{% endblock %}

+ 9 - 1
flaskbb/templates/management/management_layout.html

@@ -1,5 +1,8 @@
 {% extends theme("layout.html") %}
 {% extends theme("layout.html") %}
-
+{% block css %}
+    {{ super() }}
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/megalist-multiselect.css') }}" >
+{% endblock %}
 {% block content %}
 {% block content %}
 {%- from theme('macros.html') import navlink with context -%}
 {%- from theme('macros.html') import navlink with context -%}
 
 
@@ -21,3 +24,8 @@
 {% block management_content %}{% endblock %}
 {% block management_content %}{% endblock %}
 
 
 {% endblock %}
 {% endblock %}
+{% block javascript %}
+    {{ super() }}
+    <script src="{{ url_for('static', filename='js/modernizr.custom.js') }}"></script>
+    <script src="{{ url_for('static', filename='js/megalist-multiselect.js') }}"></script>
+{% endblock %}

+ 1 - 1
flaskbb/themes/bootstrap2/templates/layout.html

@@ -125,7 +125,7 @@
             <div id="footer">
             <div id="footer">
                 <div class="container">
                 <div class="container">
                     <p class="text-muted credit pull-left">powered by <a href="http://flask.pocoo.org">Flask</a></p>
                     <p class="text-muted credit pull-left">powered by <a href="http://flask.pocoo.org">Flask</a></p>
-                    <p class="text-muted credit pull-right">&copy; 2013 - <a href="http://flaskbb.org">FlaskBB.org</a></p>
+                    <p class="text-muted credit pull-right">&copy; 2013 - 2015 - <a href="http://flaskbb.org">FlaskBB.org</a></p>
                 </div>
                 </div>
             </div>
             </div>
             {% endblock %}
             {% endblock %}

+ 3 - 1
flaskbb/themes/bootstrap3/templates/layout.html

@@ -23,6 +23,8 @@
         <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='css/font-awesome.min.css') }}" >
         <link rel="stylesheet" href="{{ url_for('static', filename='css/font-awesome.min.css') }}" >
         <link rel="stylesheet" href="{{ url_for('static', filename='css/code.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='css/code.css') }}">
+        <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
+        <link rel="stylesheet" href="{{ url_for('static', filename='css/font-awesome.min.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='css/flaskbb.css') }}">
         <link rel="stylesheet" href="{{ url_for('static', filename='css/flaskbb.css') }}">
         {% endblock %}
         {% endblock %}
 
 
@@ -128,7 +130,7 @@
         <div id="footer">
         <div id="footer">
             <div class="container">
             <div class="container">
                 <p class="text-muted credit pull-left">powered by <a href="http://flask.pocoo.org">Flask</a></p>
                 <p class="text-muted credit pull-left">powered by <a href="http://flask.pocoo.org">Flask</a></p>
-                <p class="text-muted credit pull-right">&copy; 2013 - <a href="http://flaskbb.org">FlaskBB.org</a></p>
+                <p class="text-muted credit pull-right">&copy; 2013 - 2015 - <a href="http://flaskbb.org">FlaskBB.org</a></p>
             </div>
             </div>
         </div>
         </div>
         {% endblock %}
         {% endblock %}

+ 375 - 381
flaskbb/translations/messages.pot

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2015-01-26 12:56+0100\n"
+"POT-Creation-Date: 2015-07-14 22:49+0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,7 +21,7 @@ msgstr ""
 msgid "Password Reset"
 msgid "Password Reset"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:24 flaskbb/management/forms.py:27
+#: flaskbb/auth/forms.py:24 flaskbb/management/forms.py:37
 msgid "You can only use letters, numbers or dashes."
 msgid "You can only use letters, numbers or dashes."
 msgstr ""
 msgstr ""
 
 
@@ -34,7 +34,7 @@ msgid "A Username or E-Mail Address is required."
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/auth/forms.py:32 flaskbb/auth/forms.py:49 flaskbb/auth/forms.py:83
 #: flaskbb/auth/forms.py:32 flaskbb/auth/forms.py:49 flaskbb/auth/forms.py:83
-#: flaskbb/auth/forms.py:104 flaskbb/user/forms.py:71
+#: flaskbb/auth/forms.py:104 flaskbb/user/forms.py:67
 msgid "Password"
 msgid "Password"
 msgstr ""
 msgstr ""
 
 
@@ -46,41 +46,42 @@ msgstr ""
 msgid "Remember Me"
 msgid "Remember Me"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:37 flaskbb/templates/layout.html:90
+#: flaskbb/auth/forms.py:37 flaskbb/templates/layout.html:89
 #: flaskbb/templates/auth/login.html:1 flaskbb/templates/auth/login.html:8
 #: flaskbb/templates/auth/login.html:1 flaskbb/templates/auth/login.html:8
-#: flaskbb/themes/bootstrap2/templates/layout.html:91
-#: flaskbb/themes/bootstrap3/templates/layout.html:90
+#: flaskbb/themes/bootstrap2/templates/layout.html:90
+#: flaskbb/themes/bootstrap3/templates/layout.html:89
 msgid "Login"
 msgid "Login"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:41 flaskbb/management/forms.py:43
+#: flaskbb/auth/forms.py:41 flaskbb/management/forms.py:55
 #: flaskbb/templates/forum/memberlist.html:31
 #: flaskbb/templates/forum/memberlist.html:31
+#: flaskbb/templates/forum/search_result.html:83
 #: flaskbb/templates/management/banned_users.html:41
 #: flaskbb/templates/management/banned_users.html:41
 #: flaskbb/templates/management/users.html:41
 #: flaskbb/templates/management/users.html:41
 msgid "Username"
 msgid "Username"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:42 flaskbb/management/forms.py:44
-#: flaskbb/user/forms.py:110
+#: flaskbb/auth/forms.py:42 flaskbb/management/forms.py:56
+#: flaskbb/message/forms.py:23
 msgid "A Username is required."
 msgid "A Username is required."
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/auth/forms.py:45 flaskbb/auth/forms.py:90 flaskbb/auth/forms.py:100
 #: flaskbb/auth/forms.py:45 flaskbb/auth/forms.py:90 flaskbb/auth/forms.py:100
-#: flaskbb/management/forms.py:47
+#: flaskbb/management/forms.py:59
 msgid "E-Mail Address"
 msgid "E-Mail Address"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/auth/forms.py:46 flaskbb/auth/forms.py:101
 #: flaskbb/auth/forms.py:46 flaskbb/auth/forms.py:101
-#: flaskbb/management/forms.py:48 flaskbb/user/forms.py:41
+#: flaskbb/management/forms.py:60 flaskbb/user/forms.py:37
 msgid "A E-Mail Address is required."
 msgid "A E-Mail Address is required."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:47 flaskbb/management/forms.py:49
-#: flaskbb/user/forms.py:42 flaskbb/user/forms.py:47 flaskbb/user/forms.py:50
+#: flaskbb/auth/forms.py:47 flaskbb/management/forms.py:61
+#: flaskbb/user/forms.py:38 flaskbb/user/forms.py:43 flaskbb/user/forms.py:46
 msgid "Invalid E-Mail Address."
 msgid "Invalid E-Mail Address."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:51 flaskbb/auth/forms.py:106 flaskbb/user/forms.py:73
+#: flaskbb/auth/forms.py:51 flaskbb/auth/forms.py:106 flaskbb/user/forms.py:69
 msgid "Passwords must match."
 msgid "Passwords must match."
 msgstr ""
 msgstr ""
 
 
@@ -92,20 +93,20 @@ msgstr ""
 msgid "I accept the Terms of Service"
 msgid "I accept the Terms of Service"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:57 flaskbb/templates/layout.html:96
+#: flaskbb/auth/forms.py:57 flaskbb/templates/layout.html:95
 #: flaskbb/templates/auth/register.html:1
 #: flaskbb/templates/auth/register.html:1
 #: flaskbb/templates/auth/register.html:8
 #: flaskbb/templates/auth/register.html:8
-#: flaskbb/themes/bootstrap2/templates/layout.html:97
-#: flaskbb/themes/bootstrap3/templates/layout.html:96
+#: flaskbb/themes/bootstrap2/templates/layout.html:96
+#: flaskbb/themes/bootstrap3/templates/layout.html:95
 msgid "Register"
 msgid "Register"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:62 flaskbb/management/forms.py:104
+#: flaskbb/auth/forms.py:62 flaskbb/management/forms.py:116
 msgid "This Username is already taken."
 msgid "This Username is already taken."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:67 flaskbb/management/forms.py:118
-#: flaskbb/user/forms.py:64
+#: flaskbb/auth/forms.py:67 flaskbb/management/forms.py:130
+#: flaskbb/user/forms.py:60
 msgid "This E-Mail Address is already taken."
 msgid "This E-Mail Address is already taken."
 msgstr ""
 msgstr ""
 
 
@@ -126,12 +127,12 @@ msgstr ""
 msgid "Request Password"
 msgid "Request Password"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/auth/forms.py:110 flaskbb/templates/layout.html:97
+#: flaskbb/auth/forms.py:110 flaskbb/templates/layout.html:96
 #: flaskbb/templates/auth/forgot_password.html:8
 #: flaskbb/templates/auth/forgot_password.html:8
 #: flaskbb/templates/auth/reset_password.html:1
 #: flaskbb/templates/auth/reset_password.html:1
 #: flaskbb/templates/auth/reset_password.html:8
 #: flaskbb/templates/auth/reset_password.html:8
-#: flaskbb/themes/bootstrap2/templates/layout.html:98
-#: flaskbb/themes/bootstrap3/templates/layout.html:97
+#: flaskbb/themes/bootstrap2/templates/layout.html:97
+#: flaskbb/themes/bootstrap3/templates/layout.html:96
 msgid "Reset Password"
 msgid "Reset Password"
 msgstr ""
 msgstr ""
 
 
@@ -183,8 +184,8 @@ msgid "You cannot post a reply without content."
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/forum/forms.py:25 flaskbb/forum/forms.py:39
 #: flaskbb/forum/forms.py:25 flaskbb/forum/forms.py:39
-#: flaskbb/templates/forum/topic.html:135
-#: flaskbb/templates/forum/topic_controls.html:48
+#: flaskbb/templates/forum/topic.html:146
+#: flaskbb/templates/forum/topic_controls.html:25
 msgid "Reply"
 msgid "Reply"
 msgstr ""
 msgstr ""
 
 
@@ -198,8 +199,8 @@ msgid "Track this Topic"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/forum/forms.py:40 flaskbb/forum/forms.py:61
 #: flaskbb/forum/forms.py:40 flaskbb/forum/forms.py:61
-#: flaskbb/templates/forum/new_post.html:22
-#: flaskbb/templates/forum/new_topic.html:21
+#: flaskbb/templates/forum/new_post.html:28
+#: flaskbb/templates/forum/new_topic.html:27
 msgid "Preview"
 msgid "Preview"
 msgstr ""
 msgstr ""
 
 
@@ -230,15 +231,17 @@ msgid "Report Post"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/forum/forms.py:85 flaskbb/forum/forms.py:89
 #: flaskbb/forum/forms.py:85 flaskbb/forum/forms.py:89
-#: flaskbb/forum/forms.py:104 flaskbb/templates/layout.html:51
+#: flaskbb/forum/forms.py:104 flaskbb/templates/layout.html:50
 #: flaskbb/templates/forum/memberlist.html:21
 #: flaskbb/templates/forum/memberlist.html:21
 #: flaskbb/templates/forum/search_form.html:1
 #: flaskbb/templates/forum/search_form.html:1
 #: flaskbb/templates/forum/search_form.html:9
 #: flaskbb/templates/forum/search_form.html:9
 #: flaskbb/templates/forum/search_form.html:13
 #: flaskbb/templates/forum/search_form.html:13
+#: flaskbb/templates/forum/search_result.html:1
+#: flaskbb/templates/forum/search_result.html:9
 #: flaskbb/templates/management/banned_users.html:31
 #: flaskbb/templates/management/banned_users.html:31
 #: flaskbb/templates/management/users.html:31
 #: flaskbb/templates/management/users.html:31
-#: flaskbb/themes/bootstrap2/templates/layout.html:52
-#: flaskbb/themes/bootstrap3/templates/layout.html:51
+#: flaskbb/themes/bootstrap2/templates/layout.html:51
+#: flaskbb/themes/bootstrap3/templates/layout.html:50
 msgid "Search"
 msgid "Search"
 msgstr ""
 msgstr ""
 
 
@@ -251,6 +254,7 @@ msgid "Post"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/forum/forms.py:101 flaskbb/templates/forum/forum.html:50
 #: flaskbb/forum/forms.py:101 flaskbb/templates/forum/forum.html:50
+#: flaskbb/templates/forum/search_result.html:113
 #: flaskbb/templates/forum/topictracker.html:28
 #: flaskbb/templates/forum/topictracker.html:28
 #: flaskbb/templates/management/reports.html:27
 #: flaskbb/templates/management/reports.html:27
 #: flaskbb/templates/management/unread_reports.html:27
 #: flaskbb/templates/management/unread_reports.html:27
@@ -258,29 +262,31 @@ msgstr ""
 msgid "Topic"
 msgid "Topic"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/forms.py:102 flaskbb/templates/layout.html:49
+#: flaskbb/forum/forms.py:102 flaskbb/templates/layout.html:48
 #: flaskbb/templates/forum/category.html:8
 #: flaskbb/templates/forum/category.html:8
 #: flaskbb/templates/forum/category_layout.html:11
 #: flaskbb/templates/forum/category_layout.html:11
 #: flaskbb/templates/forum/forum.html:9 flaskbb/templates/forum/index.html:5
 #: flaskbb/templates/forum/forum.html:9 flaskbb/templates/forum/index.html:5
 #: flaskbb/templates/forum/memberlist.html:8
 #: flaskbb/templates/forum/memberlist.html:8
-#: flaskbb/templates/forum/new_post.html:9
-#: flaskbb/templates/forum/new_topic.html:9
+#: flaskbb/templates/forum/new_post.html:15
+#: flaskbb/templates/forum/new_topic.html:15
 #: flaskbb/templates/forum/search_form.html:8
 #: flaskbb/templates/forum/search_form.html:8
-#: flaskbb/templates/forum/topic.html:9
+#: flaskbb/templates/forum/search_result.html:8
+#: flaskbb/templates/forum/search_result.html:185
+#: flaskbb/templates/forum/topic.html:14
 #: flaskbb/templates/forum/topictracker.html:9
 #: flaskbb/templates/forum/topictracker.html:9
-#: flaskbb/templates/management/forums.html:35
+#: flaskbb/templates/management/forums.html:38
 #: flaskbb/templates/message/message_layout.html:6
 #: flaskbb/templates/message/message_layout.html:6
 #: flaskbb/templates/user/all_posts.html:6
 #: flaskbb/templates/user/all_posts.html:6
 #: flaskbb/templates/user/all_topics.html:6
 #: flaskbb/templates/user/all_topics.html:6
 #: flaskbb/templates/user/profile.html:4
 #: flaskbb/templates/user/profile.html:4
 #: flaskbb/templates/user/settings_layout.html:6
 #: flaskbb/templates/user/settings_layout.html:6
-#: flaskbb/themes/bootstrap2/templates/layout.html:50
-#: flaskbb/themes/bootstrap3/templates/layout.html:49
+#: flaskbb/themes/bootstrap2/templates/layout.html:49
+#: flaskbb/themes/bootstrap3/templates/layout.html:48
 msgid "Forum"
 msgid "Forum"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/forms.py:102
-#: flaskbb/templates/management/management_layout.html:8
+#: flaskbb/forum/forms.py:102 flaskbb/templates/forum/search_result.html:77
+#: flaskbb/templates/management/management_layout.html:12
 #: flaskbb/templates/management/overview.html:29
 #: flaskbb/templates/management/overview.html:29
 #: flaskbb/templates/management/users.html:1
 #: flaskbb/templates/management/users.html:1
 msgid "Users"
 msgid "Users"
@@ -310,325 +316,335 @@ msgstr ""
 msgid "You do not have the permissions to trivialize this topic."
 msgid "You do not have the permissions to trivialize this topic."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:281
+#: flaskbb/forum/views.py:282
 msgid "You do not have the permissions to move this topic."
 msgid "You do not have the permissions to move this topic."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:286
+#: flaskbb/forum/views.py:287
 #, python-format
 #, python-format
 msgid "Could not move the topic to forum %(title)s."
 msgid "Could not move the topic to forum %(title)s."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:290
+#: flaskbb/forum/views.py:291
 #, python-format
 #, python-format
 msgid "Topic was moved to forum %(title)s."
 msgid "Topic was moved to forum %(title)s."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:306
+#: flaskbb/forum/views.py:310
 msgid "You do not have the permissions to merge this topic."
 msgid "You do not have the permissions to merge this topic."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:311
+#: flaskbb/forum/views.py:315
 msgid "Could not merge the topics."
 msgid "Could not merge the topics."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:314
+#: flaskbb/forum/views.py:318
 msgid "Topics succesfully merged."
 msgid "Topics succesfully merged."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:325 flaskbb/forum/views.py:352
+#: flaskbb/forum/views.py:329 flaskbb/forum/views.py:356
 msgid "You do not have the permissions to post in this topic."
 msgid "You do not have the permissions to post in this topic."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:378
+#: flaskbb/forum/views.py:382
 msgid "You do not have the permissions to edit this post."
 msgid "You do not have the permissions to edit this post."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:408
+#: flaskbb/forum/views.py:412
 msgid "You do not have the permissions to delete this post."
 msgid "You do not have the permissions to delete this post."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:432
+#: flaskbb/forum/views.py:436
 msgid "Thanks for reporting."
 msgid "Thanks for reporting."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:469
+#: flaskbb/forum/views.py:473
 #, python-format
 #, python-format
 msgid "Forum %(forum)s marked as read."
 msgid "Forum %(forum)s marked as read."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/forum/views.py:491
+#: flaskbb/forum/views.py:495
 msgid "All forums marked as read."
 msgid "All forums marked as read."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:54 flaskbb/templates/user/profile.html:77
+#: flaskbb/management/forms.py:66 flaskbb/templates/user/profile.html:77
+#: flaskbb/user/forms.py:81
 msgid "Birthday"
 msgid "Birthday"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:58 flaskbb/user/forms.py:85
+#: flaskbb/management/forms.py:70 flaskbb/user/forms.py:85
 msgid "Gender"
 msgid "Gender"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:60 flaskbb/user/forms.py:87
+#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:87
 msgid "Male"
 msgid "Male"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:61 flaskbb/user/forms.py:88
+#: flaskbb/management/forms.py:73 flaskbb/user/forms.py:88
 msgid "Female"
 msgid "Female"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:63 flaskbb/templates/user/profile.html:73
+#: flaskbb/management/forms.py:75 flaskbb/templates/user/profile.html:73
 #: flaskbb/user/forms.py:90
 #: flaskbb/user/forms.py:90
 msgid "Location"
 msgid "Location"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:66 flaskbb/templates/forum/topic.html:109
+#: flaskbb/management/forms.py:78 flaskbb/templates/forum/topic.html:114
 #: flaskbb/user/forms.py:93
 #: flaskbb/user/forms.py:93
 msgid "Website"
 msgid "Website"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:69 flaskbb/user/forms.py:96
+#: flaskbb/management/forms.py:81 flaskbb/user/forms.py:96
 msgid "Avatar"
 msgid "Avatar"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:99
+#: flaskbb/management/forms.py:84 flaskbb/user/forms.py:99
 msgid "Forum Signature"
 msgid "Forum Signature"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:75 flaskbb/user/forms.py:102
+#: flaskbb/management/forms.py:87 flaskbb/user/forms.py:102
 msgid "Notes"
 msgid "Notes"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:79
+#: flaskbb/management/forms.py:91
 msgid "Primary Group"
 msgid "Primary Group"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:84
+#: flaskbb/management/forms.py:96
 msgid "Secondary Groups"
 msgid "Secondary Groups"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:90 flaskbb/management/forms.py:200
-#: flaskbb/management/forms.py:308 flaskbb/management/forms.py:397
-#: flaskbb/templates/management/settings.html:39 flaskbb/user/forms.py:36
-#: flaskbb/user/forms.py:52 flaskbb/user/forms.py:77 flaskbb/user/forms.py:105
+#: flaskbb/management/forms.py:102 flaskbb/management/forms.py:212
+#: flaskbb/management/forms.py:328 flaskbb/management/forms.py:434
+#: flaskbb/templates/management/settings.html:39 flaskbb/user/forms.py:32
+#: flaskbb/user/forms.py:48 flaskbb/user/forms.py:73 flaskbb/user/forms.py:105
 msgid "Save"
 msgid "Save"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:137 flaskbb/templates/management/groups.html:25
+#: flaskbb/management/forms.py:149 flaskbb/templates/management/groups.html:25
 msgid "Group Name"
 msgid "Group Name"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:138
+#: flaskbb/management/forms.py:150
 msgid "A Group name is required."
 msgid "A Group name is required."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:140 flaskbb/management/forms.py:267
-#: flaskbb/management/forms.py:385 flaskbb/templates/management/groups.html:26
+#: flaskbb/management/forms.py:152 flaskbb/management/forms.py:279
+#: flaskbb/management/forms.py:422 flaskbb/templates/management/groups.html:26
 msgid "Description"
 msgid "Description"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:144
+#: flaskbb/management/forms.py:156
 msgid "Is Admin Group?"
 msgid "Is Admin Group?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:145
+#: flaskbb/management/forms.py:157
 msgid "With this option the group has access to the admin panel."
 msgid "With this option the group has access to the admin panel."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:149
+#: flaskbb/management/forms.py:161
 msgid "Is Super Moderator Group?"
 msgid "Is Super Moderator Group?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:150
+#: flaskbb/management/forms.py:162
 msgid "Check this if the users in this group are allowed to moderate every forum."
 msgid "Check this if the users in this group are allowed to moderate every forum."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:154
+#: flaskbb/management/forms.py:166
 msgid "Is Moderator Group?"
 msgid "Is Moderator Group?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:155
+#: flaskbb/management/forms.py:167
 msgid ""
 msgid ""
 "Check this if the users in this group are allowed to moderate specified "
 "Check this if the users in this group are allowed to moderate specified "
 "forums."
 "forums."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:159
+#: flaskbb/management/forms.py:171
 msgid "Is Banned Group?"
 msgid "Is Banned Group?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:160
+#: flaskbb/management/forms.py:172
 msgid "Only one Banned group is allowed."
 msgid "Only one Banned group is allowed."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:163
+#: flaskbb/management/forms.py:175
 msgid "Is Guest Group?"
 msgid "Is Guest Group?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:164
+#: flaskbb/management/forms.py:176
 msgid "Only one Guest group is allowed."
 msgid "Only one Guest group is allowed."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:167
+#: flaskbb/management/forms.py:179
 msgid "Can edit posts"
 msgid "Can edit posts"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:168
+#: flaskbb/management/forms.py:180
 msgid "Check this if the users in this group can edit posts."
 msgid "Check this if the users in this group can edit posts."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:171
+#: flaskbb/management/forms.py:183
 msgid "Can delete posts"
 msgid "Can delete posts"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:172
+#: flaskbb/management/forms.py:184
 msgid "Check this is the users in this group can delete posts."
 msgid "Check this is the users in this group can delete posts."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:175
+#: flaskbb/management/forms.py:187
 msgid "Can delete topics"
 msgid "Can delete topics"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:176
+#: flaskbb/management/forms.py:188
 msgid "Check this is the users in this group can delete topics."
 msgid "Check this is the users in this group can delete topics."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:180
+#: flaskbb/management/forms.py:192
 msgid "Can create topics"
 msgid "Can create topics"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:181
+#: flaskbb/management/forms.py:193
 msgid "Check this is the users in this group can create topics."
 msgid "Check this is the users in this group can create topics."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:185
+#: flaskbb/management/forms.py:197
 msgid "Can post replies"
 msgid "Can post replies"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:186
+#: flaskbb/management/forms.py:198
 msgid "Check this is the users in this group can post replies."
 msgid "Check this is the users in this group can post replies."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:190
+#: flaskbb/management/forms.py:202
 msgid "Moderators can edit user profiles"
 msgid "Moderators can edit user profiles"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:191
+#: flaskbb/management/forms.py:203
 msgid ""
 msgid ""
 "Allow moderators to edit a another users profile including password and "
 "Allow moderators to edit a another users profile including password and "
 "email changes."
 "email changes."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:196
+#: flaskbb/management/forms.py:208
 msgid "Moderators can ban users"
 msgid "Moderators can ban users"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:197
+#: flaskbb/management/forms.py:209
 msgid "Allow moderators to ban other users."
 msgid "Allow moderators to ban other users."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:214
+#: flaskbb/management/forms.py:226
 msgid "This Group name is already taken."
 msgid "This Group name is already taken."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:228
+#: flaskbb/management/forms.py:240
 msgid "There is already a Banned group."
 msgid "There is already a Banned group."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:242
+#: flaskbb/management/forms.py:254
 msgid "There is already a Guest group."
 msgid "There is already a Guest group."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:262
+#: flaskbb/management/forms.py:274
 msgid "Forum Title"
 msgid "Forum Title"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:263
+#: flaskbb/management/forms.py:275
 msgid "A Forum Title is required."
 msgid "A Forum Title is required."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:269 flaskbb/management/forms.py:387
+#: flaskbb/management/forms.py:281 flaskbb/management/forms.py:424
 msgid "You can format your description with BBCode."
 msgid "You can format your description with BBCode."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:273 flaskbb/management/forms.py:391
+#: flaskbb/management/forms.py:285 flaskbb/management/forms.py:428
 msgid "Position"
 msgid "Position"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:275
+#: flaskbb/management/forms.py:287
 msgid "The Forum Position is required."
 msgid "The Forum Position is required."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:279
+#: flaskbb/management/forms.py:291
 msgid "Category"
 msgid "Category"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:283
+#: flaskbb/management/forms.py:295
 msgid "The category that contains this forum."
 msgid "The category that contains this forum."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:287
+#: flaskbb/management/forms.py:299
 msgid "External Link"
 msgid "External Link"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:289
+#: flaskbb/management/forms.py:301
 msgid "A link to a website i.e. 'http://flaskbb.org'."
 msgid "A link to a website i.e. 'http://flaskbb.org'."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:293
+#: flaskbb/management/forms.py:305
 #: flaskbb/templates/forum/category_layout.html:60
 #: flaskbb/templates/forum/category_layout.html:60
+#: flaskbb/templates/forum/search_result.html:233
 msgid "Moderators"
 msgid "Moderators"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:294
+#: flaskbb/management/forms.py:306
 msgid ""
 msgid ""
 "Comma seperated usernames. Leave it blank if you do not want to set any "
 "Comma seperated usernames. Leave it blank if you do not want to set any "
 "moderators."
 "moderators."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:299
+#: flaskbb/management/forms.py:311
 msgid "Show Moderators"
 msgid "Show Moderators"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:300
+#: flaskbb/management/forms.py:312
 msgid "Do you want show the moderators on the index page?"
 msgid "Do you want show the moderators on the index page?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:304
+#: flaskbb/management/forms.py:316
 msgid "Locked?"
 msgid "Locked?"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:305
+#: flaskbb/management/forms.py:317
 msgid "Disable new posts and topics in this forum."
 msgid "Disable new posts and topics in this forum."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:313
+#: flaskbb/management/forms.py:321
+msgid "Group Access to Forum"
+msgstr ""
+
+#: flaskbb/management/forms.py:325
+msgid "Select user groups that can access this forum."
+msgstr ""
+
+#: flaskbb/management/forms.py:333
 msgid "You cannot convert a forum that contain topics in a external link."
 msgid "You cannot convert a forum that contain topics in a external link."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:318
+#: flaskbb/management/forms.py:338
 msgid "You also need to specify some moderators."
 msgid "You also need to specify some moderators."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:339
+#: flaskbb/management/forms.py:359
 #, python-format
 #, python-format
 msgid "%(user)s is not in a moderators group."
 msgid "%(user)s is not in a moderators group."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:345
+#: flaskbb/management/forms.py:365
 #, python-format
 #, python-format
 msgid "User %(moderator)s not found."
 msgid "User %(moderator)s not found."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:381
+#: flaskbb/management/forms.py:418
 msgid "Category Title"
 msgid "Category Title"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:382
+#: flaskbb/management/forms.py:419
 msgid "A Category Title is required."
 msgid "A Category Title is required."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/forms.py:393
+#: flaskbb/management/forms.py:430
 msgid "The Category Position is required."
 msgid "The Category Position is required."
 msgstr ""
 msgstr ""
 
 
@@ -727,23 +743,23 @@ msgstr ""
 msgid "Add Group"
 msgid "Add Group"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:370
+#: flaskbb/management/views.py:368
 msgid "Forum successfully updated."
 msgid "Forum successfully updated."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:381
+#: flaskbb/management/views.py:379
 msgid "Edit Forum"
 msgid "Edit Forum"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:394
+#: flaskbb/management/views.py:392
 msgid "Forum successfully deleted."
 msgid "Forum successfully deleted."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:406
+#: flaskbb/management/views.py:404
 msgid "Forum successfully added."
 msgid "Forum successfully added."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:414
+#: flaskbb/management/views.py:413
 #: flaskbb/templates/management/category_form.html:11
 #: flaskbb/templates/management/category_form.html:11
 #: flaskbb/templates/management/forum_form.html:11
 #: flaskbb/templates/management/forum_form.html:11
 #: flaskbb/templates/management/forums.html:10
 #: flaskbb/templates/management/forums.html:10
@@ -751,26 +767,26 @@ msgstr ""
 msgid "Add Forum"
 msgid "Add Forum"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:424
+#: flaskbb/management/views.py:423
 msgid "Category successfully added."
 msgid "Category successfully added."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:428
+#: flaskbb/management/views.py:427
 #: flaskbb/templates/management/category_form.html:12
 #: flaskbb/templates/management/category_form.html:12
 #: flaskbb/templates/management/forum_form.html:12
 #: flaskbb/templates/management/forum_form.html:12
 #: flaskbb/templates/management/forums.html:11
 #: flaskbb/templates/management/forums.html:11
 msgid "Add Category"
 msgid "Add Category"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:440
+#: flaskbb/management/views.py:439
 msgid "Category successfully updated."
 msgid "Category successfully updated."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:444
+#: flaskbb/management/views.py:443
 msgid "Edit Category"
 msgid "Edit Category"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:457
+#: flaskbb/management/views.py:456
 msgid "Category with all associated forums deleted."
 msgid "Category with all associated forums deleted."
 msgstr ""
 msgstr ""
 
 
@@ -778,92 +794,162 @@ msgstr ""
 msgid "Plugin is enabled. Please reload your app."
 msgid "Plugin is enabled. Please reload your app."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:485
+#: flaskbb/management/views.py:486
+msgid "Plugin is already enabled. Please reload  your app."
+msgstr ""
+
+#: flaskbb/management/views.py:490
 msgid ""
 msgid ""
 "If you are using a host which doesn't support writting on the disk, this "
 "If you are using a host which doesn't support writting on the disk, this "
 "won't work - than you need to delete the 'DISABLED' file by yourself."
 "won't work - than you need to delete the 'DISABLED' file by yourself."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:489
+#: flaskbb/management/views.py:495
 msgid "Couldn't enable Plugin."
 msgid "Couldn't enable Plugin."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:500
+#: flaskbb/management/views.py:506
 #, python-format
 #, python-format
 msgid "Plugin %(plugin)s not found."
 msgid "Plugin %(plugin)s not found."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:512
+#: flaskbb/management/views.py:518
 msgid "Plugin is disabled. Please reload your app."
 msgid "Plugin is disabled. Please reload your app."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:514
+#: flaskbb/management/views.py:521
 msgid ""
 msgid ""
 "If you are using a host which doesn't support writting on the disk, this "
 "If you are using a host which doesn't support writting on the disk, this "
 "won't work - than you need to create a 'DISABLED' file by yourself."
 "won't work - than you need to create a 'DISABLED' file by yourself."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:529
+#: flaskbb/management/views.py:536
 msgid "Plugin has been uninstalled."
 msgid "Plugin has been uninstalled."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:531
+#: flaskbb/management/views.py:538
 msgid "Cannot uninstall Plugin."
 msgid "Cannot uninstall Plugin."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:544
+#: flaskbb/management/views.py:551
 msgid "Plugin has been installed."
 msgid "Plugin has been installed."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/management/views.py:546
+#: flaskbb/management/views.py:553
 msgid "Cannot install Plugin."
 msgid "Cannot install Plugin."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:50 flaskbb/templates/forum/memberlist.html:1
+#: flaskbb/message/forms.py:22
+msgid "To User"
+msgstr ""
+
+#: flaskbb/message/forms.py:25
+msgid "Subject"
+msgstr ""
+
+#: flaskbb/message/forms.py:26
+msgid "A Subject is required."
+msgstr ""
+
+#: flaskbb/message/forms.py:28 flaskbb/message/forms.py:57
+msgid "Message"
+msgstr ""
+
+#: flaskbb/message/forms.py:29 flaskbb/message/forms.py:58
+msgid "A Message is required."
+msgstr ""
+
+#: flaskbb/message/forms.py:31
+msgid "Start Conversation"
+msgstr ""
+
+#: flaskbb/message/forms.py:32
+msgid "Save Conversation"
+msgstr ""
+
+#: flaskbb/message/forms.py:37
+msgid "The Username you entered doesn't exist"
+msgstr ""
+
+#: flaskbb/message/forms.py:39
+msgid "You cannot send a PM to yourself."
+msgstr ""
+
+#: flaskbb/message/forms.py:59
+msgid "Send Message"
+msgstr ""
+
+#: flaskbb/message/views.py:70 flaskbb/message/views.py:126
+msgid ""
+"You cannot send any messages anymore because you havereached your message"
+" limit."
+msgstr ""
+
+#: flaskbb/message/views.py:143 flaskbb/message/views.py:214
+msgid "Message saved."
+msgstr ""
+
+#: flaskbb/message/views.py:167 flaskbb/message/views.py:232
+msgid "Message sent."
+msgstr ""
+
+#: flaskbb/message/views.py:173
+msgid "Compose Message"
+msgstr ""
+
+#: flaskbb/message/views.py:200
+msgid "You cannot edit a sent message."
+msgstr ""
+
+#: flaskbb/message/views.py:240
+msgid "Edit Message"
+msgstr ""
+
+#: flaskbb/templates/layout.html:49 flaskbb/templates/forum/memberlist.html:1
 #: flaskbb/templates/forum/memberlist.html:9
 #: flaskbb/templates/forum/memberlist.html:9
-#: flaskbb/themes/bootstrap2/templates/layout.html:51
-#: flaskbb/themes/bootstrap3/templates/layout.html:50
+#: flaskbb/themes/bootstrap2/templates/layout.html:50
+#: flaskbb/themes/bootstrap3/templates/layout.html:49
 msgid "Memberlist"
 msgid "Memberlist"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:65 flaskbb/templates/forum/topictracker.html:1
+#: flaskbb/templates/layout.html:64 flaskbb/templates/forum/topictracker.html:1
 #: flaskbb/templates/forum/topictracker.html:22
 #: flaskbb/templates/forum/topictracker.html:22
-#: flaskbb/themes/bootstrap2/templates/layout.html:66
-#: flaskbb/themes/bootstrap3/templates/layout.html:65
+#: flaskbb/themes/bootstrap2/templates/layout.html:65
+#: flaskbb/themes/bootstrap3/templates/layout.html:64
 msgid "Topic Tracker"
 msgid "Topic Tracker"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:68
-#: flaskbb/templates/management/management_layout.html:12
+#: flaskbb/templates/layout.html:67
+#: flaskbb/templates/management/management_layout.html:16
 #: flaskbb/templates/user/settings_layout.html:8
 #: flaskbb/templates/user/settings_layout.html:8
-#: flaskbb/themes/bootstrap2/templates/layout.html:69
-#: flaskbb/themes/bootstrap3/templates/layout.html:68
+#: flaskbb/themes/bootstrap2/templates/layout.html:68
+#: flaskbb/themes/bootstrap3/templates/layout.html:67
 msgid "Settings"
 msgid "Settings"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:70 flaskbb/templates/management/forums.html:36
-#: flaskbb/themes/bootstrap2/templates/layout.html:71
-#: flaskbb/themes/bootstrap3/templates/layout.html:70
+#: flaskbb/templates/layout.html:69 flaskbb/templates/management/forums.html:39
+#: flaskbb/themes/bootstrap2/templates/layout.html:70
+#: flaskbb/themes/bootstrap3/templates/layout.html:69
 msgid "Management"
 msgid "Management"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:74
-#: flaskbb/themes/bootstrap2/templates/layout.html:75
-#: flaskbb/themes/bootstrap3/templates/layout.html:74
+#: flaskbb/templates/layout.html:73
+#: flaskbb/themes/bootstrap2/templates/layout.html:74
+#: flaskbb/themes/bootstrap3/templates/layout.html:73
 msgid "Logout"
 msgid "Logout"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:83
+#: flaskbb/templates/layout.html:82
 msgid "Private Messages"
 msgid "Private Messages"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/layout.html:84
-#: flaskbb/themes/bootstrap2/templates/layout.html:85
-#: flaskbb/themes/bootstrap3/templates/layout.html:84
+#: flaskbb/templates/layout.html:83
+#: flaskbb/themes/bootstrap2/templates/layout.html:84
+#: flaskbb/themes/bootstrap3/templates/layout.html:83
 msgid "New Message"
 msgid "New Message"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/macros.html:281
+#: flaskbb/templates/macros.html:313
 msgid "Pages"
 msgid "Pages"
 msgstr ""
 msgstr ""
 
 
@@ -934,6 +1020,8 @@ msgid "Something went wrong!"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/category_layout.html:12
 #: flaskbb/templates/forum/category_layout.html:12
+#: flaskbb/templates/forum/search_result.html:108
+#: flaskbb/templates/forum/search_result.html:186
 #: flaskbb/templates/management/overview.html:23
 #: flaskbb/templates/management/overview.html:23
 msgid "Topics"
 msgid "Topics"
 msgstr ""
 msgstr ""
@@ -941,12 +1029,16 @@ msgstr ""
 #: flaskbb/templates/forum/category_layout.html:13
 #: flaskbb/templates/forum/category_layout.html:13
 #: flaskbb/templates/forum/forum.html:52
 #: flaskbb/templates/forum/forum.html:52
 #: flaskbb/templates/forum/memberlist.html:32
 #: flaskbb/templates/forum/memberlist.html:32
-#: flaskbb/templates/forum/topic.html:70
+#: flaskbb/templates/forum/search_result.html:13
+#: flaskbb/templates/forum/search_result.html:43
+#: flaskbb/templates/forum/search_result.html:84
+#: flaskbb/templates/forum/search_result.html:115
+#: flaskbb/templates/forum/search_result.html:187
+#: flaskbb/templates/forum/topic.html:75
 #: flaskbb/templates/forum/topictracker.html:30
 #: flaskbb/templates/forum/topictracker.html:30
 #: flaskbb/templates/management/banned_users.html:42
 #: flaskbb/templates/management/banned_users.html:42
 #: flaskbb/templates/management/overview.html:17
 #: flaskbb/templates/management/overview.html:17
 #: flaskbb/templates/management/users.html:42
 #: flaskbb/templates/management/users.html:42
-#: flaskbb/templates/message/view_message.html:29
 #: flaskbb/templates/user/all_topics.html:28
 #: flaskbb/templates/user/all_topics.html:28
 #: flaskbb/templates/user/profile.html:56
 #: flaskbb/templates/user/profile.html:56
 msgid "Posts"
 msgid "Posts"
@@ -954,6 +1046,8 @@ msgstr ""
 
 
 #: flaskbb/templates/forum/category_layout.html:14
 #: flaskbb/templates/forum/category_layout.html:14
 #: flaskbb/templates/forum/forum.html:56
 #: flaskbb/templates/forum/forum.html:56
+#: flaskbb/templates/forum/search_result.html:119
+#: flaskbb/templates/forum/search_result.html:188
 #: flaskbb/templates/forum/topictracker.html:34
 #: flaskbb/templates/forum/topictracker.html:34
 #: flaskbb/templates/user/all_topics.html:32
 #: flaskbb/templates/user/all_topics.html:32
 msgid "Last Post"
 msgid "Last Post"
@@ -961,7 +1055,10 @@ msgstr ""
 
 
 #: flaskbb/templates/forum/category_layout.html:80
 #: flaskbb/templates/forum/category_layout.html:80
 #: flaskbb/templates/forum/forum.html:86 flaskbb/templates/forum/forum.html:105
 #: flaskbb/templates/forum/forum.html:86 flaskbb/templates/forum/forum.html:105
-#: flaskbb/templates/forum/topic.html:35
+#: flaskbb/templates/forum/search_result.html:142
+#: flaskbb/templates/forum/search_result.html:161
+#: flaskbb/templates/forum/search_result.html:253
+#: flaskbb/templates/forum/topic.html:40
 #: flaskbb/templates/forum/topictracker.html:52
 #: flaskbb/templates/forum/topictracker.html:52
 #: flaskbb/templates/forum/topictracker.html:70
 #: flaskbb/templates/forum/topictracker.html:70
 #: flaskbb/templates/management/plugins.html:30
 #: flaskbb/templates/management/plugins.html:30
@@ -971,36 +1068,36 @@ msgid "by"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/category_layout.html:88
 #: flaskbb/templates/forum/category_layout.html:88
+#: flaskbb/templates/forum/search_result.html:261
 #: flaskbb/templates/user/all_posts.html:40
 #: flaskbb/templates/user/all_posts.html:40
 msgid "No posts."
 msgid "No posts."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/forum.html:22
-#: flaskbb/templates/management/unread_reports.html:44
+#: flaskbb/templates/forum/forum.html:23
+#: flaskbb/templates/management/unread_reports.html:51
 msgid "Mark as Read"
 msgid "Mark as Read"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/forum.html:27
+#: flaskbb/templates/forum/forum.html:29
 msgid "Locked"
 msgid "Locked"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/forum.html:31
+#: flaskbb/templates/forum/forum.html:33
 #: flaskbb/templates/forum/new_topic.html:1
 #: flaskbb/templates/forum/new_topic.html:1
-#: flaskbb/templates/forum/new_topic.html:11
-#: flaskbb/templates/forum/new_topic.html:16
+#: flaskbb/templates/forum/new_topic.html:17
+#: flaskbb/templates/forum/new_topic.html:22
 msgid "New Topic"
 msgid "New Topic"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/forum.html:54
 #: flaskbb/templates/forum/forum.html:54
+#: flaskbb/templates/forum/search_result.html:117
 #: flaskbb/templates/forum/topictracker.html:32
 #: flaskbb/templates/forum/topictracker.html:32
 #: flaskbb/templates/user/all_topics.html:30
 #: flaskbb/templates/user/all_topics.html:30
 msgid "Views"
 msgid "Views"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/forum.html:117
 #: flaskbb/templates/forum/forum.html:117
-#: flaskbb/templates/forum/topictracker.html:82
-#: flaskbb/templates/user/all_topics.html:72
-msgid "No topics."
+msgid "No Topics."
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/index.html:17
 #: flaskbb/templates/forum/index.html:17
@@ -1040,12 +1137,14 @@ msgid "Guests online"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/memberlist.html:33
 #: flaskbb/templates/forum/memberlist.html:33
+#: flaskbb/templates/forum/search_result.html:85
 #: flaskbb/templates/management/banned_users.html:43
 #: flaskbb/templates/management/banned_users.html:43
 #: flaskbb/templates/management/users.html:43
 #: flaskbb/templates/management/users.html:43
 msgid "Date registered"
 msgid "Date registered"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/memberlist.html:34
 #: flaskbb/templates/forum/memberlist.html:34
+#: flaskbb/templates/forum/search_result.html:86
 #: flaskbb/templates/management/banned_users.html:44
 #: flaskbb/templates/management/banned_users.html:44
 #: flaskbb/templates/management/users.html:44
 #: flaskbb/templates/management/users.html:44
 #: flaskbb/templates/user/profile.html:48
 #: flaskbb/templates/user/profile.html:48
@@ -1053,8 +1152,8 @@ msgid "Group"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/new_post.html:1
 #: flaskbb/templates/forum/new_post.html:1
-#: flaskbb/templates/forum/new_post.html:12
-#: flaskbb/templates/forum/new_post.html:17
+#: flaskbb/templates/forum/new_post.html:18
+#: flaskbb/templates/forum/new_post.html:23
 msgid "New Post"
 msgid "New Post"
 msgstr ""
 msgstr ""
 
 
@@ -1064,7 +1163,7 @@ msgid "Online Users"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/forum/report_post.html:17
 #: flaskbb/templates/forum/report_post.html:17
-#: flaskbb/templates/forum/topic.html:116
+#: flaskbb/templates/forum/topic.html:121
 msgid "Report"
 msgid "Report"
 msgstr ""
 msgstr ""
 
 
@@ -1072,86 +1171,111 @@ msgstr ""
 msgid "Close"
 msgid "Close"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:2
-#, python-format
-msgid "%(title)s - Topic"
+#: flaskbb/templates/forum/search_result.html:44
+#: flaskbb/templates/forum/topic.html:76
+msgid "Registered since"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:35
-msgid "Last modified"
+#: flaskbb/templates/forum/search_result.html:50
+#: flaskbb/templates/forum/topic.html:82
+msgid "Guest"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:71
-#: flaskbb/templates/message/view_message.html:30
-msgid "Registered since"
+#: flaskbb/templates/forum/search_result.html:69
+msgid "No posts found matching your search criteria."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:77
-msgid "Guest"
+#: flaskbb/templates/forum/search_result.html:100
+#: flaskbb/templates/management/banned_users.html:68
+#: flaskbb/templates/management/users.html:86
+msgid "No users found matching your search criteria."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:106
+#: flaskbb/templates/forum/search_result.html:172
+msgid "No topics found matching your search criteria."
+msgstr ""
+
+#: flaskbb/templates/forum/search_result.html:180
+#: flaskbb/templates/management/forums.html:1
+#: flaskbb/templates/management/management_layout.html:18
+msgid "Forums"
+msgstr ""
+
+#: flaskbb/templates/forum/search_result.html:269
+msgid "No forums found matching your search criteria."
+msgstr ""
+
+#: flaskbb/templates/forum/topic.html:2
+#, python-format
+msgid "%(title)s - Topic"
+msgstr ""
+
+#: flaskbb/templates/forum/topic.html:40
+msgid "Last modified"
+msgstr ""
+
+#: flaskbb/templates/forum/topic.html:111
 msgid "PM"
 msgid "PM"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:120
+#: flaskbb/templates/forum/topic.html:125
 #: flaskbb/templates/management/forums.html:28
 #: flaskbb/templates/management/forums.html:28
-#: flaskbb/templates/management/forums.html:56
+#: flaskbb/templates/management/forums.html:59
 #: flaskbb/templates/management/groups.html:37
 #: flaskbb/templates/management/groups.html:37
 #: flaskbb/templates/management/users.html:58
 #: flaskbb/templates/management/users.html:58
-#: flaskbb/templates/message/drafts.html:21
 msgid "Edit"
 msgid "Edit"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:124
-#: flaskbb/templates/forum/topic.html:128
-#: flaskbb/templates/management/forums.html:29
-#: flaskbb/templates/management/forums.html:57
-#: flaskbb/templates/management/groups.html:38
-#: flaskbb/templates/management/users.html:70
-#: flaskbb/templates/message/drafts.html:22
-#: flaskbb/templates/message/inbox.html:28
-#: flaskbb/templates/message/sent.html:24
-#: flaskbb/templates/message/trash.html:22
+#: flaskbb/templates/forum/topic.html:131
+#: flaskbb/templates/forum/topic.html:138
+#: flaskbb/templates/management/forums.html:31
+#: flaskbb/templates/management/forums.html:62
+#: flaskbb/templates/management/groups.html:40
+#: flaskbb/templates/management/users.html:78
 msgid "Delete"
 msgid "Delete"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic.html:133
+#: flaskbb/templates/forum/topic.html:144
 msgid "Quote"
 msgid "Quote"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic_controls.html:9
+#: flaskbb/templates/forum/topic_controls.html:11
+msgid "Untrack Topic"
+msgstr ""
+
+#: flaskbb/templates/forum/topic_controls.html:18
+msgid "Track Topic"
+msgstr ""
+
+#: flaskbb/templates/forum/topic_controls.html:36
 msgid "Delete Topic"
 msgid "Delete Topic"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic_controls.html:15
+#: flaskbb/templates/forum/topic_controls.html:45
 msgid "Lock Topic"
 msgid "Lock Topic"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic_controls.html:19
+#: flaskbb/templates/forum/topic_controls.html:52
 msgid "Unlock Topic"
 msgid "Unlock Topic"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic_controls.html:24
+#: flaskbb/templates/forum/topic_controls.html:61
 msgid "Highlight Topic"
 msgid "Highlight Topic"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic_controls.html:28
+#: flaskbb/templates/forum/topic_controls.html:68
 msgid "Trivialize Topic"
 msgid "Trivialize Topic"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/forum/topic_controls.html:38
-msgid "Untrack Topic"
-msgstr ""
-
-#: flaskbb/templates/forum/topic_controls.html:42
-msgid "Track Topic"
-msgstr ""
-
 #: flaskbb/templates/forum/topictracker.html:10
 #: flaskbb/templates/forum/topictracker.html:10
 msgid "Tracked Topics"
 msgid "Tracked Topics"
 msgstr ""
 msgstr ""
 
 
+#: flaskbb/templates/forum/topictracker.html:82
+#: flaskbb/templates/user/all_topics.html:72
+msgid "No topics."
+msgstr ""
+
 #: flaskbb/templates/management/banned_users.html:1
 #: flaskbb/templates/management/banned_users.html:1
 #: flaskbb/templates/management/banned_users.html:11
 #: flaskbb/templates/management/banned_users.html:11
 #: flaskbb/templates/management/banned_users.html:20
 #: flaskbb/templates/management/banned_users.html:20
@@ -1174,16 +1298,11 @@ msgstr ""
 msgid "Manage"
 msgid "Manage"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/banned_users.html:58
-#: flaskbb/templates/management/users.html:66
+#: flaskbb/templates/management/banned_users.html:60
+#: flaskbb/templates/management/users.html:71
 msgid "Unban"
 msgid "Unban"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/banned_users.html:65
-#: flaskbb/templates/management/users.html:77
-msgid "No users found matching your search criteria."
-msgstr ""
-
 #: flaskbb/templates/management/category_form.html:10
 #: flaskbb/templates/management/category_form.html:10
 #: flaskbb/templates/management/forum_form.html:10
 #: flaskbb/templates/management/forum_form.html:10
 #: flaskbb/templates/management/forums.html:9
 #: flaskbb/templates/management/forums.html:9
@@ -1191,9 +1310,8 @@ msgstr ""
 msgid "Manage Forums"
 msgid "Manage Forums"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/forums.html:1
-#: flaskbb/templates/management/management_layout.html:14
-msgid "Forums"
+#: flaskbb/templates/management/forum_form.html:37
+msgid "Group Access to the forum"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/management/group_form.html:10
 #: flaskbb/templates/management/group_form.html:10
@@ -1203,21 +1321,21 @@ msgid "Manage Groups"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/management/groups.html:1
 #: flaskbb/templates/management/groups.html:1
-#: flaskbb/templates/management/management_layout.html:13
+#: flaskbb/templates/management/management_layout.html:17
 msgid "Groups"
 msgid "Groups"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/management_layout.html:7
+#: flaskbb/templates/management/management_layout.html:11
 #: flaskbb/templates/management/overview.html:1
 #: flaskbb/templates/management/overview.html:1
 msgid "Overview"
 msgid "Overview"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/management_layout.html:9
+#: flaskbb/templates/management/management_layout.html:13
 #: flaskbb/templates/management/reports.html:1
 #: flaskbb/templates/management/reports.html:1
 msgid "Reports"
 msgid "Reports"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/management_layout.html:15
+#: flaskbb/templates/management/management_layout.html:19
 #: flaskbb/templates/management/plugins.html:1
 #: flaskbb/templates/management/plugins.html:1
 msgid "Plugins"
 msgid "Plugins"
 msgstr ""
 msgstr ""
@@ -1254,19 +1372,19 @@ msgstr ""
 msgid "Version"
 msgid "Version"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/plugins.html:34
+#: flaskbb/templates/management/plugins.html:36
 msgid "Enable"
 msgid "Enable"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/plugins.html:36
+#: flaskbb/templates/management/plugins.html:41
 msgid "Disable"
 msgid "Disable"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/plugins.html:42
+#: flaskbb/templates/management/plugins.html:50
 msgid "Install"
 msgid "Install"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/plugins.html:45
+#: flaskbb/templates/management/plugins.html:56
 msgid "Uninstall"
 msgid "Uninstall"
 msgstr ""
 msgstr ""
 
 
@@ -1308,86 +1426,42 @@ msgstr ""
 msgid "Unread Reports"
 msgid "Unread Reports"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/unread_reports.html:31
+#: flaskbb/templates/management/unread_reports.html:34
 msgid "Mark all as Read"
 msgid "Mark all as Read"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/unread_reports.html:49
+#: flaskbb/templates/management/unread_reports.html:57
 msgid "No unread reports."
 msgid "No unread reports."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/management/users.html:62
+#: flaskbb/templates/management/users.html:64
 msgid "Ban"
 msgid "Ban"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/message/drafts.html:1
-#: flaskbb/templates/message/message_layout.html:18
-msgid "Drafts"
-msgstr ""
-
-#: flaskbb/templates/message/drafts.html:8
-#: flaskbb/templates/message/sent.html:8 flaskbb/templates/message/trash.html:8
-msgid "To"
-msgstr ""
-
-#: flaskbb/templates/message/drafts.html:9
-#: flaskbb/templates/message/inbox.html:9 flaskbb/templates/message/sent.html:9
-#: flaskbb/templates/message/trash.html:9 flaskbb/user/forms.py:112
-msgid "Subject"
-msgstr ""
-
-#: flaskbb/templates/message/drafts.html:10
-#: flaskbb/templates/message/inbox.html:10
-#: flaskbb/templates/message/sent.html:10
-#: flaskbb/templates/message/trash.html:10
-msgid "Date"
-msgstr ""
-
-#: flaskbb/templates/message/drafts.html:11
-#: flaskbb/templates/message/inbox.html:11
-#: flaskbb/templates/message/message_layout.html:15
-#: flaskbb/templates/message/sent.html:11
-#: flaskbb/templates/message/trash.html:11
-msgid "Options"
+#: flaskbb/templates/message/conversation_list.html:4
+msgid "Conversations"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/message/drafts.html:18
-#: flaskbb/templates/message/trash.html:18
-msgid "No Subject"
+#: flaskbb/templates/message/conversation_list.html:77
+msgid "No conversations found."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/message/drafts.html:28
-#: flaskbb/templates/message/inbox.html:33
-#: flaskbb/templates/message/sent.html:29
-#: flaskbb/templates/message/trash.html:28
-msgid "This message folder is empty."
+#: flaskbb/templates/message/drafts.html:1
+#: flaskbb/templates/message/message_layout.html:18
+msgid "Drafts"
 msgstr ""
 msgstr ""
 
 
 #: flaskbb/templates/message/inbox.html:1
 #: flaskbb/templates/message/inbox.html:1
 #: flaskbb/templates/message/message_layout.html:16
 #: flaskbb/templates/message/message_layout.html:16
-#: flaskbb/themes/bootstrap2/templates/layout.html:84
-#: flaskbb/themes/bootstrap3/templates/layout.html:83
+#: flaskbb/themes/bootstrap2/templates/layout.html:83
+#: flaskbb/themes/bootstrap3/templates/layout.html:82
 msgid "Inbox"
 msgid "Inbox"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/message/inbox.html:8
-msgid "From"
-msgstr ""
-
-#: flaskbb/templates/message/inbox.html:20
-#: flaskbb/templates/message/sent.html:20
-#: flaskbb/templates/message/view_message.html:33
-msgid "deleted"
-msgstr ""
-
 #: flaskbb/templates/message/message_layout.html:8
 #: flaskbb/templates/message/message_layout.html:8
 msgid "Private Message"
 msgid "Private Message"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/message/message_layout.html:15
-msgid "New PM"
-msgstr ""
-
 #: flaskbb/templates/message/message_layout.html:17
 #: flaskbb/templates/message/message_layout.html:17
 msgid "Sent"
 msgid "Sent"
 msgstr ""
 msgstr ""
@@ -1401,18 +1475,6 @@ msgstr ""
 msgid "Sent Messages"
 msgid "Sent Messages"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/templates/message/trash.html:17
-msgid "No User"
-msgstr ""
-
-#: flaskbb/templates/message/trash.html:21
-msgid "Restore"
-msgstr ""
-
-#: flaskbb/templates/message/view_message.html:49
-msgid "No Message"
-msgstr ""
-
 #: flaskbb/templates/user/all_posts.html:8
 #: flaskbb/templates/user/all_posts.html:8
 #: flaskbb/templates/user/profile.html:29
 #: flaskbb/templates/user/profile.html:29
 msgid "All Posts"
 msgid "All Posts"
@@ -1494,127 +1556,59 @@ msgstr ""
 msgid "Account Settings"
 msgid "Account Settings"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:27
-msgid "Only jpg, jpeg, png and gifs are allowed!"
-msgstr ""
-
-#: flaskbb/user/forms.py:33
+#: flaskbb/user/forms.py:29
 msgid "Language"
 msgid "Language"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:34
+#: flaskbb/user/forms.py:30
 msgid "Theme"
 msgid "Theme"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:40
+#: flaskbb/user/forms.py:36
 msgid "Old E-Mail Address"
 msgid "Old E-Mail Address"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:44
+#: flaskbb/user/forms.py:40
 msgid "New E-Mail Address"
 msgid "New E-Mail Address"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:46
+#: flaskbb/user/forms.py:42
 msgid "E-Mails must match."
 msgid "E-Mails must match."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:49
+#: flaskbb/user/forms.py:45
 msgid "Confirm E-Mail Address"
 msgid "Confirm E-Mail Address"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:68
+#: flaskbb/user/forms.py:64
 msgid "Old Password"
 msgid "Old Password"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:69
+#: flaskbb/user/forms.py:65
 msgid "Password required"
 msgid "Password required"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:75
+#: flaskbb/user/forms.py:71
 msgid "Confirm New Password"
 msgid "Confirm New Password"
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/forms.py:82
-msgid "Your Birthday"
-msgstr ""
-
-#: flaskbb/user/forms.py:109
-msgid "To User"
-msgstr ""
-
-#: flaskbb/user/forms.py:113
-msgid "A Subject is required."
-msgstr ""
-
-#: flaskbb/user/forms.py:115
-msgid "Message"
-msgstr ""
-
-#: flaskbb/user/forms.py:116
-msgid "A Message is required."
-msgstr ""
-
-#: flaskbb/user/forms.py:118
-msgid "Send Message"
-msgstr ""
-
-#: flaskbb/user/forms.py:119
-msgid "Save Message"
-msgstr ""
-
-#: flaskbb/user/forms.py:124
-msgid "The Username you entered doesn't exist"
-msgstr ""
-
-#: flaskbb/user/forms.py:126
-msgid "You cannot send a PM to yourself."
+#: flaskbb/user/forms.py:77
+msgid "Old Password is wrong."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/views.py:69
+#: flaskbb/user/views.py:66
 msgid "Settings updated."
 msgid "Settings updated."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/views.py:85
+#: flaskbb/user/views.py:82
 msgid "Password updated."
 msgid "Password updated."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/views.py:97
+#: flaskbb/user/views.py:94
 msgid "E-Mail Address updated."
 msgid "E-Mail Address updated."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/views.py:110
+#: flaskbb/user/views.py:107
 msgid "Details updated."
 msgid "Details updated."
 msgstr ""
 msgstr ""
 
 
-#: flaskbb/user/views.py:183 flaskbb/user/views.py:230
-msgid "Message saved."
-msgstr ""
-
-#: flaskbb/user/views.py:201 flaskbb/user/views.py:247
-msgid "Message sent."
-msgstr ""
-
-#: flaskbb/user/views.py:207
-msgid "Compose Message"
-msgstr ""
-
-#: flaskbb/user/views.py:216
-msgid "You cannot edit a sent message."
-msgstr ""
-
-#: flaskbb/user/views.py:255
-msgid "Edit Message"
-msgstr ""
-
-#: flaskbb/user/views.py:264
-msgid "Message moved to Trash."
-msgstr ""
-
-#: flaskbb/user/views.py:274
-msgid "Message restored from Trash."
-msgstr ""
-
-#: flaskbb/user/views.py:283
-msgid "Message deleted."
-msgstr ""
-

+ 1608 - 0
flaskbb/translations/zh_CN/LC_MESSAGES/messages.po

@@ -0,0 +1,1608 @@
+# Chinese (Simplified, China) translations for PROJECT.
+# Copyright (C) 2015 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <realityone@me.com>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.1-dev\n"
+"Report-Msgid-Bugs-To: https://github.com/flaskbb\n"
+"POT-Creation-Date: 2015-07-14 22:49+0800\n"
+"PO-Revision-Date: 2015-07-15 10:53+0800\n"
+"Language-Team: FlaskBB in zh_Hans_CN\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"
+"Last-Translator: ZHOU JIAHUI <realityone@me.com>\n"
+"Language: zh\n"
+"X-Generator: Poedit 1.8.1\n"
+
+#: flaskbb/email.py:20
+msgid "Password Reset"
+msgstr "密码重置"
+
+#: flaskbb/auth/forms.py:24 flaskbb/management/forms.py:37
+msgid "You can only use letters, numbers or dashes."
+msgstr "您只能使用字母、数字与短划线"
+
+#: flaskbb/auth/forms.py:28
+msgid "Username or E-Mail Address"
+msgstr "用户名或邮箱地址"
+
+#: flaskbb/auth/forms.py:29
+msgid "A Username or E-Mail Address is required."
+msgstr "请输入用户名或邮箱地址。"
+
+#: flaskbb/auth/forms.py:32 flaskbb/auth/forms.py:49 flaskbb/auth/forms.py:83
+#: flaskbb/auth/forms.py:104 flaskbb/user/forms.py:67
+msgid "Password"
+msgstr "密码"
+
+#: flaskbb/auth/forms.py:33 flaskbb/auth/forms.py:84
+msgid "A Password is required."
+msgstr "请输入密码。"
+
+#: flaskbb/auth/forms.py:35
+msgid "Remember Me"
+msgstr "记住我"
+
+#: flaskbb/auth/forms.py:37 flaskbb/templates/layout.html:89
+#: flaskbb/templates/auth/login.html:1 flaskbb/templates/auth/login.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:90
+#: flaskbb/themes/bootstrap3/templates/layout.html:89
+msgid "Login"
+msgstr "登录"
+
+#: flaskbb/auth/forms.py:41 flaskbb/management/forms.py:55
+#: flaskbb/templates/forum/memberlist.html:31
+#: flaskbb/templates/forum/search_result.html:83
+#: flaskbb/templates/management/banned_users.html:41
+#: flaskbb/templates/management/users.html:41
+msgid "Username"
+msgstr "用户名"
+
+#: flaskbb/auth/forms.py:42 flaskbb/management/forms.py:56
+#: flaskbb/message/forms.py:23
+msgid "A Username is required."
+msgstr "请输入用户名。"
+
+#: flaskbb/auth/forms.py:45 flaskbb/auth/forms.py:90 flaskbb/auth/forms.py:100
+#: flaskbb/management/forms.py:59
+msgid "E-Mail Address"
+msgstr "邮箱地址"
+
+#: flaskbb/auth/forms.py:46 flaskbb/auth/forms.py:101
+#: flaskbb/management/forms.py:60 flaskbb/user/forms.py:37
+msgid "A E-Mail Address is required."
+msgstr "请输入邮箱地址"
+
+#: flaskbb/auth/forms.py:47 flaskbb/management/forms.py:61
+#: flaskbb/user/forms.py:38 flaskbb/user/forms.py:43 flaskbb/user/forms.py:46
+msgid "Invalid E-Mail Address."
+msgstr "不正确的邮箱地址"
+
+#: flaskbb/auth/forms.py:51 flaskbb/auth/forms.py:106 flaskbb/user/forms.py:69
+msgid "Passwords must match."
+msgstr "两次输入的密码不一致。"
+
+#: flaskbb/auth/forms.py:53 flaskbb/auth/forms.py:108
+msgid "Confirm Password"
+msgstr "校验密码"
+
+#: flaskbb/auth/forms.py:55
+msgid "I accept the Terms of Service"
+msgstr "我同意并遵守服务条例"
+
+#: flaskbb/auth/forms.py:57 flaskbb/templates/layout.html:95
+#: flaskbb/templates/auth/register.html:1 flaskbb/templates/auth/register.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:96
+#: flaskbb/themes/bootstrap3/templates/layout.html:95
+msgid "Register"
+msgstr "注册"
+
+#: flaskbb/auth/forms.py:62 flaskbb/management/forms.py:116
+msgid "This Username is already taken."
+msgstr "该用户名已被使用。"
+
+#: flaskbb/auth/forms.py:67 flaskbb/management/forms.py:130
+#: flaskbb/user/forms.py:60
+msgid "This E-Mail Address is already taken."
+msgstr "该邮箱已被使用。"
+
+#: flaskbb/auth/forms.py:79
+msgid "Captcha"
+msgstr "验证码"
+
+#: flaskbb/auth/forms.py:86 flaskbb/templates/auth/reauth.html:1
+#: flaskbb/templates/auth/reauth.html:9
+msgid "Refresh Login"
+msgstr "重新登录"
+
+#: flaskbb/auth/forms.py:91
+msgid "A E-Mail Address is reguired."
+msgstr "请输入邮箱地址。"
+
+#: flaskbb/auth/forms.py:94 flaskbb/templates/auth/forgot_password.html:1
+msgid "Request Password"
+msgstr "请求重置密码"
+
+#: flaskbb/auth/forms.py:110 flaskbb/templates/layout.html:96
+#: flaskbb/templates/auth/forgot_password.html:8
+#: flaskbb/templates/auth/reset_password.html:1
+#: flaskbb/templates/auth/reset_password.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:97
+#: flaskbb/themes/bootstrap3/templates/layout.html:96
+msgid "Reset Password"
+msgstr "重置密码"
+
+#: flaskbb/auth/forms.py:115
+msgid "Wrong E-Mail Address."
+msgstr "错误的邮箱地址"
+
+#: flaskbb/auth/views.py:45
+msgid "Wrong Username or Password."
+msgstr "错误的用户名或密码。"
+
+#: flaskbb/auth/views.py:60
+msgid "Reauthenticated."
+msgstr "重新认证身份。"
+
+#: flaskbb/auth/views.py:96
+msgid "Thanks for registering."
+msgstr "感谢您的注册。"
+
+#: flaskbb/auth/views.py:118
+msgid "E-Mail sent! Please check your inbox."
+msgstr "邮件已发送至您的邮箱"
+
+#: flaskbb/auth/views.py:121
+msgid ""
+"You have entered a Username or E-Mail Address that is not linked with your "
+"account."
+msgstr "您输入的邮箱没有关联至您的账户。"
+
+#: flaskbb/auth/views.py:141
+msgid "Your Password Token is invalid."
+msgstr "您的令牌已不合法。"
+
+#: flaskbb/auth/views.py:145
+msgid "Your Password Token is expired."
+msgstr "你的令牌已过期。"
+
+#: flaskbb/auth/views.py:151
+msgid "Your Password has been updated."
+msgstr "您的密码已被更新。"
+
+#: flaskbb/forum/forms.py:22
+msgid "Quick Reply"
+msgstr "快速回复"
+
+#: flaskbb/forum/forms.py:23 flaskbb/forum/forms.py:34 flaskbb/forum/forms.py:55
+msgid "You cannot post a reply without content."
+msgstr "您不能发表一个空的回复。"
+
+#: flaskbb/forum/forms.py:25 flaskbb/forum/forms.py:39
+#: flaskbb/templates/forum/topic.html:146
+#: flaskbb/templates/forum/topic_controls.html:25
+msgid "Reply"
+msgstr "回复"
+
+#: flaskbb/forum/forms.py:33 flaskbb/forum/forms.py:54 flaskbb/forum/forms.py:100
+msgid "Content"
+msgstr "内容"
+
+#: flaskbb/forum/forms.py:36 flaskbb/forum/forms.py:57
+msgid "Track this Topic"
+msgstr "关注该主题"
+
+#: flaskbb/forum/forms.py:40 flaskbb/forum/forms.py:61
+#: flaskbb/templates/forum/new_post.html:28
+#: flaskbb/templates/forum/new_topic.html:27
+msgid "Preview"
+msgstr "预览"
+
+#: flaskbb/forum/forms.py:51
+msgid "Topic Title"
+msgstr "标题"
+
+#: flaskbb/forum/forms.py:52
+msgid "Please choose a Topic Title."
+msgstr "请输入一个标题"
+
+#: flaskbb/forum/forms.py:60
+msgid "Post Topic"
+msgstr "发表主题"
+
+#: flaskbb/forum/forms.py:73 flaskbb/templates/management/reports.html:29
+#: flaskbb/templates/management/unread_reports.html:29
+msgid "Reason"
+msgstr "原因"
+
+#: flaskbb/forum/forms.py:74
+msgid "What's the reason for reporting this post?"
+msgstr "您为何报告这个主题?"
+
+#: flaskbb/forum/forms.py:77 flaskbb/templates/forum/report_post.html:1
+#: flaskbb/templates/forum/report_post.html:13
+msgid "Report Post"
+msgstr "报告主题"
+
+#: flaskbb/forum/forms.py:85 flaskbb/forum/forms.py:89 flaskbb/forum/forms.py:104
+#: flaskbb/templates/layout.html:50 flaskbb/templates/forum/memberlist.html:21
+#: flaskbb/templates/forum/search_form.html:1
+#: flaskbb/templates/forum/search_form.html:9
+#: flaskbb/templates/forum/search_form.html:13
+#: flaskbb/templates/forum/search_result.html:1
+#: flaskbb/templates/forum/search_result.html:9
+#: flaskbb/templates/management/banned_users.html:31
+#: flaskbb/templates/management/users.html:31
+#: flaskbb/themes/bootstrap2/templates/layout.html:51
+#: flaskbb/themes/bootstrap3/templates/layout.html:50
+msgid "Search"
+msgstr "搜索"
+
+#: flaskbb/forum/forms.py:97
+msgid "Criteria"
+msgstr "条件"
+
+#: flaskbb/forum/forms.py:101
+msgid "Post"
+msgstr "回复"
+
+#: flaskbb/forum/forms.py:101 flaskbb/templates/forum/forum.html:50
+#: flaskbb/templates/forum/search_result.html:113
+#: flaskbb/templates/forum/topictracker.html:28
+#: flaskbb/templates/management/reports.html:27
+#: flaskbb/templates/management/unread_reports.html:27
+#: flaskbb/templates/user/all_topics.html:26
+msgid "Topic"
+msgstr "主题"
+
+#: flaskbb/forum/forms.py:102 flaskbb/templates/layout.html:48
+#: flaskbb/templates/forum/category.html:8
+#: flaskbb/templates/forum/category_layout.html:11
+#: flaskbb/templates/forum/forum.html:9 flaskbb/templates/forum/index.html:5
+#: flaskbb/templates/forum/memberlist.html:8
+#: flaskbb/templates/forum/new_post.html:15
+#: flaskbb/templates/forum/new_topic.html:15
+#: flaskbb/templates/forum/search_form.html:8
+#: flaskbb/templates/forum/search_result.html:8
+#: flaskbb/templates/forum/search_result.html:185
+#: flaskbb/templates/forum/topic.html:14
+#: flaskbb/templates/forum/topictracker.html:9
+#: flaskbb/templates/management/forums.html:38
+#: flaskbb/templates/message/message_layout.html:6
+#: flaskbb/templates/user/all_posts.html:6
+#: flaskbb/templates/user/all_topics.html:6 flaskbb/templates/user/profile.html:4
+#: flaskbb/templates/user/settings_layout.html:6
+#: flaskbb/themes/bootstrap2/templates/layout.html:49
+#: flaskbb/themes/bootstrap3/templates/layout.html:48
+msgid "Forum"
+msgstr "论坛"
+
+#: flaskbb/forum/forms.py:102 flaskbb/templates/forum/search_result.html:77
+#: flaskbb/templates/management/management_layout.html:12
+#: flaskbb/templates/management/overview.html:29
+#: flaskbb/templates/management/users.html:1
+msgid "Users"
+msgstr "用户"
+
+#: 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 this topic."
+msgstr "你没有权限删除该主题。"
+
+#: flaskbb/forum/views.py:208
+msgid "You do not have the permissions to lock this topic."
+msgstr "您没有权限锁上该主题。"
+
+#: flaskbb/forum/views.py:227
+msgid "You do not have the permissions to unlock this topic."
+msgstr "您没有权限解锁该主题。"
+
+#: flaskbb/forum/views.py:243
+msgid "You do not have the permissions to highlight this topic."
+msgstr "您没有权限高亮该主题。"
+
+#: flaskbb/forum/views.py:260
+msgid "You do not have the permissions to trivialize this topic."
+msgstr "您没有权限去高亮该主题。"
+
+#: flaskbb/forum/views.py:282
+msgid "You do not have the permissions to move this topic."
+msgstr "您没有权限移动该主题。"
+
+#: flaskbb/forum/views.py:287
+#, python-format
+msgid "Could not move the topic to forum %(title)s."
+msgstr "无法将本主题移动至 %(title)s。"
+
+#: flaskbb/forum/views.py:291
+#, python-format
+msgid "Topic was moved to forum %(title)s."
+msgstr "本主题已被移动至 %(title)s。"
+
+#: flaskbb/forum/views.py:310
+msgid "You do not have the permissions to merge this topic."
+msgstr "您没有权限合并该主题。"
+
+#: flaskbb/forum/views.py:315
+msgid "Could not merge the topics."
+msgstr "无法合并主题。"
+
+#: flaskbb/forum/views.py:318
+msgid "Topics succesfully merged."
+msgstr "主题已被成功合并。"
+
+#: flaskbb/forum/views.py:329 flaskbb/forum/views.py:356
+msgid "You do not have the permissions to post in this topic."
+msgstr "您没有权限回复该主题。"
+
+#: flaskbb/forum/views.py:382
+msgid "You do not have the permissions to edit this post."
+msgstr "您没有权限编辑本条回复。"
+
+#: flaskbb/forum/views.py:412
+msgid "You do not have the permissions to delete this post."
+msgstr "您没有权限删除本条回复。"
+
+#: flaskbb/forum/views.py:436
+msgid "Thanks for reporting."
+msgstr "感谢您的报告。"
+
+#: flaskbb/forum/views.py:473
+#, python-format
+msgid "Forum %(forum)s marked as read."
+msgstr "论坛 %(forum)s 已被标记为已读。"
+
+#: flaskbb/forum/views.py:495
+msgid "All forums marked as read."
+msgstr "所有论坛已被标记为已读。"
+
+#: flaskbb/management/forms.py:66 flaskbb/templates/user/profile.html:77
+#: flaskbb/user/forms.py:81
+msgid "Birthday"
+msgstr "生日"
+
+#: flaskbb/management/forms.py:70 flaskbb/user/forms.py:85
+msgid "Gender"
+msgstr "性别"
+
+#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:87
+msgid "Male"
+msgstr "男"
+
+#: flaskbb/management/forms.py:73 flaskbb/user/forms.py:88
+msgid "Female"
+msgstr "女"
+
+#: flaskbb/management/forms.py:75 flaskbb/templates/user/profile.html:73
+#: flaskbb/user/forms.py:90
+msgid "Location"
+msgstr "位置"
+
+#: flaskbb/management/forms.py:78 flaskbb/templates/forum/topic.html:114
+#: flaskbb/user/forms.py:93
+msgid "Website"
+msgstr "网站"
+
+#: flaskbb/management/forms.py:81 flaskbb/user/forms.py:96
+msgid "Avatar"
+msgstr "头像"
+
+#: flaskbb/management/forms.py:84 flaskbb/user/forms.py:99
+msgid "Forum Signature"
+msgstr "签名"
+
+#: flaskbb/management/forms.py:87 flaskbb/user/forms.py:102
+msgid "Notes"
+msgstr "个人说明"
+
+#: flaskbb/management/forms.py:91
+msgid "Primary Group"
+msgstr "主用户组"
+
+#: flaskbb/management/forms.py:96
+msgid "Secondary Groups"
+msgstr "次要用户组"
+
+#: flaskbb/management/forms.py:102 flaskbb/management/forms.py:212
+#: flaskbb/management/forms.py:328 flaskbb/management/forms.py:434
+#: flaskbb/templates/management/settings.html:39 flaskbb/user/forms.py:32
+#: flaskbb/user/forms.py:48 flaskbb/user/forms.py:73 flaskbb/user/forms.py:105
+msgid "Save"
+msgstr "保存"
+
+#: flaskbb/management/forms.py:149 flaskbb/templates/management/groups.html:25
+msgid "Group Name"
+msgstr "用户组名"
+
+#: flaskbb/management/forms.py:150
+msgid "A Group name is required."
+msgstr "请输入用户组名。"
+
+#: flaskbb/management/forms.py:152 flaskbb/management/forms.py:279
+#: flaskbb/management/forms.py:422 flaskbb/templates/management/groups.html:26
+msgid "Description"
+msgstr "描述"
+
+#: flaskbb/management/forms.py:156
+msgid "Is Admin Group?"
+msgstr "管理员组?"
+
+#: flaskbb/management/forms.py:157
+msgid "With this option the group has access to the admin panel."
+msgstr "勾上本选项后本群用户能够访问管理员面板。"
+
+#: flaskbb/management/forms.py:161
+msgid "Is Super Moderator Group?"
+msgstr "超级版主组?"
+
+#: flaskbb/management/forms.py:162
+msgid "Check this if the users in this group are allowed to moderate every forum."
+msgstr "勾上本选项后本群用户能够管理所有论坛。"
+
+#: flaskbb/management/forms.py:166
+msgid "Is Moderator Group?"
+msgstr "版主组?"
+
+#: flaskbb/management/forms.py:167
+msgid ""
+"Check this if the users in this group are allowed to moderate specified forums."
+msgstr "勾上本选项后本群用户能够管理特定的论坛。"
+
+#: flaskbb/management/forms.py:171
+msgid "Is Banned Group?"
+msgstr "禁止用户组?"
+
+#: flaskbb/management/forms.py:172
+msgid "Only one Banned group is allowed."
+msgstr "只允许存在一个禁止用户组。"
+
+#: flaskbb/management/forms.py:175
+msgid "Is Guest Group?"
+msgstr "来宾组?"
+
+#: flaskbb/management/forms.py:176
+msgid "Only one Guest group is allowed."
+msgstr "只允许存在一个来宾用户组。"
+
+#: flaskbb/management/forms.py:179
+msgid "Can edit posts"
+msgstr "能编辑回复"
+
+#: flaskbb/management/forms.py:180
+msgid "Check this if the users in this group can edit posts."
+msgstr "勾上本选项后本群用户可以编辑回复。"
+
+#: flaskbb/management/forms.py:183
+msgid "Can delete posts"
+msgstr "能删除回复"
+
+#: flaskbb/management/forms.py:184
+msgid "Check this is the users in this group can delete posts."
+msgstr "勾上本选项后本群用户可以删除回复。"
+
+#: flaskbb/management/forms.py:187
+msgid "Can delete topics"
+msgstr "能删除主题"
+
+#: flaskbb/management/forms.py:188
+msgid "Check this is the users in this group can delete topics."
+msgstr "勾上本选项后本群用户可以删除主题。"
+
+#: flaskbb/management/forms.py:192
+msgid "Can create topics"
+msgstr "能创建主题"
+
+#: flaskbb/management/forms.py:193
+msgid "Check this is the users in this group can create topics."
+msgstr "勾上本选项后本群用户可以创建回复。"
+
+#: flaskbb/management/forms.py:197
+msgid "Can post replies"
+msgstr "能创建回复"
+
+#: flaskbb/management/forms.py:198
+msgid "Check this is the users in this group can post replies."
+msgstr "勾上本选项后本群用户可以创建回复。"
+
+#: flaskbb/management/forms.py:202
+msgid "Moderators can edit user profiles"
+msgstr "版主能够修改用户资料"
+
+#: flaskbb/management/forms.py:203
+msgid ""
+"Allow moderators to edit a another users profile including password and email "
+"changes."
+msgstr "允许版主能够编辑其他用户的资料(包括密码和邮箱地址)"
+
+#: flaskbb/management/forms.py:208
+msgid "Moderators can ban users"
+msgstr "版主能够禁止用户"
+
+#: flaskbb/management/forms.py:209
+msgid "Allow moderators to ban other users."
+msgstr "允许版主能够禁止其他用户。"
+
+#: flaskbb/management/forms.py:226
+msgid "This Group name is already taken."
+msgstr "该用户组名已被使用"
+
+#: flaskbb/management/forms.py:240
+msgid "There is already a Banned group."
+msgstr "已存在禁止用户组了。"
+
+#: flaskbb/management/forms.py:254
+msgid "There is already a Guest group."
+msgstr "已存在来宾用户组。"
+
+#: flaskbb/management/forms.py:274
+msgid "Forum Title"
+msgstr "论坛标题"
+
+#: flaskbb/management/forms.py:275
+msgid "A Forum Title is required."
+msgstr "请输入论坛标题。"
+
+#: flaskbb/management/forms.py:281 flaskbb/management/forms.py:424
+msgid "You can format your description with BBCode."
+msgstr "您可以使用 BBCode 来格式化您的描述。"
+
+#: flaskbb/management/forms.py:285 flaskbb/management/forms.py:428
+msgid "Position"
+msgstr "先后位置"
+
+#: flaskbb/management/forms.py:287
+msgid "The Forum Position is required."
+msgstr "请输入一个先后位置。"
+
+#: flaskbb/management/forms.py:291
+msgid "Category"
+msgstr "分类"
+
+#: flaskbb/management/forms.py:295
+msgid "The category that contains this forum."
+msgstr "该论坛将位与此分类之下。"
+
+#: flaskbb/management/forms.py:299
+msgid "External Link"
+msgstr "外部链接"
+
+#: flaskbb/management/forms.py:301
+msgid "A link to a website i.e. 'http://flaskbb.org'."
+msgstr "链接到外部的网站(例:’http://flaskbb.org')。"
+
+#: flaskbb/management/forms.py:305
+#: flaskbb/templates/forum/category_layout.html:60
+#: flaskbb/templates/forum/search_result.html:233
+msgid "Moderators"
+msgstr "版主"
+
+#: flaskbb/management/forms.py:306
+msgid ""
+"Comma seperated usernames. Leave it blank if you do not want to set any "
+"moderators."
+msgstr "使用逗号来分割用户。如果您不想设置版主的话留空即可。"
+
+#: flaskbb/management/forms.py:311
+msgid "Show Moderators"
+msgstr "展示版主"
+
+#: flaskbb/management/forms.py:312
+msgid "Do you want show the moderators on the index page?"
+msgstr "您是否想在主页上显示版主?"
+
+#: flaskbb/management/forms.py:316
+msgid "Locked?"
+msgstr "加锁?"
+
+#: flaskbb/management/forms.py:317
+msgid "Disable new posts and topics in this forum."
+msgstr "在该论坛禁止发表新主题与回复。"
+
+#: flaskbb/management/forms.py:321
+msgid "Group Access to Forum"
+msgstr "能够访问本论坛的用户组。"
+
+#: flaskbb/management/forms.py:325
+msgid "Select user groups that can access this forum."
+msgstr "请选择可以访问本论坛的用户组。"
+
+#: flaskbb/management/forms.py:333
+msgid "You cannot convert a forum that contain topics in a external link."
+msgstr "您不能改变一个主题包含外部链接的论坛。"
+
+#: flaskbb/management/forms.py:338
+msgid "You also need to specify some moderators."
+msgstr "您需要指定版主。"
+
+#: flaskbb/management/forms.py:359
+#, python-format
+msgid "%(user)s is not in a moderators group."
+msgstr "用户 %(user)s 不在版主组里。"
+
+#: flaskbb/management/forms.py:365
+#, python-format
+msgid "User %(moderator)s not found."
+msgstr "没有找到用户 %(moderator)s。"
+
+#: flaskbb/management/forms.py:418
+msgid "Category Title"
+msgstr "分类标题"
+
+#: flaskbb/management/forms.py:419
+msgid "A Category Title is required."
+msgstr "请输入分类标题。"
+
+#: flaskbb/management/forms.py:430
+msgid "The Category Position is required."
+msgstr "请输入分类的先后位置。"
+
+#: flaskbb/management/views.py:85
+msgid "Settings saved."
+msgstr "设置已被保存。"
+
+#: flaskbb/management/views.py:124
+msgid "You are not allowed to edit this user."
+msgstr "您没有权限编辑该用户。"
+
+#: flaskbb/management/views.py:144
+msgid "User successfully updated."
+msgstr "用户信息已被成功更新。"
+
+#: flaskbb/management/views.py:148
+msgid "Edit User"
+msgstr "编辑用户。"
+
+#: flaskbb/management/views.py:156
+msgid "User successfully deleted."
+msgstr "删除用户成功。"
+
+#: flaskbb/management/views.py:166
+msgid "User successfully added."
+msgstr "添加用户成功。"
+
+#: flaskbb/management/views.py:170
+#: flaskbb/templates/management/banned_users.html:14
+#: flaskbb/templates/management/user_form.html:14
+#: flaskbb/templates/management/users.html:14
+msgid "Add User"
+msgstr "添加用户"
+
+#: flaskbb/management/views.py:199
+msgid "You do not have the permissions to ban this user."
+msgstr "您没有权限禁止该用户。"
+
+#: flaskbb/management/views.py:209
+msgid "A moderator cannot ban an admin user."
+msgstr "一个版主无法禁止一个管理员。"
+
+#: flaskbb/management/views.py:213
+msgid "User is now banned."
+msgstr "该用户已被禁止。"
+
+#: flaskbb/management/views.py:215
+msgid "Could not ban user."
+msgstr "无法禁止该用户。"
+
+#: flaskbb/management/views.py:224
+msgid "You do not have the permissions to unban this user."
+msgstr "您没有权限恢复该用户。"
+
+#: flaskbb/management/views.py:231
+msgid "User is now unbanned."
+msgstr "该用户已被恢复。"
+
+#: flaskbb/management/views.py:233
+msgid "Could not unban user."
+msgstr "无法恢复该用户。"
+
+#: flaskbb/management/views.py:271
+#, python-format
+msgid "Report %(id)s is already marked as read."
+msgstr "报告 %(id)s 已被标记为已读。"
+
+#: flaskbb/management/views.py:278
+#, python-format
+msgid "Report %(id)s marked as read."
+msgstr "报告 %(id)s 被标记为已读。"
+
+#: flaskbb/management/views.py:292
+msgid "All reports were marked as read."
+msgstr "所有报告被标记为已读。"
+
+#: flaskbb/management/views.py:323
+msgid "Group successfully updated."
+msgstr "成功更新用户组信息。"
+
+#: flaskbb/management/views.py:327
+msgid "Edit Group"
+msgstr "编辑用户组"
+
+#: flaskbb/management/views.py:335
+msgid "Group successfully deleted."
+msgstr "删除用户组成功。"
+
+#: flaskbb/management/views.py:345
+msgid "Group successfully added."
+msgstr "添加用户组成功。"
+
+#: flaskbb/management/views.py:349
+#: flaskbb/templates/management/group_form.html:11
+#: flaskbb/templates/management/groups.html:10
+msgid "Add Group"
+msgstr "添加用户组"
+
+#: flaskbb/management/views.py:368
+msgid "Forum successfully updated."
+msgstr "成功更新论坛信息。"
+
+#: flaskbb/management/views.py:379
+msgid "Edit Forum"
+msgstr "编辑论坛"
+
+#: flaskbb/management/views.py:392
+msgid "Forum successfully deleted."
+msgstr "删除论坛成功。"
+
+#: flaskbb/management/views.py:404
+msgid "Forum successfully added."
+msgstr "添加论坛成功。"
+
+#: flaskbb/management/views.py:413
+#: flaskbb/templates/management/category_form.html:11
+#: flaskbb/templates/management/forum_form.html:11
+#: flaskbb/templates/management/forums.html:10
+#: flaskbb/templates/management/forums.html:27
+msgid "Add Forum"
+msgstr "添加论坛"
+
+#: flaskbb/management/views.py:423
+msgid "Category successfully added."
+msgstr "添加分类成功。"
+
+#: flaskbb/management/views.py:427
+#: flaskbb/templates/management/category_form.html:12
+#: flaskbb/templates/management/forum_form.html:12
+#: flaskbb/templates/management/forums.html:11
+msgid "Add Category"
+msgstr "添加分类"
+
+#: flaskbb/management/views.py:439
+msgid "Category successfully updated."
+msgstr "成功更新分类信息。"
+
+#: flaskbb/management/views.py:443
+msgid "Edit Category"
+msgstr "编辑分类"
+
+#: flaskbb/management/views.py:456
+msgid "Category with all associated forums deleted."
+msgstr "该分类与所有相关联的论坛已被删除。"
+
+#: flaskbb/management/views.py:483
+msgid "Plugin is enabled. Please reload your app."
+msgstr "插件启用成功。请重新载入您的应用。"
+
+#: flaskbb/management/views.py:486
+msgid "Plugin is already enabled. Please reload  your app."
+msgstr "插件已被启用。请重新载入您的应用。"
+
+#: flaskbb/management/views.py:490
+msgid ""
+"If you are using a host which doesn't support writting on the disk, this won't "
+"work - than you need to delete the 'DISABLED' file by yourself."
+msgstr ""
+"如果您部署的环境无法写入磁盘,那么刚才的设置不会起作用——您需要自己删除 "
+"「DISABLED」文件。"
+
+#: flaskbb/management/views.py:495
+msgid "Couldn't enable Plugin."
+msgstr "无法启用插件。"
+
+#: flaskbb/management/views.py:506
+#, python-format
+msgid "Plugin %(plugin)s not found."
+msgstr "没有找到此插件:%(plugin)s。"
+
+#: flaskbb/management/views.py:518
+msgid "Plugin is disabled. Please reload your app."
+msgstr "插件已被禁用。请重新载入您的引用。"
+
+#: flaskbb/management/views.py:521
+msgid ""
+"If you are using a host which doesn't support writting on the disk, this won't "
+"work - than you need to create a 'DISABLED' file by yourself."
+msgstr ""
+"如果您部署的环境无法写入磁盘,那么刚才的设置不会起作用——您需要自己创建"
+"「DISABLED」文件。"
+
+#: flaskbb/management/views.py:536
+msgid "Plugin has been uninstalled."
+msgstr "插件已被卸载。"
+
+#: flaskbb/management/views.py:538
+msgid "Cannot uninstall Plugin."
+msgstr "无法卸载插件。"
+
+#: flaskbb/management/views.py:551
+msgid "Plugin has been installed."
+msgstr "插件已被安装。"
+
+#: flaskbb/management/views.py:553
+msgid "Cannot install Plugin."
+msgstr "无法安装插件。"
+
+#: flaskbb/message/forms.py:22
+msgid "To User"
+msgstr "给用户"
+
+#: flaskbb/message/forms.py:25
+msgid "Subject"
+msgstr "主题"
+
+#: flaskbb/message/forms.py:26
+msgid "A Subject is required."
+msgstr "请输入主题"
+
+#: flaskbb/message/forms.py:28 flaskbb/message/forms.py:57
+msgid "Message"
+msgstr "消息"
+
+#: flaskbb/message/forms.py:29 flaskbb/message/forms.py:58
+msgid "A Message is required."
+msgstr "请输入消息。"
+
+#: flaskbb/message/forms.py:31
+msgid "Start Conversation"
+msgstr "新建会话"
+
+#: flaskbb/message/forms.py:32
+msgid "Save Conversation"
+msgstr "保存会话"
+
+#: flaskbb/message/forms.py:37
+msgid "The Username you entered doesn't exist"
+msgstr "您输入的用户不存在。"
+
+#: flaskbb/message/forms.py:39
+msgid "You cannot send a PM to yourself."
+msgstr "您无法给自己发送私人消息。"
+
+#: flaskbb/message/forms.py:59
+msgid "Send Message"
+msgstr "发送消息"
+
+#: flaskbb/message/views.py:70 flaskbb/message/views.py:126
+msgid ""
+"You cannot send any messages anymore because you havereached your message limit."
+msgstr "由于您超出了限额,因此您将无法继续发送任何消息。"
+
+#: flaskbb/message/views.py:143 flaskbb/message/views.py:214
+msgid "Message saved."
+msgstr "消息已被保存。"
+
+#: flaskbb/message/views.py:167 flaskbb/message/views.py:232
+msgid "Message sent."
+msgstr "消息已被发送。"
+
+#: flaskbb/message/views.py:173
+msgid "Compose Message"
+msgstr "撰写消息"
+
+#: flaskbb/message/views.py:200
+msgid "You cannot edit a sent message."
+msgstr "您无法编辑一条已被发送的消息。"
+
+#: flaskbb/message/views.py:240
+msgid "Edit Message"
+msgstr "编辑消息"
+
+#: flaskbb/templates/layout.html:49 flaskbb/templates/forum/memberlist.html:1
+#: flaskbb/templates/forum/memberlist.html:9
+#: flaskbb/themes/bootstrap2/templates/layout.html:50
+#: flaskbb/themes/bootstrap3/templates/layout.html:49
+msgid "Memberlist"
+msgstr "用户列表"
+
+#: flaskbb/templates/layout.html:64 flaskbb/templates/forum/topictracker.html:1
+#: flaskbb/templates/forum/topictracker.html:22
+#: flaskbb/themes/bootstrap2/templates/layout.html:65
+#: flaskbb/themes/bootstrap3/templates/layout.html:64
+msgid "Topic Tracker"
+msgstr "关注的主题"
+
+#: flaskbb/templates/layout.html:67
+#: flaskbb/templates/management/management_layout.html:16
+#: flaskbb/templates/user/settings_layout.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:68
+#: flaskbb/themes/bootstrap3/templates/layout.html:67
+msgid "Settings"
+msgstr "设置"
+
+#: flaskbb/templates/layout.html:69 flaskbb/templates/management/forums.html:39
+#: flaskbb/themes/bootstrap2/templates/layout.html:70
+#: flaskbb/themes/bootstrap3/templates/layout.html:69
+msgid "Management"
+msgstr "管理"
+
+#: flaskbb/templates/layout.html:73
+#: flaskbb/themes/bootstrap2/templates/layout.html:74
+#: flaskbb/themes/bootstrap3/templates/layout.html:73
+msgid "Logout"
+msgstr "注销"
+
+#: flaskbb/templates/layout.html:82
+msgid "Private Messages"
+msgstr "私人消息"
+
+#: flaskbb/templates/layout.html:83
+#: flaskbb/themes/bootstrap2/templates/layout.html:84
+#: flaskbb/themes/bootstrap3/templates/layout.html:83
+msgid "New Message"
+msgstr "新消息"
+
+#: flaskbb/templates/macros.html:313
+msgid "Pages"
+msgstr "页面"
+
+#: flaskbb/templates/auth/login.html:18
+msgid "Not a member yet?"
+msgstr "还未注册?"
+
+#: flaskbb/templates/auth/login.html:19
+msgid "Forgot Password?"
+msgstr "忘记密码?"
+
+#: flaskbb/templates/email/reset_password.html:1
+#, python-format
+msgid "Dear %(user)s,"
+msgstr "亲爱的 %(user)s,"
+
+#: flaskbb/templates/email/reset_password.html:2
+msgid "To reset your password click on the following link:"
+msgstr "点击该链接来重置您的密码:"
+
+#: flaskbb/templates/email/reset_password.html:4
+msgid "Sincerely,"
+msgstr "真诚的"
+
+#: flaskbb/templates/email/reset_password.html:5
+msgid "The Administration"
+msgstr "管理人员"
+
+#: flaskbb/templates/errors/forbidden_page.html:1
+msgid "No Access - 403"
+msgstr "无法访问 - 403"
+
+#: flaskbb/templates/errors/forbidden_page.html:7
+msgid "Forbidden Page"
+msgstr "被禁止的页面"
+
+#: flaskbb/templates/errors/forbidden_page.html:8
+msgid "You do not have the permission to view this page."
+msgstr "您没有权限访问该页面。"
+
+#: flaskbb/templates/errors/forbidden_page.html:9
+#: flaskbb/templates/errors/page_not_found.html:9
+msgid "Back to the Forums"
+msgstr "返回论坛。"
+
+#: flaskbb/templates/errors/page_not_found.html:1
+msgid "Oh noes! 404"
+msgstr "没有该页面 - 404"
+
+#: flaskbb/templates/errors/page_not_found.html:7
+msgid "Oops, Page not found!"
+msgstr "没有找到用户 %(moderator)s。"
+
+#: flaskbb/templates/errors/page_not_found.html:8
+msgid "The page you were looking for does not exist."
+msgstr "您查找的页面不存在。"
+
+#: flaskbb/templates/errors/server_error.html:1
+msgid "Server Error - 500"
+msgstr ""
+
+#: flaskbb/templates/errors/server_error.html:7
+msgid "Server Error"
+msgstr ""
+
+#: flaskbb/templates/errors/server_error.html:8
+msgid "Something went wrong!"
+msgstr "服务器报告了一个错误。"
+
+#: flaskbb/templates/forum/category_layout.html:12
+#: flaskbb/templates/forum/search_result.html:108
+#: flaskbb/templates/forum/search_result.html:186
+#: flaskbb/templates/management/overview.html:23
+msgid "Topics"
+msgstr "主题"
+
+#: flaskbb/templates/forum/category_layout.html:13
+#: flaskbb/templates/forum/forum.html:52
+#: flaskbb/templates/forum/memberlist.html:32
+#: flaskbb/templates/forum/search_result.html:13
+#: flaskbb/templates/forum/search_result.html:43
+#: flaskbb/templates/forum/search_result.html:84
+#: flaskbb/templates/forum/search_result.html:115
+#: flaskbb/templates/forum/search_result.html:187
+#: flaskbb/templates/forum/topic.html:75
+#: flaskbb/templates/forum/topictracker.html:30
+#: flaskbb/templates/management/banned_users.html:42
+#: flaskbb/templates/management/overview.html:17
+#: flaskbb/templates/management/users.html:42
+#: flaskbb/templates/user/all_topics.html:28
+#: flaskbb/templates/user/profile.html:56
+msgid "Posts"
+msgstr "回复量"
+
+#: flaskbb/templates/forum/category_layout.html:14
+#: flaskbb/templates/forum/forum.html:56
+#: flaskbb/templates/forum/search_result.html:119
+#: flaskbb/templates/forum/search_result.html:188
+#: flaskbb/templates/forum/topictracker.html:34
+#: flaskbb/templates/user/all_topics.html:32
+msgid "Last Post"
+msgstr "最后的回复"
+
+#: flaskbb/templates/forum/category_layout.html:80
+#: flaskbb/templates/forum/forum.html:86 flaskbb/templates/forum/forum.html:105
+#: flaskbb/templates/forum/search_result.html:142
+#: flaskbb/templates/forum/search_result.html:161
+#: flaskbb/templates/forum/search_result.html:253
+#: flaskbb/templates/forum/topic.html:40
+#: flaskbb/templates/forum/topictracker.html:52
+#: flaskbb/templates/forum/topictracker.html:70
+#: flaskbb/templates/management/plugins.html:30
+#: flaskbb/templates/user/all_topics.html:42
+#: flaskbb/templates/user/all_topics.html:60
+msgid "by"
+msgstr "来自"
+
+#: flaskbb/templates/forum/category_layout.html:88
+#: flaskbb/templates/forum/search_result.html:261
+#: flaskbb/templates/user/all_posts.html:40
+msgid "No posts."
+msgstr "暂无回复。"
+
+#: flaskbb/templates/forum/forum.html:23
+#: flaskbb/templates/management/unread_reports.html:51
+msgid "Mark as Read"
+msgstr "标记为已读。"
+
+#: flaskbb/templates/forum/forum.html:29
+msgid "Locked"
+msgstr "锁定"
+
+#: flaskbb/templates/forum/forum.html:33 flaskbb/templates/forum/new_topic.html:1
+#: flaskbb/templates/forum/new_topic.html:17
+#: flaskbb/templates/forum/new_topic.html:22
+msgid "New Topic"
+msgstr "新建主题"
+
+#: flaskbb/templates/forum/forum.html:54
+#: flaskbb/templates/forum/search_result.html:117
+#: flaskbb/templates/forum/topictracker.html:32
+#: flaskbb/templates/user/all_topics.html:30
+msgid "Views"
+msgstr "浏览"
+
+#: flaskbb/templates/forum/forum.html:117
+msgid "No Topics."
+msgstr "没有主题"
+
+#: flaskbb/templates/forum/index.html:17
+msgid "Board Statistics"
+msgstr "论坛统计"
+
+#: flaskbb/templates/forum/index.html:18
+msgid "Who is online?"
+msgstr "谁在线上?"
+
+#: flaskbb/templates/forum/index.html:25
+msgid "Total number of registered users"
+msgstr "所有注册用户数量"
+
+#: flaskbb/templates/forum/index.html:26
+msgid "Total number of topics"
+msgstr "所有主题数量"
+
+#: flaskbb/templates/forum/index.html:27
+msgid "Total number of posts"
+msgstr "所有回复数量"
+
+#: flaskbb/templates/forum/index.html:30
+msgid "Newest registered user"
+msgstr "最新注册用户"
+
+#: flaskbb/templates/forum/index.html:30
+msgid "No users"
+msgstr "暂无用户"
+
+#: flaskbb/templates/forum/index.html:31
+msgid "Registered users online"
+msgstr "在线注册用户"
+
+#: flaskbb/templates/forum/index.html:33
+msgid "Guests online"
+msgstr "在线来宾用户"
+
+#: flaskbb/templates/forum/memberlist.html:33
+#: flaskbb/templates/forum/search_result.html:85
+#: flaskbb/templates/management/banned_users.html:43
+#: flaskbb/templates/management/users.html:43
+msgid "Date registered"
+msgstr "注册日期"
+
+#: flaskbb/templates/forum/memberlist.html:34
+#: flaskbb/templates/forum/search_result.html:86
+#: flaskbb/templates/management/banned_users.html:44
+#: flaskbb/templates/management/users.html:44
+#: flaskbb/templates/user/profile.html:48
+msgid "Group"
+msgstr "用户组"
+
+#: flaskbb/templates/forum/new_post.html:1
+#: flaskbb/templates/forum/new_post.html:18
+#: flaskbb/templates/forum/new_post.html:23
+msgid "New Post"
+msgstr "新建回复"
+
+#: flaskbb/templates/forum/online_users.html:1
+#: flaskbb/templates/forum/online_users.html:10
+msgid "Online Users"
+msgstr "在线用户"
+
+#: flaskbb/templates/forum/report_post.html:17
+#: flaskbb/templates/forum/topic.html:121
+msgid "Report"
+msgstr "报告"
+
+#: flaskbb/templates/forum/report_post.html:18
+msgid "Close"
+msgstr "关闭"
+
+#: flaskbb/templates/forum/search_result.html:44
+#: flaskbb/templates/forum/topic.html:76
+msgid "Registered since"
+msgstr "注册于"
+
+#: flaskbb/templates/forum/search_result.html:50
+#: flaskbb/templates/forum/topic.html:82
+msgid "Guest"
+msgstr "来宾用户"
+
+#: flaskbb/templates/forum/search_result.html:69
+msgid "No posts found matching your search criteria."
+msgstr "没有找到符合条件的回复。"
+
+#: flaskbb/templates/forum/search_result.html:100
+#: flaskbb/templates/management/banned_users.html:68
+#: flaskbb/templates/management/users.html:86
+msgid "No users found matching your search criteria."
+msgstr "没有找到符合条件的用户。"
+
+#: flaskbb/templates/forum/search_result.html:172
+msgid "No topics found matching your search criteria."
+msgstr "没有找到符合条件的主题。"
+
+#: flaskbb/templates/forum/search_result.html:180
+#: flaskbb/templates/management/forums.html:1
+#: flaskbb/templates/management/management_layout.html:18
+msgid "Forums"
+msgstr "论坛"
+
+#: flaskbb/templates/forum/search_result.html:269
+msgid "No forums found matching your search criteria."
+msgstr "没有找到符合条件的论坛。"
+
+#: flaskbb/templates/forum/topic.html:2
+#, python-format
+msgid "%(title)s - Topic"
+msgstr "%(title)s - 主题"
+
+#: flaskbb/templates/forum/topic.html:40
+msgid "Last modified"
+msgstr "最后修改"
+
+#: flaskbb/templates/forum/topic.html:111
+msgid "PM"
+msgstr "私人消息"
+
+#: flaskbb/templates/forum/topic.html:125
+#: flaskbb/templates/management/forums.html:28
+#: flaskbb/templates/management/forums.html:59
+#: flaskbb/templates/management/groups.html:37
+#: flaskbb/templates/management/users.html:58
+msgid "Edit"
+msgstr "编辑"
+
+#: flaskbb/templates/forum/topic.html:131 flaskbb/templates/forum/topic.html:138
+#: flaskbb/templates/management/forums.html:31
+#: flaskbb/templates/management/forums.html:62
+#: flaskbb/templates/management/groups.html:40
+#: flaskbb/templates/management/users.html:78
+msgid "Delete"
+msgstr "删除"
+
+#: flaskbb/templates/forum/topic.html:144
+msgid "Quote"
+msgstr "引用"
+
+#: flaskbb/templates/forum/topic_controls.html:11
+msgid "Untrack Topic"
+msgstr "不再关注"
+
+#: flaskbb/templates/forum/topic_controls.html:18
+msgid "Track Topic"
+msgstr "关注主题"
+
+#: flaskbb/templates/forum/topic_controls.html:36
+msgid "Delete Topic"
+msgstr "删除主题"
+
+#: flaskbb/templates/forum/topic_controls.html:45
+msgid "Lock Topic"
+msgstr "锁上主题"
+
+#: flaskbb/templates/forum/topic_controls.html:52
+msgid "Unlock Topic"
+msgstr "解锁主题"
+
+#: flaskbb/templates/forum/topic_controls.html:61
+msgid "Highlight Topic"
+msgstr "高亮主题"
+
+#: flaskbb/templates/forum/topic_controls.html:68
+msgid "Trivialize Topic"
+msgstr "普通化主题"
+
+#: flaskbb/templates/forum/topictracker.html:10
+msgid "Tracked Topics"
+msgstr "关注的主题"
+
+#: flaskbb/templates/forum/topictracker.html:82
+#: flaskbb/templates/user/all_topics.html:72
+msgid "No topics."
+msgstr "暂无主题。"
+
+#: flaskbb/templates/management/banned_users.html:1
+#: flaskbb/templates/management/banned_users.html:11
+#: flaskbb/templates/management/banned_users.html:20
+#: flaskbb/templates/management/user_form.html:11
+#: flaskbb/templates/management/users.html:11
+msgid "Banned Users"
+msgstr "被禁止的用户"
+
+#: flaskbb/templates/management/banned_users.html:10
+#: flaskbb/templates/management/user_form.html:10
+#: flaskbb/templates/management/users.html:10
+#: flaskbb/templates/management/users.html:20
+msgid "Manage Users"
+msgstr "管理员"
+
+#: flaskbb/templates/management/banned_users.html:45
+#: flaskbb/templates/management/groups.html:27
+#: flaskbb/templates/management/plugins.html:14
+#: flaskbb/templates/management/users.html:45
+msgid "Manage"
+msgstr "管理"
+
+#: flaskbb/templates/management/banned_users.html:60
+#: flaskbb/templates/management/users.html:71
+msgid "Unban"
+msgstr "恢复"
+
+#: flaskbb/templates/management/category_form.html:10
+#: flaskbb/templates/management/forum_form.html:10
+#: flaskbb/templates/management/forums.html:9
+#: flaskbb/templates/management/forums.html:17
+msgid "Manage Forums"
+msgstr "管理论坛"
+
+#: flaskbb/templates/management/forum_form.html:37
+msgid "Group Access to the forum"
+msgstr "能够访问本论坛的用户组。"
+
+#: flaskbb/templates/management/group_form.html:10
+#: flaskbb/templates/management/groups.html:9
+#: flaskbb/templates/management/groups.html:15
+msgid "Manage Groups"
+msgstr "管理用户组"
+
+#: flaskbb/templates/management/groups.html:1
+#: flaskbb/templates/management/management_layout.html:17
+msgid "Groups"
+msgstr "用户组"
+
+#: flaskbb/templates/management/management_layout.html:11
+#: flaskbb/templates/management/overview.html:1
+msgid "Overview"
+msgstr "概况"
+
+#: flaskbb/templates/management/management_layout.html:13
+#: flaskbb/templates/management/reports.html:1
+msgid "Reports"
+msgstr "报告"
+
+#: flaskbb/templates/management/management_layout.html:19
+#: flaskbb/templates/management/plugins.html:1
+msgid "Plugins"
+msgstr "插件"
+
+#: flaskbb/templates/management/overview.html:10
+msgid "Global Statistics"
+msgstr "全局统计"
+
+#: flaskbb/templates/management/overview.html:15
+msgid "FlaskBB Version"
+msgstr ""
+
+#: flaskbb/templates/management/overview.html:21
+msgid "Python Version"
+msgstr ""
+
+#: flaskbb/templates/management/overview.html:27
+msgid "Flask Version"
+msgstr ""
+
+#: flaskbb/templates/management/plugins.html:7
+msgid "Manage Plugins"
+msgstr "管理插件"
+
+#: flaskbb/templates/management/plugins.html:12
+msgid "Plugin"
+msgstr "插件"
+
+#: flaskbb/templates/management/plugins.html:13
+msgid "Information"
+msgstr "信息"
+
+#: flaskbb/templates/management/plugins.html:28
+msgid "Version"
+msgstr "版本"
+
+#: flaskbb/templates/management/plugins.html:36
+msgid "Enable"
+msgstr "启用"
+
+#: flaskbb/templates/management/plugins.html:41
+msgid "Disable"
+msgstr "禁用"
+
+#: flaskbb/templates/management/plugins.html:50
+msgid "Install"
+msgstr "安装"
+
+#: flaskbb/templates/management/plugins.html:56
+msgid "Uninstall"
+msgstr "卸载"
+
+#: flaskbb/templates/management/reports.html:10
+#: flaskbb/templates/management/unread_reports.html:10
+msgid "Show unread reports"
+msgstr "显示未读报告"
+
+#: flaskbb/templates/management/reports.html:11
+#: flaskbb/templates/management/unread_reports.html:11
+msgid "Show all reports"
+msgstr "显示所有报告"
+
+#: flaskbb/templates/management/reports.html:16
+msgid "All Reports"
+msgstr "所有报告"
+
+#: flaskbb/templates/management/reports.html:26
+#: flaskbb/templates/management/unread_reports.html:26
+msgid "Poster"
+msgstr "回复者"
+
+#: flaskbb/templates/management/reports.html:28
+#: flaskbb/templates/management/unread_reports.html:28
+msgid "Reporter"
+msgstr "报告者"
+
+#: flaskbb/templates/management/reports.html:30
+#: flaskbb/templates/management/unread_reports.html:30
+msgid "Reported"
+msgstr "已被报告"
+
+#: flaskbb/templates/management/reports.html:45
+msgid "No reports."
+msgstr "还没有报告。"
+
+#: flaskbb/templates/management/unread_reports.html:1
+#: flaskbb/templates/management/unread_reports.html:16
+msgid "Unread Reports"
+msgstr "未读报告"
+
+#: flaskbb/templates/management/unread_reports.html:34
+msgid "Mark all as Read"
+msgstr "将所有标记为已读。"
+
+#: flaskbb/templates/management/unread_reports.html:57
+msgid "No unread reports."
+msgstr "没有未读的报告。"
+
+#: flaskbb/templates/management/users.html:64
+msgid "Ban"
+msgstr "禁止"
+
+#: flaskbb/templates/message/conversation_list.html:4
+msgid "Conversations"
+msgstr "所有会话"
+
+#: flaskbb/templates/message/conversation_list.html:77
+msgid "No conversations found."
+msgstr "未找到任何会话。"
+
+#: flaskbb/templates/message/drafts.html:1
+#: flaskbb/templates/message/message_layout.html:18
+msgid "Drafts"
+msgstr "草稿箱"
+
+#: flaskbb/templates/message/inbox.html:1
+#: flaskbb/templates/message/message_layout.html:16
+#: flaskbb/themes/bootstrap2/templates/layout.html:83
+#: flaskbb/themes/bootstrap3/templates/layout.html:82
+msgid "Inbox"
+msgstr "收件箱"
+
+#: flaskbb/templates/message/message_layout.html:8
+msgid "Private Message"
+msgstr "私人消息"
+
+#: flaskbb/templates/message/message_layout.html:17
+msgid "Sent"
+msgstr "发件箱"
+
+#: flaskbb/templates/message/message_layout.html:19
+#: flaskbb/templates/message/trash.html:1
+msgid "Trash"
+msgstr "废纸篓"
+
+#: flaskbb/templates/message/sent.html:1
+msgid "Sent Messages"
+msgstr "发送消息"
+
+#: flaskbb/templates/user/all_posts.html:8 flaskbb/templates/user/profile.html:29
+msgid "All Posts"
+msgstr "所有回复"
+
+#: flaskbb/templates/user/all_posts.html:18
+#, python-format
+msgid "All Posts created by %(user)s"
+msgstr "所有回复由 %(user)s 创建"
+
+#: flaskbb/templates/user/all_topics.html:8
+#: flaskbb/templates/user/profile.html:28
+msgid "All Topics"
+msgstr "所有主题"
+
+#: flaskbb/templates/user/all_topics.html:20
+#, python-format
+msgid "All Topics created by %(user)s"
+msgstr "所有主题由 %(user)s 创建"
+
+#: flaskbb/templates/user/change_email.html:6
+#: flaskbb/templates/user/settings_layout.html:18
+msgid "Change E-Mail Address"
+msgstr "修改邮箱"
+
+#: flaskbb/templates/user/change_password.html:6
+#: flaskbb/templates/user/settings_layout.html:19
+msgid "Change Password"
+msgstr "修改密码"
+
+#: flaskbb/templates/user/change_user_details.html:6
+#: flaskbb/templates/user/settings_layout.html:17
+msgid "Change User Details"
+msgstr "修改用户信息"
+
+#: flaskbb/templates/user/general_settings.html:6
+#: flaskbb/templates/user/settings_layout.html:16
+msgid "General Settings"
+msgstr "通用设置"
+
+#: flaskbb/templates/user/profile.html:11
+msgid "Info"
+msgstr "信息"
+
+#: flaskbb/templates/user/profile.html:12
+msgid "User Stats"
+msgstr "用户状态"
+
+#: flaskbb/templates/user/profile.html:40
+msgid "User has not added any notes about him."
+msgstr "该用户没有添加关于他的备注。"
+
+#: flaskbb/templates/user/profile.html:52
+msgid "Joined"
+msgstr "加入于"
+
+#: flaskbb/templates/user/profile.html:60
+msgid "Last seen"
+msgstr "最后上线于"
+
+#: flaskbb/templates/user/profile.html:61
+msgid "Never seen"
+msgstr "从未上线"
+
+#: flaskbb/templates/user/profile.html:64
+msgid "Last post"
+msgstr "最后发表回复"
+
+#: flaskbb/templates/user/profile.html:68
+msgid "Never"
+msgstr "从未"
+
+#: flaskbb/templates/user/profile.html:74 flaskbb/templates/user/profile.html:78
+msgid "No Info"
+msgstr "没有信息"
+
+#: flaskbb/templates/user/settings_layout.html:15
+msgid "Account Settings"
+msgstr "账户设置"
+
+#: flaskbb/user/forms.py:29
+msgid "Language"
+msgstr "语言"
+
+#: flaskbb/user/forms.py:30
+msgid "Theme"
+msgstr "主题"
+
+#: flaskbb/user/forms.py:36
+msgid "Old E-Mail Address"
+msgstr "原邮箱地址"
+
+#: flaskbb/user/forms.py:40
+msgid "New E-Mail Address"
+msgstr "新邮箱地址"
+
+#: flaskbb/user/forms.py:42
+msgid "E-Mails must match."
+msgstr "两次输入的邮箱不一致。"
+
+#: flaskbb/user/forms.py:45
+msgid "Confirm E-Mail Address"
+msgstr "校对邮箱地址"
+
+#: flaskbb/user/forms.py:64
+msgid "Old Password"
+msgstr "原密码"
+
+#: flaskbb/user/forms.py:65
+msgid "Password required"
+msgstr "请输入密码"
+
+#: flaskbb/user/forms.py:71
+msgid "Confirm New Password"
+msgstr "校对密码"
+
+#: flaskbb/user/forms.py:77
+msgid "Old Password is wrong."
+msgstr "原密码错误。"
+
+#: flaskbb/user/views.py:66
+msgid "Settings updated."
+msgstr "设置更新成功。"
+
+#: flaskbb/user/views.py:82
+msgid "Password updated."
+msgstr "密码修改成功。"
+
+#: flaskbb/user/views.py:94
+msgid "E-Mail Address updated."
+msgstr "邮箱更新成功。"
+
+#: flaskbb/user/views.py:107
+msgid "Details updated."
+msgstr "更新用户信息成功。"

+ 1615 - 0
flaskbb/translations/zh_TW/LC_MESSAGES/messages.po

@@ -0,0 +1,1615 @@
+# Chinese (Traditional, Taiwan) translations for PROJECT.
+# Copyright (C) 2015 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.1-dev\n"
+"Report-Msgid-Bugs-To: http://github.com/flaskbb/issues\n"
+"POT-Creation-Date: 2015-07-14 22:49+0800\n"
+"PO-Revision-Date: 2015-07-16 15:04+0800\n"
+"Last-Translator: Sean Chen <seanchen0617@gmail.com>\n"
+"Language-Team: zh_Hant_TW <LL@li.org>\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/email.py:20
+msgid "Password Reset"
+msgstr "重設密碼"
+
+#: flaskbb/auth/forms.py:24 flaskbb/management/forms.py:37
+msgid "You can only use letters, numbers or dashes."
+msgstr "你只可以使用英文字母、數字或短橫線"
+
+#: flaskbb/auth/forms.py:28
+msgid "Username or E-Mail Address"
+msgstr "使用者名稱或電子郵件帳號"
+
+#: flaskbb/auth/forms.py:29
+msgid "A Username or E-Mail Address is required."
+msgstr "請輸入使用者名稱或電子郵件帳號"
+
+#: flaskbb/auth/forms.py:32 flaskbb/auth/forms.py:49 flaskbb/auth/forms.py:83
+#: flaskbb/auth/forms.py:104 flaskbb/user/forms.py:67
+msgid "Password"
+msgstr "密碼"
+
+#: flaskbb/auth/forms.py:33 flaskbb/auth/forms.py:84
+msgid "A Password is required."
+msgstr "請輸入密碼"
+
+#: flaskbb/auth/forms.py:35
+msgid "Remember Me"
+msgstr "記住我"
+
+#: flaskbb/auth/forms.py:37 flaskbb/templates/layout.html:89
+#: flaskbb/templates/auth/login.html:1 flaskbb/templates/auth/login.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:90
+#: flaskbb/themes/bootstrap3/templates/layout.html:89
+msgid "Login"
+msgstr "登入"
+
+#: flaskbb/auth/forms.py:41 flaskbb/management/forms.py:55
+#: flaskbb/templates/forum/memberlist.html:31
+#: flaskbb/templates/forum/search_result.html:83
+#: flaskbb/templates/management/banned_users.html:41
+#: flaskbb/templates/management/users.html:41
+msgid "Username"
+msgstr "使用者名稱"
+
+#: flaskbb/auth/forms.py:42 flaskbb/management/forms.py:56
+#: flaskbb/message/forms.py:23
+msgid "A Username is required."
+msgstr "請輸入使用者名稱"
+
+#: flaskbb/auth/forms.py:45 flaskbb/auth/forms.py:90 flaskbb/auth/forms.py:100
+#: flaskbb/management/forms.py:59
+msgid "E-Mail Address"
+msgstr "電子郵件帳號"
+
+#: flaskbb/auth/forms.py:46 flaskbb/auth/forms.py:101
+#: flaskbb/management/forms.py:60 flaskbb/user/forms.py:37
+msgid "A E-Mail Address is required."
+msgstr "請輸入電子郵件帳號"
+
+#: flaskbb/auth/forms.py:47 flaskbb/management/forms.py:61
+#: flaskbb/user/forms.py:38 flaskbb/user/forms.py:43 flaskbb/user/forms.py:46
+msgid "Invalid E-Mail Address."
+msgstr "無效的電子郵件帳號"
+
+#: flaskbb/auth/forms.py:51 flaskbb/auth/forms.py:106 flaskbb/user/forms.py:69
+msgid "Passwords must match."
+msgstr "輸入的密碼不一致"
+
+#: flaskbb/auth/forms.py:53 flaskbb/auth/forms.py:108
+msgid "Confirm Password"
+msgstr "確認密碼"
+
+#: flaskbb/auth/forms.py:55
+msgid "I accept the Terms of Service"
+msgstr "我接受服務條款"
+
+#: flaskbb/auth/forms.py:57 flaskbb/templates/layout.html:95
+#: flaskbb/templates/auth/register.html:1
+#: flaskbb/templates/auth/register.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:96
+#: flaskbb/themes/bootstrap3/templates/layout.html:95
+msgid "Register"
+msgstr "註冊"
+
+#: flaskbb/auth/forms.py:62 flaskbb/management/forms.py:116
+msgid "This Username is already taken."
+msgstr "使用者名稱已使用"
+
+#: flaskbb/auth/forms.py:67 flaskbb/management/forms.py:130
+#: flaskbb/user/forms.py:60
+msgid "This E-Mail Address is already taken."
+msgstr "電子郵件帳號已使用"
+
+#: flaskbb/auth/forms.py:79
+msgid "Captcha"
+msgstr "驗證碼"
+
+#: flaskbb/auth/forms.py:86 flaskbb/templates/auth/reauth.html:1
+#: flaskbb/templates/auth/reauth.html:9
+msgid "Refresh Login"
+msgstr "重新登入"
+
+#: flaskbb/auth/forms.py:91
+msgid "A E-Mail Address is reguired."
+msgstr "請輸入電子郵件帳號"
+
+#: flaskbb/auth/forms.py:94 flaskbb/templates/auth/forgot_password.html:1
+msgid "Request Password"
+msgstr "取得密碼"
+
+#: flaskbb/auth/forms.py:110 flaskbb/templates/layout.html:96
+#: flaskbb/templates/auth/forgot_password.html:8
+#: flaskbb/templates/auth/reset_password.html:1
+#: flaskbb/templates/auth/reset_password.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:97
+#: flaskbb/themes/bootstrap3/templates/layout.html:96
+msgid "Reset Password"
+msgstr "重置密碼"
+
+#: flaskbb/auth/forms.py:115
+msgid "Wrong E-Mail Address."
+msgstr "電子郵件帳號錯誤"
+
+#: flaskbb/auth/views.py:45
+msgid "Wrong Username or Password."
+msgstr "使用者名稱或密碼錯誤"
+
+#: flaskbb/auth/views.py:60
+msgid "Reauthenticated."
+msgstr "已重新驗證"
+
+#: flaskbb/auth/views.py:96
+msgid "Thanks for registering."
+msgstr "感謝註冊"
+
+#: flaskbb/auth/views.py:118
+msgid "E-Mail sent! Please check your inbox."
+msgstr "已寄出電子郵件!請確認您的信箱"
+
+#: flaskbb/auth/views.py:121
+msgid ""
+"You have entered a Username or E-Mail Address that is not linked with "
+"your account."
+msgstr "您輸入了未連結到您的帳號的使用者名稱或電子郵件帳號"
+
+#: flaskbb/auth/views.py:141
+msgid "Your Password Token is invalid."
+msgstr "您的重置密碼記號已失效"
+
+#: flaskbb/auth/views.py:145
+msgid "Your Password Token is expired."
+msgstr "您的重置密碼記號已過期"
+
+#: flaskbb/auth/views.py:151
+msgid "Your Password has been updated."
+msgstr "您的密碼已更新"
+
+#: flaskbb/forum/forms.py:22
+msgid "Quick Reply"
+msgstr "快速回文"
+
+#: flaskbb/forum/forms.py:23 flaskbb/forum/forms.py:34
+#: flaskbb/forum/forms.py:55
+msgid "You cannot post a reply without content."
+msgstr "您不能發表空白的回文"
+
+#: flaskbb/forum/forms.py:25 flaskbb/forum/forms.py:39
+#: flaskbb/templates/forum/topic.html:146
+#: flaskbb/templates/forum/topic_controls.html:25
+msgid "Reply"
+msgstr "回文"
+
+#: flaskbb/forum/forms.py:33 flaskbb/forum/forms.py:54
+#: flaskbb/forum/forms.py:100
+msgid "Content"
+msgstr "內容"
+
+#: flaskbb/forum/forms.py:36 flaskbb/forum/forms.py:57
+msgid "Track this Topic"
+msgstr "追蹤這個主題"
+
+#: flaskbb/forum/forms.py:40 flaskbb/forum/forms.py:61
+#: flaskbb/templates/forum/new_post.html:28
+#: flaskbb/templates/forum/new_topic.html:27
+msgid "Preview"
+msgstr "預覽"
+
+#: flaskbb/forum/forms.py:51
+msgid "Topic Title"
+msgstr "標題"
+
+#: flaskbb/forum/forms.py:52
+msgid "Please choose a Topic Title."
+msgstr "請選擇一個標題"
+
+#: flaskbb/forum/forms.py:60
+msgid "Post Topic"
+msgstr "發表文章"
+
+#: flaskbb/forum/forms.py:73 flaskbb/templates/management/reports.html:29
+#: flaskbb/templates/management/unread_reports.html:29
+msgid "Reason"
+msgstr "原因"
+
+#: flaskbb/forum/forms.py:74
+msgid "What's the reason for reporting this post?"
+msgstr "為什麼要舉報這篇文章"
+
+#: flaskbb/forum/forms.py:77 flaskbb/templates/forum/report_post.html:1
+#: flaskbb/templates/forum/report_post.html:13
+msgid "Report Post"
+msgstr "舉報文章"
+
+#: flaskbb/forum/forms.py:85 flaskbb/forum/forms.py:89
+#: flaskbb/forum/forms.py:104 flaskbb/templates/layout.html:50
+#: flaskbb/templates/forum/memberlist.html:21
+#: flaskbb/templates/forum/search_form.html:1
+#: flaskbb/templates/forum/search_form.html:9
+#: flaskbb/templates/forum/search_form.html:13
+#: flaskbb/templates/forum/search_result.html:1
+#: flaskbb/templates/forum/search_result.html:9
+#: flaskbb/templates/management/banned_users.html:31
+#: flaskbb/templates/management/users.html:31
+#: flaskbb/themes/bootstrap2/templates/layout.html:51
+#: flaskbb/themes/bootstrap3/templates/layout.html:50
+msgid "Search"
+msgstr "搜尋"
+
+#: flaskbb/forum/forms.py:97
+msgid "Criteria"
+msgstr "搜尋條件"
+
+#: flaskbb/forum/forms.py:101
+msgid "Post"
+msgstr "文章"
+
+#: flaskbb/forum/forms.py:101 flaskbb/templates/forum/forum.html:50
+#: flaskbb/templates/forum/search_result.html:113
+#: flaskbb/templates/forum/topictracker.html:28
+#: flaskbb/templates/management/reports.html:27
+#: flaskbb/templates/management/unread_reports.html:27
+#: flaskbb/templates/user/all_topics.html:26
+msgid "Topic"
+msgstr "主題"
+
+#: flaskbb/forum/forms.py:102 flaskbb/templates/layout.html:48
+#: flaskbb/templates/forum/category.html:8
+#: flaskbb/templates/forum/category_layout.html:11
+#: flaskbb/templates/forum/forum.html:9 flaskbb/templates/forum/index.html:5
+#: flaskbb/templates/forum/memberlist.html:8
+#: flaskbb/templates/forum/new_post.html:15
+#: flaskbb/templates/forum/new_topic.html:15
+#: flaskbb/templates/forum/search_form.html:8
+#: flaskbb/templates/forum/search_result.html:8
+#: flaskbb/templates/forum/search_result.html:185
+#: flaskbb/templates/forum/topic.html:14
+#: flaskbb/templates/forum/topictracker.html:9
+#: flaskbb/templates/management/forums.html:38
+#: flaskbb/templates/message/message_layout.html:6
+#: flaskbb/templates/user/all_posts.html:6
+#: flaskbb/templates/user/all_topics.html:6
+#: flaskbb/templates/user/profile.html:4
+#: flaskbb/templates/user/settings_layout.html:6
+#: flaskbb/themes/bootstrap2/templates/layout.html:49
+#: flaskbb/themes/bootstrap3/templates/layout.html:48
+msgid "Forum"
+msgstr "論壇"
+
+#: flaskbb/forum/forms.py:102 flaskbb/templates/forum/search_result.html:77
+#: flaskbb/templates/management/management_layout.html:12
+#: flaskbb/templates/management/overview.html:29
+#: flaskbb/templates/management/users.html:1
+msgid "Users"
+msgstr "使用者"
+
+#: 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 this topic."
+msgstr "您沒有權限刪除這個主題"
+
+#: flaskbb/forum/views.py:208
+msgid "You do not have the permissions to lock this topic."
+msgstr "您沒有權限鎖定這個主題"
+
+#: flaskbb/forum/views.py:227
+msgid "You do not have the permissions to unlock this topic."
+msgstr "您沒有權限解鎖這個主題"
+
+#: flaskbb/forum/views.py:243
+msgid "You do not have the permissions to highlight this topic."
+msgstr "您沒有權限強調這個主題"
+
+#: flaskbb/forum/views.py:260
+msgid "You do not have the permissions to trivialize this topic."
+msgstr "您沒有權限取消強調這個主題"
+
+#: flaskbb/forum/views.py:282
+msgid "You do not have the permissions to move this topic."
+msgstr "您沒有權限移動這個主題"
+
+#: flaskbb/forum/views.py:287
+#, python-format
+msgid "Could not move the topic to forum %(title)s."
+msgstr "無法移動主題至 %(title)s"
+
+#: flaskbb/forum/views.py:291
+#, python-format
+msgid "Topic was moved to forum %(title)s."
+msgstr "已移動主題至 %(title)s"
+
+#: flaskbb/forum/views.py:310
+msgid "You do not have the permissions to merge this topic."
+msgstr "您沒有權限合併這個主題"
+
+#: flaskbb/forum/views.py:315
+msgid "Could not merge the topics."
+msgstr "無法合併主題"
+
+#: flaskbb/forum/views.py:318
+msgid "Topics succesfully merged."
+msgstr "已成功合併主題"
+
+#: flaskbb/forum/views.py:329 flaskbb/forum/views.py:356
+msgid "You do not have the permissions to post in this topic."
+msgstr "您沒有權限回覆這個主題"
+
+#: flaskbb/forum/views.py:382
+msgid "You do not have the permissions to edit this post."
+msgstr "您沒有權限編輯這篇文章"
+
+#: flaskbb/forum/views.py:412
+msgid "You do not have the permissions to delete this post."
+msgstr "您沒有權限刪除這篇文章"
+
+#: flaskbb/forum/views.py:436
+msgid "Thanks for reporting."
+msgstr "謝謝您的舉報"
+
+#: flaskbb/forum/views.py:473
+#, python-format
+msgid "Forum %(forum)s marked as read."
+msgstr "%(forum)s 論壇已經標記為已讀"
+
+#: flaskbb/forum/views.py:495
+msgid "All forums marked as read."
+msgstr "全部論壇已標記為已讀"
+
+#: flaskbb/management/forms.py:66 flaskbb/templates/user/profile.html:77
+#: flaskbb/user/forms.py:81
+msgid "Birthday"
+msgstr "生日"
+
+#: flaskbb/management/forms.py:70 flaskbb/user/forms.py:85
+msgid "Gender"
+msgstr "性別"
+
+#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:87
+msgid "Male"
+msgstr "男"
+
+#: flaskbb/management/forms.py:73 flaskbb/user/forms.py:88
+msgid "Female"
+msgstr "女"
+
+#: flaskbb/management/forms.py:75 flaskbb/templates/user/profile.html:73
+#: flaskbb/user/forms.py:90
+msgid "Location"
+msgstr "位置"
+
+#: flaskbb/management/forms.py:78 flaskbb/templates/forum/topic.html:114
+#: flaskbb/user/forms.py:93
+msgid "Website"
+msgstr "網站"
+
+#: flaskbb/management/forms.py:81 flaskbb/user/forms.py:96
+msgid "Avatar"
+msgstr "頭像"
+
+#: flaskbb/management/forms.py:84 flaskbb/user/forms.py:99
+msgid "Forum Signature"
+msgstr "簽名檔"
+
+#: flaskbb/management/forms.py:87 flaskbb/user/forms.py:102
+msgid "Notes"
+msgstr "說明"
+
+#: flaskbb/management/forms.py:91
+msgid "Primary Group"
+msgstr "主要群組"
+
+#: flaskbb/management/forms.py:96
+msgid "Secondary Groups"
+msgstr "次要群組"
+
+#: flaskbb/management/forms.py:102 flaskbb/management/forms.py:212
+#: flaskbb/management/forms.py:328 flaskbb/management/forms.py:434
+#: flaskbb/templates/management/settings.html:39 flaskbb/user/forms.py:32
+#: flaskbb/user/forms.py:48 flaskbb/user/forms.py:73 flaskbb/user/forms.py:105
+msgid "Save"
+msgstr "儲存"
+
+#: flaskbb/management/forms.py:149 flaskbb/templates/management/groups.html:25
+msgid "Group Name"
+msgstr "群組名稱"
+
+#: flaskbb/management/forms.py:150
+msgid "A Group name is required."
+msgstr "請輸入群組名稱"
+
+#: flaskbb/management/forms.py:152 flaskbb/management/forms.py:279
+#: flaskbb/management/forms.py:422 flaskbb/templates/management/groups.html:26
+msgid "Description"
+msgstr "說明"
+
+#: flaskbb/management/forms.py:156
+msgid "Is Admin Group?"
+msgstr "是否為管理者群組?"
+
+#: flaskbb/management/forms.py:157
+msgid "With this option the group has access to the admin panel."
+msgstr "選擇這個選項讓這個群組可使用管理者功能"
+
+#: flaskbb/management/forms.py:161
+msgid "Is Super Moderator Group?"
+msgstr "是否為超級協調者群組?"
+
+#: flaskbb/management/forms.py:162
+msgid "Check this if the users in this group are allowed to moderate every forum."
+msgstr "選擇這個選項讓此群組中的使用者可管理每個論壇"
+
+#: flaskbb/management/forms.py:166
+msgid "Is Moderator Group?"
+msgstr "是否為協調者群組"
+
+#: flaskbb/management/forms.py:167
+msgid ""
+"Check this if the users in this group are allowed to moderate specified "
+"forums."
+msgstr "選擇這個選項讓此群組中的使用者可管理特定的論壇"
+
+#: flaskbb/management/forms.py:171
+msgid "Is Banned Group?"
+msgstr "是否為禁止群組?"
+
+#: flaskbb/management/forms.py:172
+msgid "Only one Banned group is allowed."
+msgstr "只可選擇一個禁止群組"
+
+#: flaskbb/management/forms.py:175
+msgid "Is Guest Group?"
+msgstr "是否為遊客群組?"
+
+#: flaskbb/management/forms.py:176
+msgid "Only one Guest group is allowed."
+msgstr "只可選擇一個遊客群組"
+
+#: flaskbb/management/forms.py:179
+msgid "Can edit posts"
+msgstr "可編輯文章"
+
+#: flaskbb/management/forms.py:180
+msgid "Check this if the users in this group can edit posts."
+msgstr "選擇這個選項讓此群組中的使用者可編輯文章"
+
+#: flaskbb/management/forms.py:183
+msgid "Can delete posts"
+msgstr "可刪除文章"
+
+#: flaskbb/management/forms.py:184
+msgid "Check this is the users in this group can delete posts."
+msgstr "選擇這個選項讓此群組中的使用者可刪除文章"
+
+#: flaskbb/management/forms.py:187
+msgid "Can delete topics"
+msgstr "可刪除主題"
+
+#: flaskbb/management/forms.py:188
+msgid "Check this is the users in this group can delete topics."
+msgstr "選擇這個選項讓此群組中的使用者可刪除主題"
+
+#: flaskbb/management/forms.py:192
+msgid "Can create topics"
+msgstr "可新增主題"
+
+#: flaskbb/management/forms.py:193
+msgid "Check this is the users in this group can create topics."
+msgstr "選擇這個選項讓此群組中的使用者可新增主題"
+
+#: flaskbb/management/forms.py:197
+msgid "Can post replies"
+msgstr "可發表回覆"
+
+#: flaskbb/management/forms.py:198
+msgid "Check this is the users in this group can post replies."
+msgstr "選擇這個選項讓此群組中的使用者可發表回覆"
+
+#: flaskbb/management/forms.py:202
+msgid "Moderators can edit user profiles"
+msgstr "協調者可編輯使用者檔案"
+
+#: flaskbb/management/forms.py:203
+msgid ""
+"Allow moderators to edit a another users profile including password and "
+"email changes."
+msgstr "允許協調者可編輯其他使用者檔案,包含密碼與電子郵件"
+
+#: flaskbb/management/forms.py:208
+msgid "Moderators can ban users"
+msgstr "協調者可禁止使用者"
+
+#: flaskbb/management/forms.py:209
+msgid "Allow moderators to ban other users."
+msgstr "允許協調者可禁止其他使用者"
+
+#: flaskbb/management/forms.py:226
+msgid "This Group name is already taken."
+msgstr "群組名稱已使用"
+
+#: flaskbb/management/forms.py:240
+msgid "There is already a Banned group."
+msgstr "已有一個禁止群組"
+
+#: flaskbb/management/forms.py:254
+msgid "There is already a Guest group."
+msgstr "已有一個遊客群組"
+
+#: flaskbb/management/forms.py:274
+msgid "Forum Title"
+msgstr "論壇標題"
+
+#: flaskbb/management/forms.py:275
+msgid "A Forum Title is required."
+msgstr "請輸入論壇標題"
+
+#: flaskbb/management/forms.py:281 flaskbb/management/forms.py:424
+msgid "You can format your description with BBCode."
+msgstr "您可以用 BBCode 編輯您的說明"
+
+#: flaskbb/management/forms.py:285 flaskbb/management/forms.py:428
+msgid "Position"
+msgstr "位置"
+
+#: flaskbb/management/forms.py:287
+msgid "The Forum Position is required."
+msgstr "請選擇論壇所在ㄙ位置"
+
+#: flaskbb/management/forms.py:291
+msgid "Category"
+msgstr "類別"
+
+#: flaskbb/management/forms.py:295
+msgid "The category that contains this forum."
+msgstr "論壇的類別"
+
+#: flaskbb/management/forms.py:299
+msgid "External Link"
+msgstr "外部連結"
+
+#: flaskbb/management/forms.py:301
+msgid "A link to a website i.e. 'http://flaskbb.org'."
+msgstr "網站連結 如:'http://flaskbb.org'"
+
+#: flaskbb/management/forms.py:305
+#: flaskbb/templates/forum/category_layout.html:60
+#: flaskbb/templates/forum/search_result.html:233
+msgid "Moderators"
+msgstr "協調者"
+
+#: flaskbb/management/forms.py:306
+msgid ""
+"Comma seperated usernames. Leave it blank if you do not want to set any "
+"moderators."
+msgstr "用逗號分開使用者名稱。如果您不想設定任何協調者請留空白"
+
+#: flaskbb/management/forms.py:311
+msgid "Show Moderators"
+msgstr "顯示協調者"
+
+#: flaskbb/management/forms.py:312
+msgid "Do you want show the moderators on the index page?"
+msgstr "您是否想在首頁顯示協調者?"
+
+#: flaskbb/management/forms.py:316
+msgid "Locked?"
+msgstr "鎖定?"
+
+#: flaskbb/management/forms.py:317
+msgid "Disable new posts and topics in this forum."
+msgstr "此論壇禁止所有新文章與主題"
+
+#: flaskbb/management/forms.py:321
+msgid "Group Access to Forum"
+msgstr "可使用此論壇的群組"
+
+#: flaskbb/management/forms.py:325
+msgid "Select user groups that can access this forum."
+msgstr "選擇可使用此論壇的使用者群組"
+
+#: flaskbb/management/forms.py:333
+msgid "You cannot convert a forum that contain topics in a external link."
+msgstr "您無法將包含主題的論壇轉換成外部連結"
+
+#: flaskbb/management/forms.py:338
+msgid "You also need to specify some moderators."
+msgstr "您需要指定幾位協調者"
+
+#: flaskbb/management/forms.py:359
+#, python-format
+msgid "%(user)s is not in a moderators group."
+msgstr "%(user)s 不屬於協調者群組"
+
+#: flaskbb/management/forms.py:365
+#, python-format
+msgid "User %(moderator)s not found."
+msgstr "使用者 %(moderator)s 不存在"
+
+#: flaskbb/management/forms.py:418
+msgid "Category Title"
+msgstr "類別標題"
+
+#: flaskbb/management/forms.py:419
+msgid "A Category Title is required."
+msgstr "請輸入類別標題"
+
+#: flaskbb/management/forms.py:430
+msgid "The Category Position is required."
+msgstr "請選擇類別所在位置"
+
+#: flaskbb/management/views.py:85
+msgid "Settings saved."
+msgstr "設定已儲存"
+
+#: flaskbb/management/views.py:124
+msgid "You are not allowed to edit this user."
+msgstr "您沒有權限編輯這個使用者"
+
+#: flaskbb/management/views.py:144
+msgid "User successfully updated."
+msgstr "已成功更新使用者"
+
+#: flaskbb/management/views.py:148
+msgid "Edit User"
+msgstr "編輯使用者"
+
+#: flaskbb/management/views.py:156
+msgid "User successfully deleted."
+msgstr "已成功刪除使用者"
+
+#: flaskbb/management/views.py:166
+msgid "User successfully added."
+msgstr "已成功新增使用者"
+
+#: flaskbb/management/views.py:170
+#: flaskbb/templates/management/banned_users.html:14
+#: flaskbb/templates/management/user_form.html:14
+#: flaskbb/templates/management/users.html:14
+msgid "Add User"
+msgstr "新增使用者"
+
+#: flaskbb/management/views.py:199
+msgid "You do not have the permissions to ban this user."
+msgstr "您沒有權限禁止此使用者"
+
+#: flaskbb/management/views.py:209
+msgid "A moderator cannot ban an admin user."
+msgstr "協調者不能禁止管理者"
+
+#: flaskbb/management/views.py:213
+msgid "User is now banned."
+msgstr "已禁止此使用者"
+
+#: flaskbb/management/views.py:215
+msgid "Could not ban user."
+msgstr "不能禁止此使用者"
+
+#: flaskbb/management/views.py:224
+msgid "You do not have the permissions to unban this user."
+msgstr "您沒有權限取消禁止此使用者"
+
+#: flaskbb/management/views.py:231
+msgid "User is now unbanned."
+msgstr "已取消禁止此使用者"
+
+#: flaskbb/management/views.py:233
+msgid "Could not unban user."
+msgstr "不能取消禁止此使用者"
+
+#: flaskbb/management/views.py:271
+#, python-format
+msgid "Report %(id)s is already marked as read."
+msgstr "舉報 %(id)s 已標記為已讀"
+
+#: flaskbb/management/views.py:278
+#, python-format
+msgid "Report %(id)s marked as read."
+msgstr "舉報 %(id)s 標記為已讀"
+
+#: flaskbb/management/views.py:292
+msgid "All reports were marked as read."
+msgstr "全部舉報已標記為已讀"
+
+#: flaskbb/management/views.py:323
+msgid "Group successfully updated."
+msgstr "已成功更新群組"
+
+#: flaskbb/management/views.py:327
+msgid "Edit Group"
+msgstr "編輯群組"
+
+#: flaskbb/management/views.py:335
+msgid "Group successfully deleted."
+msgstr "已成功刪除群組"
+
+#: flaskbb/management/views.py:345
+msgid "Group successfully added."
+msgstr "已成功新增群組"
+
+#: flaskbb/management/views.py:349
+#: flaskbb/templates/management/group_form.html:11
+#: flaskbb/templates/management/groups.html:10
+msgid "Add Group"
+msgstr "新增群組"
+
+#: flaskbb/management/views.py:368
+msgid "Forum successfully updated."
+msgstr "已成功更新論壇"
+
+#: flaskbb/management/views.py:379
+msgid "Edit Forum"
+msgstr "編輯論壇"
+
+#: flaskbb/management/views.py:392
+msgid "Forum successfully deleted."
+msgstr "已成功刪除論壇"
+
+#: flaskbb/management/views.py:404
+msgid "Forum successfully added."
+msgstr "已成功新增論壇"
+
+#: flaskbb/management/views.py:413
+#: flaskbb/templates/management/category_form.html:11
+#: flaskbb/templates/management/forum_form.html:11
+#: flaskbb/templates/management/forums.html:10
+#: flaskbb/templates/management/forums.html:27
+msgid "Add Forum"
+msgstr "新增論壇"
+
+#: flaskbb/management/views.py:423
+msgid "Category successfully added."
+msgstr "已成功新增類別"
+
+#: flaskbb/management/views.py:427
+#: flaskbb/templates/management/category_form.html:12
+#: flaskbb/templates/management/forum_form.html:12
+#: flaskbb/templates/management/forums.html:11
+msgid "Add Category"
+msgstr "新增類別"
+
+#: flaskbb/management/views.py:439
+msgid "Category successfully updated."
+msgstr "已成功更新類別"
+
+#: flaskbb/management/views.py:443
+msgid "Edit Category"
+msgstr "編輯類別"
+
+#: flaskbb/management/views.py:456
+msgid "Category with all associated forums deleted."
+msgstr "已刪除類別與所有相關論壇"
+
+#: flaskbb/management/views.py:483
+msgid "Plugin is enabled. Please reload your app."
+msgstr "外掛已啟用。請重新啟動應用程式"
+
+#: flaskbb/management/views.py:486
+msgid "Plugin is already enabled. Please reload  your app."
+msgstr "外掛已啟用。請重新啟動應用程式"
+
+#: flaskbb/management/views.py:490
+msgid ""
+"If you are using a host which doesn't support writting on the disk, this "
+"won't work - than you need to delete the 'DISABLED' file by yourself."
+msgstr "如果您使用的主機不支援寫入至磁碟,則此功能無法使用,您必須自行刪除 'DISABLED' 檔案"
+
+#: flaskbb/management/views.py:495
+msgid "Couldn't enable Plugin."
+msgstr "無法啟用外掛"
+
+#: flaskbb/management/views.py:506
+#, python-format
+msgid "Plugin %(plugin)s not found."
+msgstr "外掛 %(plugin)s 不存在"
+
+#: flaskbb/management/views.py:518
+msgid "Plugin is disabled. Please reload your app."
+msgstr "外掛已停用。請重新啟動應用程式"
+
+#: flaskbb/management/views.py:521
+msgid ""
+"If you are using a host which doesn't support writting on the disk, this "
+"won't work - than you need to create a 'DISABLED' file by yourself."
+msgstr "如果您使用的主機不支援寫入至磁碟,則此功能無法使用,您必須自行刪除 'DISABLED' 檔案"
+
+#: flaskbb/management/views.py:536
+msgid "Plugin has been uninstalled."
+msgstr "外掛已移除"
+
+#: flaskbb/management/views.py:538
+msgid "Cannot uninstall Plugin."
+msgstr "無法移除外掛"
+
+#: flaskbb/management/views.py:551
+msgid "Plugin has been installed."
+msgstr "外掛已安裝"
+
+#: flaskbb/management/views.py:553
+msgid "Cannot install Plugin."
+msgstr "無法安裝外掛"
+
+#: flaskbb/message/forms.py:22
+msgid "To User"
+msgstr "收件者"
+
+#: flaskbb/message/forms.py:25
+msgid "Subject"
+msgstr "主題"
+
+#: flaskbb/message/forms.py:26
+msgid "A Subject is required."
+msgstr "請輸入主題"
+
+#: flaskbb/message/forms.py:28 flaskbb/message/forms.py:57
+msgid "Message"
+msgstr "訊息"
+
+#: flaskbb/message/forms.py:29 flaskbb/message/forms.py:58
+msgid "A Message is required."
+msgstr "請輸入訊息"
+
+#: flaskbb/message/forms.py:31
+msgid "Start Conversation"
+msgstr "開始對話"
+
+#: flaskbb/message/forms.py:32
+msgid "Save Conversation"
+msgstr "儲存對話"
+
+#: flaskbb/message/forms.py:37
+msgid "The Username you entered doesn't exist"
+msgstr "您輸入的使用者不存在"
+
+#: flaskbb/message/forms.py:39
+msgid "You cannot send a PM to yourself."
+msgstr "您無法發送訊息給自己"
+
+#: flaskbb/message/forms.py:59
+msgid "Send Message"
+msgstr "發送訊息"
+
+#: flaskbb/message/views.py:70 flaskbb/message/views.py:126
+msgid ""
+"You cannot send any messages anymore because you havereached your message"
+" limit."
+msgstr "您無法發送任何訊息,因為您的訊息已達到上限"
+
+#: flaskbb/message/views.py:143 flaskbb/message/views.py:214
+msgid "Message saved."
+msgstr "訊息已儲存"
+
+#: flaskbb/message/views.py:167 flaskbb/message/views.py:232
+msgid "Message sent."
+msgstr "訊息已送出"
+
+#: flaskbb/message/views.py:173
+msgid "Compose Message"
+msgstr "編寫訊息"
+
+#: flaskbb/message/views.py:200
+msgid "You cannot edit a sent message."
+msgstr "您無法編輯一個已送出的訊息"
+
+#: flaskbb/message/views.py:240
+msgid "Edit Message"
+msgstr "編輯訊息"
+
+#: flaskbb/templates/layout.html:49 flaskbb/templates/forum/memberlist.html:1
+#: flaskbb/templates/forum/memberlist.html:9
+#: flaskbb/themes/bootstrap2/templates/layout.html:50
+#: flaskbb/themes/bootstrap3/templates/layout.html:49
+msgid "Memberlist"
+msgstr "使用者列表"
+
+#: flaskbb/templates/layout.html:64 flaskbb/templates/forum/topictracker.html:1
+#: flaskbb/templates/forum/topictracker.html:22
+#: flaskbb/themes/bootstrap2/templates/layout.html:65
+#: flaskbb/themes/bootstrap3/templates/layout.html:64
+msgid "Topic Tracker"
+msgstr "追蹤的主題"
+
+#: flaskbb/templates/layout.html:67
+#: flaskbb/templates/management/management_layout.html:16
+#: flaskbb/templates/user/settings_layout.html:8
+#: flaskbb/themes/bootstrap2/templates/layout.html:68
+#: flaskbb/themes/bootstrap3/templates/layout.html:67
+msgid "Settings"
+msgstr "設定"
+
+#: flaskbb/templates/layout.html:69 flaskbb/templates/management/forums.html:39
+#: flaskbb/themes/bootstrap2/templates/layout.html:70
+#: flaskbb/themes/bootstrap3/templates/layout.html:69
+msgid "Management"
+msgstr "管理"
+
+#: flaskbb/templates/layout.html:73
+#: flaskbb/themes/bootstrap2/templates/layout.html:74
+#: flaskbb/themes/bootstrap3/templates/layout.html:73
+msgid "Logout"
+msgstr "登出"
+
+#: flaskbb/templates/layout.html:82
+msgid "Private Messages"
+msgstr "訊息"
+
+#: flaskbb/templates/layout.html:83
+#: flaskbb/themes/bootstrap2/templates/layout.html:84
+#: flaskbb/themes/bootstrap3/templates/layout.html:83
+msgid "New Message"
+msgstr "新訊息"
+
+#: flaskbb/templates/macros.html:313
+msgid "Pages"
+msgstr "頁"
+
+#: flaskbb/templates/auth/login.html:18
+msgid "Not a member yet?"
+msgstr "還不是使用者?"
+
+#: flaskbb/templates/auth/login.html:19
+msgid "Forgot Password?"
+msgstr "忘記密碼?"
+
+#: flaskbb/templates/email/reset_password.html:1
+#, python-format
+msgid "Dear %(user)s,"
+msgstr "親愛的 %(user)s,"
+
+#: flaskbb/templates/email/reset_password.html:2
+msgid "To reset your password click on the following link:"
+msgstr "點擊下面的連結來重設您的密碼:"
+
+#: flaskbb/templates/email/reset_password.html:4
+msgid "Sincerely,"
+msgstr "真誠地,"
+
+#: flaskbb/templates/email/reset_password.html:5
+msgid "The Administration"
+msgstr "管理團隊"
+
+#: flaskbb/templates/errors/forbidden_page.html:1
+msgid "No Access - 403"
+msgstr "無法使用 - 403"
+
+#: flaskbb/templates/errors/forbidden_page.html:7
+msgid "Forbidden Page"
+msgstr "被禁止的頁面"
+
+#: flaskbb/templates/errors/forbidden_page.html:8
+msgid "You do not have the permission to view this page."
+msgstr ""
+
+#: flaskbb/templates/errors/forbidden_page.html:9
+#: flaskbb/templates/errors/page_not_found.html:9
+msgid "Back to the Forums"
+msgstr "回到論壇"
+
+#: flaskbb/templates/errors/page_not_found.html:1
+msgid "Oh noes! 404"
+msgstr "找不到頁面! 404"
+
+#: flaskbb/templates/errors/page_not_found.html:7
+msgid "Oops, Page not found!"
+msgstr "糟糕,找不到頁面!"
+
+#: flaskbb/templates/errors/page_not_found.html:8
+msgid "The page you were looking for does not exist."
+msgstr "您找尋的頁面不存在"
+
+#: flaskbb/templates/errors/server_error.html:1
+msgid "Server Error - 500"
+msgstr "伺服器錯誤 - 500"
+
+#: flaskbb/templates/errors/server_error.html:7
+msgid "Server Error"
+msgstr "伺服器錯誤"
+
+#: flaskbb/templates/errors/server_error.html:8
+msgid "Something went wrong!"
+msgstr "發生錯誤!"
+
+#: flaskbb/templates/forum/category_layout.html:12
+#: flaskbb/templates/forum/search_result.html:108
+#: flaskbb/templates/forum/search_result.html:186
+#: flaskbb/templates/management/overview.html:23
+msgid "Topics"
+msgstr "主題數"
+
+#: flaskbb/templates/forum/category_layout.html:13
+#: flaskbb/templates/forum/forum.html:52
+#: flaskbb/templates/forum/memberlist.html:32
+#: flaskbb/templates/forum/search_result.html:13
+#: flaskbb/templates/forum/search_result.html:43
+#: flaskbb/templates/forum/search_result.html:84
+#: flaskbb/templates/forum/search_result.html:115
+#: flaskbb/templates/forum/search_result.html:187
+#: flaskbb/templates/forum/topic.html:75
+#: flaskbb/templates/forum/topictracker.html:30
+#: flaskbb/templates/management/banned_users.html:42
+#: flaskbb/templates/management/overview.html:17
+#: flaskbb/templates/management/users.html:42
+#: flaskbb/templates/user/all_topics.html:28
+#: flaskbb/templates/user/profile.html:56
+msgid "Posts"
+msgstr "文章數"
+
+#: flaskbb/templates/forum/category_layout.html:14
+#: flaskbb/templates/forum/forum.html:56
+#: flaskbb/templates/forum/search_result.html:119
+#: flaskbb/templates/forum/search_result.html:188
+#: flaskbb/templates/forum/topictracker.html:34
+#: flaskbb/templates/user/all_topics.html:32
+msgid "Last Post"
+msgstr "最後一篇文章"
+
+#: flaskbb/templates/forum/category_layout.html:80
+#: flaskbb/templates/forum/forum.html:86 flaskbb/templates/forum/forum.html:105
+#: flaskbb/templates/forum/search_result.html:142
+#: flaskbb/templates/forum/search_result.html:161
+#: flaskbb/templates/forum/search_result.html:253
+#: flaskbb/templates/forum/topic.html:40
+#: flaskbb/templates/forum/topictracker.html:52
+#: flaskbb/templates/forum/topictracker.html:70
+#: flaskbb/templates/management/plugins.html:30
+#: flaskbb/templates/user/all_topics.html:42
+#: flaskbb/templates/user/all_topics.html:60
+msgid "by"
+msgstr "由"
+
+#: flaskbb/templates/forum/category_layout.html:88
+#: flaskbb/templates/forum/search_result.html:261
+#: flaskbb/templates/user/all_posts.html:40
+msgid "No posts."
+msgstr "無文章"
+
+#: flaskbb/templates/forum/forum.html:23
+#: flaskbb/templates/management/unread_reports.html:51
+msgid "Mark as Read"
+msgstr "標示為已讀"
+
+#: flaskbb/templates/forum/forum.html:29
+msgid "Locked"
+msgstr "鎖定"
+
+#: flaskbb/templates/forum/forum.html:33
+#: flaskbb/templates/forum/new_topic.html:1
+#: flaskbb/templates/forum/new_topic.html:17
+#: flaskbb/templates/forum/new_topic.html:22
+msgid "New Topic"
+msgstr "新主題"
+
+#: flaskbb/templates/forum/forum.html:54
+#: flaskbb/templates/forum/search_result.html:117
+#: flaskbb/templates/forum/topictracker.html:32
+#: flaskbb/templates/user/all_topics.html:30
+msgid "Views"
+msgstr "閱覽數"
+
+#: flaskbb/templates/forum/forum.html:117
+msgid "No Topics."
+msgstr "無主題"
+
+#: flaskbb/templates/forum/index.html:17
+msgid "Board Statistics"
+msgstr "統計"
+
+#: flaskbb/templates/forum/index.html:18
+msgid "Who is online?"
+msgstr "誰在線上?"
+
+#: flaskbb/templates/forum/index.html:25
+msgid "Total number of registered users"
+msgstr "註冊使用者總數"
+
+#: flaskbb/templates/forum/index.html:26
+msgid "Total number of topics"
+msgstr "主題總數"
+
+#: flaskbb/templates/forum/index.html:27
+msgid "Total number of posts"
+msgstr "文章總數"
+
+#: flaskbb/templates/forum/index.html:30
+msgid "Newest registered user"
+msgstr "最新註冊使用者"
+
+#: flaskbb/templates/forum/index.html:30
+msgid "No users"
+msgstr "無使用者"
+
+#: flaskbb/templates/forum/index.html:31
+msgid "Registered users online"
+msgstr "線上註冊使用者"
+
+#: flaskbb/templates/forum/index.html:33
+msgid "Guests online"
+msgstr "線上遊客"
+
+#: flaskbb/templates/forum/memberlist.html:33
+#: flaskbb/templates/forum/search_result.html:85
+#: flaskbb/templates/management/banned_users.html:43
+#: flaskbb/templates/management/users.html:43
+msgid "Date registered"
+msgstr "註冊日期"
+
+#: flaskbb/templates/forum/memberlist.html:34
+#: flaskbb/templates/forum/search_result.html:86
+#: flaskbb/templates/management/banned_users.html:44
+#: flaskbb/templates/management/users.html:44
+#: flaskbb/templates/user/profile.html:48
+msgid "Group"
+msgstr "群組"
+
+#: flaskbb/templates/forum/new_post.html:1
+#: flaskbb/templates/forum/new_post.html:18
+#: flaskbb/templates/forum/new_post.html:23
+msgid "New Post"
+msgstr "新增文章"
+
+#: flaskbb/templates/forum/online_users.html:1
+#: flaskbb/templates/forum/online_users.html:10
+msgid "Online Users"
+msgstr "線上使用者"
+
+#: flaskbb/templates/forum/report_post.html:17
+#: flaskbb/templates/forum/topic.html:121
+msgid "Report"
+msgstr "舉報"
+
+#: flaskbb/templates/forum/report_post.html:18
+msgid "Close"
+msgstr "關閉"
+
+#: flaskbb/templates/forum/search_result.html:44
+#: flaskbb/templates/forum/topic.html:76
+msgid "Registered since"
+msgstr "註冊於"
+
+#: flaskbb/templates/forum/search_result.html:50
+#: flaskbb/templates/forum/topic.html:82
+msgid "Guest"
+msgstr "遊客"
+
+#: flaskbb/templates/forum/search_result.html:69
+msgid "No posts found matching your search criteria."
+msgstr "您輸入的搜尋條件未發現符合文章"
+
+#: flaskbb/templates/forum/search_result.html:100
+#: flaskbb/templates/management/banned_users.html:68
+#: flaskbb/templates/management/users.html:86
+msgid "No users found matching your search criteria."
+msgstr "您輸入的搜尋條件未發現符合使用者"
+
+#: flaskbb/templates/forum/search_result.html:172
+msgid "No topics found matching your search criteria."
+msgstr "您輸入的搜尋條件未發現符合主題"
+
+#: flaskbb/templates/forum/search_result.html:180
+#: flaskbb/templates/management/forums.html:1
+#: flaskbb/templates/management/management_layout.html:18
+msgid "Forums"
+msgstr "論壇"
+
+#: flaskbb/templates/forum/search_result.html:269
+msgid "No forums found matching your search criteria."
+msgstr "您輸入的搜尋條件未發現符合論壇"
+
+#: flaskbb/templates/forum/topic.html:2
+#, python-format
+msgid "%(title)s - Topic"
+msgstr "%(title)s - 主題"
+
+#: flaskbb/templates/forum/topic.html:40
+msgid "Last modified"
+msgstr "最後修改"
+
+#: flaskbb/templates/forum/topic.html:111
+msgid "PM"
+msgstr "發送訊息"
+
+#: flaskbb/templates/forum/topic.html:125
+#: flaskbb/templates/management/forums.html:28
+#: flaskbb/templates/management/forums.html:59
+#: flaskbb/templates/management/groups.html:37
+#: flaskbb/templates/management/users.html:58
+msgid "Edit"
+msgstr "編輯"
+
+#: flaskbb/templates/forum/topic.html:131
+#: flaskbb/templates/forum/topic.html:138
+#: flaskbb/templates/management/forums.html:31
+#: flaskbb/templates/management/forums.html:62
+#: flaskbb/templates/management/groups.html:40
+#: flaskbb/templates/management/users.html:78
+msgid "Delete"
+msgstr "刪除"
+
+#: flaskbb/templates/forum/topic.html:144
+msgid "Quote"
+msgstr "引用"
+
+#: flaskbb/templates/forum/topic_controls.html:11
+msgid "Untrack Topic"
+msgstr "取消追蹤"
+
+#: flaskbb/templates/forum/topic_controls.html:18
+msgid "Track Topic"
+msgstr "追蹤主題"
+
+#: flaskbb/templates/forum/topic_controls.html:36
+msgid "Delete Topic"
+msgstr "刪除主題"
+
+#: flaskbb/templates/forum/topic_controls.html:45
+msgid "Lock Topic"
+msgstr "鎖定主題"
+
+#: flaskbb/templates/forum/topic_controls.html:52
+msgid "Unlock Topic"
+msgstr "解鎖主題"
+
+#: flaskbb/templates/forum/topic_controls.html:61
+msgid "Highlight Topic"
+msgstr "強調主題"
+
+#: flaskbb/templates/forum/topic_controls.html:68
+msgid "Trivialize Topic"
+msgstr "取消強調"
+
+#: flaskbb/templates/forum/topictracker.html:10
+msgid "Tracked Topics"
+msgstr "追蹤的主題"
+
+#: flaskbb/templates/forum/topictracker.html:82
+#: flaskbb/templates/user/all_topics.html:72
+msgid "No topics."
+msgstr "無主題"
+
+#: flaskbb/templates/management/banned_users.html:1
+#: flaskbb/templates/management/banned_users.html:11
+#: flaskbb/templates/management/banned_users.html:20
+#: flaskbb/templates/management/user_form.html:11
+#: flaskbb/templates/management/users.html:11
+msgid "Banned Users"
+msgstr "禁止的使用者"
+
+#: flaskbb/templates/management/banned_users.html:10
+#: flaskbb/templates/management/user_form.html:10
+#: flaskbb/templates/management/users.html:10
+#: flaskbb/templates/management/users.html:20
+msgid "Manage Users"
+msgstr "管理使用者"
+
+#: flaskbb/templates/management/banned_users.html:45
+#: flaskbb/templates/management/groups.html:27
+#: flaskbb/templates/management/plugins.html:14
+#: flaskbb/templates/management/users.html:45
+msgid "Manage"
+msgstr "管理"
+
+#: flaskbb/templates/management/banned_users.html:60
+#: flaskbb/templates/management/users.html:71
+msgid "Unban"
+msgstr "取消禁止"
+
+#: flaskbb/templates/management/category_form.html:10
+#: flaskbb/templates/management/forum_form.html:10
+#: flaskbb/templates/management/forums.html:9
+#: flaskbb/templates/management/forums.html:17
+msgid "Manage Forums"
+msgstr "管理論壇"
+
+#: flaskbb/templates/management/forum_form.html:37
+msgid "Group Access to the forum"
+msgstr "可使用此論壇的群組"
+
+#: flaskbb/templates/management/group_form.html:10
+#: flaskbb/templates/management/groups.html:9
+#: flaskbb/templates/management/groups.html:15
+msgid "Manage Groups"
+msgstr "管理群組"
+
+#: flaskbb/templates/management/groups.html:1
+#: flaskbb/templates/management/management_layout.html:17
+msgid "Groups"
+msgstr "群組"
+
+#: flaskbb/templates/management/management_layout.html:11
+#: flaskbb/templates/management/overview.html:1
+msgid "Overview"
+msgstr "概況"
+
+#: flaskbb/templates/management/management_layout.html:13
+#: flaskbb/templates/management/reports.html:1
+msgid "Reports"
+msgstr "舉報"
+
+#: flaskbb/templates/management/management_layout.html:19
+#: flaskbb/templates/management/plugins.html:1
+msgid "Plugins"
+msgstr "外掛"
+
+#: flaskbb/templates/management/overview.html:10
+msgid "Global Statistics"
+msgstr "統計"
+
+#: flaskbb/templates/management/overview.html:15
+msgid "FlaskBB Version"
+msgstr "FlaskBB 版本"
+
+#: flaskbb/templates/management/overview.html:21
+msgid "Python Version"
+msgstr "Python 版本"
+
+#: flaskbb/templates/management/overview.html:27
+msgid "Flask Version"
+msgstr "Flask 版本"
+
+#: flaskbb/templates/management/plugins.html:7
+msgid "Manage Plugins"
+msgstr "管理外掛"
+
+#: flaskbb/templates/management/plugins.html:12
+msgid "Plugin"
+msgstr "外掛"
+
+#: flaskbb/templates/management/plugins.html:13
+msgid "Information"
+msgstr "資訊"
+
+#: flaskbb/templates/management/plugins.html:28
+msgid "Version"
+msgstr "版本"
+
+#: flaskbb/templates/management/plugins.html:36
+msgid "Enable"
+msgstr "啟用"
+
+#: flaskbb/templates/management/plugins.html:41
+msgid "Disable"
+msgstr "取消"
+
+#: flaskbb/templates/management/plugins.html:50
+msgid "Install"
+msgstr "安裝"
+
+#: flaskbb/templates/management/plugins.html:56
+msgid "Uninstall"
+msgstr "移除"
+
+#: flaskbb/templates/management/reports.html:10
+#: flaskbb/templates/management/unread_reports.html:10
+msgid "Show unread reports"
+msgstr "顯示未讀舉報"
+
+#: flaskbb/templates/management/reports.html:11
+#: flaskbb/templates/management/unread_reports.html:11
+msgid "Show all reports"
+msgstr "顯示所有舉報"
+
+#: flaskbb/templates/management/reports.html:16
+msgid "All Reports"
+msgstr "所有舉報"
+
+#: flaskbb/templates/management/reports.html:26
+#: flaskbb/templates/management/unread_reports.html:26
+msgid "Poster"
+msgstr "回覆者"
+
+#: flaskbb/templates/management/reports.html:28
+#: flaskbb/templates/management/unread_reports.html:28
+msgid "Reporter"
+msgstr "舉報者"
+
+#: flaskbb/templates/management/reports.html:30
+#: flaskbb/templates/management/unread_reports.html:30
+msgid "Reported"
+msgstr "已舉報"
+
+#: flaskbb/templates/management/reports.html:45
+msgid "No reports."
+msgstr "無舉報"
+
+#: flaskbb/templates/management/unread_reports.html:1
+#: flaskbb/templates/management/unread_reports.html:16
+msgid "Unread Reports"
+msgstr "未讀舉報"
+
+#: flaskbb/templates/management/unread_reports.html:34
+msgid "Mark all as Read"
+msgstr "全部標記為已讀"
+
+#: flaskbb/templates/management/unread_reports.html:57
+msgid "No unread reports."
+msgstr "沒有未讀舉報"
+
+#: flaskbb/templates/management/users.html:64
+msgid "Ban"
+msgstr "禁止"
+
+#: flaskbb/templates/message/conversation_list.html:4
+msgid "Conversations"
+msgstr "對話"
+
+#: flaskbb/templates/message/conversation_list.html:77
+msgid "No conversations found."
+msgstr "沒有對話"
+
+#: flaskbb/templates/message/drafts.html:1
+#: flaskbb/templates/message/message_layout.html:18
+msgid "Drafts"
+msgstr "草稿"
+
+#: flaskbb/templates/message/inbox.html:1
+#: flaskbb/templates/message/message_layout.html:16
+#: flaskbb/themes/bootstrap2/templates/layout.html:83
+#: flaskbb/themes/bootstrap3/templates/layout.html:82
+msgid "Inbox"
+msgstr "收件箱"
+
+#: flaskbb/templates/message/message_layout.html:8
+msgid "Private Message"
+msgstr "訊息"
+
+#: flaskbb/templates/message/message_layout.html:17
+msgid "Sent"
+msgstr "已送出"
+
+#: flaskbb/templates/message/message_layout.html:19
+#: flaskbb/templates/message/trash.html:1
+msgid "Trash"
+msgstr "垃圾"
+
+#: flaskbb/templates/message/sent.html:1
+msgid "Sent Messages"
+msgstr "已送出訊息"
+
+#: flaskbb/templates/user/all_posts.html:8
+#: flaskbb/templates/user/profile.html:29
+msgid "All Posts"
+msgstr "所有文章"
+
+#: flaskbb/templates/user/all_posts.html:18
+#, python-format
+msgid "All Posts created by %(user)s"
+msgstr "%(user)s 的所有文章"
+
+#: flaskbb/templates/user/all_topics.html:8
+#: flaskbb/templates/user/profile.html:28
+msgid "All Topics"
+msgstr "所有主題"
+
+#: flaskbb/templates/user/all_topics.html:20
+#, python-format
+msgid "All Topics created by %(user)s"
+msgstr "%(user)s 的所有主題"
+
+#: flaskbb/templates/user/change_email.html:6
+#: flaskbb/templates/user/settings_layout.html:18
+msgid "Change E-Mail Address"
+msgstr "變更電子郵件"
+
+#: flaskbb/templates/user/change_password.html:6
+#: flaskbb/templates/user/settings_layout.html:19
+msgid "Change Password"
+msgstr "變更密碼"
+
+#: flaskbb/templates/user/change_user_details.html:6
+#: flaskbb/templates/user/settings_layout.html:17
+msgid "Change User Details"
+msgstr "變更使用者內容"
+
+#: flaskbb/templates/user/general_settings.html:6
+#: flaskbb/templates/user/settings_layout.html:16
+msgid "General Settings"
+msgstr "一般設定"
+
+#: flaskbb/templates/user/profile.html:11
+msgid "Info"
+msgstr "資訊"
+
+#: flaskbb/templates/user/profile.html:12
+msgid "User Stats"
+msgstr "使用者統計"
+
+#: flaskbb/templates/user/profile.html:40
+msgid "User has not added any notes about him."
+msgstr "使用者未新增說明"
+
+#: flaskbb/templates/user/profile.html:52
+msgid "Joined"
+msgstr "加入於"
+
+#: flaskbb/templates/user/profile.html:60
+msgid "Last seen"
+msgstr "最後一次登入"
+
+#: flaskbb/templates/user/profile.html:61
+msgid "Never seen"
+msgstr "從未登入"
+
+#: flaskbb/templates/user/profile.html:64
+msgid "Last post"
+msgstr "最後一篇文章"
+
+#: flaskbb/templates/user/profile.html:68
+msgid "Never"
+msgstr "從未"
+
+#: flaskbb/templates/user/profile.html:74
+#: flaskbb/templates/user/profile.html:78
+msgid "No Info"
+msgstr "沒有資訊"
+
+#: flaskbb/templates/user/settings_layout.html:15
+msgid "Account Settings"
+msgstr "帳號設定"
+
+#: flaskbb/user/forms.py:29
+msgid "Language"
+msgstr "語言"
+
+#: flaskbb/user/forms.py:30
+msgid "Theme"
+msgstr "主題"
+
+#: flaskbb/user/forms.py:36
+msgid "Old E-Mail Address"
+msgstr "原電子郵件帳號"
+
+#: flaskbb/user/forms.py:40
+msgid "New E-Mail Address"
+msgstr "新電子郵件帳號"
+
+#: flaskbb/user/forms.py:42
+msgid "E-Mails must match."
+msgstr "電子郵件帳號需一致"
+
+#: flaskbb/user/forms.py:45
+msgid "Confirm E-Mail Address"
+msgstr "確認電子郵件帳號"
+
+#: flaskbb/user/forms.py:64
+msgid "Old Password"
+msgstr "原密碼"
+
+#: flaskbb/user/forms.py:65
+msgid "Password required"
+msgstr "請輸入密碼"
+
+#: flaskbb/user/forms.py:71
+msgid "Confirm New Password"
+msgstr "確認密碼"
+
+#: flaskbb/user/forms.py:77
+msgid "Old Password is wrong."
+msgstr "原密碼錯誤"
+
+#: flaskbb/user/views.py:66
+msgid "Settings updated."
+msgstr "已更新設定"
+
+#: flaskbb/user/views.py:82
+msgid "Password updated."
+msgstr "已更新密碼"
+
+#: flaskbb/user/views.py:94
+msgid "E-Mail Address updated."
+msgstr "已更新電子郵件帳號"
+
+#: flaskbb/user/views.py:107
+msgid "Details updated."
+msgstr "已更新內容"
+

+ 21 - 0
flaskbb/user/models.py

@@ -63,6 +63,16 @@ class Group(db.Model, CRUDMixin):
         """
         """
         return "<{} {}>".format(self.__class__.__name__, self.id)
         return "<{} {}>".format(self.__class__.__name__, self.id)
 
 
+    @classmethod
+    def selectable_groups_choices(cls):
+        return Group.query.order_by(Group.name.asc()).with_entities(
+            Group.id, Group.name
+        ).all()
+
+    @classmethod
+    def get_guest_group(cls):
+        return Group.query.filter(cls.guest == True).first()
+
 
 
 class User(db.Model, UserMixin, CRUDMixin):
 class User(db.Model, UserMixin, CRUDMixin):
     __tablename__ = "users"
     __tablename__ = "users"
@@ -129,6 +139,11 @@ class User(db.Model, UserMixin, CRUDMixin):
         return self.get_permissions()
         return self.get_permissions()
 
 
     @property
     @property
+    def groups(self):
+        """Returns user groups"""
+        return self.get_groups()
+
+    @property
     def days_registered(self):
     def days_registered(self):
         """Returns the amount of days the user is registered."""
         """Returns the amount of days the user is registered."""
         days_registered = (datetime.utcnow() - self.date_joined).days
         days_registered = (datetime.utcnow() - self.date_joined).days
@@ -309,6 +324,11 @@ class User(db.Model, UserMixin, CRUDMixin):
             groups_users.c.group_id == group.id).count() > 0
             groups_users.c.group_id == group.id).count() > 0
 
 
     @cache.memoize(timeout=max_integer)
     @cache.memoize(timeout=max_integer)
+    def get_groups(self):
+        """Returns all the groups the user is in."""
+        return [self.primary_group] + list(self.secondary_groups)
+
+    @cache.memoize(timeout=max_integer)
     def get_permissions(self, exclude=None):
     def get_permissions(self, exclude=None):
         """Returns a dictionary with all the permissions the user has.
         """Returns a dictionary with all the permissions the user has.
 
 
@@ -343,6 +363,7 @@ class User(db.Model, UserMixin, CRUDMixin):
         """Invalidates this objects cached metadata."""
         """Invalidates this objects cached metadata."""
 
 
         cache.delete_memoized(self.get_permissions, self)
         cache.delete_memoized(self.get_permissions, self)
+        cache.delete_memoized(self.get_groups, self)
 
 
     def ban(self):
     def ban(self):
         """Bans the user. Returns True upon success."""
         """Bans the user. Returns True upon success."""

+ 48 - 0
flaskbb/utils/decorators.py

@@ -38,3 +38,51 @@ def moderator_required(f):
 
 
         return f(*args, **kwargs)
         return f(*args, **kwargs)
     return decorated
     return decorated
+
+
+def can_access_forum(func):
+    def decorated(*args, **kwargs):
+        forum_id = kwargs['forum_id'] if 'forum_id' in kwargs else args[1]
+        from flaskbb.forum.models import Forum
+        from flaskbb.user.models import Group
+
+        # get list of user group ids
+        if current_user.is_authenticated():
+            user_groups = [gr.id for gr in current_user.groups]
+        else:
+            user_groups = [Group.get_guest_group().id]
+
+        user_forums = Forum.query.filter(
+            Forum.id == forum_id, Forum.groups.any(Group.id.in_(user_groups))
+        ).all()
+
+        if len(user_forums) < 1:
+            abort(403)
+
+        return func(*args, **kwargs)
+    return decorated
+
+
+def can_access_topic(func):
+    def decorated(*args, **kwargs):
+        topic_id = kwargs['topic_id'] if 'topic_id' in kwargs else args[1]
+        from flaskbb.forum.models import Forum, Topic
+        from flaskbb.user.models import Group
+
+        topic = Topic.query.get(topic_id == topic_id)
+        # get list of user group ids
+        if current_user.is_authenticated():
+            user_groups = [gr.id for gr in current_user.groups]
+        else:
+            user_groups = [Group.get_guest_group().id]
+
+        user_forums = Forum.query.filter(
+            Forum.id == topic.forum.id,
+            Forum.groups.any(Group.id.in_(user_groups))
+        ).all()
+
+        if len(user_forums) < 1:
+            abort(403)
+
+        return func(*args, **kwargs)
+    return decorated

+ 5 - 1
flaskbb/utils/markup.py

@@ -12,6 +12,10 @@ from pygments.formatters import HtmlFormatter
 _re_emoji = re.compile(r':([a-z0-9\+\-_]+):', re.I)
 _re_emoji = re.compile(r':([a-z0-9\+\-_]+):', re.I)
 _re_user = re.compile(r'@(\w+)', re.I)
 _re_user = re.compile(r'@(\w+)', re.I)
 
 
+# base directory of flaskbb - used to collect the emojis
+_basedir = os.path.join(os.path.abspath(os.path.dirname(
+                        os.path.dirname(__file__))))
+
 
 
 def collect_emojis():
 def collect_emojis():
     """Returns a dictionary containing all emojis with their
     """Returns a dictionary containing all emojis with their
@@ -19,7 +23,7 @@ def collect_emojis():
     dictionary.
     dictionary.
     """
     """
     emojis = dict()
     emojis = dict()
-    full_path = os.path.join(os.path.abspath("flaskbb"), "static", "emoji")
+    full_path = os.path.join(_basedir, "static", "emoji")
     # return an empty dictionary if the path doesn't exist
     # return an empty dictionary if the path doesn't exist
     if not os.path.exists(full_path):
     if not os.path.exists(full_path):
         return emojis
         return emojis

+ 6 - 4
flaskbb/utils/populate.py

@@ -27,8 +27,9 @@ def delete_settings_from_fixture(fixture):
 
 
         for settings in settingsgroup[1]["settings"]:
         for settings in settingsgroup[1]["settings"]:
             setting = Setting.query.filter_by(key=settings[0]).first()
             setting = Setting.query.filter_by(key=settings[0]).first()
-            deleted_settings[group].append(setting)
-            setting.delete()
+            if setting:
+                deleted_settings[group].append(setting)
+                setting.delete()
 
 
         group.delete()
         group.delete()
 
 
@@ -62,8 +63,9 @@ def create_settings_from_fixture(fixture):
 
 
                 settingsgroup=group.key
                 settingsgroup=group.key
             )
             )
-            setting.save()
-            created_settings[group].append(setting)
+            if setting:
+                setting.save()
+                created_settings[group].append(setting)
 
 
     return created_settings
     return created_settings
 
 

+ 31 - 0
flaskbb/utils/widgets.py

@@ -8,6 +8,7 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
+import simplejson as json
 from datetime import datetime
 from datetime import datetime
 from wtforms.widgets.core import Select, HTMLString, html_params
 from wtforms.widgets.core import Select, HTMLString, html_params
 
 
@@ -92,3 +93,33 @@ class SelectBirthdayWidget(object):
             html.append(' ')
             html.append(' ')
 
 
         return HTMLString(''.join(html))
         return HTMLString(''.join(html))
+
+
+class MultiSelect(object):
+    """
+    Renders a megalist-multiselect widget.
+
+
+    The field must provide an `iter_choices()` method which the widget will
+    call on rendering; this method must yield tuples of
+    `(value, label, selected)`.
+    """
+
+    def __call__(self, field, **kwargs):
+        kwargs.setdefault('id', field.id)
+        src_list, dst_list = [], []
+
+        for val, label, selected in field.iter_choices():
+            if selected:
+                dst_list.append({'label':label, 'listValue':val})
+            else:
+                src_list.append({'label':label, 'listValue':val})
+        kwargs.update(
+            {
+                'data-provider-src':json.dumps(src_list),
+                'data-provider-dst':json.dumps(dst_list)
+            }
+        )
+        html = ['<div %s>' % html_params(name=field.name, **kwargs)]
+        html.append('</div>')
+        return HTMLString(''.join(html))

+ 4 - 5
manage.py

@@ -79,15 +79,14 @@ def dropdb():
 @manager.command
 @manager.command
 def populate(dropdb=False, createdb=False):
 def populate(dropdb=False, createdb=False):
     """Creates the database with some default data.
     """Creates the database with some default data.
-    If you do not want to drop or create the db add
-    '-c' (to not create the db) and '-d' (to not drop the db)
+    To drop or create the databse use the '-d' or '-c' options.
     """
     """
 
 
-    if not dropdb:
+    if dropdb:
         print("Dropping database...")
         print("Dropping database...")
         db.drop_all()
         db.drop_all()
 
 
-    if not createdb:
+    if createdb:
         print("Creating database...")
         print("Creating database...")
         upgrade()
         upgrade()
 
 
@@ -303,7 +302,7 @@ def download_emoji():
             f = open(full_path, 'wb')
             f = open(full_path, 'wb')
             f.write(requests.get(image["download_url"]).content)
             f.write(requests.get(image["download_url"]).content)
             f.close()
             f.close()
-            if count == cached_count+50:
+            if count == cached_count + 50:
                 cached_count = count
                 cached_count = count
                 print("{} out of {} Emojis downloaded...".format(
                 print("{} out of {} Emojis downloaded...".format(
                       cached_count, len(response.json())))
                       cached_count, len(response.json())))

+ 31 - 0
migrations/versions/127be3fb000_added_m2m_forumgroups_table.py

@@ -0,0 +1,31 @@
+"""Added m2m forumgroups table
+
+Revision ID: 127be3fb000
+Revises: 514ca0a3282c
+Create Date: 2015-04-08 22:25:52.809557
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '127be3fb000'
+down_revision = '514ca0a3282c'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    ### commands auto generated by Alembic - please adjust! ###
+    op.create_table('forumgroups',
+    sa.Column('group_id', sa.Integer(), nullable=False),
+    sa.Column('forum_id', sa.Integer(), nullable=False),
+    sa.ForeignKeyConstraint(['forum_id'], ['forums.id'], name='fk_forum_id', use_alter=True),
+    sa.ForeignKeyConstraint(['group_id'], ['groups.id'], )
+    )
+    ### end Alembic commands ###
+
+
+def downgrade():
+    ### commands auto generated by Alembic - please adjust! ###
+    op.drop_table('forumgroups')
+    ### end Alembic commands ###

+ 1 - 1
migrations/versions/8ad96e49dc6_init.py

@@ -51,7 +51,7 @@ def upgrade():
     op.create_table('settings',
     op.create_table('settings',
     sa.Column('key', sa.String(length=255), nullable=False),
     sa.Column('key', sa.String(length=255), nullable=False),
     sa.Column('value', sa.PickleType(), nullable=False),
     sa.Column('value', sa.PickleType(), nullable=False),
-    sa.Column('settingsgroup', sa.String(), nullable=False),
+    sa.Column('settingsgroup', sa.String(length=255), nullable=False),
     sa.Column('name', sa.String(length=200), nullable=False),
     sa.Column('name', sa.String(length=200), nullable=False),
     sa.Column('description', sa.Text(), nullable=False),
     sa.Column('description', sa.Text(), nullable=False),
     sa.Column('value_type', sa.String(length=20), nullable=False),
     sa.Column('value_type', sa.String(length=20), nullable=False),

+ 2 - 1
tests/fixtures/forum.py

@@ -15,9 +15,10 @@ def category(database):
 
 
 
 
 @pytest.fixture
 @pytest.fixture
-def forum(category, default_settings):
+def forum(category, default_settings, default_groups):
     """A single forum in a category."""
     """A single forum in a category."""
     forum = Forum(title="Test Forum", category_id=category.id)
     forum = Forum(title="Test Forum", category_id=category.id)
+    forum.groups  = default_groups
     forum.save()
     forum.save()
     return forum
     return forum
 
 

+ 1 - 4
tests/unit/test_forum_models.py

@@ -123,13 +123,10 @@ def test_category_get_all(forum, user):
 def test_forum_save(category, moderator_user):
 def test_forum_save(category, moderator_user):
     """Test the save forum method"""
     """Test the save forum method"""
     forum = Forum(title="Test Forum", category_id=category.id)
     forum = Forum(title="Test Forum", category_id=category.id)
+    forum.moderators = [moderator_user]
     forum.save()
     forum.save()
 
 
     assert forum.title == "Test Forum"
     assert forum.title == "Test Forum"
-
-    # Test with adding a moderator
-    forum.save([moderator_user])
-
     assert forum.moderators == [moderator_user]
     assert forum.moderators == [moderator_user]