diff --git a/lib/gitlab/i18n/po_linter.rb b/lib/gitlab/i18n/po_linter.rb
index 0c7da1d19525f5760b84f2b529760853d4a1969e..b85fd991a94d933551426a24b71b67b008b62032 100644
--- a/lib/gitlab/i18n/po_linter.rb
+++ b/lib/gitlab/i18n/po_linter.rb
@@ -75,10 +75,18 @@ def validate_entry(entry)
         validate_unescaped_chars(errors, entry)
         validate_html(errors, entry)
         validate_translation(errors, entry)
+        validate_namespace(errors, entry)
 
         errors
       end
 
+      def validate_namespace(errors, entry)
+        if entry.translations_contain_namespace?
+          errors << 'contains a namespace, remove it from the translation. For more information see ' \
+                    'https://docs.gitlab.com/ee/development/i18n/translation.html#namespaced-strings'
+        end
+      end
+
       def validate_unescaped_chars(errors, entry)
         if entry.msgid_contains_unescaped_chars?
           errors << 'contains unescaped `%`, escape it using `%%`'
diff --git a/lib/gitlab/i18n/translation_entry.rb b/lib/gitlab/i18n/translation_entry.rb
index 6f4451d42eeaf5e2b738e9a257ad70b1a74b954d..22ec6e8dbe80a0c0818789f5994f4563bb330dd2 100644
--- a/lib/gitlab/i18n/translation_entry.rb
+++ b/lib/gitlab/i18n/translation_entry.rb
@@ -5,6 +5,7 @@ module I18n
     class TranslationEntry
       PERCENT_REGEX = /(?:^|[^%])%(?!{\w*}|[a-z%])/
       ANGLE_BRACKET_REGEX = /[<>]/
+      NAMESPACE_REGEX = /^((?u)\w+|\s)*\|/
 
       attr_reader :nplurals, :entry_data
 
@@ -68,6 +69,14 @@ def translations_have_multiple_lines?
         translation_entries.any?(Array)
       end
 
+      def translations_contain_namespace?
+        all_translations.any? { |translation| contains_namespace?(translation) }
+      end
+
+      def contains_namespace?(string)
+        string =~ NAMESPACE_REGEX
+      end
+
       def msgid_contains_unescaped_chars?
         contains_unescaped_chars?(msgid)
       end
diff --git a/spec/fixtures/namespaces.po b/spec/fixtures/namespaces.po
new file mode 100644
index 0000000000000000000000000000000000000000..962bb4f0752c3aa34880c3a7ea79579323456b1b
--- /dev/null
+++ b/spec/fixtures/namespaces.po
@@ -0,0 +1,25 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the gitlab package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Language-Team: Spanish\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+
+msgid "404|Not found"
+msgstr "404|No encontrado"
+
+msgid "CommitHistory|1 commit"
+msgid_plural "CommitHistory|%d commits"
+msgstr[0] "CommitHistory|1 cambio"
+msgstr[1] "CommitHistory|%d cambios"
diff --git a/spec/fixtures/valid.po b/spec/fixtures/valid.po
index 3a833e0f65661bc05a5a29b6a63702a71046da16..2b4a914be38e7cbca0e6455a20805d4db559f3cc 100644
--- a/spec/fixtures/valid.po
+++ b/spec/fixtures/valid.po
@@ -1131,3 +1131,9 @@ msgid "CycleAnalytics|%{stageName}"
 msgid_plural "CycleAnalytics|%d stages selected"
 msgstr[0] "%{stageName}"
 msgstr[1] "%d stages selected"
+
+msgid "Example: (feature|hotfix)\\/.*"
+msgstr "Ejemplo: (feature|hotfix)\\/.*"
+
+msgid "Example: (jar|exe)$"
+msgstr "Ejemplo: (jar|exe)$"
diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb
index 3f0eb66886eac9d9009f9c2696534491e5555e12..ed29c7c4e37aeba5d4625c29870deb1d63b2f393 100644
--- a/spec/lib/gitlab/i18n/po_linter_spec.rb
+++ b/spec/lib/gitlab/i18n/po_linter_spec.rb
@@ -47,6 +47,24 @@ def fake_translation(msgid:, translation:, plural_id: nil, plurals: [])
       end
     end
 
+    context 'for a translations with namespaces' do
+      let(:po_path) { 'spec/fixtures/namespaces.po' }
+
+      it 'has an error for translation with namespace' do
+        message_id = "404|Not found"
+        expected_message = "contains a namespace, remove it from the translation. For more information see https://docs.gitlab.com/ee/development/i18n/translation.html#namespaced-strings"
+
+        expect(errors[message_id]).to include(expected_message)
+      end
+
+      it 'has an error for plural translation with namespace' do
+        message_id = "CommitHistory|1 commit"
+        expected_message = "contains a namespace, remove it from the translation. For more information see https://docs.gitlab.com/ee/development/i18n/translation.html#namespaced-strings"
+
+        expect(errors[message_id]).to include(expected_message)
+      end
+    end
+
     context 'for a translations with newlines' do
       let(:po_path) { 'spec/fixtures/newlines.po' }
 
@@ -214,6 +232,7 @@ def fake_translation(msgid:, translation:, plural_id: nil, plurals: [])
       expect(linter).to receive(:validate_number_of_plurals).with([], fake_entry)
       expect(linter).to receive(:validate_unescaped_chars).with([], fake_entry)
       expect(linter).to receive(:validate_translation).with([], fake_entry)
+      expect(linter).to receive(:validate_namespace).with([], fake_entry)
       expect(linter).to receive(:validate_html).with([], fake_entry)
 
       linter.validate_entry(fake_entry)
diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb
index 3531314cf9cd40053222d2dabca256886a17bade..ddb72335ef9162deda1a826f1b36b376115c3f20 100644
--- a/spec/lib/gitlab/i18n/translation_entry_spec.rb
+++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb
@@ -133,6 +133,140 @@
     end
   end
 
+  describe '#translations_contain_namespace' do
+    it 'is true when the msgstr contains namespace' do
+      data = {
+        msgid: '404|Not found',
+        msgstr: '404|No encontrado'
+      }
+      entry = described_class.new(entry_data: data, nplurals: 2)
+
+      expect(entry.translations_contain_namespace?).to be_truthy
+    end
+
+    it 'is true when one plural translation contains namespace' do
+      data = {
+        msgid: 'Test|hello world',
+        msgid_plural: 'Test|hello worlds',
+        "msgstr[0]" => 'Test|hello world',
+        "msgstr[1]" => 'hello worlds'
+      }
+      entry = described_class.new(entry_data: data, nplurals: 2)
+
+      expect(entry.translations_contain_namespace?).to be_truthy
+    end
+
+    it 'is true when all plural translation contains namespace' do
+      data = {
+        msgid: 'Test|hello world',
+        msgid_plural: 'Test|hello worlds',
+        "msgstr[0]" => 'Test|hello world',
+        "msgstr[1]" => 'Test|hello worlds'
+      }
+      entry = described_class.new(entry_data: data, nplurals: 2)
+
+      expect(entry.translations_contain_namespace?).to be_truthy
+    end
+
+    it "is false when the msgstr doesn't contain namespace" do
+      data = {
+        msgid: '404|Not found',
+        msgstr: 'No encontrado'
+      }
+      entry = described_class.new(entry_data: data, nplurals: 2)
+
+      expect(entry.translations_contain_namespace?).to be_falsy
+    end
+
+    it "is false when the msgstr contains namespace but source is from list of false positives" do
+      data = {
+        msgid: 'Example: (jar|exe)$',
+        msgstr: 'Ejemplo: (jar|exe)$'
+      }
+      entry = described_class.new(entry_data: data, nplurals: 2)
+
+      data_two = {
+        msgid: 'Example: (feature|hotfix)\\\\/.*',
+        msgstr: 'Ejemplo: (feature|hotfix)\\\\/.*'
+      }
+      entry_two = described_class.new(entry_data: data_two, nplurals: 2)
+
+      expect(entry_two.translations_contain_namespace?).to be_falsy
+      expect(entry.translations_contain_namespace?).to be_falsy
+    end
+
+    it 'is false when no plural translation contains namespace' do
+      data = {
+        msgid: 'Test|hello world',
+        msgid_plural: 'Test|hello worlds',
+        "msgstr[0]" => 'hello world',
+        "msgstr[1]" => 'hello worlds'
+      }
+      entry = described_class.new(entry_data: data, nplurals: 2)
+
+      expect(entry.translations_contain_namespace?).to be_falsy
+    end
+  end
+
+  describe '#contains_namespace' do
+    let(:data) { { msgid: '' } }
+    let(:entry) { described_class.new(entry_data: data, nplurals: 2) }
+
+    it 'is true when the string contains a namespace' do
+      string = '404|Not found'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is true when the string contains a namespace with leading spaces' do
+      string = ' 404|Not found'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is true when the string contains a namespace with trailing spaces' do
+      string = '404 |Not found'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is true when the string contains a namespace with leading and trailing spaces' do
+      string = ' 404 |Not found'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is true when the string contains a namespace with a space' do
+      string = '40 4|Not found'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is true when the string contains a namespace with unicode characters' do
+      string = 'ZobrazeníČasu|System'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is true when the string contains a namespace with unicode characters and a space' do
+      string = 'Zobrazení Času|System'
+
+      expect(entry.contains_namespace?(string)).to be_truthy
+    end
+
+    it 'is false when the string contains a pipe, but not a namespace' do
+      string = 'Example: (jar|exe)$'
+
+      expect(entry.contains_namespace?(string)).to be_falsy
+    end
+
+    it 'is false when the string does not contain a namespace' do
+      string = 'Not found'
+
+      expect(entry.contains_namespace?(string)).to be_falsy
+    end
+  end
+
   describe '#contains_unescaped_chars' do
     let(:data) { { msgid: '' } }
     let(:entry) { described_class.new(entry_data: data, nplurals: 2) }