Bläddra i källkod

Merge pull request #296 from ckeditor/t/ckeditor5/1151

Feature: Allowed specifying editor content language in `Locale`. Implemented the (UI and content) language direction discovery in `Locale`. Implemented `Locale#uiLanguage`, `Locale#uiLanguageDirection`, `Locale#contentLanguage`, and `Locale#contentLanguageDirection` properties. See ckeditor/ckeditor5#1151.

BREAKING CHANGE: The`Locale()` constructor arguments syntax has changed. Please refer to the API documentation to learn more.

BREAKING CHANGE: The `Locale#language` property has been deprecated by `Locale#uiLanguage`. Please refer to the API documentation to learn more.
Piotrek Koszuliński 6 år sedan
förälder
incheckning
b4e3068894

+ 1 - 0
packages/ckeditor5-utils/package.json

@@ -14,6 +14,7 @@
   },
   "devDependencies": {
     "@ckeditor/ckeditor5-build-classic": "^12.3.1",
+    "@ckeditor/ckeditor5-editor-classic": "^12.1.3",
     "@ckeditor/ckeditor5-core": "^12.2.1",
     "@ckeditor/ckeditor5-engine": "^13.2.1",
     "eslint": "^5.5.0",

+ 62 - 8
packages/ckeditor5-utils/src/locale.js

@@ -9,27 +9,73 @@
 
 import { translate } from './translation-service';
 
+const RTL_LANGUAGE_CODES = [ 'ar', 'fa', 'he', 'ku', 'ug' ];
+
 /**
  * Represents the localization services.
  */
 export default class Locale {
 	/**
-	 * Creates a new instance of the Locale class.
+	 * Creates a new instance of the Locale class. Learn more about
+	 * {@glink features/ui-language configuring language of the editor}.
 	 *
-	 * @param {String} [language='en'] The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+	 * @param {Object} [options] Locale configuration.
+	 * @param {String} [options.uiLanguage='en'] The editor UI language code in the
+	 * [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. See {@link #uiLanguage}.
+	 * @param {String} [options.contentLanguage] The editor content language code in the
+	 * [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. If not specified, the same as `options.language`.
+	 * See {@link #contentLanguage}.
 	 */
-	constructor( language ) {
+	constructor( options = {} ) {
+		/**
+		 * The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+		 *
+		 * If the {@link #contentLanguage content language} was not specified in the `Locale` constructor,
+		 * it also defines the language of the content.
+		 *
+		 * @readonly
+		 * @member {String}
+		 */
+		this.uiLanguage = options.uiLanguage || 'en';
+
+		/**
+		 * The editor content language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+		 *
+		 * Usually the same as {@link #uiLanguage editor language}, it can be customized by passing an optional
+		 * argument to the `Locale` constructor.
+		 *
+		 * @readonly
+		 * @member {String}
+		 */
+		this.contentLanguage = options.contentLanguage || this.uiLanguage;
+
 		/**
-		 * The language code in [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
+		 * Text direction of the {@link #uiLanguage editor UI language}. Either `'ltr'` or `'rtl'`.
 		 *
 		 * @readonly
 		 * @member {String}
 		 */
-		this.language = language || 'en';
+		this.uiLanguageDirection = getLanguageDirection( this.uiLanguage );
 
 		/**
-		 * Translates the given string to the {@link #language}. This method is also available in {@link module:core/editor/editor~Editor#t}
-		 * and {@link module:ui/view~View#t}.
+		 * Text direction of the {@link #contentLanguage editor content language}.
+		 *
+		 * If the content language was passed directly to the `Locale` constructor, this property represents the
+		 * direction of that language.
+		 *
+		 * If the {@link #contentLanguage editor content language} was derived from the {@link #uiLanguage editor language},
+		 * the content language direction is the same as the {@link #uiLanguageDirection UI language direction}.
+		 *
+		 * The value is either `'ltr'` or `'rtl'`.
+		 *
+		 * @readonly
+		 * @member {String}
+		 */
+		this.contentLanguageDirection = getLanguageDirection( this.contentLanguage );
+
+		/**
+		 * Translates the given string to the {@link #uiLanguage}. This method is also available in
+		 * {@link module:core/editor/editor~Editor#t} and {@link module:ui/view~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.
@@ -55,7 +101,7 @@ export default class Locale {
 	 * @private
 	 */
 	_t( str, values ) {
-		let translatedString = translate( this.language, str );
+		let translatedString = translate( this.uiLanguage, str );
 
 		if ( values ) {
 			translatedString = translatedString.replace( /%(\d+)/g, ( match, index ) => {
@@ -66,3 +112,11 @@ export default class Locale {
 		return translatedString;
 	}
 }
+
+// Helps determine whether a language is LTR or RTL.
+//
+// @param {String} language The ISO 639-1 language code.
+// @returns {String} 'ltr' or 'rtl
+function getLanguageDirection( languageCode ) {
+	return RTL_LANGUAGE_CODES.includes( languageCode ) ? 'rtl' : 'ltr';
+}

+ 87 - 5
packages/ckeditor5-utils/tests/locale.js

@@ -13,16 +13,98 @@ describe( 'Locale', () => {
 	} );
 
 	describe( 'constructor', () => {
-		it( 'sets the language', () => {
-			const locale = new Locale( 'pl' );
+		it( 'sets the #language', () => {
+			const locale = new Locale( {
+				uiLanguage: 'pl'
+			} );
 
-			expect( locale ).to.have.property( 'language', 'pl' );
+			expect( locale ).to.have.property( 'uiLanguage', 'pl' );
 		} );
 
-		it( 'defaults language to en', () => {
+		it( 'sets the #contentLanguage', () => {
+			const locale = new Locale( {
+				uiLanguage: 'pl',
+				contentLanguage: 'en'
+			} );
+
+			expect( locale ).to.have.property( 'uiLanguage', 'pl' );
+			expect( locale ).to.have.property( 'contentLanguage', 'en' );
+		} );
+
+		it( 'defaults #language to en', () => {
 			const locale = new Locale();
 
-			expect( locale ).to.have.property( 'language', 'en' );
+			expect( locale ).to.have.property( 'uiLanguage', 'en' );
+		} );
+
+		it( 'inherits the #contentLanguage from the #language (if not passed)', () => {
+			const locale = new Locale( {
+				uiLanguage: 'pl'
+			} );
+
+			expect( locale ).to.have.property( 'uiLanguage', 'pl' );
+			expect( locale ).to.have.property( 'contentLanguage', 'pl' );
+		} );
+
+		it( 'determines the #uiLanguageDirection', () => {
+			expect( new Locale( {
+				uiLanguage: 'pl'
+			} ) ).to.have.property( 'uiLanguageDirection', 'ltr' );
+
+			expect( new Locale( {
+				uiLanguage: 'en'
+			} ) ).to.have.property( 'uiLanguageDirection', 'ltr' );
+
+			expect( new Locale( {
+				uiLanguage: 'ar'
+			} ) ).to.have.property( 'uiLanguageDirection', 'rtl' );
+
+			expect( new Locale( {
+				uiLanguage: 'fa'
+			} ) ).to.have.property( 'uiLanguageDirection', 'rtl' );
+
+			expect( new Locale( {
+				uiLanguage: 'he'
+			} ) ).to.have.property( 'uiLanguageDirection', 'rtl' );
+
+			expect( new Locale( {
+				uiLanguage: 'ku'
+			} ) ).to.have.property( 'uiLanguageDirection', 'rtl' );
+
+			expect( new Locale( {
+				uiLanguage: 'ug'
+			} ) ).to.have.property( 'uiLanguageDirection', 'rtl' );
+		} );
+
+		it( 'determines the #contentLanguageDirection (not passed)', () => {
+			expect( new Locale( {
+				uiLanguage: 'pl'
+			} ) ).to.have.property( 'contentLanguageDirection', 'ltr' );
+
+			expect( new Locale( {
+				uiLanguage: 'en'
+			} ) ).to.have.property( 'contentLanguageDirection', 'ltr' );
+
+			expect( new Locale( {
+				uiLanguage: 'ar'
+			} ) ).to.have.property( 'contentLanguageDirection', 'rtl' );
+		} );
+
+		it( 'determines the #contentLanguageDirection (passed)', () => {
+			expect( new Locale( {
+				uiLanguage: 'pl',
+				contentLanguage: 'pl'
+			} ) ).to.have.property( 'contentLanguageDirection', 'ltr' );
+
+			expect( new Locale( {
+				uiLanguage: 'en',
+				contentLanguage: 'ar'
+			} ) ).to.have.property( 'contentLanguageDirection', 'rtl' );
+
+			expect( new Locale( {
+				uiLanguage: 'ar',
+				contentLanguage: 'pl'
+			} ) ).to.have.property( 'contentLanguageDirection', 'ltr' );
 		} );
 	} );
 

+ 28 - 0
packages/ckeditor5-utils/tests/manual/locale/locale.html

@@ -0,0 +1,28 @@
+<h2>language = 'en' (LTR)</h2>
+
+<div id="editor-language">
+	<p>This is an <a href="https://ckeditor.com">editor</a> instance.</p>
+	<figure class="image image-style-side"><img src="./sample.jpg"><figcaption>Caption</figcaption></figure>
+</div>
+
+<h2>language = 'ar' (RTL)</h2>
+
+<div id="editor-language-rtl">
+	<p>مرحبا <a href="https://ckeditor.com">editor</a></p>
+	<figure class="image image-style-side"><img src="./sample.jpg"><figcaption>Caption</figcaption></figure>
+</div>
+
+<h2>language = 'en' (LTR), <br />contentLanguage = 'ar' (RTL)</h2>
+
+<div id="editor-language-rtl-content">
+	<p>مرحبا <a href="https://ckeditor.com">editor</a></p>
+	<figure class="image image-style-side"><img src="./sample.jpg"><figcaption>Caption</figcaption></figure>
+</div>
+
+<h2>language = 'ar' (RTL), <br />contentLanguage = 'en' (LTR)</h2>
+
+<div id="editor-language-rtl-ui">
+	<p>This is an <a href="https://ckeditor.com">editor</a> instance.</p>
+	<figure class="image image-style-side"><img src="./sample.jpg"><figcaption>Caption</figcaption></figure>
+</div>
+

+ 88 - 0
packages/ckeditor5-utils/tests/manual/locale/locale.js

@@ -0,0 +1,88 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global window, console, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+
+const config = {
+	plugins: [ ArticlePluginSet ],
+	image: {
+		toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
+	},
+	table: {
+		contentToolbar: [
+			'tableColumn',
+			'tableRow',
+			'mergeTableCells'
+		]
+	},
+	toolbar: [
+		'heading',
+		'|',
+		'bold', 'italic', 'link',
+		'bulletedList', 'numberedList',
+		'blockQuote', 'insertTable', 'mediaEmbed',
+		'undo', 'redo'
+	]
+};
+
+ClassicEditor
+	.create( document.querySelector( '#editor-language' ), Object.assign( {}, config, {
+		language: 'en'
+	} ) )
+	.then( newEditor => {
+		window.editorLanguage = newEditor;
+
+		console.log( 'Editor created, locale:', newEditor.locale );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+
+ClassicEditor
+	.create( document.querySelector( '#editor-language-rtl' ), Object.assign( {}, config, {
+		language: 'ar'
+	} ) )
+	.then( newEditor => {
+		window.editorLanguageRTL = newEditor;
+
+		console.log( 'Editor created, locale:', newEditor.locale );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+
+ClassicEditor
+	.create( document.querySelector( '#editor-language-rtl-content' ), Object.assign( {}, config, {
+		language: {
+			content: 'ar'
+		}
+	} ) )
+	.then( newEditor => {
+		window.editorLanguageRTLContent = newEditor;
+
+		console.log( 'Editor created, locale:', newEditor.locale );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+
+ClassicEditor
+	.create( document.querySelector( '#editor-language-rtl-ui' ), Object.assign( {}, config, {
+		language: {
+			ui: 'ar',
+			content: 'en'
+		}
+	} ) )
+	.then( newEditor => {
+		window.editorLanguageRTLUI = newEditor;
+
+		console.log( 'Editor created, locale:', newEditor.locale );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 39 - 0
packages/ckeditor5-utils/tests/manual/locale/locale.md

@@ -0,0 +1,39 @@
+## Editor locales
+
+**Note**: For the best testing, run manual tests adding Arabic to [additional languages configuration](https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/testing-environment.html#running-manual-tests).
+
+---
+
+Play with different editor configurations to make sure localization and the right–to–left language support work fine.
+
+### language = 'en' (LTR)
+
+1. Make sure the UI of the editor is English.
+2. Make sure the language of the content is English (`lang` attribute of th editable) and aligned to the **left** side.
+3. Delete **all** the content.
+4. Write something in Arabic.
+5. The text you've just written should be aligned **right**.
+
+### language = 'ar' (RTL)
+
+1. Make sure the UI of the editor is Arabic.
+2. Make sure the language of the content is Arabic (`lang` attribute of th editable) and aligned to the **right** side.
+3. Delete **all** the content.
+4. Write something in English.
+5. The text you've just written should be aligned **left**.
+
+### language = 'en' (LTR), contentLanguage = 'ar' (RTL)
+
+1. Make sure the UI of the editor is English.
+2. Make sure the language of the content is Arabic (`lang` attribute of th editable) and aligned to the **right** side.
+3. Delete **all** the content.
+4. Write something in English.
+5. The text you've just written should be aligned **right**.
+
+### language = 'ar' (RTL), contentLanguage = 'en' (LTR)
+
+1. Make sure the UI of the editor is Arabic.
+2. Make sure the language of the content is English (`lang` attribute of th editable) and aligned to the **left** side.
+3. Delete **all** the content.
+4. Write something in Arabic.
+5. The text you've just written should be aligned **left**.

BIN
packages/ckeditor5-utils/tests/manual/locale/sample.jpg