ckeditor5的locale方案會(huì)往全局變量window.CKEDITOR_TRANSLATIONS
上掛載映射用的字典信息:
window.CKEDITOR_TRANSLATIONS = {
pl: {
dictionary: {
'Cancel': 'Anuluj',
'Add space': [ 'Dodaj spacj?', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ]
},
// A function that returns the plural form index.
getPluralForm: n => n !==1
}
add
方法會(huì)更新window.CKEDITOR_TRANSLATIONS
中對(duì)應(yīng)語(yǔ)言下dictionary
和getPluralForm
方法:
locale = new Locale( {
uiLanguage: 'pl',
contentLanguage: 'de'
} );
const getPolishPluralForm = n => n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2;
add('pl', {
'foo': 'foo_pl',
'bar': [ 'bar_pl_0', '%0 bar_pl_1', '%0 bar_pl_2' ]
}, getPolishPluralForm);
addTranslations( 'de', {
'foo': 'foo_de',
'bar': [ 'bar_de_0', '%0 bar_de_1', '%0 bar_de_2' ]
} );
// 測(cè)試用例
const t = locale.t;
expect( t( { string: 'bar', plural: '%0 bars' }, [ 1 ] ), 1 ).to.equal( 'bar_pl_0' );
expect( t( { string: 'bar', plural: '%0 bars' }, [ 2 ] ), 2 ).to.equal( '2 bar_pl_1' );
expect( t( { string: 'bar', plural: '%0 bars' }, [ 5 ] ), 3 ).to.equal( '5 bar_pl_2' );
// 更新示例
add( 'pl', {
'ADD_SPACE': [ '%1 spacj?', '%1 %0 spacje', '%1 %0 spacji' ],
'Add': 'Dodaj',
'Remove': 'Usuń'
} );
const addOrRemoveSpaceMessage = { string: '%1 a space', plural: '%1 %0 spaces', id: 'ADD_SPACE' };
expect( t( addOrRemoveSpaceMessage, [ 1, t( 'Add' ) ] ), 1 ).to.equal( 'Dodaj spacj?' );
expect( t( addOrRemoveSpaceMessage, [ 2, t( 'Remove' ) ] ), 2 ).to.equal( 'Usuń 2 spacje' );
expect( t( addOrRemoveSpaceMessage, [ 5, t( 'Add' ) ] ), 3 ).to.equal( 'Dodaj 5 spacji' );
t(message, values = [])方法
- 如果
message
是字符串脆贵,那么將message
轉(zhuǎn)換為對(duì)象形式message = { string: message}
如果values
不是數(shù)組狮腿,那么將values
轉(zhuǎn)換為數(shù)組形式values = [ values]
- 如果存在
message.plural
,則常量quantity
取值values[0]
,否則quantity
設(shè)置為1 - 執(zhí)行
translate(language, message, quantity)
方法到映射的值,可能直接就是原始的message
字符串,也可能是values中某個(gè)選項(xiàng)何什,而這個(gè)選項(xiàng)中可能有類(lèi)似%o
、%1
這樣的插值疙筹,這就需要執(zhí)行下一步的interpolateString
方法再次轉(zhuǎn)換富俄。translate(language, message, quantity)
執(zhí)行邏輯如下:
1.如果只有一種語(yǔ)言禁炒,也就是Object.keys( window.CKEDITOR_TRANSLATIONS ).length === 1
的話,將用這唯一的語(yǔ)言來(lái)設(shè)置入?yún)?code>language
2.如果window.CKEDITOR_TRANSLATIONS
中沒(méi)有任何設(shè)置或者找不到messageId
(即message.id || message.string)對(duì)應(yīng)的翻譯霍比,那么當(dāng)入?yún)?code>quantity為1
時(shí)幕袱,直接返回message.string
,不為1
時(shí)悠瞬,直接返回message.plural
3.否則们豌,當(dāng)messageId
(即message.id || message.string)對(duì)應(yīng)的翻譯是字符串時(shí),直接返回該字符串浅妆,例如上例中t( 'Add' )
返回的就是Dodaj
望迎;如果對(duì)應(yīng)的翻譯是數(shù)組(用arr
臨時(shí)表示),那么就要根據(jù)上例中add
方法的第三個(gè)入?yún)⒓春瘮?shù)getPluralForm
和quantity
參數(shù)凌外,來(lái)獲取一個(gè)下標(biāo)辩尊,然后返回arr[下標(biāo)]
,例如上例中t( addOrRemoveSpaceMessage, [ 2, t( 'Remove' ) ] )
其實(shí)就是t({ string: '%1 a space', plural: '%1 %0 spaces', id: 'ADD_SPACE' }, [2, 'Usuń'])
康辑,會(huì)根據(jù)idADD_SPACE
找到對(duì)應(yīng)的翻譯選項(xiàng)數(shù)組[ '%1 spacj?', '%1 %0 spacje', '%1 %0 spacji' ]
摄欲,然后通過(guò)getPolishPluralForm(2)
計(jì)算出下標(biāo)為1,最終返回'%1 %0 spacje'
疮薇。 - 執(zhí)行
interpolateString
胸墙,如上文(上一節(jié)第3小節(jié))中示例最后返回'%1 %0 spacje'
,執(zhí)行interpolateString('%1 %0 spacje', [2, 'Usuń'])
按咒,結(jié)果為'Usuń 2 spacje'
迟隅。
// Fills the `%0, %1, ...` string placeholders with values.
function interpolateString( string, values ) {
return string.replace( /%(\d+)/g, ( match, index ) => {
return ( index < values.length ) ? values[ index ] : match;
} );
}
Setting the UI language
- 支持rtl語(yǔ)言,如阿拉伯語(yǔ)励七;
- 富文本UI語(yǔ)言默認(rèn)是英語(yǔ)智袭,支持npm、cdn和zip三種方式加載其他語(yǔ)言包呀伙;
- 也可以借助
ckeditor5-dev-webpack-plugin
插件补履,引用其他語(yǔ)言包添坊,其原理大概是加載po
文件剿另,將po
文件中的翻譯納入ckeditor中。大致步驟如下:
1.在po
文件中設(shè)置翻譯用的映射信息
而zh-ch.po
文件中是一些翻譯用的映射信息贬蛙,截圖如下:
2.加載po
文件
loadPackage( pathToPackage ) {
if ( this._handledPackages.has( pathToPackage ) ) {
return;
}
this._handledPackages.add( pathToPackage );
const pathToTranslationDirectory = this._getPathToTranslationDirectory( pathToPackage, this._language );
// pathToPoFile即為po文件路徑雨女,如node_modules\_@ckeditor_ckeditor5-heading@16.0.0@@ckeditor\ckeditor5-heading\lang\translations\zh-cn.po
const pathToPoFile = pathToTranslationDirectory + path.sep + this._language + '.po';
this._loadPoFile( pathToPoFile );
}
如上代碼,會(huì)輸出各ckeditor包中指定目錄下指定語(yǔ)言的po
文件阳准,如node_modules\_@ckeditor_ckeditor5-heading@16.0.0@@ckeditor\ckeditor5-heading\lang\translations\zh-cn.po
3.translateSource
方法(類(lèi)似babel)通過(guò)acorn
修改源碼氛堕,將調(diào)用t()
方法的地方,全部替換成翻譯后的文本
function translateSource( source, sourceFile, translateString ) {
const comments = [];
const tokens = [];
const errors = [];
const ast = acorn.parse( source, {
sourceType: 'module',
ranges: true,
onComment: comments,
onToken: tokens,
ecmaVersion: 9
} );
let changesInCode = false;
walk.simple( ast, {
CallExpression: node => {
if ( node.callee.name !== 't' ) {
return;
}
if ( node.arguments[ 0 ].type !== 'Literal' ) {
errors.push( `First t() call argument should be a string literal in ${ sourceFile }.` );
return;
}
changesInCode = true;
node.arguments[ 0 ].value = translateString( node.arguments[ 0 ].value );
}
} );
// Optimization for files without t() calls.
if ( !changesInCode ) {
return { output: source, errors };
}
escodegen.attachComments( ast, comments, tokens );
const output = escodegen.generate( ast, {
comment: true
} );
return { output, errors };
};
效果舉例:例如“圖2:效果圖”中標(biāo)題下拉框野蝇,對(duì)應(yīng)heading
插件讼稚,其依賴一個(gè)插件叫HeadingUI
括儒,它的init
方法片段如下:
init() {
const dropdownTooltip = t( 'Heading' );
}
經(jīng)過(guò)translateSource
代碼轉(zhuǎn)換后,代碼轉(zhuǎn)為:
init() {
const dropdownTooltip = t( '段落123' );
}
再例如锐想,ckeditor5/src/utils中:
const localizedTitles = {
Paragraph: t( 'Paragraph' ),
'Heading 1': t( 'Heading 1' ),
'Heading 2': t( 'Heading 2' ),
'Heading 3': t( 'Heading 3' ),
'Heading 4': t( 'Heading 4' ),
'Heading 5': t( 'Heading 5' ),
'Heading 6': t( 'Heading 6' )
};
會(huì)轉(zhuǎn)換為:
const localizedTitles = {
Paragraph: t('段落123'),
'Heading 1': t('標(biāo)題 1'),
'Heading 2': t('標(biāo)題 2'),
'Heading 3': t('標(biāo)題 3'),
'Heading 4': t('標(biāo)題 4'),
'Heading 5': t('標(biāo)題 5'),
'Heading 6': t('標(biāo)題 6')
};
通過(guò)上述方法帮寻,實(shí)現(xiàn)了“圖2:效果圖”中標(biāo)題下拉框中的“段落123”的效果。