diff --git a/core/lib/Drupal/Component/Gettext/PoStreamReader.php b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
index 24e3936..02cd902 100644
--- a/core/lib/Drupal/Component/Gettext/PoStreamReader.php
+++ b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
@@ -165,7 +165,7 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
    */
   public function open() {
     if (!empty($this->_uri)) {
-      $this->_fd = fopen($this->_uri, 'rb');
+      $this->_fd = fopen(drupal_realpath($this->_uri), 'rb');
       $this->_size = ftell($this->_fd);
       $this->readHeader();
     }
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
index a19351d..fc09cab 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
@@ -35,8 +35,14 @@ class LocaleFileImportStatus extends WebTestBase {
     $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages'));
     $this->drupalLogin($admin_user);
 
-    // Set the translation file directory.
-    variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests');
+    // Set the translation file directory to something writable.
+    $destination = conf_path() . '/files/translations';;
+    file_prepare_directory($dir, FILE_CREATE_DIRECTORY);
+    variable_set('locale_translate_file_directory', $destination);
+
+    // Copy test po files to the same directory.
+    file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.de.po', $destination, FILE_EXISTS_ERROR);
+    file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.xx.po', $destination, FILE_EXISTS_ERROR);
   }
 
   /**
@@ -79,8 +85,7 @@ class LocaleFileImportStatus extends WebTestBase {
    *   A file object of type stdClass.
    */
   function mockImportedPoFile($langcode, $timestamp_difference = 0) {
-    $dir = variable_get('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests');
-    $testfile_uri = $dir . '/test.' . $langcode . '.po';
+    $testfile_uri = 'translations://test.' . $langcode . '.po';
 
     $file = locale_translate_file_create($testfile_uri);
     $file->original_timestamp = $file->timestamp;
@@ -191,11 +196,10 @@ class LocaleFileImportStatus extends WebTestBase {
   function testDeleteLanguage() {
     $dir = conf_path() . '/files/translations';
     file_prepare_directory($dir, FILE_CREATE_DIRECTORY);
-    variable_set('locale_translate_file_directory', $dir);
     $langcode = 'de';
     $this->addLanguage($langcode);
-    $file_uri = $dir . '/po_' . $this->randomName() . '.' . $langcode . '.po';
-    file_put_contents($file_uri, $this->randomName());
+    $file_uri = 'translations://po_' . $this->randomName() . '.' . $langcode . '.po';
+    file_put_contents(drupal_realpath($file_uri), $this->randomName());
     $this->assertTrue(is_file($file_uri), 'Translation file is created.');
     language_delete($langcode);
     $this->assertTrue($file_uri);
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
index 589c555..7d52b45 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
@@ -36,8 +36,14 @@ class LocaleImportFunctionalTest extends WebTestBase {
 
   function setUp() {
     parent::setUp();
-    // Set the translation file directory.
-    variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale') . '/tests');
+    // Set the translation file directory to something writable.
+    $destination = conf_path() . '/files/translations';;
+    file_prepare_directory($dir, FILE_CREATE_DIRECTORY);
+    variable_set('locale_translate_file_directory', $destination);
+
+    // Copy test po files to the same directory.
+    file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.de.po', $destination, FILE_EXISTS_ERROR);
+    file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.xx.po', $destination, FILE_EXISTS_ERROR);
 
     $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
     $this->drupalLogin($this->admin_user);
diff --git a/core/modules/locale/lib/Drupal/locale/TranslationsStream.php b/core/modules/locale/lib/Drupal/locale/TranslationsStream.php
new file mode 100644
index 0000000..3028a32
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/TranslationsStream.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\TranslationStream.
+ */
+
+namespace Drupal\locale;
+
+use Drupal\Core\StreamWrapper\LocalStream;
+
+/**
+ * Defines a Drupal translations (translations://) stream wrapper class.
+ *
+ * Provides support for storing translation files.
+ */
+class TranslationsStream extends LocalStream {
+
+  /**
+   * Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
+   */
+  function getDirectoryPath() {
+    return variable_get('locale_translate_file_directory',
+      conf_path() . '/files/translations');
+  }
+
+  /**
+   * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl().
+   * @throws \LogicException PO files URL should not be public.
+   */
+  function getExternalUrl() {
+    throw new \LogicException('PO files URL should not be public.');
+  }
+}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 0581af4..3ee77a4 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -94,7 +94,8 @@ function locale_translate_import_form_submit($form, &$form_state) {
   $validators = array('file_validate_extensions' => array('po'));
 
   // Ensure we have the file uploaded.
-  if ($file = file_save_upload('file', $validators)) {
+  $destination = variable_get('locale_translate_file_directory', '');
+  if ($file = file_save_upload('file', $validators, 'translations://')) {
 
     // Add language, if not yet supported.
     $language = language_load($form_state['values']['langcode']);
@@ -112,6 +113,7 @@ function locale_translate_import_form_submit($form, &$form_state) {
       'overwrite_options' => $form_state['values']['overwrite_options'],
       'customized' => $form_state['values']['customized'] ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED,
     );
+
     $batch = locale_translate_batch_build(array($file->uri => $file), $options);
     batch_set($batch);
   }
@@ -348,7 +350,14 @@ function locale_translate_batch_import_files($options, $force = FALSE) {
  */
 function locale_translate_get_interface_translation_files($langcode = NULL) {
   $directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations');
-  return file_scan_directory($directory, '!' . (!empty($langcode) ? '\.' . preg_quote($langcode, '!') : '') . '\.po$!', array('recurse' => FALSE));
+  $return = file_scan_directory($directory, '!' . (!empty($langcode) ? '\.' . preg_quote($langcode, '!') : '') . '\.po$!', array('recurse' => FALSE));
+
+  foreach ($return as $filepath => $file) {
+    $file->uri = 'translations://' . $file->filename;
+    $return[$file->uri] = $file;
+    unset($return[$filepath]);
+  }
+  return $return;
 }
 
 /**
@@ -432,7 +441,8 @@ function locale_translate_batch_import($filepath, $options, &$context) {
   // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
   // we can extract the language code to use for the import from the end.
   if ($options['langcode'] || preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $matches)) {
-    $file = entity_create('file', array('filename' => drupal_basename($filepath), 'uri' => $filepath));
+    $basename = drupal_basename($filepath);
+    $file = entity_create('file', array('filename' => $basename, 'uri' => 'translations://'. $basename));
     // We need only the last match, but only if the langcode is not explicitly
     // specified in the $options array.
     if (!$options['langcode'] && is_array($matches)) {
@@ -441,7 +451,7 @@ function locale_translate_batch_import($filepath, $options, &$context) {
     try {
       if (empty($context['sandbox'])) {
         $context['sandbox']['parse_state'] = array(
-          'filesize' => filesize($file->uri),
+          'filesize' => filesize(drupal_realpath($file->uri)),
           'chunk_size' => 200,
           'seek' => 0,
         );
@@ -538,7 +548,7 @@ function locale_translate_file_create($filepath) {
   $file = new stdClass();
   $file->filename = drupal_basename($filepath);
   $file->uri = $filepath;
-  $file->timestamp = filemtime($file->uri);
+  $file->timestamp = filemtime(drupal_realpath($file->uri));
   return $file;
 }
 
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index aec8978..ee26f4d 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -13,6 +13,7 @@
 
 use Drupal\locale\LocaleLookup;
 use Drupal\locale\LocaleConfigSubscriber;
+use Drupal\locale\TranslationsStream;
 
 /**
  * Regular expression pattern used to localize JavaScript strings.
@@ -171,6 +172,21 @@ function locale_theme() {
 }
 
 /**
+ * Implements hook_stream_wrappers().
+ */
+function locale_stream_wrappers() {
+  $wrappers = array(
+    'translations' => array(
+      'name' => t('Translation files'),
+      'class' => 'Drupal\locale\TranslationsStream',
+      'description' => t('Translation files'),
+      'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
+    ),
+  );
+  return $wrappers;
+}
+
+/**
  * Implements hook_language_insert().
  */
 function locale_language_insert($language) {
