diff --git a/app/code/Magento/Checkout/Test/Unit/Controller/Cart/IndexTest.php b/app/code/Magento/Checkout/Test/Unit/Controller/Cart/IndexTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..43b5fb8657ded5fc413dc2aaa4f5a627d5af6183
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Unit/Controller/Cart/IndexTest.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Checkout\Test\Unit\Controller\Cart;
+
+use Magento\Checkout\Controller\Cart\Index;
+
+/**
+ * Class IndexTest
+ */
+class IndexTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Index
+     */
+    protected $controller;
+
+    /**
+     * @var \Magento\Checkout\Model\Session | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $checkoutSession;
+
+    /**
+     * @var \Magento\Framework\App\Request\Http | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $request;
+
+    /**
+     * @var \Magento\Framework\App\Response\Http | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $response;
+
+    /**
+     * @var \Magento\Quote\Model\Quote | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $quote;
+
+    /**
+     * @var \Magento\Framework\Event\Manager | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $eventManager;
+
+    /**
+     * @var \Magento\Framework\Event\Manager | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $objectManagerMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $cart;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $scopeConfig;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $messageManager;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $resultPageFactory;
+
+    /**
+     * @return void
+     */
+    public function setUp()
+    {
+        $this->request = $this->getMock('Magento\Framework\App\Request\Http', [], [], '', false);
+        $this->response = $this->getMock('Magento\Framework\App\Response\Http', [], [], '', false);
+        $this->quote = $this->getMock('Magento\Quote\Model\Quote', [], [], '', false);
+        $this->eventManager = $this->getMock('Magento\Framework\Event\Manager', [], [], '', false);
+        $this->checkoutSession = $this->getMock('Magento\Checkout\Model\Session', [], [], '', false);
+
+        $this->objectManagerMock = $this->getMock('Magento\Framework\ObjectManager\ObjectManager', [], [], '', false);
+
+        $this->messageManager = $this->getMockBuilder('Magento\Framework\Message\ManagerInterface')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $context = $this->getMock('Magento\Framework\App\Action\Context', [], [], '', false);
+        $context->expects($this->once())
+            ->method('getObjectManager')
+            ->willReturn($this->objectManagerMock);
+        $context->expects($this->once())
+            ->method('getRequest')
+            ->willReturn($this->request);
+        $context->expects($this->once())
+            ->method('getResponse')
+            ->willReturn($this->response);
+        $context->expects($this->once())
+            ->method('getEventManager')
+            ->willReturn($this->eventManager);
+        $context->expects($this->once())
+            ->method('getMessageManager')
+            ->willReturn($this->messageManager);
+
+        $this->cart = $this->getMockBuilder('Magento\Checkout\Model\Cart')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->scopeConfig = $this->getMockBuilder('Magento\Framework\App\Config\ScopeConfigInterface')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->resultPageFactory = $this->getMockBuilder('Magento\Framework\View\Result\PageFactory')
+            ->disableOriginalConstructor()
+            ->setMethods(['create'])
+            ->getMock();
+
+        $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $this->controller = $objectManagerHelper->getObject(
+            'Magento\Checkout\Controller\Cart\Index',
+            [
+                'context' => $context,
+                'checkoutSession' => $this->checkoutSession,
+                'cart' => $this->cart,
+                'scopeConfig' => $this->scopeConfig,
+                'resultPageFactory' => $this->resultPageFactory
+            ]
+        );
+    }
+
+    /**
+     * @return void
+     */
+    public function testExecuteWithMessages()
+    {
+        $layout = $this->getMockBuilder('Magento\Framework\View\Layout')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $layout->expects($this->once())
+            ->method('initMessages');
+
+        $title = $this->getMockBuilder('Magento\Framework\View\Page\Title')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $title->expects($this->once())
+            ->method('set')
+            ->with('Shopping Cart');
+
+        $config = $this->getMockBuilder('Magento\Framework\View\Page\Config')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $config->expects($this->once())
+            ->method('getTitle')
+            ->willReturn($title);
+
+        $page = $this->getMockBuilder('Magento\Framework\View\Result\Page')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $page->expects($this->once())
+            ->method('getLayout')
+            ->willReturn($layout);
+        $page->expects($this->once())
+            ->method('getConfig')
+            ->willReturn($config);
+
+        $this->resultPageFactory->expects($this->once())
+            ->method('create')
+            ->willReturn($page);
+        $result = $this->controller->execute();
+        $this->assertInstanceOf('Magento\Framework\View\Result\Page', $result);
+    }
+}
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js b/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js
index 21b896ecf549ab69eebdc51606ab470afdfa7aa1..bdc635d337b1bd760aa82fa043ec8074b25236e1 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/opc-checkout-method.js
@@ -234,11 +234,9 @@ define([
                             if (msg) {
                                 if (Array.isArray(msg)) {
                                     msg = msg.reduce(function (str, chunk) {
-                                        str += '\n' + $.mage.__(chunk);
+                                        str += '\n' + chunk;
                                         return str;
                                     }, '');
-                                } else {
-                                    msg = $.mage.__(msg);
                                 }
 
                                 $(this.options.countrySelector).trigger('change');
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js b/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js
index d8ccb7eecc89cffc5a06899d03e43795c139740d..ac833eb7df2a1ab41c4cf827fa36ccc6b4458de8 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/opcheckout.js
@@ -198,9 +198,9 @@ define([
 
                                 $(this.options.countrySelector).trigger('change');
 
-                                alert($.mage.__(msg));
+                                alert(msg);
                             } else {
-                                alert($.mage.__(response.error));
+                                alert(response.error);
                             }
 
                             return;
diff --git a/app/code/Magento/Core/etc/di.xml b/app/code/Magento/Core/etc/di.xml
index 7c0da9d131ee661dffc057d94b4c413292dfd8c5..bb3e14b8fb1be9b9fd1bfd5109410ace0f0dad67 100644
--- a/app/code/Magento/Core/etc/di.xml
+++ b/app/code/Magento/Core/etc/di.xml
@@ -52,4 +52,25 @@
             <argument name="scopeType" xsi:type="const">Magento\Store\Model\ScopeInterface::SCOPE_STORE</argument>
         </arguments>
     </type>
+    <type name="Magento\Framework\View\Asset\PreProcessor\Pool">
+        <arguments>
+            <argument name="preProcessors" xsi:type="array">
+                <item name="less" xsi:type="array">
+                    <item name="css" xsi:type="array">
+                        <item name="less_css" xsi:type="string">Magento\Framework\Css\PreProcessor\Less</item>
+                        <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item>
+                    </item>
+                    <item name="less" xsi:type="array">
+                        <item name="magento_import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\MagentoImport</item>
+                        <item name="import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\Import</item>
+                    </item>
+                </item>
+                <item name="css" xsi:type="array">
+                    <item name="css" xsi:type="array">
+                        <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item>
+                    </item>
+                </item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php
index 7520b9d412947e3b731941d1b43f31547d8c3096..dfd4fcce3fd7b34aad809a63f249975896741d9a 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php
@@ -7,43 +7,76 @@ namespace Magento\Customer\Block\Adminhtml\Edit\Tab\View;
 
 use Magento\Customer\Api\AccountManagementInterface;
 use Magento\Customer\Controller\RegistryConstants;
-use Magento\Customer\Model\AccountManagement;
 use Magento\Customer\Model\Address\Mapper;
 use Magento\Framework\Exception\NoSuchEntityException;
 use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
 
 /**
- * Adminhtml customer view personal information sales block
+ * Adminhtml customer view personal information sales block.
+ *
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class PersonalInfo extends \Magento\Backend\Block\Template
 {
     /**
+     * Interval in minutes that shows how long customer will be marked 'Online'
+     * since his last activity. Used only if it's impossible to get such setting
+     * from configuration.
+     */
+    const DEFAULT_ONLINE_MINUTES_INTERVAL = 15;
+
+    /**
+     * Customer
+     *
      * @var \Magento\Customer\Api\Data\CustomerInterface
      */
     protected $customer;
 
     /**
+     * Customer log
+     *
+     * @var \Magento\Customer\Model\Log
+     */
+    protected $customerLog;
+
+    /**
+     * Customer logger
+     *
+     * @var \Magento\Customer\Model\Logger
+     */
+    protected $customerLogger;
+
+    /**
+     * Account management
+     *
      * @var AccountManagementInterface
      */
     protected $accountManagement;
 
     /**
+     * Customer group repository
+     *
      * @var \Magento\Customer\Api\GroupRepositoryInterface
      */
     protected $groupRepository;
 
     /**
+     * Customer data factory
+     *
      * @var \Magento\Customer\Api\Data\CustomerInterfaceFactory
      */
     protected $customerDataFactory;
 
     /**
+     * Address helper
+     *
      * @var \Magento\Customer\Helper\Address
      */
     protected $addressHelper;
 
     /**
+     * Date time
+     *
      * @var \Magento\Framework\Stdlib\DateTime
      */
     protected $dateTime;
@@ -56,11 +89,15 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     protected $coreRegistry;
 
     /**
+     * Address mapper
+     *
      * @var Mapper
      */
     protected $addressMapper;
 
     /**
+     * Data object helper
+     *
      * @var \Magento\Framework\Api\DataObjectHelper
      */
     protected $dataObjectHelper;
@@ -75,6 +112,7 @@ class PersonalInfo extends \Magento\Backend\Block\Template
      * @param \Magento\Framework\Registry $registry
      * @param Mapper $addressMapper
      * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+     * @param \Magento\Customer\Model\Logger $customerLogger
      * @param array $data
      * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
@@ -88,6 +126,7 @@ class PersonalInfo extends \Magento\Backend\Block\Template
         \Magento\Framework\Registry $registry,
         Mapper $addressMapper,
         \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
+        \Magento\Customer\Model\Logger $customerLogger,
         array $data = []
     ) {
         $this->coreRegistry = $registry;
@@ -98,10 +137,14 @@ class PersonalInfo extends \Magento\Backend\Block\Template
         $this->dateTime = $dateTime;
         $this->addressMapper = $addressMapper;
         $this->dataObjectHelper = $dataObjectHelper;
+        $this->customerLogger = $customerLogger;
+
         parent::__construct($context, $data);
     }
 
     /**
+     * Retrieve customer object
+     *
      * @return \Magento\Customer\Api\Data\CustomerInterface
      */
     public function getCustomer()
@@ -118,6 +161,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Retrieve customer id
+     *
      * @return string|null
      */
     public function getCustomerId()
@@ -125,6 +170,22 @@ class PersonalInfo extends \Magento\Backend\Block\Template
         return $this->coreRegistry->registry(RegistryConstants::CURRENT_CUSTOMER_ID);
     }
 
+    /**
+     * Retrieves customer log model
+     *
+     * @return \Magento\Customer\Model\Log
+     */
+    protected function getCustomerLog()
+    {
+        if (!$this->customerLog) {
+            $this->customerLog = $this->customerLogger->get(
+                $this->getCustomer()->getId()
+            );
+        }
+
+        return $this->customerLog;
+    }
+
     /**
      * Returns customer's created date in the assigned store
      *
@@ -143,6 +204,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Retrieve store default timezone from configuration
+     *
      * @return string
      */
     public function getStoreCreateDateTimezone()
@@ -169,6 +232,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Check if account is confirmed
+     *
      * @return \Magento\Framework\Phrase
      */
     public function getIsConfirmedStatus()
@@ -186,6 +251,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Retrieve store
+     *
      * @return null|string
      */
     public function getCreatedInStore()
@@ -196,6 +263,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Retrieve billing address html
+     *
      * @return \Magento\Framework\Phrase|string
      */
     public function getBillingAddressHtml()
@@ -218,6 +287,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Retrieve group name
+     *
      * @return string|null
      */
     public function getGroupName()
@@ -233,6 +304,8 @@ class PersonalInfo extends \Magento\Backend\Block\Template
     }
 
     /**
+     * Retrieve customer group by id
+     *
      * @param int $groupId
      * @return \Magento\Customer\Api\Data\GroupInterface|null
      */
@@ -245,4 +318,106 @@ class PersonalInfo extends \Magento\Backend\Block\Template
         }
         return $group;
     }
+
+    /**
+     * Returns timezone of the store to which customer assigned.
+     *
+     * @return string
+     */
+    public function getStoreLastLoginDateTimezone()
+    {
+        return $this->_scopeConfig->getValue(
+            $this->_localeDate->getDefaultTimezonePath(),
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+            $this->getCustomer()->getStoreId()
+        );
+    }
+
+    /**
+     * Get customer's current status.
+     *
+     * Customer considered 'Offline' in the next cases:
+     *
+     * - customer has never been logged in;
+     * - customer clicked 'Log Out' link\button;
+     * - predefined interval has passed since customer's last activity.
+     *
+     * In all other cases customer considered 'Online'.
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getCurrentStatus()
+    {
+        $lastLoginTime = $this->getCustomerLog()->getLastLoginAt();
+
+        // Customer has never been logged in.
+        if (!$lastLoginTime) {
+            return __('Offline');
+        }
+
+        $lastLogoutTime = $this->getCustomerLog()->getLastLogoutAt();
+
+        // Customer clicked 'Log Out' link\button.
+        if ($lastLogoutTime && strtotime($lastLogoutTime) > strtotime($lastLoginTime)) {
+            return __('Offline');
+        }
+
+        // Predefined interval has passed since customer's last activity.
+        $interval = $this->getOnlineMinutesInterval();
+        $currentTimestamp = (new \DateTime())->getTimestamp();
+        $lastVisitTime = $this->getCustomerLog()->getLastVisitAt();
+
+        if ($lastVisitTime && $currentTimestamp - strtotime($lastVisitTime) > $interval * 60) {
+            return __('Offline');
+        }
+
+        return __('Online');
+    }
+
+    /**
+     * Get customer last login date.
+     *
+     * @return \Magento\Framework\Phrase|string
+     */
+    public function getLastLoginDate()
+    {
+        $date = $this->getCustomerLog()->getLastLoginAt();
+
+        if ($date) {
+            return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true);
+        }
+
+        return __('Never');
+    }
+
+    /**
+     * Returns customer last login date in store's timezone.
+     *
+     * @return \Magento\Framework\Phrase|string
+     */
+    public function getStoreLastLoginDate()
+    {
+        $date = strtotime($this->getCustomerLog()->getLastLoginAt());
+
+        if ($date) {
+            $date = $this->_localeDate->scopeDate($this->getCustomer()->getStoreId(), $date, true);
+            return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true);
+        }
+
+        return __('Never');
+    }
+
+    /**
+     * Returns interval that shows how long customer will be considered 'Online'.
+     *
+     * @return int Interval in minutes
+     */
+    protected function getOnlineMinutesInterval()
+    {
+        $configValue = $this->_scopeConfig->getValue(
+            'customer/online_customers/online_minutes_interval',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+        return intval($configValue) > 0 ? intval($configValue) : self::DEFAULT_ONLINE_MINUTES_INTERVAL;
+    }
 }
diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php
index 7fb85ee1caf5ab41cce6309d8843ccacd43f070b..1b9c851c5c3ef0bbb8610ebfb5265bef1b1eeb60 100644
--- a/app/code/Magento/Customer/Model/AccountManagement.php
+++ b/app/code/Magento/Customer/Model/AccountManagement.php
@@ -30,7 +30,7 @@ use Magento\Framework\Exception\NoSuchEntityException;
 use Magento\Framework\Exception\State\ExpiredException;
 use Magento\Framework\Exception\State\InputMismatchException;
 use Magento\Framework\Exception\State\InvalidTransitionException;
-use Psr\Log\LoggerInterface as Logger;
+use Psr\Log\LoggerInterface as PsrLogger;
 use Magento\Framework\Mail\Exception as MailException;
 use Magento\Framework\Mail\Template\TransportBuilder;
 use Magento\Framework\Math\Random;
@@ -132,7 +132,7 @@ class AccountManagement implements AccountManagementInterface
     private $url;
 
     /**
-     * @var Logger
+     * @var PsrLogger
      */
     protected $logger;
 
@@ -215,7 +215,7 @@ class AccountManagement implements AccountManagementInterface
      * @param CustomerMetadataInterface $customerMetadataService
      * @param CustomerRegistry $customerRegistry
      * @param \Magento\Framework\Url $url
-     * @param Logger $logger
+     * @param PsrLogger $logger
      * @param Encryptor $encryptor
      * @param ConfigShare $configShare
      * @param StringHelper $stringHelper
@@ -243,7 +243,7 @@ class AccountManagement implements AccountManagementInterface
         CustomerMetadataInterface $customerMetadataService,
         CustomerRegistry $customerRegistry,
         \Magento\Framework\Url $url,
-        Logger $logger,
+        PsrLogger $logger,
         Encryptor $encryptor,
         ConfigShare $configShare,
         StringHelper $stringHelper,
diff --git a/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php b/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php
index dfab0a4e277e5280e96c0e2a3df6209393b7682b..247ce01208341bebd1dfbbe7088034771a0be672 100644
--- a/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php
+++ b/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php
@@ -9,7 +9,7 @@ use Magento\Directory\Helper\Data as DirectoryHelper;
 use Magento\Eav\Model\AttributeDataFactory;
 use Magento\Framework\App\RequestInterface;
 use Magento\Framework\Locale\ResolverInterface;
-use Psr\Log\LoggerInterface as Logger;
+use Psr\Log\LoggerInterface as PsrLogger;
 use Magento\Framework\Stdlib\DateTime\TimezoneInterface as MagentoTimezone;
 
 /**
@@ -25,13 +25,13 @@ class Postcode extends \Magento\Eav\Model\Attribute\Data\AbstractData
 
     /**
      * @param MagentoTimezone $localeDate
-     * @param Logger $logger
+     * @param PsrLogger $logger
      * @param ResolverInterface $localeResolver
      * @param DirectoryHelper $directoryHelper
      */
     public function __construct(
         MagentoTimezone $localeDate,
-        Logger $logger,
+        PsrLogger $logger,
         ResolverInterface $localeResolver,
         DirectoryHelper $directoryHelper
     ) {
@@ -108,6 +108,7 @@ class Postcode extends \Magento\Eav\Model\Attribute\Data\AbstractData
      *
      * @param string $format
      * @return string|array
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function outputValue($format = AttributeDataFactory::OUTPUT_FORMAT_TEXT)
     {
diff --git a/app/code/Magento/Customer/Model/Log.php b/app/code/Magento/Customer/Model/Log.php
new file mode 100644
index 0000000000000000000000000000000000000000..2c65bdcb20cc46472e603af03cd819a08b0a675a
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Log.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Model;
+
+/**
+ * Customer log model.
+ *
+ * Contains customer log data.
+ */
+class Log
+{
+    /**
+     * Customer ID.
+     *
+     * @var int
+     */
+    protected $customerId;
+
+    /**
+     * Date and time of customer's last login.
+     *
+     * @var string
+     */
+    protected $lastLoginAt;
+
+    /**
+     * Date and time of customer's last logout.
+     *
+     * @var string
+     */
+    protected $lastVisitAt;
+
+    /**
+     * Date and time of customer's last visit.
+     *
+     * @var string
+     */
+    protected $lastLogoutAt;
+
+    /**
+     * @param int $customerId
+     * @param string $lastLoginAt
+     * @param string $lastVisitAt
+     * @param string $lastLogoutAt
+     */
+    public function __construct($customerId = null, $lastLoginAt = null, $lastVisitAt = null, $lastLogoutAt = null)
+    {
+        $this->customerId = $customerId;
+        $this->lastLoginAt = $lastLoginAt;
+        $this->lastVisitAt = $lastVisitAt;
+        $this->lastLogoutAt = $lastLogoutAt;
+    }
+
+    /**
+     * Retrieve customer id
+     *
+     * @return int
+     */
+    public function getCustomerId()
+    {
+        return $this->customerId;
+    }
+
+    /**
+     * Retrieve last login date as string
+     *
+     * @return string
+     */
+    public function getLastLoginAt()
+    {
+        return $this->lastLoginAt;
+    }
+
+    /**
+     * Retrieve last visit date as string
+     *
+     * @return string
+     */
+    public function getLastVisitAt()
+    {
+        return $this->lastVisitAt;
+    }
+
+    /**
+     * Retrieve last logout date as string
+     *
+     * @return string
+     */
+    public function getLastLogoutAt()
+    {
+        return $this->lastLogoutAt;
+    }
+}
diff --git a/app/code/Magento/Customer/Model/Logger.php b/app/code/Magento/Customer/Model/Logger.php
new file mode 100644
index 0000000000000000000000000000000000000000..e29ceb3cf0c09c805b8eecbf46a05b1356213e6f
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Logger.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Model;
+
+/**
+ * Customer log data logger.
+ *
+ * Saves and retrieves customer log data.
+ */
+class Logger
+{
+    /**
+     * Resource instance.
+     *
+     * @var \Magento\Framework\App\Resource
+     */
+    protected $resource;
+
+    /**
+     * @var \Magento\Customer\Model\LogFactory
+     */
+    protected $logFactory;
+
+    /**
+     * @param \Magento\Framework\App\Resource $resource
+     * @param \Magento\Customer\Model\LogFactory $logFactory
+     */
+    public function __construct(
+        \Magento\Framework\App\Resource $resource,
+        \Magento\Customer\Model\LogFactory $logFactory
+    ) {
+        $this->resource = $resource;
+        $this->logFactory = $logFactory;
+    }
+
+    /**
+     * Save (insert new or update existing) log.
+     *
+     * @param int $customerId
+     * @param array $data
+     * @return $this
+     * @throws \InvalidArgumentException
+     */
+    public function log($customerId, array $data)
+    {
+        $data = array_filter($data);
+
+        if (!$data) {
+            throw new \InvalidArgumentException("Log data is empty");
+        }
+
+        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */
+        $adapter = $this->resource->getConnection('write');
+
+        $adapter->insertOnDuplicate(
+            $this->resource->getTableName('customer_log'),
+            array_merge(['customer_id' => $customerId], $data),
+            array_keys($data)
+        );
+
+        return $this;
+    }
+
+    /**
+     * Load log by Customer Id.
+     *
+     * @param int $customerId
+     * @return Log
+     */
+    public function get($customerId = null)
+    {
+        $data = (null !== $customerId) ? $this->loadLogData($customerId) : [];
+
+        return $this->logFactory->create(
+            [
+                'customerId' => isset($data['customer_id']) ? $data['customer_id'] : null,
+                'lastLoginAt' => isset($data['last_login_at']) ? $data['last_login_at'] : null,
+                'lastLogoutAt' => isset($data['last_logout_at']) ? $data['last_logout_at'] : null,
+                'lastVisitAt' => isset($data['last_visit_at']) ? $data['last_visit_at'] : null
+            ]
+        );
+    }
+
+    /**
+     * Load customer log data by customer id
+     *
+     * @param int $customerId
+     * @return array
+     */
+    protected function loadLogData($customerId)
+    {
+        /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */
+        $adapter = $this->resource->getConnection('read');
+
+        $select = $adapter->select()
+            ->from(
+                ['cl' => $this->resource->getTableName('customer_log')]
+            )
+            ->joinLeft(
+                ['cv' => $this->resource->getTableName('customer_visitor')],
+                'cv.customer_id = cl.customer_id',
+                ['last_visit_at']
+            )
+            ->where(
+                'cl.customer_id = ?',
+                $customerId
+            )
+            ->order(
+                'cv.visitor_id DESC'
+            )
+            ->limit(1);
+
+        return $adapter->fetchRow($select);
+    }
+}
diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php b/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php
index c65d0e7adaa2208981f16ac3de73566ae711570c..0af212b81b191a9c2d8cca6094fafdc6b996865f 100644
--- a/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php
+++ b/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php
@@ -9,7 +9,7 @@ use Magento\Customer\Api\Data\AttributeMetadataInterface;
 use Magento\Customer\Model\Metadata\ElementFactory;
 use Magento\Directory\Helper\Data as DirectoryHelper;
 use Magento\Framework\Locale\ResolverInterface;
-use Psr\Log\LoggerInterface as Logger;
+use Psr\Log\LoggerInterface as PsrLogger;
 use Magento\Framework\Stdlib\DateTime\TimezoneInterface as MagentoTimezone;
 
 /**
@@ -24,7 +24,7 @@ class Postcode extends AbstractData
 
     /**
      * @param MagentoTimezone $localeDate
-     * @param Logger $logger
+     * @param PsrLogger $logger
      * @param AttributeMetadataInterface $attribute
      * @param ResolverInterface $localeResolver
      * @param string $value
@@ -34,7 +34,7 @@ class Postcode extends AbstractData
      */
     public function __construct(
         MagentoTimezone $localeDate,
-        Logger $logger,
+        PsrLogger $logger,
         AttributeMetadataInterface $attribute,
         ResolverInterface $localeResolver,
         $value,
diff --git a/app/code/Magento/Customer/Model/Observer/Log.php b/app/code/Magento/Customer/Model/Observer/Log.php
new file mode 100644
index 0000000000000000000000000000000000000000..539221b5e378f47a537816c4d1e3a14457ab6ea1
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Observer/Log.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Model\Observer;
+
+use Magento\Customer\Model\Logger;
+use Magento\Framework\Stdlib\DateTime;
+use Magento\Framework\Event\Observer;
+
+/**
+ * Customer log observer.
+ */
+class Log
+{
+    /**
+     * Logger of customer's log data.
+     *
+     * @var Logger
+     */
+    protected $logger;
+
+    /**
+     * @param Logger $logger
+     */
+    public function __construct(Logger $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Handler for 'customer_login' event.
+     *
+     * @param Observer $observer
+     * @return void
+     */
+    public function logLastLoginAt(Observer $observer)
+    {
+        $this->logger->log(
+            $observer->getEvent()->getCustomer()->getId(),
+            ['last_login_at' => (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)]
+        );
+    }
+
+    /**
+     * Handler for 'customer_logout' event.
+     *
+     * @param Observer $observer
+     * @return void
+     */
+    public function logLastLogoutAt(Observer $observer)
+    {
+        $this->logger->log(
+            $observer->getEvent()->getCustomer()->getId(),
+            ['last_logout_at' => (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)]
+        );
+    }
+}
diff --git a/app/code/Magento/Customer/Model/Resource/Visitor.php b/app/code/Magento/Customer/Model/Resource/Visitor.php
index 639590bd07e85009d7002a63e1d16f678f875f18..8cc63d45988cad52b67034d5f07a32af0bfbc338 100644
--- a/app/code/Magento/Customer/Model/Resource/Visitor.php
+++ b/app/code/Magento/Customer/Model/Resource/Visitor.php
@@ -58,6 +58,7 @@ class Visitor extends \Magento\Framework\Model\Resource\Db\AbstractDb
     protected function _prepareDataForSave(\Magento\Framework\Model\AbstractModel $visitor)
     {
         return [
+            'customer_id' => $visitor->getCustomerId(),
             'session_id' => $visitor->getSessionId(),
             'last_visit_at' => $visitor->getLastVisitAt()
         ];
diff --git a/app/code/Magento/Customer/Model/Vat.php b/app/code/Magento/Customer/Model/Vat.php
index d20d51b2f788155be9e6b62bea29cbe69e5cecad..0930595e6758900822694ea2b2b410ff490999df 100644
--- a/app/code/Magento/Customer/Model/Vat.php
+++ b/app/code/Magento/Customer/Model/Vat.php
@@ -6,7 +6,7 @@
 namespace Magento\Customer\Model;
 
 use Magento\Framework\App\Config\ScopeConfigInterface;
-use Psr\Log\LoggerInterface as Logger;
+use Psr\Log\LoggerInterface as PsrLogger;
 use Magento\Store\Model\ScopeInterface;
 
 /**
@@ -68,17 +68,17 @@ class Vat
     protected $scopeConfig;
 
     /**
-     * @var Logger
+     * @var PsrLogger
      */
     protected $logger;
 
     /**
      * @param ScopeConfigInterface $scopeConfig
-     * @param Logger $logger
+     * @param PsrLogger $logger
      */
     public function __construct(
         ScopeConfigInterface $scopeConfig,
-        Logger $logger
+        PsrLogger $logger
     ) {
         $this->scopeConfig = $scopeConfig;
         $this->logger = $logger;
diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php
index 4fc1c3a8026535a7f3bad674f5c1abea19b7fb16..132fd35acc9d1c66adc5be763fd2214760fb6247 100644
--- a/app/code/Magento/Customer/Model/Visitor.php
+++ b/app/code/Magento/Customer/Model/Visitor.php
@@ -133,14 +133,15 @@ class Visitor extends \Magento\Framework\Model\AbstractModel
         if ($this->skipRequestLogging || $this->isModuleIgnored($observer)) {
             return $this;
         }
+
         if ($this->session->getVisitorData()) {
             $this->setData($this->session->getVisitorData());
         }
+
+        $this->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT));
+
         if (!$this->getId()) {
             $this->setSessionId($this->session->getSessionId());
-            $this->setLastVisitAt(
-                (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)
-            );
             $this->save();
             $this->_eventManager->dispatch('visitor_init', ['visitor' => $this]);
             $this->session->setVisitorData($this->getData());
diff --git a/app/code/Magento/Customer/Setup/UpgradeSchema.php b/app/code/Magento/Customer/Setup/UpgradeSchema.php
index 6ebf692405649991e59149a9f47a5ffda6508ce3..2b7a160eb3de8495fa5fa096bb76dd41a3e748e3 100644
--- a/app/code/Magento/Customer/Setup/UpgradeSchema.php
+++ b/app/code/Magento/Customer/Setup/UpgradeSchema.php
@@ -6,6 +6,8 @@
 
 namespace Magento\Customer\Setup;
 
+use Magento\Framework\DB\Ddl\Table;
+use Magento\Framework\DB\Adapter\AdapterInterface;
 use Magento\Framework\Setup\UpgradeSchemaInterface;
 use Magento\Framework\Setup\ModuleContextInterface;
 use Magento\Framework\Setup\SchemaSetupInterface;
@@ -17,12 +19,14 @@ class UpgradeSchema implements UpgradeSchemaInterface
 {
     /**
      * {@inheritdoc}
+     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
      */
     public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
     {
+        $setup->startSetup();
+
         if (version_compare($context->getVersion(), '2.0.0.1') < 0) {
             $installer = $setup;
-            $installer->startSetup();
             $connection = $installer->getConnection();
 
             $tableNames = [
@@ -57,7 +61,96 @@ class UpgradeSchema implements UpgradeSchemaInterface
             );
             $connection->dropColumn($installer->getTable('customer_entity'), 'entity_type_id');
             $connection->dropColumn($installer->getTable('customer_entity'), 'attribute_set_id');
-            $installer->endSetup();
         }
+
+        if (version_compare($context->getVersion(), '2.0.0.2') < 0) {
+            /**
+             * Update 'customer_visitor' table.
+             */
+            $setup->getConnection()
+                ->addColumn(
+                    $setup->getTable('customer_visitor'),
+                    'customer_id',
+                    [
+                        'type' => Table::TYPE_INTEGER,
+                        'after' => 'visitor_id',
+                        'comment' => 'Customer ID'
+                    ]
+                );
+
+            $setup->getConnection()
+                ->addIndex(
+                    $setup->getTable('customer_visitor'),
+                    $setup->getIdxName(
+                        $setup->getTable('customer_visitor'),
+                        ['customer_id']
+                    ),
+                    'customer_id'
+                );
+
+            /**
+             * Create 'customer_log' table.
+             */
+            $table = $setup->getConnection()
+                ->newTable(
+                    $setup->getTable('customer_log')
+                )
+                ->addColumn(
+                    'log_id',
+                    Table::TYPE_INTEGER,
+                    null,
+                    [
+                        'nullable' => false,
+                        'identity' => true,
+                        'primary' => true
+                    ],
+                    'Log ID'
+                )
+                ->addColumn(
+                    'customer_id',
+                    Table::TYPE_INTEGER,
+                    null,
+                    [
+                        'nullable' => false
+                    ],
+                    'Customer ID'
+                )
+                ->addColumn(
+                    'last_login_at',
+                    Table::TYPE_TIMESTAMP,
+                    null,
+                    [
+                        'nullable' => true,
+                        'default' => null
+                    ],
+                    'Last Login Time'
+                )
+                ->addColumn(
+                    'last_logout_at',
+                    Table::TYPE_TIMESTAMP,
+                    null,
+                    [
+                        'nullable' => true,
+                        'default' => null
+                    ],
+                    'Last Logout Time'
+                )
+                ->addIndex(
+                    $setup->getIdxName(
+                        $setup->getTable('customer_log'),
+                        ['customer_id'],
+                        AdapterInterface::INDEX_TYPE_UNIQUE
+                    ),
+                    ['customer_id'],
+                    [
+                        'type' => AdapterInterface::INDEX_TYPE_UNIQUE
+                    ]
+                )
+                ->setComment('Customer Log Table');
+
+            $setup->getConnection()->createTable($table);
+        }
+
+        $setup->endSetup();
     }
 }
diff --git a/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php b/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a196a2917f99477245f5f7e8cc7fd6b74533dfcb
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Unit/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Test\Unit\Block\Adminhtml\Edit\Tab\View;
+
+use Magento\Customer\Block\Adminhtml\Edit\Tab\View\PersonalInfo;
+use Magento\Framework\Stdlib\DateTime;
+
+/**
+ * Customer personal information template block test.
+ */
+class PersonalInfoTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string
+     */
+    protected $defaultTimezone = 'America/Los_Angeles';
+
+    /**
+     * @var string
+     */
+    protected $pathToDefaultTimezone = 'path/to/default/timezone';
+
+    /**
+     * @var PersonalInfo
+     */
+    protected $block;
+
+    /**
+     * @var \Magento\Customer\Model\Log|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $customerLog;
+
+    /**
+     * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $localeDate;
+
+    /**
+     * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $scopeConfig;
+
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        $customer = $this->getMock(
+            'Magento\Customer\Api\Data\CustomerInterface',
+            [],
+            [],
+            '',
+            false
+        );
+        $customer->expects($this->any())->method('getId')->willReturn(1);
+        $customer->expects($this->any())->method('getStoreId')->willReturn(1);
+
+        $customerDataFactory = $this->getMock(
+            'Magento\Customer\Api\Data\CustomerInterfaceFactory',
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $customerDataFactory->expects($this->any())->method('create')->willReturn($customer);
+
+        $backendSession = $this->getMock(
+            'Magento\Backend\Model\Session',
+            ['getCustomerData'],
+            [],
+            '',
+            false
+        );
+        $backendSession->expects($this->any())->method('getCustomerData')->willReturn(['account' => []]);
+
+        $this->customerLog = $this->getMock(
+            'Magento\Customer\Model\Log',
+            ['getLastLoginAt', 'getLastVisitAt', 'getLastLogoutAt'],
+            [],
+            '',
+            false
+        );
+        $this->customerLog->expects($this->any())->method('loadByCustomer')->willReturnSelf();
+
+        $customerLogger = $this->getMock(
+            'Magento\Customer\Model\Logger',
+            ['get'],
+            [],
+            '',
+            false
+        );
+        $customerLogger->expects($this->any())->method('get')->willReturn($this->customerLog);
+
+        $dateTime = $this->getMock(
+            'Magento\Framework\Stdlib\DateTime',
+            ['now'],
+            [],
+            '',
+            false
+        );
+        $dateTime->expects($this->any())->method('now')->willReturn('2015-03-04 12:00:00');
+
+        $this->localeDate = $this->getMock(
+            'Magento\Framework\Stdlib\DateTime\Timezone',
+            ['scopeDate', 'formatDateTime', 'getDefaultTimezonePath'],
+            [],
+            '',
+            false
+        );
+        $this->localeDate
+            ->expects($this->any())
+            ->method('getDefaultTimezonePath')
+            ->willReturn($this->pathToDefaultTimezone);
+
+        $this->scopeConfig = $this->getMock(
+            'Magento\Framework\App\Config',
+            ['getValue'],
+            [],
+            '',
+            false
+        );
+
+        $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $this->block = $objectManagerHelper->getObject(
+            'Magento\Customer\Block\Adminhtml\Edit\Tab\View\PersonalInfo',
+            [
+                'customerDataFactory' => $customerDataFactory,
+                'dateTime' => $dateTime,
+                'customerLogger' => $customerLogger,
+                'localeDate' => $this->localeDate,
+                'scopeConfig' => $this->scopeConfig,
+                'backendSession' => $backendSession,
+            ]
+        );
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetStoreLastLoginDateTimezone()
+    {
+        $this->scopeConfig
+            ->expects($this->once())
+            ->method('getValue')
+            ->with(
+                $this->pathToDefaultTimezone,
+                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+            )
+            ->willReturn($this->defaultTimezone);
+
+        $this->assertEquals($this->defaultTimezone, $this->block->getStoreLastLoginDateTimezone());
+    }
+
+    /**
+     * @param string $status
+     * @param string|null $lastLoginAt
+     * @param string|null $lastVisitAt
+     * @param string|null $lastLogoutAt
+     * @return void
+     * @dataProvider getCurrentStatusDataProvider
+     */
+    public function testGetCurrentStatus($status, $lastLoginAt, $lastVisitAt, $lastLogoutAt)
+    {
+        $this->customerLog->expects($this->any())->method('getLastLoginAt')->willReturn($lastLoginAt);
+        $this->customerLog->expects($this->any())->method('getLastVisitAt')->willReturn($lastVisitAt);
+        $this->customerLog->expects($this->any())->method('getLastLogoutAt')->willReturn($lastLogoutAt);
+
+        $this->assertEquals($status, (string) $this->block->getCurrentStatus());
+    }
+
+    /**
+     * @return array
+     */
+    public function getCurrentStatusDataProvider()
+    {
+        return [
+            ['Offline', null, null, null],
+            ['Offline', '2015-03-04 11:00:00', null, '2015-03-04 12:00:00'],
+            ['Offline', '2015-03-04 11:00:00', '2015-03-04 11:40:00', null],
+            ['Online', '2015-03-04 11:00:00', (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT), null]
+        ];
+    }
+
+    /**
+     * @param string $result
+     * @param string|null $lastLoginAt
+     * @dataProvider getLastLoginDateDataProvider
+     * @return void
+     */
+    public function testGetLastLoginDate($result, $lastLoginAt)
+    {
+        $this->customerLog->expects($this->once())->method('getLastLoginAt')->willReturn($lastLoginAt);
+        $this->localeDate->expects($this->any())->method('formatDateTime')->willReturn($lastLoginAt);
+
+        $this->assertEquals($result, $this->block->getLastLoginDate());
+    }
+
+    /**
+     * @return array
+     */
+    public function getLastLoginDateDataProvider()
+    {
+        return [
+            ['2015-03-04 12:00:00', '2015-03-04 12:00:00'],
+            ['Never', null]
+        ];
+    }
+
+    /**
+     * @param string $result
+     * @param string|null $lastLoginAt
+     * @dataProvider getStoreLastLoginDateDataProvider
+     * @return void
+     */
+    public function testGetStoreLastLoginDate($result, $lastLoginAt)
+    {
+        $this->customerLog->expects($this->once())->method('getLastLoginAt')->willReturn($lastLoginAt);
+
+        $this->localeDate->expects($this->any())->method('scopeDate')->will($this->returnValue($lastLoginAt));
+        $this->localeDate->expects($this->any())->method('formatDateTime')->willReturn($lastLoginAt);
+
+        $this->assertEquals($result, $this->block->getStoreLastLoginDate());
+    }
+
+    /**
+     * @return array
+     */
+    public function getStoreLastLoginDateDataProvider()
+    {
+        return [
+            ['2015-03-04 12:00:00', '2015-03-04 12:00:00'],
+            ['Never', null]
+        ];
+    }
+}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/LogTest.php b/app/code/Magento/Customer/Test/Unit/Model/LogTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..96ce27aef717692871d99e6702bfde5a5314d6a9
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Unit/Model/LogTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Test\Unit\Model;
+
+/**
+ * Customer log model test.
+ */
+class LogTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * Customer log model.
+     *
+     * @var \Magento\Customer\Model\Log
+     */
+    protected $log;
+
+    /**
+     * @var array
+     */
+    protected $logData = [
+        'customer_id' => 369,
+        'last_login_at' => '2015-03-04 12:00:00',
+        'last_visit_at' => '2015-03-04 12:01:00',
+        'last_logout_at' => '2015-03-04 12:05:00',
+    ];
+
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $this->log = $objectManagerHelper->getObject(
+            'Magento\Customer\Model\Log',
+            [
+                'customerId' => $this->logData['customer_id'],
+                'lastLoginAt' => $this->logData['last_login_at'],
+                'lastVisitAt' => $this->logData['last_visit_at'],
+                'lastLogoutAt' => $this->logData['last_logout_at']
+            ]
+        );
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetCustomerId()
+    {
+        $this->assertEquals($this->logData['customer_id'], $this->log->getCustomerId());
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetLastLoginAt()
+    {
+        $this->assertEquals($this->logData['last_login_at'], $this->log->getLastLoginAt());
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetLastVisitAt()
+    {
+        $this->assertEquals($this->logData['last_visit_at'], $this->log->getLastVisitAt());
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetLastLogoutAt()
+    {
+        $this->assertEquals($this->logData['last_logout_at'], $this->log->getLastLogoutAt());
+    }
+}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/LoggerTest.php b/app/code/Magento/Customer/Test/Unit/Model/LoggerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..88c7f55c22a3eb5ad0c5666d8ab0fcf77b87dac9
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Unit/Model/LoggerTest.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Test\Unit\Model;
+
+/**
+ * Customer log data logger test.
+ */
+class LoggerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * Customer log data logger.
+     *
+     * @var \Magento\Customer\Model\Logger
+     */
+    protected $logger;
+
+    /**
+     * @var \Magento\Customer\Model\LogFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $logFactory;
+
+    /**
+     * Resource instance.
+     *
+     * @var \Magento\Framework\App\Resource|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $resource;
+
+    /**
+     * DB connection instance.
+     *
+     * @var \Magento\Framework\DB\Adapter\Pdo|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $adapter;
+
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->adapter = $this->getMock(
+            'Magento\Framework\DB\Adapter\Pdo',
+            ['select', 'insertOnDuplicate', 'fetchRow'],
+            [],
+            '',
+            false
+        );
+        $this->resource = $this->getMock('Magento\Framework\App\Resource', [], [], '', false);
+        $this->logFactory = $this->getMock('\Magento\Customer\Model\LogFactory', ['create'], [], '', false);
+
+        $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+
+        $this->logger = $objectManagerHelper->getObject(
+            '\Magento\Customer\Model\Logger',
+            [
+                'resource' => $this->resource,
+                'logFactory' => $this->logFactory
+            ]
+        );
+    }
+
+    /**
+     * @param int $customerId
+     * @param array $data
+     * @dataProvider testLogDataProvider
+     * @return void
+     */
+    public function testLog($customerId, $data)
+    {
+        $tableName = 'customer_log_table_name';
+        $data = array_filter($data);
+
+        if (!$data) {
+            $this->setExpectedException('\InvalidArgumentException', 'Log data is empty');
+            $this->logger->log($customerId, $data);
+            return;
+        }
+
+        $this->resource->expects($this->once())
+            ->method('getConnection')
+            ->with('write')
+            ->willReturn($this->adapter);
+        $this->resource->expects($this->once())
+            ->method('getTableName')
+            ->with('customer_log')
+            ->willReturn($tableName);
+        $this->adapter->expects($this->once())
+            ->method('insertOnDuplicate')
+            ->with($tableName, array_merge(['customer_id' => $customerId], $data), array_keys($data));
+
+        $this->assertEquals($this->logger, $this->logger->log($customerId, $data));
+    }
+
+    /**
+     * @return array
+     */
+    public function testLogDataProvider()
+    {
+        return [
+            [235, ['last_login_at' => '2015-03-04 12:00:00']],
+            [235, ['last_login_at' => null]],
+        ];
+    }
+
+    /**
+     * @param int $customerId
+     * @param array $data
+     * @dataProvider testGetDataProvider
+     * @return void
+     */
+    public function testGet($customerId, $data)
+    {
+        $logArguments = [
+            'customerId' => $data['customer_id'],
+            'lastLoginAt' => $data['last_login_at'],
+            'lastLogoutAt' => $data['last_logout_at'],
+            'lastVisitAt' => $data['last_visit_at']
+        ];
+
+        $select = $this->getMock('Magento\Framework\DB\Select', [], [], '', false);
+
+        $select->expects($this->any())->method('from')->willReturnSelf();
+        $select->expects($this->any())->method('joinLeft')->willReturnSelf();
+        $select->expects($this->any())->method('where')->willReturnSelf();
+        $select->expects($this->any())->method('order')->willReturnSelf();
+        $select->expects($this->any())->method('limit')->willReturnSelf();
+
+        $this->adapter->expects($this->any())
+            ->method('select')
+            ->willReturn($select);
+
+        $this->resource->expects($this->once())
+            ->method('getConnection')
+            ->with('read')
+            ->willReturn($this->adapter);
+        $this->adapter->expects($this->any())
+            ->method('fetchRow')
+            ->with($select)
+            ->willReturn($data);
+
+        $log = $this->getMock(
+            'Magento\Customer\Model\Log',
+            [],
+            $logArguments
+        );
+
+        $this->logFactory->expects($this->any())
+            ->method('create')
+            ->with($logArguments)
+            ->willReturn($log);
+
+        $this->assertEquals($log, $this->logger->get($customerId));
+    }
+
+    /**
+     * @return array
+     */
+    public function testGetDataProvider()
+    {
+        return [
+            [
+                235,
+                [
+                    'customer_id' => 369,
+                    'last_login_at' => '2015-03-04 12:00:00',
+                    'last_visit_at' => '2015-03-04 12:01:00',
+                    'last_logout_at' => '2015-03-04 12:05:00',
+                ]
+            ],
+            [
+                235,
+                [
+                    'customer_id' => 369,
+                    'last_login_at' => '2015-03-04 12:00:00',
+                    'last_visit_at' => '2015-03-04 12:01:00',
+                    'last_logout_at' => null,
+                ]
+            ],
+            [
+                235,
+                [
+                    'customer_id' => null,
+                    'last_login_at' => null,
+                    'last_visit_at' => null,
+                    'last_logout_at' => null,
+                ]
+            ],
+        ];
+    }
+}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Observer/LogTest.php b/app/code/Magento/Customer/Test/Unit/Model/Observer/LogTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fcd3ff2c58f76fff1619f3804c0c079eb8c5c397
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Unit/Model/Observer/LogTest.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Test\Unit\Model\Observer;
+
+use Magento\Customer\Model\Logger;
+use Magento\Framework\Stdlib\DateTime;
+use Magento\Framework\Event\Observer;
+use Magento\Customer\Model\Observer\Log;
+
+/**
+ * Class LogTest
+ */
+class LogTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Log
+     */
+    protected $logObserver;
+
+    /**
+     * @var Logger | \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $loggerMock;
+
+    /**
+     * @return void
+     */
+    public function setUp()
+    {
+        $this->loggerMock = $this->getMock('Magento\Customer\Model\Logger', [], [], '', false);
+        $this->logObserver = new Log($this->loggerMock);
+    }
+
+    /**
+     * @return void
+     */
+    public function testLogLastLoginAt()
+    {
+        $id = 1;
+
+        $observerMock = $this->getMock('Magento\Framework\Event\Observer', [], [], '', false);
+        $eventMock = $this->getMock('Magento\Framework\Event', ['getCustomer'], [], '', false);
+        $customerMock = $this->getMock('Magento\Customer\Model\Customer', [], [], '', false);
+
+        $observerMock->expects($this->once())
+            ->method('getEvent')
+            ->willReturn($eventMock);
+        $eventMock->expects($this->once())
+            ->method('getCustomer')
+            ->willReturn($customerMock);
+        $customerMock->expects($this->once())
+            ->method('getId')
+            ->willReturn($id);
+
+        $this->loggerMock->expects($this->once())
+            ->method('log');
+
+        $this->logObserver->logLastLoginAt($observerMock);
+    }
+
+    /**
+     * @return void
+     */
+    public function testLogLastLogoutAt()
+    {
+        $id = 1;
+
+        $observerMock = $this->getMock('Magento\Framework\Event\Observer', [], [], '', false);
+        $eventMock = $this->getMock('Magento\Framework\Event', ['getCustomer'], [], '', false);
+        $customerMock = $this->getMock('Magento\Customer\Model\Customer', [], [], '', false);
+
+        $observerMock->expects($this->once())
+            ->method('getEvent')
+            ->willReturn($eventMock);
+        $eventMock->expects($this->once())
+            ->method('getCustomer')
+            ->willReturn($customerMock);
+        $customerMock->expects($this->once())
+            ->method('getId')
+            ->willReturn($id);
+
+        $this->loggerMock->expects($this->once())
+            ->method('log');
+
+        $this->logObserver->logLastLogoutAt($observerMock);
+    }
+}
diff --git a/app/code/Magento/Customer/etc/frontend/events.xml b/app/code/Magento/Customer/etc/frontend/events.xml
index 81c65b5aa1c648e3cfe5e9f422e16ab4a268bd4e..a91b32c0ea159ab5e69b7d80732252cf72f16814 100644
--- a/app/code/Magento/Customer/etc/frontend/events.xml
+++ b/app/code/Magento/Customer/etc/frontend/events.xml
@@ -17,6 +17,7 @@
     </event>
     <event name="customer_logout">
         <observer name="customer_visitor" instance="Magento\Customer\Model\Visitor" method="bindCustomerLogout" />
+        <observer name="customer_log_logout" instance="Magento\Customer\Model\Observer\Log" method="logLastLogoutAt" />
     </event>
     <event name="sales_quote_save_after">
         <observer name="customer_visitor" instance="Magento\Customer\Model\Visitor" method="bindQuoteCreate" />
@@ -24,4 +25,7 @@
     <event name="checkout_quote_destroy">
         <observer name="customer_visitor" instance="Magento\Customer\Model\Visitor" method="bindQuoteDestroy" />
     </event>
+    <event name="customer_login">
+        <observer name="customer_log_login" instance="Magento\Customer\Model\Observer\Log" method="logLastLoginAt" />
+    </event>
 </config>
diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml
index 33f923e05015b2ac6618f497ca5ea129be68bb74..973c14db73d61034756a46694f3abab1856ed480 100644
--- a/app/code/Magento/Customer/etc/module.xml
+++ b/app/code/Magento/Customer/etc/module.xml
@@ -6,7 +6,7 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
-    <module name="Magento_Customer" setup_version="2.0.0.1">
+    <module name="Magento_Customer" setup_version="2.0.0.2">
         <sequence>
             <module name="Magento_Eav"/>
             <module name="Magento_Directory"/>
diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml
index 51204820fd18123d88d90b6c07aadda993728468..e87d3005a1dad3e90d4d06b8c67ea73f561627e7 100644
--- a/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml
+++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/view/personal_info.phtml
@@ -10,8 +10,11 @@
  * Template for block \Magento\Customer\Block\Adminhtml\Edit\Tab\View\Status\PersonalInfo
  */
 
-$createDateAdmin    = $block->getCreateDate();
-$createDateStore    = $block->getStoreCreateDate();
+$lastLoginDateAdmin = $block->getLastLoginDate();
+$lastLoginDateStore = $block->getStoreLastLoginDate();
+
+$createDateAdmin = $block->getCreateDate();
+$createDateStore = $block->getStoreCreateDate();
 ?>
 
 <div class="fieldset-wrapper customer-information">
@@ -21,6 +24,16 @@ $createDateStore    = $block->getStoreCreateDate();
     <table class="data-table">
         <tbody>
         <?php echo $block->getChildHtml(); ?>
+        <tr>
+            <th><?php echo __('Last Logged In:') ?></th>
+            <td><?php echo $lastLoginDateAdmin ?> (<?php echo $block->getCurrentStatus() ?>)</td>
+        </tr>
+        <?php if ($lastLoginDateAdmin != $lastLoginDateStore): ?>
+        <tr>
+            <th><?php echo __('Last Logged In (%1):', $block->getStoreLastLoginDateTimezone()) ?></th>
+            <td><?php echo $lastLoginDateStore ?> (<?php echo $block->getCurrentStatus() ?>)</td>
+        </tr>
+        <?php endif; ?>
         <tr>
             <th><?php echo __('Confirmed email:') ?></th>
             <td><?php echo $block->getIsConfirmedStatus() ?></td>
diff --git a/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js b/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js
index ad58d32bcbe86077a511d6c2971e2e2efbe943ea..d19339d3f2552893755b919cf4232eef13dc86e3 100644
--- a/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js
+++ b/app/code/Magento/DesignEditor/view/adminhtml/web/js/dialog.js
@@ -99,9 +99,6 @@ define([
          * @param {Array.<Object>|Object} buttons
          */
         set: function (title, text, buttons) {
-            title = $.mage.__(title);
-            text = $.mage.__(text);
-
             this.text.set(text);
             this.title.set(title);
             this.setButtons(buttons);
@@ -120,9 +117,6 @@ define([
                 if ($.type(buttons) !== 'array') {
                     buttons = [buttons];
                 }
-                buttons.each(function(button){
-                    button.text = $.mage.__(button.text)
-                });
             }
 
             var hasToAddCancel = (addCancel == undefined && buttons.length <= 1) || addCancel == true;
@@ -146,8 +140,6 @@ define([
          * @param {number} position
          */
         addButton: function(button, position) {
-            button.text = $.mage.__(button.text)
-
             var buttons = this.options.buttons;
             buttons.splice(position, 0, button);
             this._setOption('buttons', buttons);
@@ -166,7 +158,7 @@ define([
                 position = buttonPointer;
             } else {
                 //Find 1st button with given title
-                var title = $.mage.__(buttonPointer);
+                var title = buttonPointer;
                 this.options.buttons.each(function(button, index) {
                     if (button.text == title) {
                         position = index;
diff --git a/app/code/Magento/Log/Block/Adminhtml/Customer/Edit/Tab/View/Status.php b/app/code/Magento/Log/Block/Adminhtml/Customer/Edit/Tab/View/Status.php
deleted file mode 100644
index c95e4ba9827f3facf32bf1257a2c4081a48e3a18..0000000000000000000000000000000000000000
--- a/app/code/Magento/Log/Block/Adminhtml/Customer/Edit/Tab/View/Status.php
+++ /dev/null
@@ -1,155 +0,0 @@
-<?php
-/**
- * Copyright © 2015 Magento. All rights reserved.
- * See COPYING.txt for license details.
- */
-namespace Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View;
-
-/**
- * Class Status
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- */
-class Status extends \Magento\Backend\Block\Template
-{
-    /**
-     * @var \Magento\Customer\Api\Data\CustomerInterface
-     */
-    protected $customer;
-
-    /**
-     * @var \Magento\Log\Model\Customer
-     */
-    protected $customerLog;
-
-    /**
-     * @var \Magento\Log\Model\Visitor
-     */
-    protected $modelLog;
-
-    /**
-     * @var \Magento\Log\Model\CustomerFactory
-     */
-    protected $logFactory;
-
-    /**
-     * @var \Magento\Customer\Api\Data\CustomerInterfaceFactory
-     */
-    protected $customerFactory;
-
-    /**
-     * @var \Magento\Framework\Api\DataObjectHelper
-     */
-    protected $dataObjectHelper;
-
-    /**
-     * @param \Magento\Backend\Block\Template\Context $context
-     * @param \Magento\Log\Model\CustomerFactory $logFactory
-     * @param \Magento\Log\Model\Log $modelLog
-     * @param \Magento\Framework\Stdlib\DateTime $dateTime
-     * @param \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerFactory
-     * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
-     * @param array $data
-     */
-    public function __construct(
-        \Magento\Backend\Block\Template\Context $context,
-        \Magento\Log\Model\CustomerFactory $logFactory,
-        \Magento\Log\Model\Log $modelLog,
-        \Magento\Framework\Stdlib\DateTime $dateTime,
-        \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerFactory,
-        \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
-        array $data = []
-    ) {
-        $this->logFactory = $logFactory;
-        $this->modelLog = $modelLog;
-        $this->dateTime = $dateTime;
-        $this->customerFactory = $customerFactory;
-        $this->dataObjectHelper = $dataObjectHelper;
-        parent::__construct($context, $data);
-    }
-
-    /**
-     * @return string
-     */
-    public function getStoreLastLoginDateTimezone()
-    {
-        return $this->_scopeConfig->getValue(
-            $this->_localeDate->getDefaultTimezonePath(),
-            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
-            $this->getCustomer()->getStoreId()
-        );
-    }
-
-    /**
-     * @return \Magento\Customer\Api\Data\CustomerInterface
-     */
-    public function getCustomer()
-    {
-        if (!$this->customer) {
-            $this->customer = $this->customerFactory->create();
-            $this->dataObjectHelper
-                ->populateWithArray(
-                    $this->customer,
-                    $this->_backendSession->getCustomerData()['account'],
-                    '\Magento\Customer\Api\Data\CustomerInterface'
-                );
-        }
-        return $this->customer;
-    }
-
-    /**
-     * Get customer's current status
-     *
-     * @return \Magento\Framework\Phrase
-     */
-    public function getCurrentStatus()
-    {
-        $log = $this->getCustomerLog();
-        $interval = $this->modelLog->getOnlineMinutesInterval();
-        if ($log->getLogoutAt() ||
-            (new \DateTime())->getTimestamp() - strtotime($log->getLastVisitAt()) > $interval * 60
-        ) {
-            return __('Offline');
-        }
-        return __('Online');
-    }
-
-    /**
-     * Get customer last login date
-     *
-     * @return \Magento\Framework\Phrase|string
-     */
-    public function getLastLoginDate()
-    {
-        $date = $this->getCustomerLog()->getLoginAt();
-        if ($date) {
-            return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true);
-        }
-        return __('Never');
-    }
-
-    /**
-     * @return \Magento\Framework\Phrase|string
-     */
-    public function getStoreLastLoginDate()
-    {
-        $date = $this->getCustomerLog()->getLoginAtTimestamp();
-        if ($date) {
-            $date = $this->_localeDate->scopeDate($this->getCustomer()->getStoreId(), $date, true);
-            return $this->formatDate($date, \IntlDateFormatter::MEDIUM, true);
-        }
-        return __('Never');
-    }
-
-    /**
-     * Load Customer Log model
-     *
-     * @return \Magento\Log\Model\Customer
-     */
-    public function getCustomerLog()
-    {
-        if (!$this->customerLog) {
-            $this->customerLog = $this->logFactory->create()->loadByCustomer($this->getCustomer()->getId());
-        }
-        return $this->customerLog;
-    }
-}
diff --git a/app/code/Magento/Log/Test/Unit/Block/Adminhtml/Customer/Edit/Tab/View/StatusTest.php b/app/code/Magento/Log/Test/Unit/Block/Adminhtml/Customer/Edit/Tab/View/StatusTest.php
deleted file mode 100644
index c155c22aca514c592d5a87f4b04c3d99ef491e35..0000000000000000000000000000000000000000
--- a/app/code/Magento/Log/Test/Unit/Block/Adminhtml/Customer/Edit/Tab/View/StatusTest.php
+++ /dev/null
@@ -1,162 +0,0 @@
-<?php
-/**
- * Copyright © 2015 Magento. All rights reserved.
- * See COPYING.txt for license details.
- */
-namespace Magento\Log\Test\Unit\Block\Adminhtml\Customer\Edit\Tab\View;
-
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
-
-/**
- * Class StatusTest
- * @package Magento\Log\Block\Adminhtml\Edit\Tab\View
- */
-class StatusTest extends \PHPUnit_Framework_TestCase
-{
-    /**
-     * @var \Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status
-     */
-    protected $block;
-
-    /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $logFactory;
-
-    /**
-     * @var \Magento\Log\Model\Customer|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $customerLog;
-
-    /**
-     * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $localeDate;
-
-    /**
-     * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $scopeConfig;
-
-    protected function setUp()
-    {
-        $log = $this->getMock('Magento\Log\Model\Log', ['getOnlineMinutesInterval'], [], '', false);
-        $log->expects($this->any())->method('getOnlineMinutesInterval')->will($this->returnValue(1));
-
-        $this->customerLog = $this->getMockBuilder('Magento\Log\Model\Customer')->disableOriginalConstructor()
-            ->setMethods(['getLoginAt', 'getLoginAtTimestamp', 'loadByCustomer', 'getLogoutAt', 'getLastVisitAt'])
-            ->getMock();
-        $this->customerLog->expects($this->any())->method('loadByCustomer')->will($this->returnSelf());
-
-        $this->logFactory = $this->getMockBuilder('Magento\Log\Model\CustomerFactory')->setMethods(['create'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $this->logFactory->expects($this->any())->method('create')->will($this->returnValue($this->customerLog));
-
-        $dateTime = $this->getMockBuilder('Magento\Framework\Stdlib\DateTime')->setMethods(['now'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $dateTime->expects($this->any())->method('now')->will($this->returnCallback(function () {
-            return date('Y-m-d H:i:s');
-        }));
-
-        $customer = $this->getMock('\Magento\Customer\Api\Data\CustomerInterface');
-        $customer->expects($this->any())->method('getId')->will($this->returnValue(1));
-        $customer->expects($this->any())->method('getStoreId')->will($this->returnValue(1));
-
-        $customerFactory = $this->getMockBuilder('\Magento\Customer\Api\Data\CustomerInterfaceFactory')
-            ->setMethods(['create'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $customerFactory->expects($this->any())->method('create')
-            ->will($this->returnValue($customer));
-
-        $customerData = ['account' => ['id' => 1, 'store_id' => 1]];
-        $backendSession = $this->getMockBuilder('\Magento\Backend\Model\Session')
-            ->setMethods(['getCustomerData'])->disableOriginalConstructor()->getMock();
-        $backendSession->expects($this->any())->method('getCustomerData')->will($this->returnValue($customerData));
-
-        $this->localeDate = $this->getMockBuilder('Magento\Framework\Stdlib\DateTime\Timezone')
-            ->setMethods(['scopeDate', 'formatDateTime', 'getDefaultTimezonePath'])
-            ->disableOriginalConstructor()->getMock();
-        $this->localeDate->expects($this->any())->method('getDefaultTimezonePath')
-            ->will($this->returnValue('path/to/default/timezone'));
-
-        $this->scopeConfig = $this->getMockBuilder('Magento\Framework\App\Config')
-            ->setMethods(['getValue'])
-            ->disableOriginalConstructor()->getMock();
-
-        $objectManagerHelper = new ObjectManagerHelper($this);
-        $this->block = $objectManagerHelper->getObject(
-            'Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status',
-            [
-                'logFactory' => $this->logFactory,
-                'localeDate' => $this->localeDate,
-                'scopeConfig' => $this->scopeConfig,
-                'modelLog' => $log,
-                'dateTime' => $dateTime,
-                'customerFactory' => $customerFactory,
-                'backendSession' => $backendSession
-            ]
-        );
-    }
-
-    public function testGetCustomerLog()
-    {
-        $this->logFactory->expects($this->once())->method('create')->will($this->returnValue($this->customerLog));
-        $this->assertSame($this->customerLog, $this->block->getCustomerLog());
-    }
-
-    public function testGetCurrentStatusOffline()
-    {
-        $date = date('Y-m-d H:i:s');
-        $this->customerLog->expects($this->any())->method('getLogoutAt')->will($this->returnValue($date));
-        $this->assertEquals('Offline', $this->block->getCurrentStatus());
-    }
-
-    public function testGetCurrentStatusOnline()
-    {
-        $date = date('Y-m-d H:i:s');
-        $this->customerLog->expects($this->any())->method('getLogoutAt')->will($this->returnValue(0));
-        $this->customerLog->expects($this->any())->method('getLastVisitAt')->will($this->returnValue($date));
-        $this->assertEquals('Online', $this->block->getCurrentStatus());
-    }
-
-    public function testGetLastLoginDate()
-    {
-        $date = date('Y-m-d H:i:s');
-        $this->customerLog->expects($this->any())->method('getLoginAt')->will($this->returnValue($date));
-        $this->localeDate->expects($this->once())->method('formatDateTime')->will($this->returnValue($date));
-        $this->assertEquals($date, $this->block->getLastLoginDate());
-    }
-
-    public function testAfterGetLastLoginDateNever()
-    {
-        $this->assertEquals('Never', $this->block->getLastLoginDate());
-    }
-
-    public function testGetStoreLastLoginDate()
-    {
-        $date = date('Y-m-d H:i:s');
-        $time = strtotime($date);
-
-        $this->localeDate->expects($this->once())->method('scopeDate')->will($this->returnValue($date));
-        $this->localeDate->expects($this->once())->method('formatDateTime')->will($this->returnValue($date));
-
-        $this->customerLog->expects($this->any())->method('getLoginAtTimestamp')->will($this->returnValue($time));
-        $this->assertEquals($date, $this->block->getStoreLastLoginDate());
-    }
-
-    public function testGetStoreLastLoginDateNever()
-    {
-        $this->assertEquals('Never', $this->block->getStoreLastLoginDate());
-    }
-
-    public function testGetStoreLastLoginDateTimezone()
-    {
-        $this->scopeConfig->expects($this->once())->method('getValue')
-            ->with('path/to/default/timezone', 'store', 1)
-            ->will($this->returnValue('America/Los_Angeles'));
-        $this->block->getStoreLastLoginDateTimezone();
-    }
-}
diff --git a/app/code/Magento/Log/view/adminhtml/layout/customer_form.xml b/app/code/Magento/Log/view/adminhtml/layout/customer_form.xml
deleted file mode 100644
index f53b7241321ea6154c32b08cab9e066644c755d2..0000000000000000000000000000000000000000
--- a/app/code/Magento/Log/view/adminhtml/layout/customer_form.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0"?>
-<!--
-/**
- * Copyright © 2015 Magento. All rights reserved.
- * See COPYING.txt for license details.
- */
--->
-<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
-    <body>
-        <referenceBlock name="personal_info">
-            <block class="Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status" name="view_customer_status" template="customer/status.phtml"/>
-        </referenceBlock>
-    </body>
-</page>
diff --git a/app/code/Magento/Log/view/adminhtml/templates/customer/status.phtml b/app/code/Magento/Log/view/adminhtml/templates/customer/status.phtml
deleted file mode 100644
index 3d8c1ac2912e2e312967a447b2908791598aae14..0000000000000000000000000000000000000000
--- a/app/code/Magento/Log/view/adminhtml/templates/customer/status.phtml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-/**
- * Copyright © 2015 Magento. All rights reserved.
- * See COPYING.txt for license details.
- */
-
-// @codingStandardsIgnoreFile
-
-/**
- * Template for block \Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status
- */
-
-$lastLoginDateAdmin = $block->getLastLoginDate();
-$lastLoginDateStore = $block->getStoreLastLoginDate();
-?>
-<tr>
-    <th><?php echo __('Last Logged In:') ?></th>
-    <td><?php echo $lastLoginDateAdmin ?> (<?php echo $block->getCurrentStatus() ?>)</td>
-</tr>
-<?php if ($lastLoginDateAdmin != $lastLoginDateStore): ?>
-<tr>
-    <th><?php echo __('Last Logged In (%1):', $block->getStoreLastLoginDateTimezone()) ?></th>
-    <td><?php echo $lastLoginDateStore ?> (<?php echo $block->getCurrentStatus() ?>)</td>
-</tr>
-<?php endif; ?>
diff --git a/app/code/Magento/Translation/Block/Js.php b/app/code/Magento/Translation/Block/Js.php
index dcf8f7156231b72655edf18db7832d47e7c02f16..f79360f534bab736536bdadb8a99d31c3809cb96 100644
--- a/app/code/Magento/Translation/Block/Js.php
+++ b/app/code/Magento/Translation/Block/Js.php
@@ -6,50 +6,37 @@
 
 namespace Magento\Translation\Block;
 
-use Magento\Framework\Translate\InlineInterface as InlineTranslator;
-use Magento\Translation\Model\Js as DataProvider;
 use Magento\Framework\View\Element\Template;
+use Magento\Translation\Model\Js\Config;
 
-class Js extends \Magento\Framework\View\Element\Template
+class Js extends Template
 {
     /**
-     * Data provider model
-     *
-     * @var DataProvider
-     */
-    protected $dataProvider;
-
-    /**
-     * Inline translator
-     *
-     * @var InlineTranslator
+     * @var Config
      */
-    protected $translateInline;
+    protected $config;
 
     /**
      * @param Template\Context $context
-     * @param DataProvider $dataProvider
-     * @param InlineTranslator $translateInline
+     * @param Config $config
      * @param array $data
      */
     public function __construct(
         Template\Context $context,
-        DataProvider $dataProvider,
-        InlineTranslator $translateInline,
+        Config $config,
         array $data = []
     ) {
         parent::__construct($context, $data);
-        $this->dataProvider = $dataProvider;
-        $this->translateInline = $translateInline;
+        $this->config = $config;
     }
 
     /**
-     * @return string
+     * Is js translation set to dictionary mode
+     *
+     * @return bool
      */
-    public function getTranslatedJson()
+    public function dictionaryEnabled()
     {
-        $data = $this->dataProvider->getTranslateData();
-        $this->translateInline->processResponseBody($data);
-        return \Zend_Json::encode($data);
+        return $this->config->dictionaryEnabled();
     }
 }
diff --git a/app/code/Magento/Translation/Model/Js.php b/app/code/Magento/Translation/Model/Js.php
deleted file mode 100644
index ca4bc45b96058712719e24c223c78dec3e8dcd38..0000000000000000000000000000000000000000
--- a/app/code/Magento/Translation/Model/Js.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-/**
- * Copyright © 2015 Magento. All rights reserved.
- * See COPYING.txt for license details.
- */
-
-namespace Magento\Translation\Model;
-
-class Js
-{
-    /**
-     * Translation data
-     *
-     * @var string[]
-     */
-    protected $translateData;
-
-    /**
-     * @param Js\DataProviderInterface[] $dataProviders
-     */
-    public function __construct(array $dataProviders)
-    {
-        /** @var $dataProvider Js\DataProviderInterface */
-        foreach ($dataProviders as $dataProvider) {
-            foreach ($dataProvider->getData() as $key => $translatedText) {
-                if ($key !== $translatedText) {
-                    $this->translateData[$key] = $translatedText;
-                }
-            }
-        }
-    }
-
-    /**
-     * Get translated data
-     *
-     * @return string[]
-     */
-    public function getTranslateData()
-    {
-        return $this->translateData;
-    }
-}
diff --git a/app/code/Magento/Translation/Model/Js/Config.php b/app/code/Magento/Translation/Model/Js/Config.php
new file mode 100644
index 0000000000000000000000000000000000000000..6dc1abb699d9acdc90d6325fad535150d4b09187
--- /dev/null
+++ b/app/code/Magento/Translation/Model/Js/Config.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Model\Js;
+
+use Magento\Framework\Translate\Js\Config as FrameworkJsConfig;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+
+/**
+ * Js Translation config
+ */
+class Config extends FrameworkJsConfig
+{
+    /**
+     * Both translation strategies are disabled
+     */
+    const NO_TRANSLATION = 'none';
+
+    /**
+     * Strategy when all js files are translated while publishing
+     */
+    const EMBEDDED_STRATEGY = 'embedded';
+
+    /**
+     * Strategy when dictionary is generated for dynamic translation
+     */
+    const DICTIONARY_STRATEGY = 'dictionary';
+
+    /**
+     * Configuration path to translation strategy
+     */
+    const XML_PATH_STRATEGY = 'dev/js/translate_strategy';
+
+    /**
+     * Dictionary file name
+     */
+    const DICTIONARY_FILE_NAME = 'js-translation.json';
+
+    /**
+     * Core store config
+     *
+     * @var ScopeConfigInterface
+     */
+    protected $scopeConfig;
+
+    /**
+     * Patterns to match strings for translation
+     *
+     * @var string[]
+     */
+    protected $patterns;
+
+    /**
+     * @param ScopeConfigInterface $scopeConfig
+     * @param string[] $patterns
+     */
+    public function __construct(ScopeConfigInterface $scopeConfig, array $patterns)
+    {
+        $this->scopeConfig = $scopeConfig;
+        $this->patterns = $patterns;
+        parent::__construct(
+            $this->scopeConfig->getValue(self::XML_PATH_STRATEGY) == self::DICTIONARY_STRATEGY,
+            self::DICTIONARY_FILE_NAME
+        );
+    }
+
+    /**
+     * Is Embedded Strategy selected
+     *
+     * @return bool
+     */
+    public function isEmbeddedStrategy()
+    {
+        return ($this->scopeConfig->getValue(self::XML_PATH_STRATEGY) == self::EMBEDDED_STRATEGY);
+    }
+
+    /**
+     * Is Dictionary Strategy selected
+     *
+     * @return bool
+     */
+    public function dictionaryEnabled()
+    {
+        return ($this->scopeConfig->getValue(self::XML_PATH_STRATEGY) == self::DICTIONARY_STRATEGY);
+    }
+
+    /**
+     * Retrieve translation patterns
+     *
+     * @return string[]
+     */
+    public function getPatterns()
+    {
+        return $this->patterns;
+    }
+}
diff --git a/app/code/Magento/Translation/Model/Js/Config/Source/Strategy.php b/app/code/Magento/Translation/Model/Js/Config/Source/Strategy.php
new file mode 100644
index 0000000000000000000000000000000000000000..30f1b0fe8e9a01fb9838002561820e11986c43cc
--- /dev/null
+++ b/app/code/Magento/Translation/Model/Js/Config/Source/Strategy.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Model\Js\Config\Source;
+
+use Magento\Translation\Model\Js\Config;
+
+class Strategy implements \Magento\Framework\Option\ArrayInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function toOptionArray()
+    {
+        return [
+            ['label' => __('None (Translation is disabled)'), 'value' => Config::NO_TRANSLATION],
+            ['label' => __('Dictionary (Translation on frontend side)'), 'value' => Config::DICTIONARY_STRATEGY],
+            ['label' => __('Embedded (Translation on backend side)'), 'value' => Config::EMBEDDED_STRATEGY]
+        ];
+    }
+}
diff --git a/app/code/Magento/Translation/Model/Js/DataProvider.php b/app/code/Magento/Translation/Model/Js/DataProvider.php
index 317dc602a3c7abc55998972f72d9b9f4b8450027..a9a83fcf8d6b35d942c43313f9d6204f2c087577 100644
--- a/app/code/Magento/Translation/Model/Js/DataProvider.php
+++ b/app/code/Magento/Translation/Model/Js/DataProvider.php
@@ -4,164 +4,109 @@
  * See COPYING.txt for license details.
  */
 
-// @codingStandardsIgnoreFile
-
 namespace Magento\Translation\Model\Js;
 
+use Magento\Framework\App\Utility\Files;
+use Magento\Framework\App\State;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Filesystem\Directory\ReadInterface;
+use Magento\Framework\App\Filesystem\DirectoryList;
+
+/**
+ * DataProvider for js translation
+ */
 class DataProvider implements DataProviderInterface
 {
+    /**
+     * Application state
+     *
+     * @var State
+     */
+    protected $appState;
+
+    /**
+     * Js translation configuration
+     *
+     * @var Config
+     */
+    protected $config;
+
+    /**
+     * Files utility
+     *
+     * @var Files
+     */
+    protected $filesUtility;
+
+    /**
+     * Filesystem
+     *
+     * @var ReadInterface
+     */
+    protected $rootDirectory;
+
+    /**
+     * @param State $appState
+     * @param Config $config
+     * @param Filesystem $filesystem
+     * @param Files $filesUtility
+     */
+    public function __construct(State $appState, Config $config, Filesystem $filesystem, Files $filesUtility = null)
+    {
+        $this->appState = $appState;
+        $this->config = $config;
+        $this->rootDirectory = $filesystem->getDirectoryRead(DirectoryList::ROOT);
+        $this->filesUtility = (null !== $filesUtility) ? $filesUtility : new Files(BP);
+    }
+
     /**
      * Get translation data
      *
+     * @param string $themePath
      * @return string[]
-     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     * @throws \Exception
+     * @throws \Magento\Framework\Exception
      */
-    public function getData()
+    public function getData($themePath)
     {
-        return [
-            'Complete' => __('Complete'),
-            'Upload Security Error' => __('Upload Security Error'),
-            'Upload HTTP Error' => __('Upload HTTP Error'),
-            'Upload I/O Error' => __('Upload I/O Error'),
-            'SSL Error: Invalid or self-signed certificate' => __('SSL Error: Invalid or self-signed certificate'),
-            'TB' => __('TB'),
-            'GB' => __('GB'),
-            'MB' => __('MB'),
-            'kB' => __('kB'),
-            'B' => __('B'),
-            'Add Products' => __('Add Products'),
-            'Add Products By SKU' => __('Add Products By SKU'),
-            'Insert Widget...' => __('Insert Widget...'),
-            'Please wait, loading...' => __('Please wait, loading...'),
-            'HTML tags are not allowed' => __('HTML tags are not allowed'),
-            'Please select an option.' => __('Please select an option.'),
-            'This is a required field.' => __('This is a required field.'),
-            'Please enter a valid number in this field.' => __('Please enter a valid number in this field.'),
-            'The value is not within the specified range.' => __('The value is not within the specified range.'),
-            'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.' => __('Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.'),
-            'Please use letters only (a-z or A-Z) in this field.' => __('Please use letters only (a-z or A-Z) in this field.'),
-            'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => __('Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.'),
-            'Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.' => __('Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.'),
-            'Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.' => __('Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.'),
-            'Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.' => __('Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.'),
-            'Please enter a valid date.' => __('Please enter a valid date.'),
-            'The From Date value should be less than or equal to the To Date value.' => __('The From Date value should be less than or equal to the To Date value.'),
-            'Please enter a valid email address. For example johndoe@domain.com.' => __('Please enter a valid email address. For example johndoe@domain.com.'),
-            'Please use only visible characters and spaces.' => __('Please use only visible characters and spaces.'),
-            'Please enter 6 or more characters. Leading or trailing spaces will be ignored.' => __('Please enter 6 or more characters. Leading or trailing spaces will be ignored.'),
-            'Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.' => __('Please enter 7 or more characters. Password should contain both numeric and alphabetic characters.'),
-            'Please make sure your passwords match.' => __('Please make sure your passwords match.'),
-            'Please enter a valid URL. Protocol is required (http://, https:// or ftp://)' => __('Please enter a valid URL. Protocol is required (http://, https:// or ftp://)'),
-            'Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page".' => __('Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page".'),
-            'Please enter a valid XML-identifier. For example something_1, block5, id-4.' => __('Please enter a valid XML-identifier. For example something_1, block5, id-4.'),
-            'Please enter a valid social security number. For example 123-45-6789.' => __('Please enter a valid social security number. For example 123-45-6789.'),
-            'Please enter a valid zip code. For example 90602 or 90602-1234.' => __('Please enter a valid zip code. For example 90602 or 90602-1234.'),
-            'Please enter a valid zip code.' => __('Please enter a valid zip code.'),
-            'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.' => __('Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.'),
-            'Please select one of the above options.' => __('Please select one of the above options.'),
-            'Please select one of the options.' => __('Please select one of the options.'),
-            'Please select State/Province.' => __('Please select State/Province.'),
-            'Please enter a number greater than 0 in this field.' => __('Please enter a number greater than 0 in this field.'),
-            'Please enter a number 0 or greater in this field.' => __('Please enter a number 0 or greater in this field.'),
-            'Please enter a valid credit card number.' => __('Please enter a valid credit card number.'),
-            'Credit card number does not match credit card type.' => __('Credit card number does not match credit card type.'),
-            'Card type does not match credit card number.' => __('Card type does not match credit card number.'),
-            'Incorrect credit card expiration date.' => __('Incorrect credit card expiration date.'),
-            'Please enter a valid credit card verification number.' => __('Please enter a valid credit card verification number.'),
-            'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => __('Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.'),
-            'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.' => __('Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.'),
-            'Text length does not satisfy specified text range.' => __('Text length does not satisfy specified text range.'),
-            'Please enter a number lower than 100.' => __('Please enter a number lower than 100.'),
-            'Please select a file' => __('Please select a file'),
-            'Please enter issue number or start date for switch/solo card type.' => __('Please enter issue number or start date for switch/solo card type.'),
-            'This date is a required value.' => __('This date is a required value.'),
-            'Please enter a valid day (1-%1).' => __('Please enter a valid day (1-%1).'),
-            'Please enter a valid month (1-12).' => __('Please enter a valid month (1-12).'),
-            'Please enter a valid year (1900-%1).' => __('Please enter a valid year (1900-%1).'),
-            'Please enter a valid full date' => __('Please enter a valid full date'),
-            'Allow' => __('Allow'),
-            'Activate' => __('Activate'),
-            'Reauthorize' => __('Reauthorize'),
-            'Cancel' => __('Cancel'),
-            'Done' => __('Done'),
-            'Save' => __('Save'),
-            'File extension not known or unsupported type.' => __('File extension not known or unsupported type.'),
-            'Configure Product' => __('Configure Product'),
-            'OK' => __('OK'),
-            'Gift Options for ' => __('Gift Options for '),
-            'New Option' => __('New Option'),
-            'Add Products to New Option' => __('Add Products to New Option'),
-            'Add Products to Option "%1"' => __('Add Products to Option "%1"'),
-            'Add Selected Products' => __('Add Selected Products'),
-            'Select type of option.' => __('Select type of option.'),
-            'Please add rows to option.' => __('Please add rows to option.'),
-            'Select Product' => __('Select Product'),
-            'Import' => __('Import'),
-            'Please select items.' => __('Please select items.'),
-            'Add Products to Group' => __('Add Products to Group'),
-            'start typing to search category' => __('start typing to search category'),
-            'Choose existing category.' => __('Choose existing category.'),
-            'Create Category' => __('Create Category'),
-            'Sorry, there was an unknown error.' => __('Sorry, there was an unknown error.'),
-            'Something went wrong while loading the theme.' => __('Something went wrong while loading the theme.'),
-            'We don\'t recognize or support this file extension type.' => __('We don\'t recognize or support this file extension type.'),
-            'Error' => __('Error'),
-            'No stores were reassigned.' => __('No stores were reassigned.'),
-            'Assign theme to your live store-view:' => __('Assign theme to your live store-view:'),
-            'Default title' => __('Default title'),
-            'The URL to assign stores is not defined.' => __('The URL to assign stores is not defined.'),
-            'No' => __('No'),
-            'Yes' => __('Yes'),
-            'Some problem with revert action' => __('Some problem with revert action'),
-            'Error: unknown error.' => __('Error: unknown error.'),
-            'Some problem with save action' => __('Some problem with save action'),
-            'Delete' => __('Delete'),
-            'Folder' => __('Folder'),
-            'Delete Folder' => __('Delete Folder'),
-            'Are you sure you want to delete the folder named' => __('Are you sure you want to delete the folder named'),
-            'Delete File' => __('Delete File'),
-            'Method ' => __('Method '),
-            'Please wait...' => __('Please wait...'),
-            'Loading...' => __('Loading...'),
-            'Translate' => __('Translate'),
-            'Submit' => __('Submit'),
-            'Close' => __('Close'),
-            'Please enter a value less than or equal to %s.' => __('Please enter a value less than or equal to %s.'),
-            'Please enter a value greater than or equal to %s.' => __('Please enter a value greater than or equal to %s.'),
-            'Maximum length of this field must be equal or less than %1 symbols.' => __('Maximum length of this field must be equal or less than %1 symbols.'),
-            'No records found.' => __('No records found.'),
-            'Recent items' => __('Recent items'),
-            'Show all...' => __('Show all...'),
-            'Please enter a date in the past.' => __('Please enter a date in the past.'),
-            'Please enter a date between %min and %max.' => __('Please enter a date between %min and %max.'),
-            'Please choose to register or to checkout as a guest.' => __('Please choose to register or to checkout as a guest.'),
-            'We are not able to ship to the selected shipping address. Please choose another address or edit the current address.' => __('We are not able to ship to the selected shipping address. Please choose another address or edit the current address.'),
-            'Please specify a shipping method.' => __('Please specify a shipping method.'),
-            'We can\'t complete your order because you don\'t have a payment method available.' => __('We can\'t complete your order because you don\'t have a payment method available.'),
-            'Error happened while creating wishlist. Please try again later' => __('Error happened while creating wishlist. Please try again later'),
-            'You must select items to move' => __('You must select items to move'),
-            'You must select items to copy' => __('You must select items to copy'),
-            'You are about to delete your wish list. This action cannot be undone. Are you sure you want to continue?' => __('You are about to delete your wish list. This action cannot be undone. Are you sure you want to continue?'),
-            'Please specify payment method.' => __('Please specify payment method.'),
-            'Are you sure you want to delete this address?' => __('Are you sure you want to delete this address?'),
-            'Use gift registry shipping address' => __('Use gift registry shipping address'),
-            'You can change the number of gift registry items on the Gift Registry Info page or directly in your cart, but not while in checkout.' => __('You can change the number of gift registry items on the Gift Registry Info page or directly in your cart, but not while in checkout.'),
-            'No confirmation' => __('No confirmation'),
-            'Sorry, something went wrong.' => __('Sorry, something went wrong.'),
-            'Sorry, something went wrong. Please try again later.' => __('Sorry, something went wrong. Please try again later.'),
-            'select all' => __('select all'),
-            'unselect all' => __('unselect all'),
-            'Please agree to all Terms and Conditions before placing the orders.' => __('Please agree to all Terms and Conditions before placing the orders.'),
-            'Please choose to register or to checkout as a guest' => __('Please choose to register or to checkout as a guest'),
-            'Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.' => __('Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.'),
-            'Please specify shipping method.' => __('Please specify shipping method.'),
-            'Your order cannot be completed at this time as there is no payment methods available for it.' => __('Your order cannot be completed at this time as there is no payment methods available for it.'),
-            'Edit Order' => __('Edit Order'),
-            'Ok' => __('Ok'),
-            'Please specify at least one search term.' => __('Please specify at least one search term.'),
-            'Create New Wish List' => __('Create New Wish List'),
-            'Click Details for more required fields.' => __('Click Details for more required fields.'),
-            'Incl. Tax' => __('Incl. Tax'),
-        ];
+        $dictionary = [];
+
+        $files = $this->filesUtility->getJsFiles($this->appState->getAreaCode(), $themePath);
+        foreach ($files as $filePath) {
+            $content = $this->rootDirectory->readFile($this->rootDirectory->getRelativePath($filePath[0]));
+            foreach ($this->getPhrases($content) as $phrase) {
+                $translatedPhrase = (string) __($phrase);
+                if ($phrase != $translatedPhrase) {
+                    $dictionary[$phrase] = $translatedPhrase;
+                }
+            }
+        }
+
+        return $dictionary;
+    }
+
+    /**
+     * Parse content for entries to be translated
+     *
+     * @param string $content
+     * @return string[]
+     * @throws \Exception
+     */
+    protected function getPhrases($content)
+    {
+        $phrases = [];
+        foreach ($this->config->getPatterns() as $pattern) {
+            $result = preg_match_all($pattern, $content, $matches);
+
+            if ($result) {
+                $phrases = array_merge($phrases, $matches[1]);
+            }
+            if (false === $result) {
+                throw new \Exception(
+                    sprintf('Error while generating js translation dictionary: "%s"', error_get_last())
+                );
+            }
+        }
+        return $phrases;
     }
 }
diff --git a/app/code/Magento/Translation/Model/Js/DataProviderInterface.php b/app/code/Magento/Translation/Model/Js/DataProviderInterface.php
index 74231b2ccdd959b4d50454d381121c80c7cb82bd..d9eea098e56b1e4102ba5f49b8882e9b325138f6 100644
--- a/app/code/Magento/Translation/Model/Js/DataProviderInterface.php
+++ b/app/code/Magento/Translation/Model/Js/DataProviderInterface.php
@@ -10,8 +10,8 @@ interface DataProviderInterface
 {
     /**
      * Get translation data
-     *
+     * @param string $themePath
      * @return string[]
      */
-    public function getData();
+    public function getData($themePath);
 }
diff --git a/app/code/Magento/Translation/Model/Js/PreProcessor.php b/app/code/Magento/Translation/Model/Js/PreProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..d7da55fbd50ef2d263c1b29a682d1460774da2c3
--- /dev/null
+++ b/app/code/Magento/Translation/Model/Js/PreProcessor.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Model\Js;
+
+use Magento\Framework\View\Asset\PreProcessorInterface;
+use Magento\Framework\View\Asset\PreProcessor\Chain;
+use Magento\Framework\Filesystem;
+
+/**
+ * PreProcessor responsible for replacing translation calls in js files to translated strings
+ */
+class PreProcessor implements PreProcessorInterface
+{
+    /**
+     * Javascript translation configuration
+     *
+     * @var Config
+     */
+    protected $config;
+
+    /**
+     * @param Config $config
+     */
+    public function __construct(Config $config)
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * Transform content and/or content type for the specified preprocessing chain object
+     *
+     * @param Chain $chain
+     * @return void
+     */
+    public function process(Chain $chain)
+    {
+        if ($this->config->isEmbeddedStrategy()) {
+            $chain->setContent($this->translate($chain->getContent()));
+        }
+    }
+
+    /**
+     * Replace translation calls with translation result and return content
+     *
+     * @param string $content
+     * @return string
+     */
+    public function translate($content)
+    {
+        foreach ($this->config->getPatterns() as $pattern) {
+            $content = preg_replace_callback($pattern, [$this, 'replaceCallback'], $content);
+        }
+        return $content;
+    }
+
+    /**
+     * Replace callback for preg_replace_callback function
+     *
+     * @param array $matches
+     * @return string
+     */
+    protected function replaceCallback($matches)
+    {
+        return '"' . __($matches[1]) . '"';
+    }
+}
diff --git a/app/code/Magento/Translation/Model/Json/PreProcessor.php b/app/code/Magento/Translation/Model/Json/PreProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..79c4ad0ccef842037f191f7f7f41e59f17c7ba8e
--- /dev/null
+++ b/app/code/Magento/Translation/Model/Json/PreProcessor.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Model\Json;
+
+use Magento\Framework\View\Asset\PreProcessorInterface;
+use Magento\Translation\Model\Js\Config;
+use Magento\Translation\Model\Js\DataProviderInterface;
+use Magento\Framework\View\Asset\PreProcessor\Chain;
+use Magento\Framework\View\Asset\File\FallbackContext;
+
+/**
+ * PreProcessor responsible for providing js translation dictionary
+ */
+class PreProcessor implements PreProcessorInterface
+{
+    /**
+     * Js translation configuration
+     *
+     * @var Config
+     */
+    protected $config;
+
+    /**
+     * Translation data provider
+     *
+     * @var DataProviderInterface
+     */
+    protected $dataProvider;
+
+    /**
+     * @param Config $config
+     * @param DataProviderInterface $dataProvider
+     */
+    public function __construct(
+        Config $config,
+        DataProviderInterface $dataProvider
+    ) {
+        $this->config = $config;
+        $this->dataProvider = $dataProvider;
+    }
+
+    /**
+     * Transform content and/or content type for the specified preprocessing chain object
+     *
+     * @param Chain $chain
+     * @return void
+     */
+    public function process(Chain $chain)
+    {
+        if ($this->isDictionaryPath($chain->getTargetAssetPath())) {
+            $context = $chain->getAsset()->getContext();
+            $themePath = ($context instanceof FallbackContext) ? $context->getThemePath() : '*/*';
+            $chain->setContent(json_encode($this->dataProvider->getData($themePath)));
+            $chain->setContentType('json');
+        }
+    }
+
+    /**
+     * Is provided path the path to translation dictionary
+     *
+     * @param string $path
+     * @return bool
+     */
+    protected function isDictionaryPath($path)
+    {
+        return (strpos($path, $this->config->getDictionaryFileName()) !== false);
+    }
+}
diff --git a/app/code/Magento/Translation/Test/Unit/Block/JsTest.php b/app/code/Magento/Translation/Test/Unit/Block/JsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b929b7304568653dc1fe484408b1c5acae29896e
--- /dev/null
+++ b/app/code/Magento/Translation/Test/Unit/Block/JsTest.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Test\Unit\Block;
+
+use Magento\Translation\Block\Js;
+
+class JsTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Js
+     */
+    protected $model;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $configMock;
+
+    protected function setUp()
+    {
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->configMock = $this->getMockBuilder('Magento\Translation\Model\Js\Config')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->model = $objectManager->getObject('Magento\Translation\Block\Js', ['config' => $this->configMock]);
+    }
+
+    public function testIsDictionaryStrategy()
+    {
+        $this->configMock->expects($this->once())
+            ->method('dictionaryEnabled')
+            ->willReturn(true);
+        $this->assertTrue($this->model->dictionaryEnabled());
+    }
+}
diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/Config/Source/StrategyTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/Config/Source/StrategyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..819d41663235c2739692804db7a98c97b363e207
--- /dev/null
+++ b/app/code/Magento/Translation/Test/Unit/Model/Js/Config/Source/StrategyTest.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Test\Unit\Model\Js\Config\Source;
+
+use Magento\Translation\Model\Js\Config;
+use Magento\Translation\Model\Js\Config\Source\Strategy;
+
+/**
+ * Class StrategyTest
+ */
+class StrategyTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Strategy
+     */
+    protected $model;
+
+    /**
+     * Set up
+     * @return void
+     */
+    protected function setUp()
+    {
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject('Magento\Translation\Model\Js\Config\Source\Strategy');
+    }
+
+    /**
+     * Test for toOptionArray method
+     * @return void
+     */
+    public function testToOptionArray()
+    {
+        $expected = [
+            ['label' => __('None (Translation is disabled)'), 'value' => Config::NO_TRANSLATION],
+            ['label' => 'Dictionary (Translation on frontend side)', 'value' => Config::DICTIONARY_STRATEGY],
+            ['label' => 'Embedded (Translation on backend side)', 'value' => Config::EMBEDDED_STRATEGY]
+        ];
+        $this->assertEquals($expected, $this->model->toOptionArray());
+    }
+}
diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/ConfigTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/ConfigTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0c1e59c1d3e7ca8dd2eb94a94d8dfacb75ab04f
--- /dev/null
+++ b/app/code/Magento/Translation/Test/Unit/Model/Js/ConfigTest.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Test\Unit\Model\Js;
+
+use Magento\Translation\Model\Js\Config;
+
+class ConfigTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Config
+     */
+    protected $model;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $scopeMock;
+
+    /**
+     * @var string
+     */
+    protected $patterns = ['test_pattern'];
+
+    protected function setUp()
+    {
+        $this->scopeMock = $this->getMockBuilder('Magento\Framework\App\Config\ScopeConfigInterface')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+        $this->model = $objectManager->getObject(
+            'Magento\Translation\Model\Js\Config',
+            [
+                'scopeConfig' => $this->scopeMock,
+                'patterns' => $this->patterns
+            ]
+        );
+    }
+
+    public function testIsEmbeddedStrategy()
+    {
+        $this->scopeMock->expects($this->once())
+            ->method('getValue')
+            ->with(Config::XML_PATH_STRATEGY)
+            ->willReturn(Config::EMBEDDED_STRATEGY);
+        $this->assertTrue($this->model->isEmbeddedStrategy());
+    }
+
+    public function testDictionaryEnabled()
+    {
+        $this->scopeMock->expects($this->once())
+            ->method('getValue')
+            ->with(Config::XML_PATH_STRATEGY)
+            ->willReturn(Config::DICTIONARY_STRATEGY);
+        $this->assertTrue($this->model->dictionaryEnabled());
+    }
+
+    public function testgetPatterns()
+    {
+        $this->assertEquals($this->patterns, $this->model->getPatterns());
+    }
+}
diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/DataProviderTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/DataProviderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6067c28723fb4eb764f5e2b26167a984ad9b173e
--- /dev/null
+++ b/app/code/Magento/Translation/Test/Unit/Model/Js/DataProviderTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Test\Unit\Model\Js;
+
+use Magento\Framework\App\State;
+use Magento\Framework\App\Utility\Files;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Filesystem\Directory\ReadInterface;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Translation\Model\Js\DataProvider;
+use Magento\Translation\Model\Js\Config;
+
+/**
+ * Class DataProviderTest
+ */
+class DataProviderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var DataProvider
+     */
+    protected $model;
+
+    /**
+     * @var State|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $appStateMock;
+
+    /**
+     * @var Config|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $configMock;
+
+    /**
+     * @var Files|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $filesUtilityMock;
+
+    /**
+     * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $rootDirectoryMock;
+
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->appStateMock = $this->getMock('Magento\Framework\App\State', [], [], '', false);
+        $this->configMock = $this->getMock('Magento\Translation\Model\Js\Config', [], [], '', false);
+        $this->filesUtilityMock = $this->getMock('Magento\Framework\App\Utility\Files', [], [], '', false);
+        $filesystem = $this->getMock('Magento\Framework\Filesystem', [], [], '', false);
+        $this->rootDirectoryMock = $this->getMock('Magento\Framework\Filesystem\Directory\Read', [], [], '', false);
+        $filesystem->expects($this->once())
+            ->method('getDirectoryRead')
+            ->with(DirectoryList::ROOT)
+            ->willReturn($this->rootDirectoryMock);
+        $this->model = new DataProvider(
+            $this->appStateMock,
+            $this->configMock,
+            $filesystem,
+            $this->filesUtilityMock
+        );
+    }
+
+    /**
+     * @return void
+     */
+    public function testGetData()
+    {
+        $themePath = 'blank';
+        $areaCode = 'adminhtml';
+        $files = [['path1'], ['path2']];
+
+        $relativePathMap = [
+            ['path1' => 'relativePath1'],
+            ['path2' => 'relativePath2']
+        ];
+        $contentsMap = [
+            ['relativePath1' => 'content1$.mage.__("hello1")content1'],
+            ['relativePath2' => 'content2$.mage.__("hello2")content2']
+        ];
+
+        $patterns = ['~\$\.mage\.__\([\'|\"](.+?)[\'|\"]\)~'];
+
+        $this->appStateMock->expects($this->once())
+            ->method('getAreaCode')
+            ->willReturn($areaCode);
+        $this->filesUtilityMock->expects($this->once())
+            ->method('getJsFiles')
+            ->with($areaCode, $themePath)
+            ->willReturn($files);
+
+        $this->rootDirectoryMock->expects($this->any())
+            ->method('getRelativePath')
+            ->willReturnMap($relativePathMap);
+        $this->rootDirectoryMock->expects($this->any())
+            ->method('readFile')
+            ->willReturnMap($contentsMap);
+        $this->configMock->expects($this->any())
+            ->method('getPatterns')
+            ->willReturn($patterns);
+
+        $this->assertEquals([], $this->model->getData($themePath));
+    }
+}
diff --git a/app/code/Magento/Translation/Test/Unit/Model/Js/PreProcessorTest.php b/app/code/Magento/Translation/Test/Unit/Model/Js/PreProcessorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ccfbdafeb7c32842e9460fadc44a7aa61c80389
--- /dev/null
+++ b/app/code/Magento/Translation/Test/Unit/Model/Js/PreProcessorTest.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Test\Unit\Model\Js;
+
+use Magento\Translation\Model\Js\PreProcessor;
+use Magento\Translation\Model\Js\Config;
+
+class PreProcessorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var PreProcessor
+     */
+    protected $model;
+
+    /**
+     * @var Config|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $configMock;
+
+    protected function setUp()
+    {
+        $this->configMock = $this->getMock('Magento\Translation\Model\Js\Config', [], [], '', false);
+        $this->model = new PreProcessor($this->configMock);
+    }
+
+    public function testGetData()
+    {
+        $chain = $this->getMock('Magento\Framework\View\Asset\PreProcessor\Chain', [], [], '', false);
+        $originalContent = 'content$.mage.__("hello1")content';
+        $translatedContent = 'content"hello1"content';
+        $patterns = ['~\$\.mage\.__\([\'|\"](.+?)[\'|\"]\)~'];
+
+        $this->configMock->expects($this->once())
+            ->method('isEmbeddedStrategy')
+            ->willReturn(true);
+        $chain->expects($this->once())
+            ->method('getContent')
+            ->willReturn($originalContent);
+        $this->configMock->expects($this->once())
+            ->method('getPatterns')
+            ->willReturn($patterns);
+
+        $chain->expects($this->once())
+            ->method('setContent')
+            ->with($translatedContent);
+
+        $this->model->process($chain);
+    }
+}
diff --git a/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b5e0fb2299b09aeb984eec7b80f43116f22f961
--- /dev/null
+++ b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Translation\Test\Unit\Model\Json;
+
+use Magento\Translation\Model\Js\Config;
+use Magento\Translation\Model\Js\DataProvider;
+use Magento\Translation\Model\Json\PreProcessor;
+
+class PreProcessorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var PreProcessor
+     */
+    protected $model;
+
+    /**
+     * @var Config|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $configMock;
+
+    /**
+     * @var DataProvider|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $dataProviderMock;
+
+    protected function setUp()
+    {
+        $this->configMock = $this->getMock('Magento\Translation\Model\Js\Config', [], [], '', false);
+        $this->dataProviderMock = $this->getMock('Magento\Translation\Model\Js\DataProvider', [], [], '', false);
+        $this->model = new PreProcessor($this->configMock, $this->dataProviderMock);
+    }
+
+    public function testGetData()
+    {
+        $chain = $this->getMock('Magento\Framework\View\Asset\PreProcessor\Chain', [], [], '', false);
+        $asset = $this->getMock('Magento\Framework\View\Asset\File', [], [], '', false);
+        $context = $this->getMock('Magento\Framework\View\Asset\File\FallbackContext', [], [], '', false);
+        $fileName = 'js-translation.json';
+        $targetPath = 'path/js-translation.json';
+        $themePath = '*/*';
+        $dictionary = ['hello' => 'bonjour'];
+
+        $chain->expects($this->once())
+            ->method('getTargetAssetPath')
+            ->willReturn($targetPath);
+        $this->configMock->expects($this->once())
+            ->method('getDictionaryFileName')
+            ->willReturn($fileName);
+        $chain->expects($this->once())
+            ->method('getAsset')
+            ->willReturn($asset);
+        $asset->expects($this->once())
+            ->method('getContext')
+            ->willReturn($context);
+        $context->expects($this->once())
+            ->method('getThemePath')
+            ->willReturn($themePath);
+        $this->dataProviderMock->expects($this->once())
+            ->method('getData')
+            ->with($themePath)
+            ->willReturn($dictionary);
+        $chain->expects($this->once())
+            ->method('setContent')
+            ->with(json_encode($dictionary));
+        $chain->expects($this->once())
+            ->method('setContentType')
+            ->with('json');
+
+        $this->model->process($chain);
+    }
+}
diff --git a/app/code/Magento/Translation/etc/adminhtml/system.xml b/app/code/Magento/Translation/etc/adminhtml/system.xml
new file mode 100644
index 0000000000000000000000000000000000000000..90d70e824ced6cd42cef19b0d51c39e32e5e08eb
--- /dev/null
+++ b/app/code/Magento/Translation/etc/adminhtml/system.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Config/etc/system_file.xsd">
+    <system>
+        <section id="dev">
+            <group id="js">
+                <field id="translate_strategy" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0">
+                    <label>Translation Strategy</label>
+                    <source_model>Magento\Translation\Model\Js\Config\Source\Strategy</source_model>
+                    <comment>Please put your store into maintenance mode and redeploy static files after changing strategy</comment>
+                </field>
+            </group>
+        </section>
+    </system>
+</config>
diff --git a/app/code/Magento/Translation/etc/config.xml b/app/code/Magento/Translation/etc/config.xml
index ae18303a6bc7320f54be043c77d802324d4d6d48..19cb337a6b6a804f91d385c21a64995101e8746b 100644
--- a/app/code/Magento/Translation/etc/config.xml
+++ b/app/code/Magento/Translation/etc/config.xml
@@ -15,6 +15,9 @@
                     <block_html />
                 </invalid_caches>
             </translate_inline>
+            <js>
+                <translate_strategy>none</translate_strategy>
+            </js>
         </dev>
     </default>
 </config>
diff --git a/app/code/Magento/Translation/etc/di.xml b/app/code/Magento/Translation/etc/di.xml
index 3507d99af6eec6b55fb3c2b7681e76f7510bdf34..c411e653425d197cc7fc4096d6426113ccd94aa1 100644
--- a/app/code/Magento/Translation/etc/di.xml
+++ b/app/code/Magento/Translation/etc/di.xml
@@ -13,6 +13,8 @@
     <preference for="Magento\Framework\Translate\ResourceInterface" type="Magento\Translation\Model\Resource\Translate" />
     <preference for="Magento\Framework\Translate\Inline\StateInterface" type="Magento\Framework\Translate\Inline\State" />
     <preference for="Magento\Framework\Phrase\RendererInterface" type="Magento\Framework\Phrase\Renderer\Composite" />
+    <preference for="Magento\Translation\Model\Js\DataProviderInterface" type="Magento\Translation\Model\Js\DataProvider"/>
+    <preference for="Magento\Framework\Translate\Js\Config" type="Magento\Translation\Model\Js\Config"/>
     <type name="Magento\Framework\Translate\Inline">
         <arguments>
             <argument name="templateFileName" xsi:type="string">Magento_Translation::translate_inline.phtml</argument>
@@ -52,4 +54,27 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Translation\Model\Js\Config">
+        <arguments>
+            <argument name="patterns" xsi:type="array">
+                <item name="mage_translation_widget" xsi:type="string">~\$\.mage\.__\([\'|\"](.+?)[\'|\"]\)~</item>
+            </argument>
+        </arguments>
+    </type>
+    <type name="Magento\Framework\View\Asset\PreProcessor\Pool">
+        <arguments>
+            <argument name="preProcessors" xsi:type="array">
+                <item name="js" xsi:type="array">
+                    <item name="js" xsi:type="array">
+                        <item name="js_translation" xsi:type="string">Magento\Translation\Model\Js\PreProcessor</item>
+                    </item>
+                </item>
+                <item name="json" xsi:type="array">
+                    <item name="json" xsi:type="array">
+                        <item name="json_generation" xsi:type="string">Magento\Translation\Model\Json\PreProcessor</item>
+                    </item>
+                </item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/Translation/view/base/templates/translate.phtml b/app/code/Magento/Translation/view/base/templates/translate.phtml
index be33473e166c7bb81300b8209fe7f11cda7654e3..d6c010e876528c017458f6af3c9734b10e47bce7 100644
--- a/app/code/Magento/Translation/view/base/templates/translate.phtml
+++ b/app/code/Magento/Translation/view/base/templates/translate.phtml
@@ -3,12 +3,17 @@
  * Copyright © 2015 Magento. All rights reserved.
  * See COPYING.txt for license details.
  */
+// @codingStandardsIgnoreFile
 ?>
 <?php /** @var $block \Magento\Translation\Block\Js */ ?>
+<?php if ($block->dictionaryEnabled()): ?>
 <script>
-//<![CDATA[
-    require(
-        ["jquery", "mage/translate"], function($){ $.mage.translate.add( <?php echo $block->getTranslatedJson() ?> ); }
-    )
-// ]]>
+require([
+    "jquery",
+    "mage/translate",
+    "text!<?php echo Magento\Translation\Model\Js\Config::DICTIONARY_FILE_NAME?>"
+], function($, translate, data) {
+    $.mage.translate.add(JSON.parse(data));
+});
 </script>
+<?php endif; ?>
diff --git a/app/etc/di.xml b/app/etc/di.xml
index 48402892228cc368b5457502ed828f9a27e4655d..de5633a6639e278c01403d21d01a133394e846b9 100755
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -671,27 +671,6 @@
             <argument name="raise_php_limits" xsi:type="boolean">false</argument>
         </arguments>
     </type>
-    <type name="Magento\Framework\View\Asset\PreProcessor\Pool">
-        <arguments>
-            <argument name="preProcessors" xsi:type="array">
-                <item name="less" xsi:type="array">
-                    <item name="css" xsi:type="array">
-                        <item name="less_css" xsi:type="string">Magento\Framework\Css\PreProcessor\Less</item>
-                        <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item>
-                    </item>
-                    <item name="less" xsi:type="array">
-                        <item name="magento_import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\MagentoImport</item>
-                        <item name="import" xsi:type="string">Magento\Framework\Less\PreProcessor\Instruction\Import</item>
-                    </item>
-                </item>
-                <item name="css" xsi:type="array">
-                    <item name="css" xsi:type="array">
-                        <item name="module_notation" xsi:type="string">Magento\Framework\View\Asset\PreProcessor\ModuleNotation</item>
-                    </item>
-                </item>
-            </argument>
-        </arguments>
-    </type>
     <type name="Magento\Framework\App\DefaultPath\DefaultPath">
         <arguments>
             <argument name="parts" xsi:type="array">
diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php
index e8bb754254ebeb39ae1329af1c612e69228d99c2..59747a3c427887b59895b20f16e07ce4f9fedc80 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Model/VisitorTest.php
@@ -63,18 +63,22 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
      */
     public function testClean()
     {
+        $customerIdNow = 1;
         $lastVisitNow = date('Y-m-d H:i:s', time());
         $sessionIdNow = 'asaswljxvgklasdflkjasieasd';
+        $customerIdPast = null;
         $lastVisitPast = date('Y-m-d H:i:s', time() - 172800);
         $sessionIdPast = 'kui0aa57nqddl8vk7k6ohgi352';
 
         /** @var \Magento\Customer\Model\Visitor $visitor */
         $visitor = Bootstrap::getObjectManager()->get('Magento\Customer\Model\Visitor');
+        $visitor->setCustomerId($customerIdPast);
         $visitor->setSessionId($sessionIdPast);
         $visitor->setLastVisitAt($lastVisitPast);
         $visitor->save();
         $visitorIdPast = $visitor->getId();
         $visitor->unsetData();
+        $visitor->setCustomerId($customerIdNow);
         $visitor->setSessionId($sessionIdNow);
         $visitor->setLastVisitAt($lastVisitNow);
         $visitor->save();
@@ -87,7 +91,12 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
         $visitor->unsetData();
         $visitor->load($visitorIdNow);
         $this->assertEquals(
-            ['visitor_id' => $visitorIdNow, 'session_id' => $sessionIdNow, 'last_visit_at' => $lastVisitNow],
+            [
+                'visitor_id' => $visitorIdNow,
+                'customer_id' => $customerIdNow,
+                'session_id' => $sessionIdNow,
+                'last_visit_at' => $lastVisitNow
+            ],
             $visitor->getData()
         );
     }
diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Phrase/JsTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Phrase/JsTest.php
deleted file mode 100644
index 08981d2585ce547dc6d105959a973d8afdfa6167..0000000000000000000000000000000000000000
--- a/dev/tests/static/testsuite/Magento/Test/Integrity/Phrase/JsTest.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-/**
- * Scan javascript files for invocations of mage.__() function, verifies that all the translations
- * were output to the page.
- *
- * Copyright © 2015 Magento. All rights reserved.
- * See COPYING.txt for license details.
- */
-namespace Magento\Test\Integrity\Phrase;
-
-use Magento\Tools\I18n\Parser\Adapter;
-use Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer;
-use Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer\PhraseCollector;
-
-class JsTest extends \Magento\Test\Integrity\Phrase\AbstractTestCase
-{
-    /**
-     * @var \Magento\Tools\I18n\Parser\Adapter\Js
-     */
-    protected $_parser;
-
-    /** @var \Magento\Framework\App\Utility\Files  */
-    protected $_utilityFiles;
-
-    /** @var \Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer\PhraseCollector */
-    protected $_phraseCollector;
-
-    protected function setUp()
-    {
-        $this->_parser = new \Magento\Tools\I18n\Parser\Adapter\Js();
-        $this->_utilityFiles = \Magento\Framework\App\Utility\Files::init();
-        $this->_phraseCollector = new \Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer\PhraseCollector(
-            new \Magento\Tools\I18n\Parser\Adapter\Php\Tokenizer()
-        );
-    }
-
-    public function testGetPhrasesAdminhtml()
-    {
-        $unregisteredMessages = [];
-        $untranslated = [];
-
-        $registeredPhrases = $this->_getRegisteredPhrases();
-
-        require_once BP . '/app/code/Magento/Backend/App/Area/FrontNameResolver.php';
-        foreach ($this->_getJavascriptPhrases(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) as $phrase) {
-            if (!in_array($phrase['phrase'], $registeredPhrases)) {
-                $unregisteredMessages[] = sprintf(
-                    "'%s' \n in file %s, line# %s",
-                    $phrase['phrase'],
-                    $phrase['file'],
-                    $phrase['line']
-                );
-                $untranslated[] = $phrase['phrase'];
-            }
-        }
-
-        if (count($unregisteredMessages) > 0) {
-            $this->fail(
-                'There are UI messages in javascript files for adminhtml area ' .
-                "which requires translations to be output to the page: \n\n" .
-                implode(
-                    "\n",
-                    $unregisteredMessages
-                )
-            );
-        }
-    }
-
-    public function testGetPhrasesFrontend()
-    {
-        $unregisteredMessages = [];
-        $untranslated = [];
-
-        $registeredPhrases = $this->_getRegisteredPhrases();
-
-        foreach ($this->_getJavascriptPhrases('frontend') as $phrase) {
-            if (!in_array($phrase['phrase'], $registeredPhrases)) {
-                $unregisteredMessages[] = sprintf(
-                    "'%s' \n in file %s, line# %s",
-                    $phrase['phrase'],
-                    $phrase['file'],
-                    $phrase['line']
-                );
-                $untranslated[] = $phrase['phrase'];
-            }
-        }
-
-        if (count($unregisteredMessages) > 0) {
-            $this->fail(
-                'There are UI messages in javascript files for frontend area ' .
-                "which requires translations to be output to the page: \n\n" .
-                implode(
-                    "\n",
-                    $unregisteredMessages
-                )
-            );
-        }
-    }
-
-    /**
-     * Returns an array of phrases that can be used by JS files.
-     *
-     * @return string[]
-     */
-    protected function _getRegisteredPhrases()
-    {
-        $jsHelperFile = realpath(
-            __DIR__ . '/../../../../../../../../app/code/Magento/Translation/Model/Js/DataProvider.php'
-        );
-
-        $this->_phraseCollector->parse($jsHelperFile);
-
-        $result = [];
-        foreach ($this->_phraseCollector->getPhrases() as $phrase) {
-            $result[] = stripcslashes(trim($phrase['phrase'], "'"));
-        }
-        return $result;
-    }
-
-    /**
-     * Returns an array of phrases used by JavaScript files in a specific area of magento.
-     *
-     * @param string $area of magento to search, such as 'frontend' or 'adminthml'
-     * @return string[]
-     */
-    protected function _getJavascriptPhrases($area)
-    {
-        $jsPhrases = [];
-        foreach ($this->_utilityFiles->getJsFilesForArea($area) as $file) {
-            $this->_parser->parse($file);
-            $jsPhrases = array_merge($jsPhrases, $this->_parser->getPhrases());
-        }
-        return $jsPhrases;
-    }
-}
diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php
index 1afa25055ddc04de479b612ea4e9666cd1a5f0c4..92b49b9ef7cd70ccc802117e6b8cb936c7da22a0 100644
--- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php
+++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php
@@ -3104,6 +3104,7 @@ return [
     ['Magento\Framework\Module\Updater'],
     ['Magento\Setup\Module\SetupFactory'],
     ['Magento\Framework\Module\Updater\SetupFactory'],
+    ['Magento\Log\Block\Adminhtml\Customer\Edit\Tab\View\Status'],
     ['Magento\Backend\Model\Config\Source\Yesno', 'Magento\Config\Model\Config\Source\Yesno'],
     ['Magento\Reports\Model\Resource\Shopcart\Product\Collection'],
     ['Zend_Locale', '\Locale, \ResourceBundle'],
diff --git a/dev/tools/Magento/Tools/View/Deployer.php b/dev/tools/Magento/Tools/View/Deployer.php
index fafd45319ff081370c89c805a89bcc870ca235b0..1549724017780a260891f4f386faf09962180a5d 100644
--- a/dev/tools/Magento/Tools/View/Deployer.php
+++ b/dev/tools/Magento/Tools/View/Deployer.php
@@ -10,6 +10,8 @@ use Magento\Framework\App\ObjectManagerFactory;
 use Magento\Framework\App\View\Deployment\Version;
 use Magento\Framework\App\View\Asset\Publisher;
 use Magento\Framework\App\Utility\Files;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\Translate\Js\Config as JsTranslationConfig;
 
 /**
  * A service for deploying Magento static view files for production mode
@@ -57,12 +59,23 @@ class Deployer
     /** @var \Magento\Framework\View\Asset\MinifyService */
     protected $minifyService;
 
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * @var JsTranslationConfig
+     */
+    protected $jsTranslationConfig;
+
     /**
      * @param Files $filesUtil
      * @param Deployer\Log $logger
      * @param Version\StorageInterface $versionStorage
      * @param \Magento\Framework\Stdlib\DateTime $dateTime
      * @param \Magento\Framework\View\Asset\MinifyService $minifyService
+     * @param JsTranslationConfig $jsTranslationConfig
      * @param bool $isDryRun
      */
     public function __construct(
@@ -71,6 +84,7 @@ class Deployer
         Version\StorageInterface $versionStorage,
         \Magento\Framework\Stdlib\DateTime $dateTime,
         \Magento\Framework\View\Asset\MinifyService $minifyService,
+        JsTranslationConfig $jsTranslationConfig,
         $isDryRun = false
     ) {
         $this->filesUtil = $filesUtil;
@@ -79,6 +93,7 @@ class Deployer
         $this->dateTime = $dateTime;
         $this->isDryRun = $isDryRun;
         $this->minifyService = $minifyService;
+        $this->jsTranslationConfig = $jsTranslationConfig;
     }
 
     /**
@@ -87,6 +102,7 @@ class Deployer
      * @param ObjectManagerFactory $omFactory
      * @param array $locales
      * @return void
+     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
      */
     public function deploy(ObjectManagerFactory $omFactory, array $locales)
     {
@@ -101,6 +117,7 @@ class Deployer
         foreach ($areas as $area => $themes) {
             $this->emulateApplicationArea($area);
             foreach ($locales as $locale) {
+                $this->emulateApplicationLocale($locale, $area);
                 foreach ($themes as $themePath) {
                     $this->logger->logMessage("=== {$area} -> {$themePath} -> {$locale} ===");
                     $this->count = 0;
@@ -112,6 +129,15 @@ class Deployer
                     foreach ($libFiles as $filePath) {
                         $this->deployFile($filePath, $area, $themePath, $locale, null);
                     }
+                    if ($this->jsTranslationConfig->dictionaryEnabled()) {
+                        $this->deployFile(
+                            $this->jsTranslationConfig->getDictionaryFileName(),
+                            $area,
+                            $themePath,
+                            $locale,
+                            null
+                        );
+                    }
                     $this->bundleManager->flush();
                     $this->logger->logMessage("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n");
                 }
@@ -176,19 +202,36 @@ class Deployer
      */
     private function emulateApplicationArea($areaCode)
     {
-        $objectManager = $this->omFactory->create(
+        $this->objectManager = $this->omFactory->create(
             [\Magento\Framework\App\State::PARAM_MODE => \Magento\Framework\App\State::MODE_DEFAULT]
         );
         /** @var \Magento\Framework\App\State $appState */
-        $appState = $objectManager->get('Magento\Framework\App\State');
+        $appState = $this->objectManager->get('Magento\Framework\App\State');
         $appState->setAreaCode($areaCode);
         /** @var \Magento\Framework\App\ObjectManager\ConfigLoader $configLoader */
-        $configLoader = $objectManager->get('Magento\Framework\App\ObjectManager\ConfigLoader');
-        $objectManager->configure($configLoader->load($areaCode));
-        $this->assetRepo = $objectManager->get('Magento\Framework\View\Asset\Repository');
-        $this->assetPublisher = $objectManager->create('Magento\Framework\App\View\Asset\Publisher');
-        $this->htmlMinifier = $objectManager->get('Magento\Framework\View\Template\Html\MinifierInterface');
-        $this->bundleManager = $objectManager->get('Magento\Framework\View\Asset\Bundle\Manager');
+        $configLoader = $this->objectManager->get('Magento\Framework\App\ObjectManager\ConfigLoader');
+        $this->objectManager->configure($configLoader->load($areaCode));
+        $this->assetRepo = $this->objectManager->get('Magento\Framework\View\Asset\Repository');
+
+        $this->assetPublisher = $this->objectManager->create('Magento\Framework\App\View\Asset\Publisher');
+        $this->htmlMinifier = $this->objectManager->get('Magento\Framework\View\Template\Html\MinifierInterface');
+        $this->bundleManager = $this->objectManager->get('Magento\Framework\View\Asset\Bundle\Manager');
+
+    }
+
+    /**
+     * Set application locale and load translation for area
+     *
+     * @param string $locale
+     * @param string $area
+     * @return void
+     */
+    protected function emulateApplicationLocale($locale, $area)
+    {
+        /** @var \Magento\Framework\TranslateInterface $translator */
+        $translator = $this->objectManager->get('Magento\Framework\TranslateInterface');
+        $translator->setLocale($locale);
+        $translator->loadData($area, true);
     }
 
     /**
diff --git a/lib/internal/Magento/Framework/App/Utility/Files.php b/lib/internal/Magento/Framework/App/Utility/Files.php
index 97b389f125f278e3d7f67176894ab2ec0905f06c..15869c4be7c49c67c886bab1b14484c2f5b1b5ea 100644
--- a/lib/internal/Magento/Framework/App/Utility/Files.php
+++ b/lib/internal/Magento/Framework/App/Utility/Files.php
@@ -490,18 +490,18 @@ class Files
     /**
      * Returns list of Javascript files in Magento
      *
+     * @param string $area
+     * @param string $themePath
+     * @param string $namespace
+     * @param string $module
      * @return array
      */
-    public function getJsFiles()
+    public function getJsFiles($area = '*', $themePath = '*/*', $namespace = '*', $module = '*')
     {
-        $key = __METHOD__ . $this->_path;
+        $key = $area . $themePath . $namespace . $module . __METHOD__ . $this->_path;
         if (isset(self::$_cache[$key])) {
             return self::$_cache[$key];
         }
-        $namespace = '*';
-        $module = '*';
-        $area = '*';
-        $themePath = '*/*';
         $files = self::getFiles(
             [
                 "{$this->_path}/app/code/{$namespace}/{$module}/view/{$area}/web",
diff --git a/lib/internal/Magento/Framework/Test/Unit/Translate/Js/ConfigTest.php b/lib/internal/Magento/Framework/Test/Unit/Translate/Js/ConfigTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2dcba665cde41cda8ff2f05642d8c0761d8ca1c
--- /dev/null
+++ b/lib/internal/Magento/Framework/Test/Unit/Translate/Js/ConfigTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Framework\Test\Unit\Translate\Js;
+
+use Magento\Framework\Translate\Js\Config;
+
+/**
+ * Class ConfigTest
+ */
+class ConfigTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @return void
+     */
+    public function testDefault()
+    {
+        $config = new Config();
+        $this->assertFalse($config->dictionaryEnabled());
+        $this->assertNull($config->getDictionaryFileName());
+    }
+
+    /**
+     * @return void
+     */
+    public function testCustom()
+    {
+        $path = 'path';
+        $config = new Config(true, $path);
+        $this->assertTrue($config->dictionaryEnabled());
+        $this->assertEquals($path, $config->getDictionaryFileName());
+    }
+}
diff --git a/lib/internal/Magento/Framework/Test/Unit/View/Asset/SourceTest.php b/lib/internal/Magento/Framework/Test/Unit/View/Asset/SourceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1d22dca5d9a9692a2c8fed68235edee03e965f7
--- /dev/null
+++ b/lib/internal/Magento/Framework/Test/Unit/View/Asset/SourceTest.php
@@ -0,0 +1,285 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+// @codingStandardsIgnoreFile
+
+namespace Magento\Framework\Test\Unit\View\Asset;
+
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem\DriverPool;
+use Magento\Framework\View\Asset\Source;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class SourceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $filesystem;
+
+    /**
+     * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $rootDirRead;
+
+    /**
+     * @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $varDir;
+
+    /**
+     * @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $staticDirRead;
+
+    /**
+     * @var \Magento\Framework\View\Asset\PreProcessor\Cache|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $cache;
+
+    /**
+     * @var \Magento\Framework\View\Asset\PreProcessor\Pool|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $preProcessorPool;
+
+    /**
+     * @var \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $viewFileResolution;
+
+    /**
+     * @var \Magento\Framework\View\Design\ThemeInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $theme;
+
+    /**
+     * @var Source
+     */
+    private $object;
+
+    protected function setUp()
+    {
+        $this->cache = $this->getMock(
+            'Magento\Framework\View\Asset\PreProcessor\Cache', [], [], '', false
+        );
+        $this->preProcessorPool = $this->getMock(
+            'Magento\Framework\View\Asset\PreProcessor\Pool', [], [], '', false
+        );
+        $this->viewFileResolution = $this->getMock(
+            'Magento\Framework\View\Design\FileResolution\Fallback\StaticFile', [], [], '', false
+        );
+        $this->theme = $this->getMockForAbstractClass('Magento\Framework\View\Design\ThemeInterface');
+
+        $themeList = $this->getMockForAbstractClass('Magento\Framework\View\Design\Theme\ListInterface');
+        $themeList->expects($this->any())
+            ->method('getThemeByFullPath')
+            ->with('frontend/magento_theme')
+            ->will($this->returnValue($this->theme));
+
+        $this->initFilesystem();
+
+        $this->object = new Source(
+            $this->cache,
+            $this->filesystem,
+            $this->preProcessorPool,
+            $this->viewFileResolution,
+            $themeList
+        );
+    }
+
+    public function testGetFileCached()
+    {
+        $root = '/root/some/file.ext';
+        $expected = '/var/some/file.ext';
+        $filePath = 'some/file.ext';
+        $this->viewFileResolution->expects($this->once())
+            ->method('getFile')
+            ->with('frontend', $this->theme, 'en_US', $filePath, 'Magento_Module')
+            ->will($this->returnValue($root));
+        $this->rootDirRead->expects($this->once())
+            ->method('getRelativePath')
+            ->with($root)
+            ->will($this->returnValue($filePath));
+        $this->cache->expects($this->once())
+            ->method('load')
+            ->with("some/file.ext:{$filePath}")
+            ->will($this->returnValue(serialize([DirectoryList::VAR_DIR, $filePath])));
+
+        $this->varDir->expects($this->once())->method('getAbsolutePath')
+            ->with($filePath)
+            ->will($this->returnValue($expected));
+        $this->assertSame($expected, $this->object->getFile($this->getAsset()));
+    }
+
+    /**
+     * @param string $origFile
+     * @param string $origPath
+     * @param string $origContentType
+     * @param string $origContent
+     * @param string $isMaterialization
+     * @dataProvider getFileDataProvider
+     */
+    public function testGetFile($origFile, $origPath, $origContent, $isMaterialization)
+    {
+        $filePath = 'some/file.ext';
+        $cacheValue = "{$origPath}:{$filePath}";
+        $this->viewFileResolution->expects($this->once())
+            ->method('getFile')
+            ->with('frontend', $this->theme, 'en_US', $filePath, 'Magento_Module')
+            ->will($this->returnValue($origFile));
+        $this->rootDirRead->expects($this->once())
+            ->method('getRelativePath')
+            ->with($origFile)
+            ->will($this->returnValue($origPath));
+        $this->cache->expects($this->once())
+            ->method('load')
+            ->will($this->returnValue(false));
+        $this->rootDirRead->expects($this->once())
+            ->method('readFile')
+            ->with($origPath)
+            ->will($this->returnValue($origContent));
+        $this->preProcessorPool->expects($this->once())
+            ->method('process')
+            ->will($this->returnCallback([$this, 'chainTestCallback']));
+        if ($isMaterialization) {
+            $this->varDir->expects($this->once())
+                ->method('writeFile')
+                ->with('view_preprocessed/source/some/file.ext', 'processed');
+            $this->cache->expects($this->once())
+                ->method('save')
+                ->with(
+                    serialize([DirectoryList::VAR_DIR, 'view_preprocessed/source/some/file.ext']),
+                    $cacheValue
+                );
+            $this->varDir->expects($this->once())
+                ->method('getAbsolutePath')
+                ->with('view_preprocessed/source/some/file.ext')->will($this->returnValue('result'));
+        } else {
+            $this->varDir->expects($this->never())->method('writeFile');
+            $this->cache->expects($this->once())
+                ->method('save')
+                ->with(serialize([DirectoryList::ROOT, 'source/some/file.ext']), $cacheValue);
+            $this->rootDirRead->expects($this->once())
+                ->method('getAbsolutePath')
+                ->with('source/some/file.ext')
+                ->will($this->returnValue('result'));
+        }
+        $this->assertSame('result', $this->object->getFile($this->getAsset()));
+    }
+
+    /**
+     * @param string $path
+     * @param string $expected
+     * @dataProvider getContentTypeDataProvider
+     */
+    public function testGetContentType($path, $expected)
+    {
+        $this->assertEquals($expected, $this->object->getContentType($path));
+    }
+
+    /**
+     * @return array
+     */
+    public function getContentTypeDataProvider()
+    {
+        return [
+            ['', ''],
+            ['path/file', ''],
+            ['path/file.ext', 'ext'],
+        ];
+    }
+
+    /**
+     * A callback for affecting preprocessor chain in the test
+     *
+     * @param \Magento\Framework\View\Asset\PreProcessor\Chain $chain
+     */
+    public function chainTestCallback(\Magento\Framework\View\Asset\PreProcessor\Chain $chain)
+    {
+        $chain->setContentType('ext');
+        $chain->setContent('processed');
+    }
+
+    /**
+     * @return array
+     */
+    public function getFileDataProvider()
+    {
+        return [
+            ['/root/some/file.ext', 'source/some/file.ext', 'processed', false],
+            ['/root/some/file.ext', 'source/some/file.ext', 'not_processed', true],
+            ['/root/some/file.ext2', 'source/some/file.ext2', 'processed', true],
+            ['/root/some/file.ext2', 'source/some/file.ext2', 'not_processed', true],
+        ];
+    }
+
+    protected function initFilesystem()
+    {
+        $this->filesystem = $this->getMock('Magento\Framework\Filesystem', [], [], '', false);
+        $this->rootDirRead = $this->getMockForAbstractClass('Magento\Framework\Filesystem\Directory\ReadInterface');
+        $this->staticDirRead = $this->getMockForAbstractClass('Magento\Framework\Filesystem\Directory\ReadInterface');
+        $this->varDir = $this->getMockForAbstractClass('Magento\Framework\Filesystem\Directory\WriteInterface');
+
+        $readDirMap = [
+            [DirectoryList::ROOT, DriverPool::FILE, $this->rootDirRead],
+            [DirectoryList::STATIC_VIEW, DriverPool::FILE, $this->staticDirRead],
+            [DirectoryList::VAR_DIR, DriverPool::FILE, $this->varDir],
+        ];
+
+        $this->filesystem->expects($this->any())
+            ->method('getDirectoryRead')
+            ->will($this->returnValueMap($readDirMap));
+        $this->filesystem->expects($this->any())
+            ->method('getDirectoryWrite')
+            ->with(DirectoryList::VAR_DIR)
+            ->will($this->returnValue($this->varDir));
+    }
+
+    /**
+     * Create an asset mock
+     *
+     * @param bool $isFallback
+     * @return \Magento\Framework\View\Asset\File|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected function getAsset($isFallback = true)
+    {
+        if ($isFallback) {
+            $context = new \Magento\Framework\View\Asset\File\FallbackContext(
+                'http://example.com/static/',
+                'frontend',
+                'magento_theme',
+                'en_US'
+            );
+        } else {
+            $context = new \Magento\Framework\View\Asset\File\Context(
+                'http://example.com/static/',
+                DirectoryList::STATIC_VIEW,
+                ''
+            );
+        }
+
+        $asset = $this->getMock('Magento\Framework\View\Asset\File', [], [], '', false);
+        $asset->expects($this->any())
+            ->method('getContext')
+            ->will($this->returnValue($context));
+        $asset->expects($this->any())
+            ->method('getFilePath')
+            ->will($this->returnValue('some/file.ext'));
+        $asset->expects($this->any())
+            ->method('getPath')
+            ->will($this->returnValue('some/file.ext'));
+        $asset->expects($this->any())
+            ->method('getModule')
+            ->will($this->returnValue('Magento_Module'));
+        $asset->expects($this->any())
+            ->method('getContentType')
+            ->will($this->returnValue('ext'));
+
+        return $asset;
+    }
+}
diff --git a/lib/internal/Magento/Framework/Translate/Js/Config.php b/lib/internal/Magento/Framework/Translate/Js/Config.php
new file mode 100644
index 0000000000000000000000000000000000000000..17ec1bbba4af1057cf68070bbf0a0640c064641f
--- /dev/null
+++ b/lib/internal/Magento/Framework/Translate/Js/Config.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\Translate\Js;
+
+/**
+ * Js Translation config
+ */
+class Config
+{
+    /**
+     * Should the framework generate dictionary file
+     *
+     * @var bool
+     */
+    protected $dictionaryEnabled;
+
+    /**
+     * Name of dictionary json file
+     *
+     * @var string
+     */
+    protected $dictionaryFileName;
+
+    /**
+     * @param bool $dictionaryEnabled
+     * @param string $dictionaryFileName
+     */
+    public function __construct($dictionaryEnabled = false, $dictionaryFileName = null)
+    {
+        $this->dictionaryEnabled = $dictionaryEnabled;
+        $this->dictionaryFileName = $dictionaryFileName;
+    }
+
+    /**
+     * Should the framework generate dictionary file
+     *
+     * @return bool
+     */
+    public function dictionaryEnabled()
+    {
+        return $this->dictionaryEnabled;
+    }
+
+    /**
+     * Name of dictionary json file
+     *
+     * @return string
+     */
+    public function getDictionaryFileName()
+    {
+        return $this->dictionaryFileName;
+    }
+}
diff --git a/lib/internal/Magento/Framework/View/Asset/Repository.php b/lib/internal/Magento/Framework/View/Asset/Repository.php
index 4e46b91fba0d65cbbdcafd456b9b6efcb75b4433..fda21b7564074f7412b7430e9927ddef9a64e1bf 100644
--- a/lib/internal/Magento/Framework/View/Asset/Repository.php
+++ b/lib/internal/Magento/Framework/View/Asset/Repository.php
@@ -152,7 +152,7 @@ class Repository
             $module = $params['module'];
         }
         $isSecure = isset($params['_secure']) ? (bool) $params['_secure'] : null;
-        $themePath = $this->design->getThemePath($params['themeModel']);
+        $themePath = isset($params['theme']) ? $params['theme'] : $this->design->getThemePath($params['themeModel']);
         $context = $this->getFallbackContext(
             UrlInterface::URL_TYPE_STATIC,
             $isSecure,
diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php
index 747ab55ab07c3d4952587065da0fa6f47841d33a..2518c6973eaf06b14584ed0d435a0dea5ade18b9 100644
--- a/lib/internal/Magento/Framework/View/Asset/Source.php
+++ b/lib/internal/Magento/Framework/View/Asset/Source.php
@@ -129,9 +129,6 @@ class Source
     private function preProcess(LocalInterface $asset)
     {
         $sourceFile = $this->findSourceFile($asset);
-        if (!$sourceFile) {
-            return false;
-        }
         $dirCode = DirectoryList::ROOT;
         $path = $this->rootDir->getRelativePath($sourceFile);
         $cacheId = $path . ':' . $asset->getPath();
@@ -141,7 +138,7 @@ class Source
         }
         $chain = new \Magento\Framework\View\Asset\PreProcessor\Chain(
             $asset,
-            $this->rootDir->readFile($path),
+            $path ? $this->rootDir->readFile($path) : "",
             $this->getContentType($path),
             $path,
             $this->appMode
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php
index 7c14c7b32ccd265093688328a4959bb8413a90a5..026fbf85e677e1977a0ce9e51b23664c9f848b21 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php
@@ -92,24 +92,6 @@ class SourceTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    public function testGetFileNoOriginalFile()
-    {
-        $this->viewFileResolution->expects($this->once())
-            ->method('getFile')
-            ->with('frontend', $this->theme, 'en_US', 'some/file.ext', 'Magento_Module')
-            ->will($this->returnValue(false));
-        $this->assertFalse($this->object->getFile($this->getAsset()));
-    }
-
-    public function testGetFileNoOriginalFileBasic()
-    {
-        $this->staticDirRead->expects($this->once())
-            ->method('getAbsolutePath')
-            ->with('some/file.ext')
-            ->will($this->returnValue(false));
-        $this->assertFalse($this->object->getFile($this->getAsset(false)));
-    }
-
     public function testGetFileCached()
     {
         $root = '/root/some/file.ext';
diff --git a/lib/web/jquery/jquery.validate.js b/lib/web/jquery/jquery.validate.js
index 58fdf10e665f2de752181fcfc09e1cfc5ecad2c2..85d0621b02244ae568e56509e83f9ad3dde2d626 100644
--- a/lib/web/jquery/jquery.validate.js
+++ b/lib/web/jquery/jquery.validate.js
@@ -288,22 +288,22 @@
         },
 
         messages: {
-            required: "This field is required.",
-            remote: "Please fix this field.",
-            email: "Please enter a valid email address.",
-            url: "Please enter a valid URL.",
-            date: "Please enter a valid date.",
-            dateISO: "Please enter a valid date (ISO).",
-            number: "Please enter a valid number.",
-            digits: "Please enter only digits.",
-            creditcard: "Please enter a valid credit card number.",
-            equalTo: "Please enter the same value again.",
-            maxlength: $.validator.format("Please enter no more than {0} characters."),
-            minlength: $.validator.format("Please enter at least {0} characters."),
-            rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
-            range: $.validator.format("Please enter a value between {0} and {1}."),
-            max: $.validator.format("Please enter a value less than or equal to {0}."),
-            min: $.validator.format("Please enter a value greater than or equal to {0}.")
+            required: $.mage.__("This field is required."),
+            remote: $.mage.__("Please fix this field."),
+            email: $.mage.__("Please enter a valid email address."),
+            url: $.mage.__("Please enter a valid URL."),
+            date: $.mage.__("Please enter a valid date."),
+            dateISO: $.mage.__("Please enter a valid date (ISO)."),
+            number: $.mage.__("Please enter a valid number."),
+            digits: $.mage.__("Please enter only digits."),
+            creditcard: $.mage.__("Please enter a valid credit card number."),
+            equalTo: $.mage.__("Please enter the same value again."),
+            maxlength: $.validator.format($.mage.__("Please enter no more than {0} characters.")),
+            minlength: $.validator.format($.mage.__("Please enter at least {0} characters.")),
+            rangelength: $.validator.format($.mage.__("Please enter a value between {0} and {1} characters long.")),
+            range: $.validator.format($.mage.__("Please enter a value between {0} and {1}.")),
+            max: $.validator.format($.mage.__("Please enter a value less than or equal to {0}.")),
+            min: $.validator.format($.mage.__("Please enter a value greater than or equal to {0}."))
         },
 
         autoCreateRanges: false,
@@ -621,6 +621,7 @@
             },
 
             defaultMessage: function (element, method) {
+                var noMessage = $.mage.__("Warning: No message defined for %s");
                 return this.findDefined(
                     this.customMessage(element.name, method),
                     this.customDataMessage(element, method),
@@ -628,7 +629,7 @@
                     // title is never undefined, so handle empty string as undefined
                     !this.settings.ignoreTitle && element.title || undefined,
                     $.validator.messages[method],
-                    "<strong>Warning: No message defined for " + element.name + "</strong>"
+                    "<strong>" + noMessage.replace('%s', element.name) + "</strong>"
                 );
             },
 
diff --git a/lib/web/mage/decorate.js b/lib/web/mage/decorate.js
index 0babea53af81b9f42e7db1bedf2e3266c26a278c..2c629479e497f1ccc31fdf570cf4ea44b3c050d3 100644
--- a/lib/web/mage/decorate.js
+++ b/lib/web/mage/decorate.js
@@ -111,7 +111,8 @@
         } else if (typeof method === 'object' || ! method) {
             return methods.init.apply(this, arguments);
         } else {
-            $.error($.mage.__('Method ' +  method + ' does not exist on jQuery.decorate'));
+            var message = $.mage.__('Method %s does not exist on jQuery.decorate');
+            $.error(message.replace('%s', method));
         }
     };
 
diff --git a/lib/web/mage/translate-inline-vde.js b/lib/web/mage/translate-inline-vde.js
index 6c2b3a53b7f927fe0f9ca3679a131568bb9f6038..0e70103b8f06fa3173582e16ed52cd42032d86cb 100644
--- a/lib/web/mage/translate-inline-vde.js
+++ b/lib/web/mage/translate-inline-vde.js
@@ -127,7 +127,7 @@
          */
         _checkTranslateEditing: function(event, data) {
              if (this.isBeingEdited) {
-                alert($.mage.__(data.alert_message));
+                alert(data.alert_message);
                 data.is_being_edited = true;
             }
             else {
diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js
index 6e14e33548ddd8d773f3149689b5158c089cd969..7632ce9b84977587975da37348f383f39395c89e 100644
--- a/lib/web/mage/validation.js
+++ b/lib/web/mage/validation.js
@@ -934,7 +934,8 @@
                 return true;
             },
             function() {
-                return $.mage.__('Please enter a value less than or equal to %s.').replace('%s', this.lteToVal);
+                var message = $.mage.__('Please enter a value less than or equal to %s.');
+                return message.replace('%s', this.lteToVal);
             }
         ],
         "greater-than-equals-to": [
@@ -946,7 +947,8 @@
                 return true;
             },
             function() {
-                return $.mage.__('Please enter a value greater than or equal to %s.').replace('%s', this.gteToVal);
+                var message = $.mage.__('Please enter a value greater than or equal to %s.');
+                return message.replace('%s', this.gteToVal);
             }
         ],
         "validate-emails": [
@@ -1250,7 +1252,7 @@
     var showLabel = $.validator.prototype.showLabel;
     $.extend(true, $.validator.prototype, {
         showLabel: function(element, message) {
-            showLabel.call(this, element, $.mage.__(message));
+            showLabel.call(this, element, message);
 
             // ARIA (adding aria-invalid & aria-describedby)
             var label = this.errorsFor(element),
diff --git a/lib/web/mage/validation/validation.js b/lib/web/mage/validation/validation.js
index 42013cf2ca1b41c1e6a1eb40b22cb9417f8b9d0a..d666d2025c2636cb00b27e5d2af2c8baa584323d 100644
--- a/lib/web/mage/validation/validation.js
+++ b/lib/web/mage/validation/validation.js
@@ -72,7 +72,8 @@
                 if (inputDate >= minDate && inputDate <= maxDate) {
                     return true;
                 }
-                this.dateBetweenErrorMessage = $.mage.__('Please enter a date between %min and %max.').replace('%min', minDate).replace('%max', maxDate);
+                var message = $.mage.__('Please enter a date between %min and %max.');
+                this.dateBetweenErrorMessage = message.replace('%min', minDate).replace('%max', maxDate);
                 return false;
             },
             function () {
@@ -107,14 +108,14 @@
                     return false;
                 }
                 if (year < 1900 || year > curYear) {
-                    this.dobErrorMessage =
-                        $.mage.__('Please enter a valid year (1900-%1).').replace('%1', curYear.toString());
+                    var validYearMessage = $.mage.__('Please enter a valid year (1900-%1).');
+                    this.dobErrorMessage = validYearMessage.replace('%1', curYear.toString());
                     return false;
                 }
                 var validateDayInMonth = new Date(year, month, 0).getDate();
                 if (day < 1 || day > validateDayInMonth) {
-                    this.dobErrorMessage =
-                        $.mage.__('Please enter a valid day (1-%1).').replace('%1', validateDayInMonth.toString());
+                    var validDateMessage = $.mage.__('Please enter a valid day (1-%1).');
+                    this.dobErrorMessage = validDateMessage.replace('%1', validateDayInMonth.toString());
                     return false;
                 }
                 var today = new Date(),