diff --git a/gui/slick/js/core.js b/gui/slick/js/core.js
index 29696eccf023f27ce9360fc5f1ea00665e24f1cc..6b6ff0e088fa372bd05e4afa8260ee977f7c0903 100644
--- a/gui/slick/js/core.js
+++ b/gui/slick/js/core.js
@@ -50,10 +50,11 @@ function notifyModal(message){
     $('#site-notification-modal').modal();
 }
 
-function addSiteMessage(level, message){
+function addSiteMessage(level, tag, message){
     level = level || 'danger';
+    tag = tag || '';
     message = message || '';
-    $.post(srRoot + '/ui/set_site_message', {level: level, message: message}, function (siteMessages) {
+    $.post(srRoot + '/ui/set_site_message', {level: level, tag: tag, message: message}, function (siteMessages) {
         var messagesDiv = $('#site-messages');
         if (messagesDiv !== undefined) {
             messagesDiv.empty();
@@ -4047,14 +4048,15 @@ var UTIL = {
 };
 
 // Handle js-gettext + load javascript functions
-var gt = null;
+var gt = null, _n = null;
 $.getJSON(srRoot + '/ui/locale.json', function(data) {
     if (data !== undefined) {
         gt = new Gettext(data.messages); // jshint ignore:line
     } else {
         gt = new Gettext(); // jshint ignore:line
     }
-    _ = function(str) { return gt.gettext(str); }; // Create shortcut
+    _ = function(str) { return gt.gettext(str); }; // Shortcut for normal gettext
+    _n = function(str, pluralStr, num) { return gt.ngettext(str, pluralStr, num); }; // Shortcut for plural gettext
 
     if (navigator.userAgent.indexOf('PhantomJS') === -1) {
         $(document).ready(UTIL.init);
diff --git a/gui/slick/js/core.min.js b/gui/slick/js/core.min.js
index d73dc773a4115f40ceaf6ba4a5f5f1d980327888..a1b62aac5847a7b165f1d1f6c237d64befb4d339 100644
Binary files a/gui/slick/js/core.min.js and b/gui/slick/js/core.min.js differ
diff --git a/setup.cfg b/setup.cfg
index 45c6a7239c3b18456d3f6eb4e4903e2e25002318..1114881b3785e1306d5c8afcd610ca80a2a67faf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,9 +2,12 @@
 width = 80
 charset = utf-8
 output-file = locale/messages.pot
-keywords = _ gettext ngettext
+# keywords to look for in addition to the defaults.
+keywords = _n:1,2
 copyright-holder = SickRage
 msgid-bugs-address = miigotu@gmail.com
+# place comment block with TAG (or those preceding keyword lines) in output file. Separate multiple TAGs with commas(,)
+add-comments = TRANSLATORS:
 
 [compile_catalog]
 directory = locale
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index dfcc891a5bf2157f33b28a58bcff66180e1938e5..4eb549caf74208f3d91aa594d741426bdb4d1598 100644
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -68,7 +68,7 @@ import requests
 
 from tornado.locale import load_gettext_translations
 
-gettext.install('messages', unicode=1, codeset='UTF-8')
+gettext.install('messages', unicode=1, codeset='UTF-8', names=["ngettext"])
 
 # Some strings come from metadata or libraries or 3rd party sites,
 # So we need to pre-define them to get translations for them
@@ -870,9 +870,9 @@ def initialize(consoleLogging=True):  # pylint: disable=too-many-locals, too-man
         GUI_LANG = check_setting_str(CFG, 'GUI', 'language')
 
         if GUI_LANG:
-            gettext.translation('messages', LOCALE_DIR, languages=[GUI_LANG], codeset='UTF-8').install(unicode=1)
+            gettext.translation('messages', LOCALE_DIR, languages=[GUI_LANG], codeset='UTF-8').install(unicode=1, names=["ngettext"])
         else:
-            gettext.install('messages', LOCALE_DIR, unicode=1, codeset='UTF-8')
+            gettext.install('messages', LOCALE_DIR, unicode=1, codeset='UTF-8', names=["ngettext"])
 
         load_gettext_translations(LOCALE_DIR, 'messages')
 
diff --git a/sickbeard/common.py b/sickbeard/common.py
index 6bb556ddf8e5100015be31fb04400cba2c0a9fcc..2381f29bd614b063a6b32c26c6cc195d35b44c8d 100644
--- a/sickbeard/common.py
+++ b/sickbeard/common.py
@@ -43,7 +43,7 @@ from sickrage.helper.encoding import ek
 from sickrage.recompiled import tags
 from sickrage.tagger.episode import EpisodeTags
 
-gettext.install('messages', unicode=1, codeset='UTF-8')
+gettext.install('messages', unicode=1, codeset='UTF-8', names=["ngettext"])
 
 # If some provider has an issue with functionality of SR, other than user agents, it's best to come talk to us rather than block.
 # It is no different than us going to a provider if we have questions or issues. Be a team player here.
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index 2618b9aa52b1cb442cb779fc8e1c97443c6c51f2..eca4b4ad0b7c29af272bd16bb4329dc31d86b0a7 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -1839,24 +1839,15 @@ def recursive_listdir(path):
 MESSAGE_COUNTER = 0
 
 
-def add_site_message(message, level='danger'):
+def add_site_message(message, tag=None, level='danger'):
     with sickbeard.MESSAGES_LOCK:
-        to_add = dict(level=level, message=message)
+        to_add = dict(level=level, tag=tag, message=message)
 
-        basic_update_url = sickbeard.versionChecker.UpdateManager.get_update_url().split('?')[0]
-        for index, existing in six.iteritems(sickbeard.SITE_MESSAGES):
-            if basic_update_url in existing['message'] and basic_update_url in message:
-                sickbeard.SITE_MESSAGES[index] = to_add
-                return
-
-            if message.endswith('Please use \'master\' unless specifically asked') and \
-                    existing['message'].endswith('Please use \'master\' unless specifically asked'):
-                sickbeard.SITE_MESSAGES[index] = to_add
-                return
-
-            if message.startswith('No NZB/Torrent providers found or enabled for') and \
-                    existing['message'].startswith('No NZB/Torrent providers found or enabled for'):
-                sickbeard.SITE_MESSAGES[index] = to_add
+        if tag:  # prevent duplicate messages of the same type
+            # http://www.goodmami.org/2013/01/30/Getting-only-the-first-match-in-a-list-comprehension.html
+            existing = next((x for x, msg in six.iteritems(sickbeard.SITE_MESSAGES) if msg.get('tag') == tag), None)
+            if existing:
+                sickbeard.SITE_MESSAGES[existing] = to_add
                 return
 
         global MESSAGE_COUNTER
@@ -1864,22 +1855,14 @@ def add_site_message(message, level='danger'):
         sickbeard.SITE_MESSAGES[MESSAGE_COUNTER] = to_add
 
 
-def remove_site_message(begins='', ends='', contains='', key=None):
+def remove_site_message(key=None, tag=None):
     with sickbeard.MESSAGES_LOCK:
         if key is not None and int(key) in sickbeard.SITE_MESSAGES:
             del sickbeard.SITE_MESSAGES[int(key)]
-
-        for index, existing in six.iteritems(sickbeard.SITE_MESSAGES.copy()):
-            checks = []
-            if begins and isinstance(begins, six.string_types):
-                checks.append(existing['message'].startswith(begins))
-            if ends and isinstance(ends, six.string_types):
-                checks.append(existing['message'].endsswith(ends))
-            if contains and isinstance(ends, six.string_types):
-                checks.append(contains in existing['message'])
-
-            if all(checks):
-                del sickbeard.SITE_MESSAGES[index]
+        elif tag is not None:
+            found = [idx for idx, msg in six.iteritems(sickbeard.SITE_MESSAGES) if msg.get('tag') == tag]
+            for key in found:
+                del sickbeard.SITE_MESSAGES[key]
 
 
 def sortable_name(name):
diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py
index 9c2820e8e3c4d201ede33427b14459e346f3da35..5ea124139b2054adbbcf27204283ce4d76cd77e3 100644
--- a/sickbeard/providers/__init__.py
+++ b/sickbeard/providers/__init__.py
@@ -108,9 +108,11 @@ def check_enabled_providers():
                     break
 
         if not (daily_enabled and backlog_enabled):
-            formatted_msg = "No NZB/Torrent providers found or enabled for {0}.<br/>".format(
-                (("daily searches and backlog searches", "daily searches")[backlog_enabled], "backlog searches")[daily_enabled])
-            formatted_msg += "Please <a href=\"" + sickbeard.WEB_ROOT + "/config/providers/\">check your settings</a>."
-            sickbeard.helpers.add_site_message(formatted_msg, 'danger')
+            searches = ((_('daily searches and backlog searches'), _('daily searches'))[backlog_enabled],
+                        _('backlog searches'))[daily_enabled]
+            formatted_msg = _('No NZB/Torrent providers found or enabled for {searches}.<br/>'
+                              'Please <a href="{web_root}/config/providers/">check your settings</a>.')
+            sickbeard.helpers.add_site_message(formatted_msg.format(searches=searches, web_root=sickbeard.WEB_ROOT),
+                                               tag='no_providers_enabled', level='danger')
         else:
-            sickbeard.helpers.remove_site_message(begins="No NZB/Torrent providers found or enabled for")
+            sickbeard.helpers.remove_site_message(tag='no_providers_enabled')
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index f16791dca2a7ba227a1d1821a094e754e90c9035..c63f64c51f5b89515752ba599743bbb09a0c9581 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -371,11 +371,6 @@ class GitUpdateManager(UpdateManager):
     def get_num_commits_behind(self):
         return self._num_commits_behind
 
-    @staticmethod
-    def _git_error():
-        error_message = 'Unable to find your git executable - Shutdown SickRage and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.'
-        helpers.add_site_message(error_message, 'danger')
-
     def _find_working_git(self):
         test_cmd = 'version'
 
@@ -419,9 +414,10 @@ class GitUpdateManager(UpdateManager):
                     logger.log("Not using: " + cur_git, logger.DEBUG)
 
         # Still haven't found a working git
-        error_message = 'Unable to find your git executable - Shutdown SickRage and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.'
-        helpers.add_site_message(error_message, 'danger')
-
+        helpers.add_site_message(
+            _('Unable to find your git executable - Shutdown SickRage and EITHER set git_path in '
+              'your config.ini OR delete your .git folder and run from source to enable updates.'),
+            tag='unable_to_find_git', level='danger')
         return None
 
     @staticmethod
@@ -552,12 +548,14 @@ class GitUpdateManager(UpdateManager):
                 return
 
         logger.log("cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}, num_commits_ahead = {3}".format
-                   (self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind, self._num_commits_ahead), logger.DEBUG)
+                   (self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind, self._num_commits_ahead),
+                   logger.DEBUG)
 
     def set_newest_text(self):
         if self._num_commits_ahead:
-            logger.log("Local branch is ahead of " + self.branch + ". Automatic update not possible.", logger.WARNING)
-            newest_text = "Local branch is ahead of " + self.branch + ". Automatic update not possible."
+            newest_tag = 'local_branch_ahead'
+            newest_text = 'Local branch is ahead of {branch}. Automatic update not possible.'.format(branch=self.branch)
+            logger.log(newest_text, logger.WARNING)
 
         elif self._num_commits_behind > 0:
 
@@ -567,16 +565,18 @@ class GitUpdateManager(UpdateManager):
             else:
                 url = base_url + '/commits/'
 
-            newest_text = 'There is a <a href="' + url + '" onclick="window.open(this.href); return false;">newer version available</a> '
-            newest_text += " (you're " + str(self._num_commits_behind) + " commit"
-            if self._num_commits_behind > 1:
-                newest_text += 's'
-            newest_text += ' behind)' + "&mdash; <a href=\"" + self.get_update_url() + "\">Update Now</a>"
+            newest_tag = 'newer_version_available'
+            commits_behind = ngettext("(you're {num_commits} commit behind)", "(you're {num_commits} commits behind)",
+                                      self._num_commits_behind).format(num_commits=self._num_commits_behind)
+            newest_text = _('There is a <a href="{compare_url}" onclick="window.open(this.href); return false;">'
+                            'newer version available</a> {commits_behind} &mdash; '
+                            '<a href="{update_url}">Update Now</a>').format(
+                compare_url=url, commits_behind=commits_behind, update_url=self.get_update_url())
 
         else:
             return
 
-        helpers.add_site_message(newest_text, 'success')
+        helpers.add_site_message(newest_text, tag=newest_tag, level='success')
 
     def need_update(self):
 
@@ -750,15 +750,17 @@ class SourceUpdateManager(UpdateManager):
                 # when _cur_commit_hash doesn't match anything _num_commits_behind == 100
                 self._num_commits_behind += 1
 
-        logger.log("cur_commit = " + str(self._cur_commit_hash) + ", newest_commit = " + str(self._newest_commit_hash) +
-                   ", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG)
+        logger.log("cur_commit = {0}, newest_commit = {1}, num_commits_behind = {2}".format
+                   (self._cur_commit_hash, self._newest_commit_hash, self._num_commits_behind), logger.DEBUG)
 
     def set_newest_text(self):
         if not self._cur_commit_hash:
             logger.log("Unknown current version number, don't know if we should update or not", logger.DEBUG)
 
-            newest_text = "Unknown current version number: If you've never used the SickRage upgrade system before then current version is not set."
-            newest_text += "&mdash; <a href=\"" + self.get_update_url() + "\">Update Now</a>"
+            newest_tag = 'unknown_current_version'
+            newest_text = _('Unknown current version number: '
+                            'If you\'ve never used the SickRage upgrade system before then current version is not set. '
+                            '&mdash; <a href="{update_url}">Update Now</a>').format(update_url=self.get_update_url())
 
         elif self._num_commits_behind > 0:
             base_url = 'http://github.com/' + sickbeard.GIT_ORG + '/' + sickbeard.GIT_REPO
@@ -767,15 +769,17 @@ class SourceUpdateManager(UpdateManager):
             else:
                 url = base_url + '/commits/'
 
-            newest_text = 'There is a <a href="' + url + '" onclick="window.open(this.href); return false;">newer version available</a>'
-            newest_text += " (you're " + str(self._num_commits_behind) + " commit"
-            if self._num_commits_behind > 1:
-                newest_text += "s"
-            newest_text += " behind)" + "&mdash; <a href=\"" + self.get_update_url() + "\">Update Now</a>"
+            newest_tag = 'newer_version_available'
+            commits_behind = ngettext("(you're {num_commits} commit behind)", "(you're {num_commits} commits behind)",
+                                      self._num_commits_behind).format(num_commits=self._num_commits_behind)
+            newest_text = _('There is a <a href="{compare_url}" onclick="window.open(this.href); return false;">'
+                            'newer version available</a> (you\'re {commits_behind} behind) &mdash; '
+                            '<a href="{update_url}">Update Now</a>').format(
+                compare_url=url, commits_behind=commits_behind, update_url=self.get_update_url())
         else:
             return
 
-        helpers.add_site_message(newest_text, 'success')
+        helpers.add_site_message(newest_text, tag=newest_tag, level='success')
 
     def update(self):  # pylint: disable=too-many-statements
         """
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 407c8ed6656b33e2302a7e480899eb8edb764848..78ce939e11ecb998f0109f05c994c1fee6ec0434 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -632,14 +632,15 @@ class UI(WebRoot):
 
         return json.dumps(messages)
 
-    def set_site_message(self, message, level):
+    def set_site_message(self, message, tag, level):
         self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
         if message:
-            helpers.add_site_message(message, level)
+            helpers.add_site_message(message, tag=tag, level=level)
         else:
             if sickbeard.BRANCH and sickbeard.BRANCH != 'master' and not sickbeard.DEVELOPER and self.get_current_user():
-                message = _('You\'re using the {branch} branch. Please use \'master\' unless specifically asked').format(branch=sickbeard.BRANCH)
-                helpers.add_site_message(message, 'danger')
+                message = _('You\'re using the {branch} branch. '
+                            'Please use \'master\' unless specifically asked').format(branch=sickbeard.BRANCH)
+                helpers.add_site_message(message, tag='not_using_master_branch', level='danger')
 
         return sickbeard.SITE_MESSAGES
 
@@ -3949,10 +3950,10 @@ class ConfigGeneral(Config):
         if gui_language != sickbeard.GUI_LANG:
             if gui_language:
                 # Selected language
-                gettext.translation('messages', sickbeard.LOCALE_DIR, languages=[gui_language], codeset='UTF-8').install(unicode=1)
+                gettext.translation('messages', sickbeard.LOCALE_DIR, languages=[gui_language], codeset='UTF-8').install(unicode=1, names=["ngettext"])
             else:
                 # System default language
-                gettext.install('messages', sickbeard.LOCALE_DIR, unicode=1, codeset='UTF-8')
+                gettext.install('messages', sickbeard.LOCALE_DIR, unicode=1, codeset='UTF-8', names=["ngettext"])
 
             sickbeard.GUI_LANG = gui_language