Преглед изворни кода

Merge pull request #240 from ckeditor/t/232

Implemented Locale and used Locale#t in the existing code. Closes #232.
Aleksander Nowodzinski пре 9 година
родитељ
комит
88b74d6a10

+ 20 - 5
packages/ckeditor5-engine/src/editor.js

@@ -9,6 +9,7 @@ import ObservableMixin from './observablemixin.js';
 import EditorConfig from './editorconfig.js';
 import PluginCollection from './plugincollection.js';
 import CKEditorError from './ckeditorerror.js';
+import Locale from './locale.js';
 import isArray from './lib/lodash/isArray.js';
 import utils from './utils.js';
 
@@ -33,7 +34,7 @@ export default class Editor {
 		 * editor creation and is not subject to be modified.
 		 *
 		 * @readonly
-		 * @type {HTMLElement}
+		 * @member {HTMLElement}
 		 */
 		this.element = element;
 
@@ -45,23 +46,37 @@ export default class Editor {
 		 * instance itself.
 		 *
 		 * @readonly
-		 * @type {Config}
+		 * @member {Config}
 		 */
-		this.config = new EditorConfig( config );
+		this.config = config = new EditorConfig( config );
 
 		/**
 		 * The plugins loaded and in use by this editor instance.
 		 *
 		 * @readonly
-		 * @type {PluginCollection}
+		 * @member {PluginCollection}
 		 */
 		this.plugins = new PluginCollection( this );
 
+		/**
+		 * @readonly
+		 * @member {core.Locale} core.Editor#locale
+		 */
+		this.locale = new Locale( config.lang );
+
+		/**
+		 * Shorthand for {@link core.Locale#t}.
+		 *
+		 * @see core.Locale#t
+		 * @method t
+		 */
+		this.t = this.locale.t;
+
 		/**
 		 * The chosen creator.
 		 *
 		 * @protected
-		 * @type {Creator}
+		 * @member {core.Creator} core.Editor#_creator
 		 */
 	}
 

+ 2 - 2
packages/ckeditor5-engine/src/editorui/editoruiview.js

@@ -15,8 +15,8 @@ import View from '../ui/view.js';
  */
 
 export default class EditorUIView extends View {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 		this._createBodyRegion();
 

+ 62 - 0
packages/ckeditor5-engine/src/locale.js

@@ -0,0 +1,62 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * Represents the localization services.
+ *
+ * @memberOf core
+ */
+export default class Locale {
+	/**
+	 * Creates a new instance of the Locale class. {@link Foo#bar}
+	 *
+	 * @param {String} [lang='en'] The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+	 */
+	constructor( lang ) {
+		/**
+		 * The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+		 *
+		 * @readonly
+		 * @member {String} core.Locale#lang
+		 */
+		this.lang = lang || 'en';
+
+		/**
+		 * Translates the given string to the {@link #lang}. This method is also availble in {@link core.Editor#t} and
+		 * {@link core.ui.View#t}.
+		 *
+		 * The strings may contain placeholders (`%<index>`) for values which are passed as the second argument.
+		 * `<index>` is the index in the `values` array.
+		 *
+		 *		editor.t( 'Created file "%0" in %1ms.', [ fileName, timeTaken ] );
+		 *
+		 * This method's context is statically bound to Locale instance,
+		 * so it can be called as a function:
+		 *
+		 *		const t = this.t;
+		 *		t( 'Label' );
+		 *
+		 * @method t
+		 * @param {String} str The string to translate.
+		 * @param {String[]} values Values that should be used to interpolate the string.
+		 */
+		this.t = ( ...args ) => this._t( ...args );
+	}
+
+	/**
+	 * Base for the {@link #t} method.
+	 *
+	 * @private
+	 */
+	_t( str, values ) {
+		if ( values ) {
+			str = str.replace( /\%(\d+)/g, ( match, index ) => ( index < values.length ) ? values[ index ] : match );
+		}
+
+		return str;
+	}
+}

+ 1 - 1
packages/ckeditor5-engine/src/ui/componentfactory.js

@@ -74,7 +74,7 @@ export default class ComponentFactory {
 		const component = this._components.get( name );
 
 		const model = component.model;
-		const view = new component.ViewClass( model );
+		const view = new component.ViewClass( model, this.editor.locale );
 		const controller = new component.ControllerClass( model, view, this.editor );
 
 		return controller;

+ 38 - 17
packages/ckeditor5-engine/src/ui/view.js

@@ -27,19 +27,36 @@ export default class View {
 	 * Creates an instance of the {@link View} class.
 	 *
 	 * @param {core.ui.Model} model (View)Model of this View.
+	 * @param {core.Locale} [locale] The {@link core.Editor#locale editor's locale} instance.
 	 */
-	constructor( model ) {
+	constructor( model, locale ) {
 		/**
 		 * Model of this view.
 		 *
-		 * @type {core.ui.Model}
+		 * @member {core.ui.Model} core.ui.View#model
 		 */
-		this.model = model || null;
+		this.model = model;
+
+		/**
+		 * @readonly
+		 * @member {core.Locale} core.ui.View#locale
+		 */
+		this.locale = locale;
+
+		/**
+		 * Shorthand for {@link core.Locale#t}.
+		 *
+		 * Note: If locale instance hasn't been passed to the view this method may not be available.
+		 *
+		 * @see core.Locale#t
+		 * @method t
+		 */
+		this.t = locale && locale.t;
 
 		/**
 		 * Regions of this view. See {@link core.ui.View#register}.
 		 *
-		 * @type {Collection}
+		 * @member {core.Collection} core.ui.View#regions
 		 */
 		this.regions = new Collection( {
 			idProperty: 'name'
@@ -48,38 +65,37 @@ export default class View {
 		/**
 		 * Template of this view.
 		 *
-		 * @type {Object}
+		 * @member {Object} core.ui.View#template
 		 */
-		this.template = null;
 
 		/**
 		 * Region selectors of this view. See {@link core.ui.View#register}.
 		 *
 		 * @private
-		 * @type {Object}
+		 * @member {Object} core.ui.View#_regionSelectors
 		 */
-		this._regionsSelectors = {};
+		this._regionSelectors = {};
 
 		/**
 		 * Element of this view.
 		 *
 		 * @private
-		 * @type {HTMLElement}
+		 * @member {HTMLElement} core.ui.View.#_element
 		 */
-		this._element = null;
 
 		/**
 		 * An instance of Template to generate {@link core.ui.View#_el}.
 		 *
 		 * @private
-		 * @type {Template}
+		 * @member {Template} core.ui.View#_template
 		 */
-		this._template = null;
 	}
 
 	/**
 	 * Element of this view. The element is rendered on first reference
-	 * using {@link core.ui.View#template} definition and {@link core.ui.View#_template} object.
+	 * using {@link core.ui.View#template} definition.
+	 *
+	 * @type {HTMLElement}
 	 */
 	get element() {
 		if ( this._element ) {
@@ -112,6 +128,7 @@ export default class View {
 	 * is synchronized with {@link View#model}.
 	 *
 	 * @readonly
+	 * @type core.ui.ViewModelBinding
 	 */
 	get attributeBinder() {
 		if ( this._attributeBinder ) {
@@ -194,6 +211,9 @@ export default class View {
 
 	/**
 	 * Initializes the view.
+	 *
+	 * Note: {@link core.Controller} supports if a promise is returned by this method,
+	 * what means that view initialization may be asynchronous.
 	 */
 	init() {
 		this._initRegions();
@@ -271,7 +291,7 @@ export default class View {
 			}
 		}
 
-		this._regionsSelectors[ regionName ] = regionSelector;
+		this._regionSelectors[ regionName ] = regionSelector;
 	}
 
 	/**
@@ -334,12 +354,13 @@ export default class View {
 			this.element.remove();
 		}
 
-		this.model = this.regions = this.template = this._regionsSelectors = this._element = this._template = null;
+		this.model = this.regions = this.template = this.locale = this.t = null;
+		this._regionSelectors = this._element = this._template = null;
 	}
 
 	/**
 	 * Initializes {@link core.ui.View#regions} of this view by passing a DOM element
-	 * generated from {@link core.ui.View#_regionsSelectors} into {@link Region#init}.
+	 * generated from {@link core.ui.View#_regionSelectors} into {@link Region#init}.
 	 *
 	 * @protected
 	 */
@@ -347,7 +368,7 @@ export default class View {
 		let region, regionEl, regionSelector;
 
 		for ( region of this.regions ) {
-			regionSelector = this._regionsSelectors[ region.name ];
+			regionSelector = this._regionSelectors[ region.name ];
 
 			if ( typeof regionSelector == 'string' ) {
 				regionEl = this.element.querySelector( regionSelector );

+ 27 - 0
packages/ckeditor5-engine/tests/_utils/locale-mock.js

@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * A replacement for the {@link core.Locale} class.
+ *
+ * @memberOf tests.core._utils
+ */
+export default class Locale {
+	constructor() {
+		this.t = ( str ) => `t( ${ str } )`;
+	}
+
+	/**
+	 * Injects instance of this class to the editor.
+	 *
+	 * @param {core.Editor} editor
+	 */
+	static inject( editor ) {
+		editor.locale = new Locale();
+		editor.t = editor.locale.t;
+	}
+}

+ 2 - 2
packages/ckeditor5-engine/tests/_utils/ui/boxededitorui/boxededitoruiview.js

@@ -8,8 +8,8 @@
 import EditorUIView from '/ckeditor5/core/editorui/editoruiview.js';
 
 export default class BoxedEditorUIView extends EditorUIView {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 		this.template = {
 			tag: 'div',

+ 2 - 2
packages/ckeditor5-engine/tests/_utils/ui/editable/framed/framededitableview.js

@@ -8,8 +8,8 @@
 import EditableView from '/ckeditor5/core/editable/editableview.js';
 
 export default class FramedEditableView extends EditableView {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 		const bind = this.attributeBinder;
 

+ 2 - 2
packages/ckeditor5-engine/tests/_utils/ui/editable/inline/inlineeditableview.js

@@ -8,8 +8,8 @@
 import EditableView from '/ckeditor5/core/editable/editableview.js';
 
 export default class InlineEditableView extends EditableView {
-	constructor( model, editableElement ) {
-		super( model );
+	constructor( model, locale, editableElement ) {
+		super( model, locale );
 
 		this.element = editableElement;
 	}

+ 2 - 2
packages/ckeditor5-engine/tests/_utils/ui/floatingtoolbar/floatingtoolbarview.js

@@ -8,8 +8,8 @@
 import ToolbarView from '/ckeditor5/ui/toolbar/toolbarview.js';
 
 export default class FloatingToolbarView extends ToolbarView {
-	constructor( model ) {
-		super( model );
+	constructor( model, locale ) {
+		super( model, locale );
 
 		const bind = this.attributeBinder;
 

+ 4 - 3
packages/ckeditor5-engine/tests/creator/manual/_utils/creator/classiccreator.js

@@ -49,7 +49,8 @@ export default class ClassicCreator extends Creator {
 	}
 
 	_setupToolbar() {
-		const toolbar = new Toolbar( new Model(), new ToolbarView(), this.editor );
+		const toolbarModel = new Model();
+		const toolbar = new Toolbar( toolbarModel, new ToolbarView( toolbarModel, this.editor.locale ), this.editor );
 
 		toolbar.addButtons( this.editor.config.toolbar );
 
@@ -58,7 +59,7 @@ export default class ClassicCreator extends Creator {
 
 	_createEditable() {
 		const editable = new FramedEditable( this.editor );
-		const editableView = new FramedEditableView( editable.viewModel );
+		const editableView = new FramedEditableView( editable.viewModel, this.editor.locale );
 
 		editable.view = editableView;
 
@@ -67,7 +68,7 @@ export default class ClassicCreator extends Creator {
 
 	_createEditorUI() {
 		const editorUI = new BoxedEditorUI( this.editor );
-		const editorUIView = new BoxedEditorUIView( editorUI.viewModel );
+		const editorUIView = new BoxedEditorUIView( editorUI.viewModel, this.editor.locale );
 
 		editorUI.view = editorUIView;
 

+ 6 - 4
packages/ckeditor5-engine/tests/creator/manual/_utils/creator/inlinecreator.js

@@ -47,11 +47,13 @@ export default class InlineCreator extends Creator {
 	}
 
 	_setupToolbar() {
+		const locale = this.editor.locale;
+
 		const toolbar1Model = new Model();
 		const toolbar2Model = new Model();
 
-		const toolbar1 = new FloatingToolbar( toolbar1Model, new FloatingToolbarView( toolbar1Model ), this.editor );
-		const toolbar2 = new FloatingToolbar( toolbar2Model, new FloatingToolbarView( toolbar2Model ), this.editor );
+		const toolbar1 = new FloatingToolbar( toolbar1Model, new FloatingToolbarView( toolbar1Model, locale ), this.editor );
+		const toolbar2 = new FloatingToolbar( toolbar2Model, new FloatingToolbarView( toolbar2Model, locale ), this.editor );
 
 		toolbar1.addButtons( this.editor.config.toolbar );
 		toolbar2.addButtons( this.editor.config.toolbar.reverse() );
@@ -62,7 +64,7 @@ export default class InlineCreator extends Creator {
 
 	_createEditable() {
 		const editable = new InlineEditable( this.editor );
-		const editableView = new InlineEditableView( editable.viewModel, this.editor.element );
+		const editableView = new InlineEditableView( editable.viewModel, this.editor.locale, this.editor.element );
 
 		editable.view = editableView;
 
@@ -72,7 +74,7 @@ export default class InlineCreator extends Creator {
 	_createEditorUI() {
 		const editorUI = new BoxlessEditorUI( this.editor );
 
-		editorUI.view = new EditorUIView( editorUI.viewModel );
+		editorUI.view = new EditorUIView( editorUI.viewModel, this.editor.locale );
 
 		return editorUI;
 	}

+ 4 - 2
packages/ckeditor5-engine/tests/creator/manual/_utils/imitatefeatures.js

@@ -15,10 +15,12 @@ import ButtonView from '/ckeditor5/ui/button/buttonview.js';
  * @param {core.Editor} editor
  */
 export function imitateFeatures( editor ) {
+	const t = editor.t;
+
 	const boldModel = new Model( {
 		isEnabled: true,
 		isOn: false,
-		label: 'bold'
+		label: t( 'Bold' )
 	} );
 
 	boldModel.on( 'execute', () => {
@@ -33,7 +35,7 @@ export function imitateFeatures( editor ) {
 	const italicModel = new Model( {
 		isEnabled: true,
 		isOn: false,
-		label: 'italic'
+		label: t( 'Italic' )
 	} );
 
 	italicModel.on( 'execute', () => {

+ 1 - 1
packages/ckeditor5-engine/tests/creator/manual/creator-classic.md

@@ -5,7 +5,7 @@
   * Framed editor should be created.
   * It should be rectangular (400x400).
   * Original element should disappear.
-  * There should be a toolbar with "bold" and "italic" buttons.
+  * There should be a toolbar with "Bold" and "Italic" buttons.
 3. Click "Destroy editor".
 4. Expected:
   * Editor should be destroyed.

+ 2 - 2
packages/ckeditor5-engine/tests/creator/manual/creator-inline.md

@@ -4,8 +4,8 @@
 2. Expected:
   * Inline editor should be created.
   * There should be **two** toolbars:
-    * one with "bold" and "italic" buttons,
-    * second with "italic" and "bold" buttons.
+    * one with "Bold" and "Italic" buttons,
+    * second with "Italic" and "Bold" buttons.
 3. Click "Destroy editor".
 4. Expected:
   * Editor should be destroyed (the element should not be editable).

+ 16 - 0
packages/ckeditor5-engine/tests/editor/editor.js

@@ -12,6 +12,7 @@ import coreTestUtils from '/tests/core/_utils/utils.js';
 import Editor from '/ckeditor5/core/editor.js';
 import EditorConfig from '/ckeditor5/core/editorconfig.js';
 import Plugin from '/ckeditor5/core/plugin.js';
+import Locale from '/ckeditor5/core/locale.js';
 
 const pluginClasses = {};
 let element;
@@ -51,6 +52,21 @@ describe( 'config', () => {
 	} );
 } );
 
+describe( 'locale', () => {
+	it( 'is instantiated and t() is exposed', () => {
+		const editor = new Editor( element );
+
+		expect( editor.locale ).to.be.instanceof( Locale );
+		expect( editor.t ).to.equal( editor.locale.t );
+	} );
+
+	it( 'is configured with the config.lang', () => {
+		const editor = new Editor( element, { lang: 'pl' } );
+
+		expect( editor.locale.lang ).to.equal( 'pl' );
+	} );
+} );
+
 describe( 'init', () => {
 	it( 'should return a promise that resolves properly', () => {
 		const editor = new Editor( element, {

+ 64 - 0
packages/ckeditor5-engine/tests/locale.js

@@ -0,0 +1,64 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import Locale from '/ckeditor5/core/locale.js';
+
+describe( 'Locale', () => {
+	let locale;
+
+	beforeEach( () => {
+		locale = new Locale();
+	} );
+
+	describe( 'constructor', () => {
+		it( 'sets the lang', () => {
+			const locale = new Locale( 'pl' );
+
+			expect( locale ).to.have.property( 'lang', 'pl' );
+		} );
+
+		it( 'defaults lang to en', () => {
+			const locale = new Locale();
+
+			expect( locale ).to.have.property( 'lang', 'en' );
+		} );
+	} );
+
+	describe( 't', () => {
+		it( 'has the context bound', () => {
+			const t = locale.t;
+
+			expect( t( 'Foo' ) ).to.equal( 'Foo' );
+		} );
+
+		it( 'interpolates 1 value', () => {
+			const t = locale.t;
+
+			expect( t( '%0 - %0', [ 'foo' ] ) ).to.equal( 'foo - foo' );
+		} );
+
+		it( 'interpolates 3 values', () => {
+			const t = locale.t;
+
+			expect( t( '%1 - %0 - %2', [ 'a', 'b', 'c' ] ) ).to.equal( 'b - a - c' );
+		} );
+
+		// Those test make sure that if %0 is really to be used, then it's going to work.
+		// It'd be a super rare case if one would need to use %0 and at the same time interpolate something.
+		it( 'does not interpolate placeholders if values not passed', () => {
+			const t = locale.t;
+
+			expect( t( '%1 - %0 - %2' ) ).to.equal( '%1 - %0 - %2' );
+		} );
+
+		it( 'does not interpolate those placeholders for which values has not been passed', () => {
+			const t = locale.t;
+
+			expect( t( '%1 - %0 - %2', [ 'a' ] ) ).to.equal( '%1 - a - %2' );
+		} );
+	} );
+} );

+ 94 - 78
packages/ckeditor5-engine/tests/ui/view.js

@@ -34,15 +34,23 @@ describe( 'View', () => {
 		} );
 
 		it( 'defines basic view properties', () => {
-			view = new View();
+			const model = new Model();
 
-			expect( view.model ).to.be.null;
-			expect( view.regions.length ).to.be.equal( 0 );
-			expect( view.template ).to.be.null;
-			expect( view._regionsSelectors ).to.be.empty;
-			expect( view._element ).to.be.null;
-			expect( view._template ).to.be.null;
-			expect( view.element ).to.be.null;
+			view = new View( model );
+
+			expect( view.model ).to.equal( model );
+			expect( view.t ).to.be.undefined;
+			expect( view.regions.length ).to.equal( 0 );
+		} );
+
+		it( 'defines the locale property and the t function', () => {
+			const model = new Model();
+			const locale = { t() {} };
+
+			view = new View( model, locale );
+
+			expect( view.locale ).to.equal( locale );
+			expect( view.t ).to.equal( locale.t );
 		} );
 	} );
 
@@ -86,8 +94,8 @@ describe( 'View', () => {
 
 			view.init();
 
-			expect( region1.element ).to.be.equal( view.element.firstChild );
-			expect( region2.element ).to.be.equal( view.element.lastChild );
+			expect( region1.element ).to.equal( view.element.firstChild );
+			expect( region2.element ).to.equal( view.element.lastChild );
 		} );
 
 		it( 'initializes view regions with function selector', () => {
@@ -101,8 +109,8 @@ describe( 'View', () => {
 
 			view.init();
 
-			expect( region1.element ).to.be.equal( view.element.firstChild );
-			expect( region2.element ).to.be.equal( view.element.lastChild );
+			expect( region1.element ).to.equal( view.element.firstChild );
+			expect( region2.element ).to.equal( view.element.lastChild );
 		} );
 
 		it( 'initializes view regions with boolean selector', () => {
@@ -169,6 +177,13 @@ describe( 'View', () => {
 		} );
 
 		it( 'should override an existing region with override flag', () => {
+			view.template = {
+				tag: 'div',
+				children: [
+					{ tag: 'span' }
+				]
+			};
+
 			const region1 = new Region( 'x' );
 			const region2 = new Region( 'x' );
 
@@ -176,8 +191,10 @@ describe( 'View', () => {
 			view.register( region2, true, true );
 			view.register( 'x', 'span', true );
 
-			expect( view.regions.get( 'x' ) ).to.be.equal( region2 );
-			expect( view._regionsSelectors.x ).to.be.equal( 'span' );
+			view.init();
+
+			expect( view.regions.get( 'x' ) ).to.equal( region2 );
+			expect( view.regions.get( 'x' ).element ).to.equal( view.element.firstChild );
 		} );
 
 		it( 'should not override an existing region with the same region with override flag', () => {
@@ -198,7 +215,7 @@ describe( 'View', () => {
 			setTestViewInstance( { a: 1 } );
 
 			expect( view.element ).to.be.an.instanceof( HTMLElement );
-			expect( view.element.nodeName ).to.be.equal( 'A' );
+			expect( view.element.nodeName ).to.equal( 'A' );
 		} );
 
 		it( 'can be explicitly declared', () => {
@@ -221,8 +238,8 @@ describe( 'View', () => {
 			const bind = view.attributeBinder;
 
 			expect( bind ).to.have.keys( 'to', 'if' );
-			expect( typeof bind.to ).to.be.equal( 'function' );
-			expect( typeof bind.if ).to.be.equal( 'function' );
+			expect( typeof bind.to ).to.equal( 'function' );
+			expect( typeof bind.if ).to.equal( 'function' );
 		} );
 
 		describe( 'to', () => {
@@ -254,10 +271,10 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 
 				view.model.foo = 'baz';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="baz">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="baz">abc</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – simple (Text Node)', () => {
@@ -275,10 +292,10 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 
 				view.model.foo = 'baz';
-				expect( view.element.outerHTML ).to.be.equal( '<p>baz</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>baz</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – value processing', () => {
@@ -300,10 +317,10 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 3 } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="positive">positive</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="positive">positive</p>' );
 
 				view.model.foo = -7;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="negative">negative</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="negative">negative</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – value processing (use Node)', () => {
@@ -327,10 +344,10 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 3 } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="HTMLElement positive"></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="HTMLElement positive"></p>' );
 
 				view.model.foo = -7;
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 			} );
 
 			it( 'allows binding attribute to the model – custom callback', () => {
@@ -352,10 +369,10 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'moo' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="undefined">moo</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="undefined">moo</p>' );
 
 				view.model.foo = 'changed';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="changed">changed</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="changed">changed</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – array of bindings (HTMLElement attribute)', () => {
@@ -378,11 +395,11 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'a', bar: 'b' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="ck-class a b foo-is-a ck-end">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="ck-class a b foo-is-a ck-end">abc</p>' );
 
 				view.model.foo = 'c';
 				view.model.bar = 'd';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="ck-class c d foo-is-c ck-end">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="ck-class c d foo-is-c ck-end">abc</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – array of bindings (Text Node)', () => {
@@ -408,11 +425,11 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'a', bar: 'b' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>ck-class a b foo-is-a ck-end</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>ck-class a b foo-is-a ck-end</p>' );
 
 				view.model.foo = 'c';
 				view.model.bar = 'd';
-				expect( view.element.outerHTML ).to.be.equal( '<p>ck-class c d foo-is-c ck-end</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>ck-class c d foo-is-c ck-end</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – falsy values', () => {
@@ -429,22 +446,22 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="false">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="false">abc</p>' );
 
 				view.model.foo = null;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="null">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="null">abc</p>' );
 
 				view.model.foo = undefined;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="undefined">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="undefined">abc</p>' );
 
 				view.model.foo = 0;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="0">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="0">abc</p>' );
 
 				view.model.foo = '';
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 		} );
 
@@ -478,13 +495,13 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: true } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="">abc</p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = 'bar';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="">abc</p>' );
 			} );
 
 			// TODO: Is this alright? It makes sense but it's pretty useless. Text Node cannot be
@@ -504,13 +521,13 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: true } );
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 
 				view.model.foo = 'bar';
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 			} );
 
 			it( 'allows binding attribute to the model – value of an attribute (HTMLElement attribute)', () => {
@@ -527,13 +544,13 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="bar">abc</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – value of an attribute (Text Node)', () => {
@@ -551,13 +568,13 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p></p>' );
+				expect( view.element.outerHTML ).to.equal( '<p></p>' );
 
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback', () => {
@@ -574,13 +591,13 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p class="there–is–no–foo">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="there–is–no–foo">abc</p>' );
 
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – value of an attribute processed by a callback (use Node)', () => {
@@ -597,13 +614,13 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = 'P';
-				expect( view.element.outerHTML ).to.be.equal( '<p class="eqls-tag-name">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="eqls-tag-name">abc</p>' );
 
 				view.model.foo = 64;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 
 			it( 'allows binding attribute to the model – falsy values', () => {
@@ -620,22 +637,22 @@ describe( 'View', () => {
 				} );
 
 				setTestViewInstance( { foo: 'bar' } );
-				expect( view.element.outerHTML ).to.be.equal( '<p class="foo-is-set">abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p class="foo-is-set">abc</p>' );
 
 				view.model.foo = false;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = null;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = undefined;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = '';
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 
 				view.model.foo = 0;
-				expect( view.element.outerHTML ).to.be.equal( '<p>abc</p>' );
+				expect( view.element.outerHTML ).to.equal( '<p>abc</p>' );
 			} );
 		} );
 	} );
@@ -872,9 +889,8 @@ describe( 'View', () => {
 			expect( view.model ).to.be.null;
 			expect( view.regions ).to.be.null;
 			expect( view.template ).to.be.null;
-			expect( view._regionsSelectors ).to.be.null;
-			expect( view._element ).to.be.null;
-			expect( view._template ).to.be.null;
+			expect( view.locale ).to.be.null;
+			expect( view.t ).to.be.null;
 		} );
 
 		it( 'detaches the element from DOM', () => {
@@ -897,8 +913,8 @@ describe( 'View', () => {
 			view.regions.get( 'x' ).views.add( new View() );
 			view.destroy();
 
-			expect( regionsRef.length ).to.be.equal( 0 );
-			expect( regionViewsRef.length ).to.be.equal( 0 );
+			expect( regionsRef.length ).to.equal( 0 );
+			expect( regionViewsRef.length ).to.equal( 0 );
 			expect( spy.calledOnce ).to.be.true;
 		} );
 
@@ -919,15 +935,15 @@ describe( 'View', () => {
 			const modelRef = view.model;
 			const elRef = view.element;
 
-			expect( view.element.outerHTML ).to.be.equal( '<p>bar</p>' );
+			expect( view.element.outerHTML ).to.equal( '<p>bar</p>' );
 
 			modelRef.foo = 'baz';
-			expect( view.element.outerHTML ).to.be.equal( '<p>baz</p>' );
+			expect( view.element.outerHTML ).to.equal( '<p>baz</p>' );
 
 			view.destroy();
 
 			modelRef.foo = 'abc';
-			expect( elRef.outerHTML ).to.be.equal( '<p>baz</p>' );
+			expect( elRef.outerHTML ).to.equal( '<p>baz</p>' );
 		} );
 
 		it( 'destroy a template–less view', () => {
@@ -958,11 +974,11 @@ describe( 'View', () => {
 				}
 			} );
 
-			expect( el.outerHTML ).to.be.equal( '<div class="42" id="foo" checked="checked"></div>' );
+			expect( el.outerHTML ).to.equal( '<div class="42" id="foo" checked="checked"></div>' );
 
 			view.model.b = 64;
 
-			expect( el.outerHTML ).to.be.equal( '<div class="64" id="foo" checked="checked"></div>' );
+			expect( el.outerHTML ).to.equal( '<div class="64" id="foo" checked="checked"></div>' );
 		} );
 
 		it( 'should initialize DOM listeners', () => {
@@ -999,7 +1015,7 @@ describe( 'View', () => {
 			el.appendChild( childA );
 			el.appendChild( childB );
 
-			expect( el.outerHTML ).to.be.equal( '<div><a>anchor</a><b>bold</b></div>' );
+			expect( el.outerHTML ).to.equal( '<div><a>anchor</a><b>bold</b></div>' );
 
 			const spy1 = testUtils.sinon.spy();
 			const spy2 = testUtils.sinon.spy();
@@ -1042,14 +1058,14 @@ describe( 'View', () => {
 				}
 			} );
 
-			expect( el.outerHTML ).to.be.equal( '<div id="FOO" class="applied-parent-42">' +
+			expect( el.outerHTML ).to.equal( '<div id="FOO" class="applied-parent-42">' +
 				'<a class="applied-A-42" id="applied-A">Text applied to childA.</a>' +
 				'<b class="applied-B-42" id="applied-B">Text applied to childB.</b>' +
 			'</div>' );
 
 			view.model.b = 16;
 
-			expect( el.outerHTML ).to.be.equal( '<div id="FOO" class="applied-parent-16">' +
+			expect( el.outerHTML ).to.equal( '<div id="FOO" class="applied-parent-16">' +
 				'<a class="applied-A-16" id="applied-A">Text applied to childA.</a>' +
 				'<b class="applied-B-16" id="applied-B">Text applied to childB.</b>' +
 			'</div>' );
@@ -1087,8 +1103,8 @@ describe( 'View', () => {
 			view.applyTemplateToElement( el2, def );
 
 			// Attacher function didn't change because it's still the same View instance.
-			expect( attacherFn ).to.be.equal( def.on.click );
-			expect( def.on._listenerView ).to.be.equal( view );
+			expect( attacherFn ).to.equal( def.on.click );
+			expect( def.on._listenerView ).to.equal( view );
 
 			document.body.appendChild( el1 );
 			document.body.appendChild( el2 );
@@ -1115,14 +1131,14 @@ describe( 'View', () => {
 			document.body.appendChild( el );
 
 			view1.applyTemplateToElement( el, def );
-			expect( def.on._listenerView ).to.be.equal( view1 );
+			expect( def.on._listenerView ).to.equal( view1 );
 
 			dispatchEvent( el, 'click' );
 			sinon.assert.calledOnce( spy );
 
 			// Use another view1 to see if attachers are re–defined.
 			view2.applyTemplateToElement( el, def );
-			expect( def.on._listenerView ).to.be.equal( view2 );
+			expect( def.on._listenerView ).to.equal( view2 );
 
 			dispatchEvent( el, 'click' );
 			// Spy was called for view1 (1st dispatch), then for view1 and view2 (2nd dispatch).