Browse Source

Merge branch 'master' into bulk-actions

sh4nks 9 years ago
parent
commit
1998d1e65c
35 changed files with 6182 additions and 457 deletions
  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():
-        return redirect(url_for("user.profile"))
+        return redirect(url_for("user.profile", username=current_user.username))
 
     if current_app.config["RECAPTCHA_ENABLED"]:
         from flaskbb.auth.forms import RegisterRecaptchaForm

+ 66 - 17
flaskbb/forum/models.py

@@ -11,8 +11,10 @@
 from datetime import datetime, timedelta
 
 from flask import url_for, abort
+from sqlalchemy.orm import aliased
 
 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, \
     get_forums
 from flaskbb.utils.database import CRUDMixin
@@ -38,6 +40,24 @@ topictracker = db.Table(
               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):
     __tablename__ = "topicsread"
 
@@ -302,6 +322,12 @@ class Topic(db.Model, CRUDMixin):
         """
         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):
         """Returns True if the topicsread tracker needs an update.
         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())
 
     # 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
-    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
     @property
@@ -671,22 +710,31 @@ class Forum(db.Model, CRUDMixin):
         # topicsread
         return False
 
-    def save(self, moderators=None):
+    def save(self, moderators=None, groups=None):
         """Saves a forum
 
         :param moderators: If given, it will update the moderators in this
                            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()
         return self
 
@@ -729,6 +777,7 @@ class Forum(db.Model, CRUDMixin):
 
     # Classmethods
     @classmethod
+    @can_access_forum
     def get_forum(cls, forum_id, 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)
 
     # 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
     topic.views += 1
@@ -155,7 +155,7 @@ def view_post(post_id):
 def new_topic(forum_id, slug=None):
     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."),
               "danger")
         return redirect(forum.url)
@@ -402,7 +402,7 @@ def reply_post(topic_id, post_id):
             form.save(current_user, topic)
             return redirect(post.topic.url)
     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)
 

+ 44 - 14
flaskbb/management/forms.py

@@ -10,15 +10,17 @@
 """
 from flask_wtf import Form
 from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
-                     BooleanField, SelectField, SubmitField)
+                     BooleanField, SelectField, SubmitField,
+		     HiddenField)
 from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
                                 URL, ValidationError)
 from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
                                            QuerySelectMultipleField)
+from sqlalchemy.orm.session import make_transient, make_transient_to_detached
 from flask_babelex import lazy_gettext as _
 
 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.forum.models import Forum, Category
 from flaskbb.user.models import User, Group
@@ -36,6 +38,10 @@ def selectable_categories():
     return Category.query.order_by(Category.position)
 
 
+def selectable_groups():
+    return Group.query.order_by(Group.id.asc()).all()
+
+
 def select_primary_group():
     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.")
     )
 
+    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"))
 
     def validate_external(self, field):
@@ -354,29 +368,45 @@ class ForumForm(Form):
         else:
             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()
 
 
 class EditForumForm(ForumForm):
+
+    id = HiddenField()
+
     def __init__(self, forum, *args, **kwargs):
         self.forum = forum
         kwargs['obj'] = self.forum
         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):
     pass

+ 21 - 14
flaskbb/management/views.py

@@ -512,9 +512,7 @@ def edit_forum(forum_id):
 
     form = EditForumForm(forum)
     if form.validate_on_submit():
-        form.populate_obj(forum)
-        forum.save(moderators=form.moderators.data)
-
+        form.save()
         flash(_("Forum successfully updated."), "success")
         return redirect(url_for("management.edit_forum", forum_id=forum.id))
     else:
@@ -554,6 +552,7 @@ def add_forum(category_id=None):
         flash(_("Forum successfully added."), "success")
         return redirect(url_for("management.forums"))
     else:
+        form.groups.data = Group.query.order_by(Group.id.asc()).all()
         if category_id:
             category = Category.query.filter_by(id=category_id).first()
             form.category.data = category
@@ -626,13 +625,20 @@ def enable_plugin(plugin):
 
         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:
         flash(_("Couldn't enable Plugin."), "danger")
 
@@ -655,13 +661,14 @@ def disable_plugin(plugin):
 
     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"))
 

+ 3 - 2
flaskbb/message/forms.py

@@ -47,9 +47,10 @@ class ConversationForm(Form):
             shared_id=shared_id,
             from_user_id=from_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)
 
 

+ 2 - 3
flaskbb/message/models.py

@@ -91,12 +91,11 @@ class Message(db.Model, CRUDMixin):
     def save(self, conversation=None):
         """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:
             self.conversation_id = conversation.id
-            self.user_id = conversation.from_user_id
             self.date_created = datetime.utcnow()
 
         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.",
                 '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()).\
         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()
     topic_count = Topic.query.count()

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

@@ -391,3 +391,7 @@ margin-bottom: 0px;
 .conversation-list .conversation-date {
     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>
             {% else %}
                 <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>
             {% endfor %}
             </tbody>

+ 1 - 1
flaskbb/templates/layout.html

@@ -126,7 +126,7 @@
         <div id="footer">
             <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-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>
         {% endblock %}

+ 5 - 0
flaskbb/templates/macros.html

@@ -307,6 +307,11 @@
 </li>
 {% 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='') %}
 <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.locked) }}
 
+			<div id="perms">
+			    <h4>{{ _("Group Access to the forum") }}</h4>
+			    {{ form.groups }}
+			</div>
+
                         <div class="row">
                             {{ render_submit_field(form.submit, div_class="col-lg-offset-0 col-lg-9") }}
                         </div>
@@ -42,3 +47,18 @@
     </div>
 </div>
 {% 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") %}
-
+{% block css %}
+    {{ super() }}
+    <link rel="stylesheet" href="{{ url_for('static', filename='css/megalist-multiselect.css') }}" >
+{% endblock %}
 {% block content %}
 {%- from theme('macros.html') import navlink with context -%}
 
@@ -21,3 +24,8 @@
 {% block management_content %}{% 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 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-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>
             {% 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/font-awesome.min.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') }}">
         {% endblock %}
 
@@ -128,7 +130,7 @@
         <div id="footer">
             <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-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>
         {% endblock %}

+ 375 - 381
flaskbb/translations/messages.pot

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\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"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,7 +21,7 @@ msgstr ""
 msgid "Password Reset"
 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."
 msgstr ""
 
@@ -34,7 +34,7 @@ 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:71
+#: flaskbb/auth/forms.py:104 flaskbb/user/forms.py:67
 msgid "Password"
 msgstr ""
 
@@ -46,41 +46,42 @@ msgstr ""
 msgid "Remember Me"
 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/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"
 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/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: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."
 msgstr ""
 
 #: 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"
 msgstr ""
 
 #: 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."
 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."
 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."
 msgstr ""
 
@@ -92,20 +93,20 @@ msgstr ""
 msgid "I accept the Terms of Service"
 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: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"
 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."
 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."
 msgstr ""
 
@@ -126,12 +127,12 @@ msgstr ""
 msgid "Request Password"
 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/reset_password.html:1
 #: 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"
 msgstr ""
 
@@ -183,8 +184,8 @@ msgid "You cannot post a reply without content."
 msgstr ""
 
 #: 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"
 msgstr ""
 
@@ -198,8 +199,8 @@ msgid "Track this Topic"
 msgstr ""
 
 #: 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"
 msgstr ""
 
@@ -230,15 +231,17 @@ msgid "Report Post"
 msgstr ""
 
 #: 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/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:52
-#: flaskbb/themes/bootstrap3/templates/layout.html:51
+#: flaskbb/themes/bootstrap2/templates/layout.html:51
+#: flaskbb/themes/bootstrap3/templates/layout.html:50
 msgid "Search"
 msgstr ""
 
@@ -251,6 +254,7 @@ 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
@@ -258,29 +262,31 @@ msgstr ""
 msgid "Topic"
 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_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: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/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/management/forums.html:35
+#: 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:50
-#: flaskbb/themes/bootstrap3/templates/layout.html:49
+#: flaskbb/themes/bootstrap2/templates/layout.html:49
+#: flaskbb/themes/bootstrap3/templates/layout.html:48
 msgid "Forum"
 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/users.html:1
 msgid "Users"
@@ -310,325 +316,335 @@ msgstr ""
 msgid "You do not have the permissions to trivialize this topic."
 msgstr ""
 
-#: flaskbb/forum/views.py:281
+#: flaskbb/forum/views.py:282
 msgid "You do not have the permissions to move this topic."
 msgstr ""
 
-#: flaskbb/forum/views.py:286
+#: flaskbb/forum/views.py:287
 #, python-format
 msgid "Could not move the topic to forum %(title)s."
 msgstr ""
 
-#: flaskbb/forum/views.py:290
+#: flaskbb/forum/views.py:291
 #, python-format
 msgid "Topic was moved to forum %(title)s."
 msgstr ""
 
-#: flaskbb/forum/views.py:306
+#: flaskbb/forum/views.py:310
 msgid "You do not have the permissions to merge this topic."
 msgstr ""
 
-#: flaskbb/forum/views.py:311
+#: flaskbb/forum/views.py:315
 msgid "Could not merge the topics."
 msgstr ""
 
-#: flaskbb/forum/views.py:314
+#: flaskbb/forum/views.py:318
 msgid "Topics succesfully merged."
 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."
 msgstr ""
 
-#: flaskbb/forum/views.py:378
+#: flaskbb/forum/views.py:382
 msgid "You do not have the permissions to edit this post."
 msgstr ""
 
-#: flaskbb/forum/views.py:408
+#: flaskbb/forum/views.py:412
 msgid "You do not have the permissions to delete this post."
 msgstr ""
 
-#: flaskbb/forum/views.py:432
+#: flaskbb/forum/views.py:436
 msgid "Thanks for reporting."
 msgstr ""
 
-#: flaskbb/forum/views.py:469
+#: flaskbb/forum/views.py:473
 #, python-format
 msgid "Forum %(forum)s marked as read."
 msgstr ""
 
-#: flaskbb/forum/views.py:491
+#: flaskbb/forum/views.py:495
 msgid "All forums marked as read."
 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"
 msgstr ""
 
-#: flaskbb/management/forms.py:58 flaskbb/user/forms.py:85
+#: flaskbb/management/forms.py:70 flaskbb/user/forms.py:85
 msgid "Gender"
 msgstr ""
 
-#: flaskbb/management/forms.py:60 flaskbb/user/forms.py:87
+#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:87
 msgid "Male"
 msgstr ""
 
-#: flaskbb/management/forms.py:61 flaskbb/user/forms.py:88
+#: flaskbb/management/forms.py:73 flaskbb/user/forms.py:88
 msgid "Female"
 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
 msgid "Location"
 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
 msgid "Website"
 msgstr ""
 
-#: flaskbb/management/forms.py:69 flaskbb/user/forms.py:96
+#: flaskbb/management/forms.py:81 flaskbb/user/forms.py:96
 msgid "Avatar"
 msgstr ""
 
-#: flaskbb/management/forms.py:72 flaskbb/user/forms.py:99
+#: flaskbb/management/forms.py:84 flaskbb/user/forms.py:99
 msgid "Forum Signature"
 msgstr ""
 
-#: flaskbb/management/forms.py:75 flaskbb/user/forms.py:102
+#: flaskbb/management/forms.py:87 flaskbb/user/forms.py:102
 msgid "Notes"
 msgstr ""
 
-#: flaskbb/management/forms.py:79
+#: flaskbb/management/forms.py:91
 msgid "Primary Group"
 msgstr ""
 
-#: flaskbb/management/forms.py:84
+#: flaskbb/management/forms.py:96
 msgid "Secondary Groups"
 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"
 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"
 msgstr ""
 
-#: flaskbb/management/forms.py:138
+#: flaskbb/management/forms.py:150
 msgid "A Group name is required."
 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"
 msgstr ""
 
-#: flaskbb/management/forms.py:144
+#: flaskbb/management/forms.py:156
 msgid "Is Admin Group?"
 msgstr ""
 
-#: flaskbb/management/forms.py:145
+#: flaskbb/management/forms.py:157
 msgid "With this option the group has access to the admin panel."
 msgstr ""
 
-#: flaskbb/management/forms.py:149
+#: flaskbb/management/forms.py:161
 msgid "Is Super Moderator Group?"
 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."
 msgstr ""
 
-#: flaskbb/management/forms.py:154
+#: flaskbb/management/forms.py:166
 msgid "Is Moderator Group?"
 msgstr ""
 
-#: flaskbb/management/forms.py:155
+#: 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:159
+#: flaskbb/management/forms.py:171
 msgid "Is Banned Group?"
 msgstr ""
 
-#: flaskbb/management/forms.py:160
+#: flaskbb/management/forms.py:172
 msgid "Only one Banned group is allowed."
 msgstr ""
 
-#: flaskbb/management/forms.py:163
+#: flaskbb/management/forms.py:175
 msgid "Is Guest Group?"
 msgstr ""
 
-#: flaskbb/management/forms.py:164
+#: flaskbb/management/forms.py:176
 msgid "Only one Guest group is allowed."
 msgstr ""
 
-#: flaskbb/management/forms.py:167
+#: flaskbb/management/forms.py:179
 msgid "Can edit posts"
 msgstr ""
 
-#: flaskbb/management/forms.py:168
+#: flaskbb/management/forms.py:180
 msgid "Check this if the users in this group can edit posts."
 msgstr ""
 
-#: flaskbb/management/forms.py:171
+#: flaskbb/management/forms.py:183
 msgid "Can delete posts"
 msgstr ""
 
-#: flaskbb/management/forms.py:172
+#: flaskbb/management/forms.py:184
 msgid "Check this is the users in this group can delete posts."
 msgstr ""
 
-#: flaskbb/management/forms.py:175
+#: flaskbb/management/forms.py:187
 msgid "Can delete topics"
 msgstr ""
 
-#: flaskbb/management/forms.py:176
+#: flaskbb/management/forms.py:188
 msgid "Check this is the users in this group can delete topics."
 msgstr ""
 
-#: flaskbb/management/forms.py:180
+#: flaskbb/management/forms.py:192
 msgid "Can create topics"
 msgstr ""
 
-#: flaskbb/management/forms.py:181
+#: flaskbb/management/forms.py:193
 msgid "Check this is the users in this group can create topics."
 msgstr ""
 
-#: flaskbb/management/forms.py:185
+#: flaskbb/management/forms.py:197
 msgid "Can post replies"
 msgstr ""
 
-#: flaskbb/management/forms.py:186
+#: flaskbb/management/forms.py:198
 msgid "Check this is the users in this group can post replies."
 msgstr ""
 
-#: flaskbb/management/forms.py:190
+#: flaskbb/management/forms.py:202
 msgid "Moderators can edit user profiles"
 msgstr ""
 
-#: flaskbb/management/forms.py:191
+#: flaskbb/management/forms.py:203
 msgid ""
 "Allow moderators to edit a another users profile including password and "
 "email changes."
 msgstr ""
 
-#: flaskbb/management/forms.py:196
+#: flaskbb/management/forms.py:208
 msgid "Moderators can ban users"
 msgstr ""
 
-#: flaskbb/management/forms.py:197
+#: flaskbb/management/forms.py:209
 msgid "Allow moderators to ban other users."
 msgstr ""
 
-#: flaskbb/management/forms.py:214
+#: flaskbb/management/forms.py:226
 msgid "This Group name is already taken."
 msgstr ""
 
-#: flaskbb/management/forms.py:228
+#: flaskbb/management/forms.py:240
 msgid "There is already a Banned group."
 msgstr ""
 
-#: flaskbb/management/forms.py:242
+#: flaskbb/management/forms.py:254
 msgid "There is already a Guest group."
 msgstr ""
 
-#: flaskbb/management/forms.py:262
+#: flaskbb/management/forms.py:274
 msgid "Forum Title"
 msgstr ""
 
-#: flaskbb/management/forms.py:263
+#: flaskbb/management/forms.py:275
 msgid "A Forum Title is required."
 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."
 msgstr ""
 
-#: flaskbb/management/forms.py:273 flaskbb/management/forms.py:391
+#: flaskbb/management/forms.py:285 flaskbb/management/forms.py:428
 msgid "Position"
 msgstr ""
 
-#: flaskbb/management/forms.py:275
+#: flaskbb/management/forms.py:287
 msgid "The Forum Position is required."
 msgstr ""
 
-#: flaskbb/management/forms.py:279
+#: flaskbb/management/forms.py:291
 msgid "Category"
 msgstr ""
 
-#: flaskbb/management/forms.py:283
+#: flaskbb/management/forms.py:295
 msgid "The category that contains this forum."
 msgstr ""
 
-#: flaskbb/management/forms.py:287
+#: flaskbb/management/forms.py:299
 msgid "External Link"
 msgstr ""
 
-#: flaskbb/management/forms.py:289
+#: flaskbb/management/forms.py:301
 msgid "A link to a website i.e. 'http://flaskbb.org'."
 msgstr ""
 
-#: flaskbb/management/forms.py:293
+#: 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:294
+#: 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:299
+#: flaskbb/management/forms.py:311
 msgid "Show Moderators"
 msgstr ""
 
-#: flaskbb/management/forms.py:300
+#: flaskbb/management/forms.py:312
 msgid "Do you want show the moderators on the index page?"
 msgstr ""
 
-#: flaskbb/management/forms.py:304
+#: flaskbb/management/forms.py:316
 msgid "Locked?"
 msgstr ""
 
-#: flaskbb/management/forms.py:305
+#: flaskbb/management/forms.py:317
 msgid "Disable new posts and topics in this forum."
 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."
 msgstr ""
 
-#: flaskbb/management/forms.py:318
+#: flaskbb/management/forms.py:338
 msgid "You also need to specify some moderators."
 msgstr ""
 
-#: flaskbb/management/forms.py:339
+#: flaskbb/management/forms.py:359
 #, python-format
 msgid "%(user)s is not in a moderators group."
 msgstr ""
 
-#: flaskbb/management/forms.py:345
+#: flaskbb/management/forms.py:365
 #, python-format
 msgid "User %(moderator)s not found."
 msgstr ""
 
-#: flaskbb/management/forms.py:381
+#: flaskbb/management/forms.py:418
 msgid "Category Title"
 msgstr ""
 
-#: flaskbb/management/forms.py:382
+#: flaskbb/management/forms.py:419
 msgid "A Category Title is required."
 msgstr ""
 
-#: flaskbb/management/forms.py:393
+#: flaskbb/management/forms.py:430
 msgid "The Category Position is required."
 msgstr ""
 
@@ -727,23 +743,23 @@ msgstr ""
 msgid "Add Group"
 msgstr ""
 
-#: flaskbb/management/views.py:370
+#: flaskbb/management/views.py:368
 msgid "Forum successfully updated."
 msgstr ""
 
-#: flaskbb/management/views.py:381
+#: flaskbb/management/views.py:379
 msgid "Edit Forum"
 msgstr ""
 
-#: flaskbb/management/views.py:394
+#: flaskbb/management/views.py:392
 msgid "Forum successfully deleted."
 msgstr ""
 
-#: flaskbb/management/views.py:406
+#: flaskbb/management/views.py:404
 msgid "Forum successfully added."
 msgstr ""
 
-#: flaskbb/management/views.py:414
+#: 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
@@ -751,26 +767,26 @@ msgstr ""
 msgid "Add Forum"
 msgstr ""
 
-#: flaskbb/management/views.py:424
+#: flaskbb/management/views.py:423
 msgid "Category successfully added."
 msgstr ""
 
-#: flaskbb/management/views.py:428
+#: 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:440
+#: flaskbb/management/views.py:439
 msgid "Category successfully updated."
 msgstr ""
 
-#: flaskbb/management/views.py:444
+#: flaskbb/management/views.py:443
 msgid "Edit Category"
 msgstr ""
 
-#: flaskbb/management/views.py:457
+#: flaskbb/management/views.py:456
 msgid "Category with all associated forums deleted."
 msgstr ""
 
@@ -778,92 +794,162 @@ msgstr ""
 msgid "Plugin is enabled. Please reload your app."
 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 ""
 "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 ""
 
-#: flaskbb/management/views.py:489
+#: flaskbb/management/views.py:495
 msgid "Couldn't enable Plugin."
 msgstr ""
 
-#: flaskbb/management/views.py:500
+#: flaskbb/management/views.py:506
 #, python-format
 msgid "Plugin %(plugin)s not found."
 msgstr ""
 
-#: flaskbb/management/views.py:512
+#: flaskbb/management/views.py:518
 msgid "Plugin is disabled. Please reload your app."
 msgstr ""
 
-#: flaskbb/management/views.py:514
+#: 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 ""
 
-#: flaskbb/management/views.py:529
+#: flaskbb/management/views.py:536
 msgid "Plugin has been uninstalled."
 msgstr ""
 
-#: flaskbb/management/views.py:531
+#: flaskbb/management/views.py:538
 msgid "Cannot uninstall Plugin."
 msgstr ""
 
-#: flaskbb/management/views.py:544
+#: flaskbb/management/views.py:551
 msgid "Plugin has been installed."
 msgstr ""
 
-#: flaskbb/management/views.py:546
+#: flaskbb/management/views.py:553
 msgid "Cannot install Plugin."
 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/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"
 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/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"
 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/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"
 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"
 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"
 msgstr ""
 
-#: flaskbb/templates/layout.html:83
+#: flaskbb/templates/layout.html:82
 msgid "Private Messages"
 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"
 msgstr ""
 
-#: flaskbb/templates/macros.html:281
+#: flaskbb/templates/macros.html:313
 msgid "Pages"
 msgstr ""
 
@@ -934,6 +1020,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 ""
@@ -941,12 +1029,16 @@ msgstr ""
 #: flaskbb/templates/forum/category_layout.html:13
 #: flaskbb/templates/forum/forum.html:52
 #: 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/management/banned_users.html:42
 #: flaskbb/templates/management/overview.html:17
 #: flaskbb/templates/management/users.html:42
-#: flaskbb/templates/message/view_message.html:29
 #: flaskbb/templates/user/all_topics.html:28
 #: flaskbb/templates/user/profile.html:56
 msgid "Posts"
@@ -954,6 +1046,8 @@ 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"
@@ -961,7 +1055,10 @@ msgstr ""
 
 #: flaskbb/templates/forum/category_layout.html:80
 #: 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:70
 #: flaskbb/templates/management/plugins.html:30
@@ -971,36 +1068,36 @@ 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: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"
 msgstr ""
 
-#: flaskbb/templates/forum/forum.html:27
+#: flaskbb/templates/forum/forum.html:29
 msgid "Locked"
 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: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"
 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
-#: flaskbb/templates/forum/topictracker.html:82
-#: flaskbb/templates/user/all_topics.html:72
-msgid "No topics."
+msgid "No Topics."
 msgstr ""
 
 #: flaskbb/templates/forum/index.html:17
@@ -1040,12 +1137,14 @@ 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
@@ -1053,8 +1152,8 @@ msgid "Group"
 msgstr ""
 
 #: 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"
 msgstr ""
 
@@ -1064,7 +1163,7 @@ msgid "Online Users"
 msgstr ""
 
 #: flaskbb/templates/forum/report_post.html:17
-#: flaskbb/templates/forum/topic.html:116
+#: flaskbb/templates/forum/topic.html:121
 msgid "Report"
 msgstr ""
 
@@ -1072,86 +1171,111 @@ msgstr ""
 msgid "Close"
 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 ""
 
-#: 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 ""
 
-#: 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 ""
 
-#: 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 ""
 
-#: 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"
 msgstr ""
 
-#: flaskbb/templates/forum/topic.html:120
+#: flaskbb/templates/forum/topic.html:125
 #: 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/users.html:58
-#: flaskbb/templates/message/drafts.html:21
 msgid "Edit"
 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"
 msgstr ""
 
-#: flaskbb/templates/forum/topic.html:133
+#: flaskbb/templates/forum/topic.html:144
 msgid "Quote"
 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"
 msgstr ""
 
-#: flaskbb/templates/forum/topic_controls.html:15
+#: flaskbb/templates/forum/topic_controls.html:45
 msgid "Lock Topic"
 msgstr ""
 
-#: flaskbb/templates/forum/topic_controls.html:19
+#: flaskbb/templates/forum/topic_controls.html:52
 msgid "Unlock Topic"
 msgstr ""
 
-#: flaskbb/templates/forum/topic_controls.html:24
+#: flaskbb/templates/forum/topic_controls.html:61
 msgid "Highlight Topic"
 msgstr ""
 
-#: flaskbb/templates/forum/topic_controls.html:28
+#: flaskbb/templates/forum/topic_controls.html:68
 msgid "Trivialize Topic"
 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
 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
@@ -1174,16 +1298,11 @@ msgstr ""
 msgid "Manage"
 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"
 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/forum_form.html:10
 #: flaskbb/templates/management/forums.html:9
@@ -1191,9 +1310,8 @@ msgstr ""
 msgid "Manage Forums"
 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 ""
 
 #: flaskbb/templates/management/group_form.html:10
@@ -1203,21 +1321,21 @@ msgid "Manage Groups"
 msgstr ""
 
 #: flaskbb/templates/management/groups.html:1
-#: flaskbb/templates/management/management_layout.html:13
+#: flaskbb/templates/management/management_layout.html:17
 msgid "Groups"
 msgstr ""
 
-#: flaskbb/templates/management/management_layout.html:7
+#: flaskbb/templates/management/management_layout.html:11
 #: flaskbb/templates/management/overview.html:1
 msgid "Overview"
 msgstr ""
 
-#: flaskbb/templates/management/management_layout.html:9
+#: flaskbb/templates/management/management_layout.html:13
 #: flaskbb/templates/management/reports.html:1
 msgid "Reports"
 msgstr ""
 
-#: flaskbb/templates/management/management_layout.html:15
+#: flaskbb/templates/management/management_layout.html:19
 #: flaskbb/templates/management/plugins.html:1
 msgid "Plugins"
 msgstr ""
@@ -1254,19 +1372,19 @@ msgstr ""
 msgid "Version"
 msgstr ""
 
-#: flaskbb/templates/management/plugins.html:34
+#: flaskbb/templates/management/plugins.html:36
 msgid "Enable"
 msgstr ""
 
-#: flaskbb/templates/management/plugins.html:36
+#: flaskbb/templates/management/plugins.html:41
 msgid "Disable"
 msgstr ""
 
-#: flaskbb/templates/management/plugins.html:42
+#: flaskbb/templates/management/plugins.html:50
 msgid "Install"
 msgstr ""
 
-#: flaskbb/templates/management/plugins.html:45
+#: flaskbb/templates/management/plugins.html:56
 msgid "Uninstall"
 msgstr ""
 
@@ -1308,86 +1426,42 @@ msgstr ""
 msgid "Unread Reports"
 msgstr ""
 
-#: flaskbb/templates/management/unread_reports.html:31
+#: flaskbb/templates/management/unread_reports.html:34
 msgid "Mark all as Read"
 msgstr ""
 
-#: flaskbb/templates/management/unread_reports.html:49
+#: flaskbb/templates/management/unread_reports.html:57
 msgid "No unread reports."
 msgstr ""
 
-#: flaskbb/templates/management/users.html:62
+#: flaskbb/templates/management/users.html:64
 msgid "Ban"
 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 ""
 
-#: 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 ""
 
-#: 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 ""
 
 #: flaskbb/templates/message/inbox.html:1
 #: 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"
 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
 msgid "Private Message"
 msgstr ""
 
-#: flaskbb/templates/message/message_layout.html:15
-msgid "New PM"
-msgstr ""
-
 #: flaskbb/templates/message/message_layout.html:17
 msgid "Sent"
 msgstr ""
@@ -1401,18 +1475,6 @@ msgstr ""
 msgid "Sent Messages"
 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/profile.html:29
 msgid "All Posts"
@@ -1494,127 +1556,59 @@ msgstr ""
 msgid "Account Settings"
 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"
 msgstr ""
 
-#: flaskbb/user/forms.py:34
+#: flaskbb/user/forms.py:30
 msgid "Theme"
 msgstr ""
 
-#: flaskbb/user/forms.py:40
+#: flaskbb/user/forms.py:36
 msgid "Old E-Mail Address"
 msgstr ""
 
-#: flaskbb/user/forms.py:44
+#: flaskbb/user/forms.py:40
 msgid "New E-Mail Address"
 msgstr ""
 
-#: flaskbb/user/forms.py:46
+#: flaskbb/user/forms.py:42
 msgid "E-Mails must match."
 msgstr ""
 
-#: flaskbb/user/forms.py:49
+#: flaskbb/user/forms.py:45
 msgid "Confirm E-Mail Address"
 msgstr ""
 
-#: flaskbb/user/forms.py:68
+#: flaskbb/user/forms.py:64
 msgid "Old Password"
 msgstr ""
 
-#: flaskbb/user/forms.py:69
+#: flaskbb/user/forms.py:65
 msgid "Password required"
 msgstr ""
 
-#: flaskbb/user/forms.py:75
+#: flaskbb/user/forms.py:71
 msgid "Confirm New Password"
 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 ""
 
-#: flaskbb/user/views.py:69
+#: flaskbb/user/views.py:66
 msgid "Settings updated."
 msgstr ""
 
-#: flaskbb/user/views.py:85
+#: flaskbb/user/views.py:82
 msgid "Password updated."
 msgstr ""
 
-#: flaskbb/user/views.py:97
+#: flaskbb/user/views.py:94
 msgid "E-Mail Address updated."
 msgstr ""
 
-#: flaskbb/user/views.py:110
+#: flaskbb/user/views.py:107
 msgid "Details updated."
 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)
 
+    @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):
     __tablename__ = "users"
@@ -129,6 +139,11 @@ class User(db.Model, UserMixin, CRUDMixin):
         return self.get_permissions()
 
     @property
+    def groups(self):
+        """Returns user groups"""
+        return self.get_groups()
+
+    @property
     def days_registered(self):
         """Returns the amount of days the user is registered."""
         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
 
     @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):
         """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."""
 
         cache.delete_memoized(self.get_permissions, self)
+        cache.delete_memoized(self.get_groups, self)
 
     def ban(self):
         """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 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_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():
     """Returns a dictionary containing all emojis with their
@@ -19,7 +23,7 @@ def collect_emojis():
     dictionary.
     """
     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
     if not os.path.exists(full_path):
         return emojis

+ 6 - 4
flaskbb/utils/populate.py

@@ -27,8 +27,9 @@ def delete_settings_from_fixture(fixture):
 
         for settings in settingsgroup[1]["settings"]:
             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()
 
@@ -62,8 +63,9 @@ def create_settings_from_fixture(fixture):
 
                 settingsgroup=group.key
             )
-            setting.save()
-            created_settings[group].append(setting)
+            if setting:
+                setting.save()
+                created_settings[group].append(setting)
 
     return created_settings
 

+ 31 - 0
flaskbb/utils/widgets.py

@@ -8,6 +8,7 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
+import simplejson as json
 from datetime import datetime
 from wtforms.widgets.core import Select, HTMLString, html_params
 
@@ -92,3 +93,33 @@ class SelectBirthdayWidget(object):
             html.append(' ')
 
         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
 def populate(dropdb=False, createdb=False):
     """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...")
         db.drop_all()
 
-    if not createdb:
+    if createdb:
         print("Creating database...")
         upgrade()
 
@@ -303,7 +302,7 @@ def download_emoji():
             f = open(full_path, 'wb')
             f.write(requests.get(image["download_url"]).content)
             f.close()
-            if count == cached_count+50:
+            if count == cached_count + 50:
                 cached_count = count
                 print("{} out of {} Emojis downloaded...".format(
                       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',
     sa.Column('key', sa.String(length=255), 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('description', sa.Text(), 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
-def forum(category, default_settings):
+def forum(category, default_settings, default_groups):
     """A single forum in a category."""
     forum = Forum(title="Test Forum", category_id=category.id)
+    forum.groups  = default_groups
     forum.save()
     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):
     """Test the save forum method"""
     forum = Forum(title="Test Forum", category_id=category.id)
+    forum.moderators = [moderator_user]
     forum.save()
 
     assert forum.title == "Test Forum"
-
-    # Test with adding a moderator
-    forum.save([moderator_user])
-
     assert forum.moderators == [moderator_user]