diff --git a/core/Translate/Writer.php b/core/Translate/Writer.php
index a1b295fa070ef14f3e5f3c2af74a5d543a1ea14a..66e34449ec00ff80da9a0bcffddfb2eb4aed989e 100644
--- a/core/Translate/Writer.php
+++ b/core/Translate/Writer.php
@@ -99,10 +99,16 @@ class Writer
 
     /**
      * @param string $language  ISO 639-1 alpha-2 language code
+     *
+     * @throws \Exception
      */
     public function setLanguage($language)
     {
-        $this->_language = $language;
+        if (!preg_match('/^([a-z]{2,3}(-[a-z]{2,3})?)$/i', $language)) {
+            throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($language)));
+        }
+
+        $this->_language = strtolower($language);
     }
 
     /**
@@ -185,21 +191,8 @@ class Writer
     {
         if (empty($lang)) $lang = $this->getLanguage();
 
-        if (!Common::isValidFilename($lang) ||
-            ($base !== 'lang' && $base !== 'tmp')
-        ) {
-            throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($lang)));
-        }
-
         if (!empty($this->_pluginName)) {
 
-            $installedPlugins = PluginsManager::getInstance()->readPluginsDirectory();
-
-            if (!in_array($this->_pluginName, $installedPlugins)) {
-
-                throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($lang)));
-            }
-
             if ($base == 'tmp') {
                 return sprintf('%s/tmp/plugins/%s/lang/%s.json', PIWIK_INCLUDE_PATH, $this->_pluginName, $lang);
             } else {
diff --git a/tests/PHPUnit/Core/Translate/Filter/ByBaseTranslationsTest.php b/tests/PHPUnit/Core/Translate/Filter/ByBaseTranslationsTest.php
index 6596059ba7aa89dbb66871f1d9b94817adc41079..f4ec1aaec2dda34ea5a47e07fa1623fa74091e42 100644
--- a/tests/PHPUnit/Core/Translate/Filter/ByBaseTranslationsTest.php
+++ b/tests/PHPUnit/Core/Translate/Filter/ByBaseTranslationsTest.php
@@ -30,7 +30,7 @@ class ByBaseTranslationsTest extends PHPUnit_Framework_TestCase
                     'test' => array()
                 ),
             ),
-            // empty values/plugins are removed
+            // not existing values/plugins are removed
             array(
                 array(
                     'test' => array(
@@ -55,7 +55,7 @@ class ByBaseTranslationsTest extends PHPUnit_Framework_TestCase
                     )
                 ),
             ),
-            // no change if no empty value
+            // no change if all exist
             array(
                 array(
                     'test' => array(
@@ -74,7 +74,7 @@ class ByBaseTranslationsTest extends PHPUnit_Framework_TestCase
                 ),
                 array()
             ),
-            // empty values are removed, others stay
+            // unavailable removed, others stay
             array(
                 array(
                     'empty' => array(
@@ -107,6 +107,38 @@ class ByBaseTranslationsTest extends PHPUnit_Framework_TestCase
                     )
                 )
             ),
+            array(
+                array(
+                    'empty' => array(
+                        'test' => 'test'
+                    ),
+                    'test' => array(
+                        'test' => 'test',
+                        'empty' => '     ',
+                    )
+                ),
+                array(
+                    'empty' => array(
+                        'bla' => 'test'
+                    ),
+                    'test' => array(
+                        'test' => 'test',
+                    )
+                ),
+                array(
+                    'test' => array(
+                        'test' => 'test'
+                    )
+                ),
+                array(
+                    'empty' => array(
+                        'test' => 'test'
+                    ),
+                    'test' => array(
+                        'empty' => '     ',
+                    )
+                )
+            ),
         );
     }
 
diff --git a/tests/PHPUnit/Core/Translate/Validate/CoreTranslationsTest.php b/tests/PHPUnit/Core/Translate/Validate/CoreTranslationsTest.php
index f9412d2b174fd6ef0892b191bc407cc52267f2e2..4c325db96c7c7a53d358718e658aabc82133c9eb 100644
--- a/tests/PHPUnit/Core/Translate/Validate/CoreTranslationsTest.php
+++ b/tests/PHPUnit/Core/Translate/Validate/CoreTranslationsTest.php
@@ -7,8 +7,14 @@ use Piwik\Translate\Validate\CoreTranslations;
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
-class BaseTranslationsTest extends PHPUnit_Framework_TestCase
+class CoreTranslationsTest extends PHPUnit_Framework_TestCase
 {
+    public function setUp()
+    {
+        include PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
+        include PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
+    }
+
     public function getFilterTestDataValid()
     {
         return array(
@@ -127,9 +133,6 @@ class BaseTranslationsTest extends PHPUnit_Framework_TestCase
      */
     public function testFilterInvalid($translations, $msg)
     {
-        include PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php';
-        include PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php';
-
         $filter = new CoreTranslations();
         $result = $filter->isValid($translations);
         $this->assertFalse($result);
diff --git a/tests/PHPUnit/Core/Translate/WriterTest.php b/tests/PHPUnit/Core/Translate/WriterTest.php
index d45fa635afcab5a3bac4b9fbc9084b62109267ff..9606b66151c071314bd71025a87af1338e7c7ae1 100644
--- a/tests/PHPUnit/Core/Translate/WriterTest.php
+++ b/tests/PHPUnit/Core/Translate/WriterTest.php
@@ -1,6 +1,7 @@
 <?php
 use Piwik\Common;
 use Piwik\Translate\Writer;
+use Piwik\Translate\Validate\CoreTranslations;
 
 /**
  * Piwik - Open source web analytics
@@ -50,36 +51,166 @@ class WriterTest extends PHPUnit_Framework_TestCase
     /**
      * @group Core
      * @group Translate
+     * @ expectedException Exception
+     * @dataProvider getExceptionalTranslations
+     */
+    public function testSetTranslationsThrowsException($translations, $error)
+    {
+        $writer = new Writer('de');
+        try {
+            $writer->setTranslations($translations);
+            $this->fail('Exception not thrown');
+        } catch (Exception $e) {
+            $this->assertEquals($error, $e->getMessage());
+        }
+    }
+
+    public function getExceptionalTranslations()
+    {
+        $translations = json_decode(file_get_contents(PIWIK_INCLUDE_PATH.'/lang/de.json'), true);
+        return array(
+            array(array('test' => array('test' => 'test')), CoreTranslations::__ERRORSTATE_MINIMUMTRANSLATIONS__),
+            array(array('General' => array('Locale' => '')) + $translations, CoreTranslations::__ERRORSTATE_LOCALEREQUIRED__),
+            array(array('General' => array('Locale' => 'de_DE.UTF-8')) + $translations, CoreTranslations::__ERRORSTATE_TRANSLATORINFOREQUIRED__),
+            array(array('General' => array('Locale' => 'de_DE.UTF-8',
+                                           'TranslatorName' => 'name')) + $translations, CoreTranslations::__ERRORSTATE_TRANSLATOREMAILREQUIRED__),
+            array(array('General' => array('Locale' => 'de_DE.UTF-8',
+                                           'TranslatorName' => 'name',
+                                           'TranslatorEmail' => 'name@domain.com',
+                                           'LayoutDirection' => 'fail')) + $translations, CoreTranslations::__ERRORSTATE_LAYOUTDIRECTIONINVALID__),
+            array(array('General' => array('Locale' => 'invalid',
+                                           'TranslatorName' => 'name',
+                                           'TranslatorEmail' => 'name@domain.com')) + $translations, CoreTranslations::__ERRORSTATE_LOCALEINVALID__),
+            array(array('General' => array('Locale' => 'xx_DE.UTF-8',
+                                           'TranslatorName' => 'name',
+                                           'TranslatorEmail' => 'name@domain.com',)) + $translations, CoreTranslations::__ERRORSTATE_LOCALEINVALIDLANGUAGE__),
+            array(array('General' => array('Locale' => 'de_XX.UTF-8',
+                                           'TranslatorName' => 'name',
+                                           'TranslatorEmail' => 'name@domain.com',)) + $translations, CoreTranslations::__ERRORSTATE_LOCALEINVALIDCOUNTRY__),
+            array(array('General' => array('Locale' => '<script>')) + $translations, 'script tags restricted for language files'),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group Translate
+     * @group Translate_Write
      */
     public function testSaveTranslation()
     {
-        $translations = array(
-            'General' => array(
-                'Locale' => 'en_CA.UTF-8',
-                'Id'     => 'Id'
-            ),
-            'Goals'   => array(
-                'Goals' => 'Goals',
-            ),
-            'Plugin'  => array(
-                'Body' => "Message\nBody"
-            )
+        $translations = json_decode(file_get_contents(PIWIK_INCLUDE_PATH.'/lang/en.json'), true);
+
+        $translationsToWrite = array();
+        $translationsToWrite['General'] = $translations['General'];
+        $translationsToWrite['UserLanguage'] = $translations['UserLanguage'];
+        $translationsToWrite['UserCountry'] = $translations['UserCountry'];
+
+        $translationsToWrite['General']['Yes'] = 'string with %1$s';
+        $translationsToWrite['Plugin'] = array(
+            'Body' => "Message\nBody"
         );
 
-        $translationWriter = new Writer('en', '');
-        $translationWriter->setTranslations($translations);
+        $translationWriter = new Writer('fr');
+        $translationWriter->setTranslations($translationsToWrite);
 
         $rc = $translationWriter->saveTemporary();
-        $this->assertNotEquals(false, $rc);
+        $this->assertGreaterThan(50000, $rc);
 
-        $contents = file_get_contents(PIWIK_DOCUMENT_ROOT.'/tmp/en.json');
+        $this->assertCount(4, $translationWriter->getErrors());
+    }
 
-        $options = 0;
-        if (defined('JSON_UNESCAPED_UNICODE')) $options |= JSON_UNESCAPED_UNICODE;
-        if (defined('JSON_PRETTY_PRINT')) $options |= JSON_PRETTY_PRINT;
+    /**
+     * @group Core
+     * @group Translate
+     * @dataProvider getTranslationPathTestData
+     */
+    public function testGetTranslationsPath($language, $plugin, $path)
+    {
+        $writer = new Writer($language, $plugin);
+        $this->assertEquals($path, $writer->getTranslationPath());
+    }
 
-        $expected = json_encode(json_decode('{"General":{"Locale":"en_CA.UTF-8","Id":"Id"},"Goals":{"Goals":"Goals"}}', true), $options);
+    public function getTranslationPathTestData()
+    {
+        return array(
+            array('de', null, PIWIK_INCLUDE_PATH . '/lang/de.json'),
+            array('te', null, PIWIK_INCLUDE_PATH . '/lang/te.json'),
+            array('de', 'CoreHome', PIWIK_INCLUDE_PATH . '/plugins/CoreHome/lang/de.json'),
+            array('pt-br', 'Actions', PIWIK_INCLUDE_PATH . '/plugins/Actions/lang/pt-br.json'),
+        );
+    }
 
-        $this->assertEquals($expected, $contents);
+    /**
+     * @group Core
+     * @group Translate
+     * @dataProvider getTranslationPathTemporaryTestData
+     */
+    public function testGetTemporaryTranslationPath($language, $plugin, $path)
+    {
+        $writer = new Writer($language, $plugin);
+        $this->assertEquals($path, $writer->getTemporaryTranslationPath());
+    }
+
+    public function getTranslationPathTemporaryTestData()
+    {
+        return array(
+            array('de', null, PIWIK_INCLUDE_PATH . '/tmp/de.json'),
+            array('te', null, PIWIK_INCLUDE_PATH . '/tmp/te.json'),
+            array('de', 'CoreHome', PIWIK_INCLUDE_PATH . '/tmp/plugins/CoreHome/lang/de.json'),
+            array('pt-br', 'Actions', PIWIK_INCLUDE_PATH . '/tmp/plugins/Actions/lang/pt-br.json'),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group Translate
+     * @dataProvider getValidLanguages
+     */
+    public function testSetLanguageValid($language)
+    {
+        $writer = new Writer('en', null);
+        $writer->setLanguage($language);
+        $this->assertEquals(strtolower($language), $writer->getLanguage());
+    }
+
+    public function getValidLanguages()
+    {
+        return array(
+            array('de'),
+            array('te'),
+            array('pt-br'),
+            array('tzm'),
+            array('abc'),
+            array('de-de'),
+            array('DE'),
+            array('DE-DE'),
+            array('DE-de'),
+        );
+    }
+    /**
+     * @group Core
+     * @group Translate
+     * @expectedException Exception
+     * @dataProvider getInvalidLanguages
+     */
+    public function testSetLanguageInvalid($language)
+    {
+        $writer = new Writer('en', null);
+        $writer->setLanguage($language);
+    }
+
+    public function getInvalidLanguages()
+    {
+        return array(
+            array(''),
+            array('abcd'),
+            array('pt-brfr'),
+            array('00'),
+            array('a-b'),
+            array('x3'),
+            array('X4-fd'),
+            array('12-34'),
+            array('$§'),
+        );
     }
 }