Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #296 from ckeditor/t/ckeditor5/1151
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Reinmar authored Aug 12, 2019
2 parents 3b5ed3b + 9a4babd commit 91c95f3
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 13 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
70 changes: 62 additions & 8 deletions src/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 ) => {
Expand All @@ -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';
}
92 changes: 87 additions & 5 deletions tests/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
} );
} );

Expand Down
28 changes: 28 additions & 0 deletions tests/manual/locale/locale.html
Original file line number Diff line number Diff line change
@@ -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 changes: 88 additions & 0 deletions tests/manual/locale/locale.js
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions tests/manual/locale/locale.md
Original file line number Diff line number Diff line change
@@ -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**.
Binary file added tests/manual/locale/sample.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 91c95f3

Please sign in to comment.