blob: 5c3f3e321037995a2bf072d60eaf1a9bc593a13c [file] [log] [blame]
// Copyright 2013 Google Inc. All Rights Reserved.
/**
* @fileoverview This file defines the input tool, included IME and virtual
* keyboard.
*
* @author wuyingbing@google.com (Yingbing Wu)
*/
goog.provide('i18n.input.lang.InputTool');
goog.require('goog.array');
goog.require('goog.object');
goog.require('goog.string');
goog.require('i18n.input.common.GlobalSettings');
goog.require('i18n.input.lang.InputToolCode');
goog.require('i18n.input.lang.InputToolType');
goog.scope(function() {
var GlobalSettings = i18n.input.common.GlobalSettings;
var InputToolCode = i18n.input.lang.InputToolCode;
var InputToolType = i18n.input.lang.InputToolType;
/**
* The input tool class is used to define Input Tool. Don't call the method
* directly, use InputTool.get instead.
*
* @param {!InputToolCode} inputToolCode The input tool code
* value.
* @constructor
*/
i18n.input.lang.InputTool = function(inputToolCode) {
/**
* The unique code of input tools.
*
* @type {!InputToolCode}
*/
this.code = inputToolCode;
/**
* The input tools type value.
*
* @type {?InputToolType}
*/
this.type = null;
/**
* The target language code.
*
* @type {string}
*/
this.languageCode = 'en';
/**
* The source language code.
*
* @type {string}
*/
this.sourceLanguageCode = 'en';
/**
* Keyboard layout code. Only valid if type is KBD.
* @type {string}
*/
this.layoutCode;
// Parses input tool code.
this.parseInputToolCode_();
};
var InputTool = i18n.input.lang.InputTool;
/**
* The array of rtl keyboards' input tool codes.
*
* @type {!Array.<string>}
*/
InputTool.RtlKeyboards = [
InputToolCode.KEYBOARD_ARABIC,
InputToolCode.KEYBOARD_DARI,
InputToolCode.KEYBOARD_HEBREW,
InputToolCode.KEYBOARD_PASHTO,
InputToolCode.KEYBOARD_PERSIAN,
InputToolCode.KEYBOARD_SOUTHERN_UZBEK,
InputToolCode.KEYBOARD_UIGHUR,
InputToolCode.KEYBOARD_URDU,
InputToolCode.KEYBOARD_YIDDISH];
/**
* The array of rtl ime' input tool codes.
*
* @type {!Array.<string>}
*/
InputTool.RtlIMEs = [
InputToolCode.INPUTMETHOD_TRANSLITERATION_ARABIC,
InputToolCode.INPUTMETHOD_TRANSLITERATION_HEBREW,
InputToolCode.INPUTMETHOD_TRANSLITERATION_PERSIAN,
InputToolCode.INPUTMETHOD_TRANSLITERATION_URDU];
/**
* The mapping from 3-letter language codes to 2-letter language codes.
*
* @type {!Object.<string, string>}
*/
InputTool.LanguageCodeThreeTwoMap = goog.object.create(
'arm', 'hy',
'bel', 'be',
'bul', 'bg',
'cat', 'ca',
'cze', 'cs',
'dan', 'da',
'eng', 'en',
'est', 'et',
'fao', 'fo',
'fin', 'fi',
'fra', 'fr',
'geo', 'ka',
'ger', 'de',
'gre', 'el',
'heb', 'he',
'hun', 'hu',
'ice', 'is',
'ind', 'id',
'ita', 'it',
'jpn', 'ja',
'kaz', 'kk',
'lav', 'lv',
'lit', 'lt',
'mlt', 'mt',
'mon', 'mn',
'msa', 'ms',
'nld', 'nl',
// The new specification is "nb", but NACL uses "no".
'nob', 'no',
'pol', 'pl',
'por', 'pt',
'rum', 'ro',
'rus', 'ru',
'scr', 'hr',
'slo', 'sk',
'slv', 'sl',
'spa', 'es',
'srp', 'sr',
'swe', 'sv',
'tur', 'tr',
'ukr', 'uk');
/**
* The special XKB id to language code mapping.
*
* @private {!Object.<string, string>}
*/
InputTool.XkbId2Language_ = {
// NACL treads "pt-BR", "pt-PT" the same with "pt".
'xkb:us:intl:por': 'pt',
'xkb:br::por': 'pt',
'xkb:pt::por': 'pt'
};
/**
* The input tool code and instance mapping.
*
* @type {!Object.<string, InputTool>}
* @private
*/
InputTool.instances_ = {};
/**
* Gets an input tool by code.
*
* @param {!string} inputToolCode The input tool code value.
* @return {InputTool} The input tool.
*/
InputTool.get = function(inputToolCode) {
if (!inputToolCode) {
return null;
}
// The code isn't BCP47 pattern, transfers it from old pattern.
if (!goog.object.contains(InputToolCode, inputToolCode)) {
inputToolCode = InputTool.parseToBCP47_(inputToolCode);
}
// Allow BCP47 code 'fa_t_k0_und' to 'fa-t-k0-und'.
inputToolCode = inputToolCode.replace(/_/g, '-');
// Adds '-und' to keep compatible with previous codes.
if (!goog.object.contains(InputToolCode, inputToolCode)) {
inputToolCode = InputTool.parseToBCP47_(
inputToolCode + '-und');
}
if (InputTool.instances_[inputToolCode]) {
return InputTool.instances_[inputToolCode];
}
// If the input tool code is valid.
if (goog.object.contains(InputToolCode, inputToolCode)) {
InputTool.instances_[inputToolCode] =
new InputTool(
/** @type {InputToolCode} */ (inputToolCode));
return InputTool.instances_[inputToolCode];
}
return null;
};
/**
* Language codes whose BCP47 code has a rule like:
* Has 'und-latn', then adding 'phone' at last, otherwise, 'inscript' at last.
*
* @type {!Array.<string>}
* @private
*/
InputTool.PHONETIC_INSCRIPT_LANGS_ = [
'bn', 'gu', 'pa', 'kn', 'ml', 'or', 'sa', 'ta', 'te', 'ne'
];
/**
* Special previous old code mapping to BCP47 code.
*
* @type {!Object.<string, string>}
* @private
* @const
*/
InputTool.BCP47_SPECIAL_ = {
'im_pinyin_zh_hans': InputToolCode.INPUTMETHOD_PINYIN_CHINESE_SIMPLIFIED,
'im_pinyin_zh_hant': InputToolCode.INPUTMETHOD_PINYIN_CHINESE_TRADITIONAL,
'im_t13n_ja': InputToolCode.INPUTMETHOD_TRANSLITERATION_JAPANESE,
'im_t13n_ja-Hira': InputToolCode.INPUTMETHOD_TRANSLITERATION_HIRAGANA,
'im_wubi_zh_hans': InputToolCode.INPUTMETHOD_WUBI_CHINESE_SIMPLIFIED,
'im_zhuyin_zh_hant': InputToolCode.INPUTMETHOD_ZHUYIN_CHINESE_TRADITIONAL,
'vkd_bg_phone': InputToolCode.KEYBOARD_BULGARIAN_PHONETIC,
'vkd_chr_phone': InputToolCode.KEYBOARD_CHEROKEE_PHONETIC,
'vkd_cs_qwertz': InputToolCode.KEYBOARD_CZECH_QWERTZ,
'vkd_deva_phone': InputToolCode.KEYBOARD_DEVANAGARI_PHONETIC,
'vkd_en_dvorak': InputToolCode.KEYBOARD_ENGLISH_DVORAK,
'vkd_es_es': InputToolCode.KEYBOARD_SPANISH,
'vkd_ethi': InputToolCode.KEYBOARD_ETHIOPIC,
'vkd_gu_phone': InputToolCode.KEYBOARD_GUJARATI_PHONETIC,
'vkd_guru_inscript': InputToolCode.KEYBOARD_GURMUKHI_INSCRIPT,
'vkd_guru_phone': InputToolCode.KEYBOARD_GURMUKHI_PHONETIC,
'vkd_hu_101': InputToolCode.KEYBOARD_HUNGARIAN_101,
'vkd_hy_east': InputToolCode.KEYBOARD_ARMENIAN_EASTERN,
'vkd_hy_west': InputToolCode.KEYBOARD_ARMENIAN_WESTERN,
'vkd_ka_qwerty': InputToolCode.KEYBOARD_GEORGIAN_QWERTY,
'vkd_ka_typewriter': InputToolCode.KEYBOARD_GEORGIAN_TYPEWRITER,
'vkd_ro_sr13392_primary': InputToolCode.KEYBOARD_ROMANIAN_SR13392_PRIMARY,
'vkd_ro_sr13392_secondary': InputToolCode.KEYBOARD_ROMANIAN_SR13392_SECONDARY,
'vkd_ru_phone': InputToolCode.KEYBOARD_RUSSIAN_PHONETIC,
'vkd_ru_phone_aatseel': InputToolCode.KEYBOARD_RUSSIAN_PHONETIC_AATSEEL,
'vkd_ru_phone_yazhert': InputToolCode.KEYBOARD_RUSSIAN_PHONETIC_YAZHERT,
'vkd_sk_qwerty': InputToolCode.KEYBOARD_SLOVAK_QWERTY,
'vkd_ta_itrans': InputToolCode.KEYBOARD_TAMIL_ITRANS,
'vkd_ta_tamil99': InputToolCode.KEYBOARD_TAMIL_99,
'vkd_ta_typewriter': InputToolCode.KEYBOARD_TAMIL_TYPEWRITER,
'vkd_th_pattajoti': InputToolCode.KEYBOARD_THAI_PATTAJOTI,
'vkd_th_tis': InputToolCode.KEYBOARD_THAI_TIS,
'vkd_tr_f': InputToolCode.KEYBOARD_TURKISH_F,
'vkd_tr_q': InputToolCode.KEYBOARD_TURKISH_Q,
'vkd_uk_101': InputToolCode.KEYBOARD_UKRAINIAN_101,
'vkd_us_intl': InputToolCode.KEYBOARD_FRENCH_INTL,
'vkd_uz_cyrl_phone': InputToolCode.KEYBOARD_UZBEK_CYRILLIC_PHONETIC,
'vkd_uz_cyrl_type': InputToolCode.KEYBOARD_UZBEK_CYRILLIC_TYPEWRITTER,
'vkd_vi_tcvn': InputToolCode.KEYBOARD_VIETNAMESE_TCVN,
'vkd_vi_telex': InputToolCode.KEYBOARD_VIETNAMESE_TELEX
};
/**
* BCP47 code maps to previous code.
*
* @type {!Object.<string, string>}
* @private
*/
InputTool.BCP47_SPECIAL_REVERSE_ = goog.object.transpose(
InputTool.BCP47_SPECIAL_);
/**
* Special keyboard layout code mapping. Multiple Input Tools map to the same
* layout.
*
* key: Input Tool code.
* value: layout code.
*
* @private {!Object.<string, string>}
*/
InputTool.SpecialLayoutCodes_ = goog.object.create(
InputToolCode.KEYBOARD_DUTCH_INTL, 'us_intl',
InputToolCode.KEYBOARD_FRENCH_INTL, 'us_intl',
InputToolCode.KEYBOARD_GERMAN_INTL, 'us_intl',
InputToolCode.KEYBOARD_HAITIAN, 'fr',
InputToolCode.KEYBOARD_INDONESIAN, 'latn_002',
InputToolCode.KEYBOARD_IRISH, 'latn_002',
InputToolCode.KEYBOARD_ITALIAN_INTL, 'us_intl',
InputToolCode.KEYBOARD_JAVANESE, 'latn_002',
InputToolCode.KEYBOARD_MARATHI, 'deva_phone',
InputToolCode.KEYBOARD_MARATHI_INSCRIPT, 'hi',
InputToolCode.KEYBOARD_MALAY, 'latn_002',
InputToolCode.KEYBOARD_PORTUGUESE_BRAZIL_INTL, 'us_intl',
InputToolCode.KEYBOARD_PORTUGUESE_PORTUGAL_INTL, 'us_intl',
InputToolCode.KEYBOARD_SANSKRIT_INSCRIPT, 'hi',
InputToolCode.KEYBOARD_SPANISH_INTL, 'us_intl',
InputToolCode.KEYBOARD_SWAHILI, 'latn_002',
InputToolCode.KEYBOARD_TAGALOG, 'latn_002',
InputToolCode.KEYBOARD_TIGRINYA, 'ethi',
InputToolCode.KEYBOARD_WELSH, 'latn_002');
/**
* Parses previous old code to BCP 47 code.
*
* @param {string} itCode Previous old input tool code format.
* @return {string} BCP 47 code.
* @private
*/
InputTool.parseToBCP47_ = function(itCode) {
if (InputTool.BCP47_SPECIAL_[itCode]) {
return InputTool.BCP47_SPECIAL_[itCode];
}
if (itCode == 'vkd_iw') {
return InputToolCode.KEYBOARD_HEBREW;
}
if (itCode == 'im_t13n_iw') {
return InputToolCode.INPUTMETHOD_TRANSLITERATION_HEBREW;
}
// Types 'legacy' to 'lagacy' by mistake, correct it.
// Can't put 'tr' + '-t-k0-lagacy' into BCP47_SPECIAL_ map, becasue we have
// to split 'tr-t-k0-lagacy' but JS grammar wasn't allow to
// use 'tr' + '-t-k0-lagacy' as key.
if (itCode == 'tr' + '-t-k0-lagacy') {
return InputToolCode.KEYBOARD_TURKISH_F;
}
var parts = itCode.split('_');
var code = '';
if (goog.string.startsWith(itCode, 'im_t13n')) {
// Example: 'im_t13n_hi'.
code = parts[2] + '-t-i0-und';
} else if (goog.string.startsWith(itCode, 'vkd_')) {
// Special codes for keyboard.
if (parts.length == 2) {
// Example: 'vkd_sq'
code = parts[1] + '-t-k0-und';
} else {
if (goog.array.contains(
InputTool.PHONETIC_INSCRIPT_LANGS_, parts[1])) {
if (parts[2] == 'inscript') {
code = parts[1] + '-t-k0-und';
} else {
code = parts[1] + '-t-und-latn-k0-und';
}
} else {
code = parts[1] + '-t-k0-' + parts[2];
if (!goog.object.contains(InputToolCode, code)) {
code = parts[1] + '-' + parts[2] + '-t-k0-und';
}
}
}
}
return goog.object.contains(InputToolCode, code) ? code : itCode;
};
/**
* Gets the input tools by parameters. Keep compatible with previous language
* code pair. Not support to get input tool by keyboard layout.
*
* @param {!InputToolType} type The input tool type.
* @param {!string} code It's the target language code if type is input method.
* @return {InputTool} The input tool.
*/
InputTool.getInputTool = function(type, code) {
// Makes compatible input tool code with previous language code version.
if (type == InputToolType.IME) {
if (code == 'zh' || code == 'zh-Hans') {
return InputTool.get(
InputToolCode.INPUTMETHOD_PINYIN_CHINESE_SIMPLIFIED);
} else if (code == 'zh-Hant') {
return InputTool.get(
InputToolCode.INPUTMETHOD_ZHUYIN_CHINESE_TRADITIONAL);
} else if (code == 'ja') {
return InputTool.get(
InputToolCode.INPUTMETHOD_TRANSLITERATION_JAPANESE);
} else {
return InputTool.get(code + '-t-i0-und');
}
} else if (type == InputToolType.KBD) {
return InputTool.get('vkd_' + code);
}
return null;
};
/**
* Parses BCP47 codes to the virtual keyboard layout.
*
* @private
*/
InputTool.prototype.parseLayoutCode_ = function() {
if (InputTool.SpecialLayoutCodes_[this.code]) {
this.layoutCode = InputTool.SpecialLayoutCodes_[this.code];
} else if (InputTool.BCP47_SPECIAL_REVERSE_[this.code]) {
// Removes prefix 'vkd_';
this.layoutCode = InputTool.
BCP47_SPECIAL_REVERSE_[this.code].slice(4);
} else {
var parts = this.code.split('-t-');
var countryCode = parts[0];
var inputToolType = parts[1];
countryCode = countryCode.replace(/-/g, '_');
if (countryCode == 'en_us') {
countryCode = 'us';
}
if (goog.array.contains(
InputTool.PHONETIC_INSCRIPT_LANGS_, countryCode) &&
(inputToolType == 'und-latn-k0-und' || inputToolType == 'k0-und')) {
// If it's virtual keyboard having the inscript/phonetic rule.
this.layoutCode = countryCode +
(inputToolType == 'k0-und' ? '_inscript' : '_phone');
} else if (inputToolType == 'k0-und') {
this.layoutCode = countryCode;
} else {
var matches = inputToolType.match(/k0-(.*)/);
if (matches[1]) {
this.layoutCode = countryCode + '_' + matches[1].replace(
'qwerty', 'phone').replace('-', '_');
}
}
}
};
/**
* Parses the input tool code.
* TODO(wuyingbing): We will introduce new code pattern, and then write a new
* parsing method.
*
* @private
*/
InputTool.prototype.parseInputToolCode_ = function() {
// Sets the input tool type.
if (this.code.indexOf('-i0') >= 0) {
this.type = InputToolType.IME;
if (goog.string.endsWith(this.code, '-handwrit')) {
this.type = InputToolType.HWT;
} else if (goog.string.endsWith(this.code, '-voice')) {
this.type = InputToolType.VOICE;
}
} else if (this.code.indexOf('-k0') >= 0) {
this.type = InputToolType.KBD;
} else if (goog.string.startsWith(this.code, 'xkb')) {
this.type = InputToolType.XKB;
}
// Sets target language code.
var codes = this.code.split(/-t|-i0|-k0|:/);
if (codes[0] == 'yue-hant') {
codes[0] = 'zh-Hant';
}
switch (this.code) {
// Currently most of systems doesn't support 'yue-hant', so hack it to
// 'zh-hant';
case InputToolCode.INPUTMETHOD_CANTONESE_TRADITIONAL:
codes[0] = 'zh-Hant';
break;
case InputToolCode.INPUTMETHOD_PINYIN_CHINESE_SIMPLIFIED:
case InputToolCode.INPUTMETHOD_WUBI_CHINESE_SIMPLIFIED:
codes[0] = 'zh-Hans';
break;
}
if (this.type == InputToolType.XKB) {
if (InputTool.XkbId2Language_[this.code]) {
this.languageCode = InputTool.XkbId2Language_[this.code];
} else {
this.languageCode = this.formatLanguageCode_(codes[codes.length - 1]);
}
} else {
this.languageCode = this.formatLanguageCode_(codes[0]);
// Sets source language target.
if (codes[1]) {
this.sourceLanguageCode = this.formatLanguageCode_(codes[1]);
}
}
if (this.type == InputToolType.KBD) {
this.parseLayoutCode_();
}
};
/** @override */
InputTool.prototype.toString = function() {
return this.code;
};
/**
* Gets the input tool's direction.
*
* @return {string} The direction string - 'rtl' or 'ltr'.
*/
InputTool.prototype.getDirection = function() {
return this.isRightToLeft() ? 'rtl' : 'ltr';
};
/**
* Gets the input tool's direction.
*
* @return {boolean} Whether is rtl direction of the input tool.
*/
InputTool.prototype.isRightToLeft = function() {
return goog.array.contains(InputTool.RtlIMEs, this.code) ||
goog.array.contains(InputTool.RtlKeyboards, this.code);
};
/**
* Gets whether has status bar.
*
* @return {boolean} Whether has status bar.
*/
InputTool.prototype.hasStatusBar = function() {
// Don't show status bar in moblie device.
if (!GlobalSettings.mobile && this.type == InputToolType.IME) {
return /^(zh|yue)/.test(this.code);
}
return false;
};
/**
* Format language to standard language code.
*
* @param {string} code The language code.
* @return {string} The standard language code.
* @private
*/
InputTool.prototype.formatLanguageCode_ = function(code) {
// Hack 'und-ethi' to 'et'. The major population use 'ethi' script in
// Ethiopia country. So we set 'et' as language code.
if (code == 'und-ethi') {
return 'et';
}
var parts = code.split('-');
var retCode;
if (parts.length == 2) {
if (parts[1].length == 2) {
retCode = parts[0] + '-' + parts[1].toUpperCase();
} else {
retCode = parts[0] + '-' + parts[1].charAt(0).toUpperCase() +
parts[1].slice(1);
}
} else {
if (goog.object.containsKey(InputTool.LanguageCodeThreeTwoMap, parts[0])) {
retCode = InputTool.LanguageCodeThreeTwoMap[parts[0]];
} else {
retCode = parts[0];
}
}
return retCode;
};
/**
* Returns whether the input tool is transliteration or not.
*
* @return {boolean} .
*/
InputTool.prototype.isTransliteration = function() {
var reg = new RegExp('^(am|ar|bn|el|gu|he|hi|kn|ml|mr|ne|or|fa|pa|ru|sa|' +
'sr|si|ta|te|ti|ur|uk|be|bg)');
return this.type == InputToolType.IME && reg.test(this.code);
};
/**
* Returns whether the input tool is Latin suggestion or not.
*
* @return {boolean} .
*/
InputTool.prototype.isLatin = function() {
return this.type == InputToolType.IME &&
/^(en|fr|de|it|es|nl|pt|tr|sv|da|fi|no)/.test(this.code);
};
}); // goog.scope