1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { 17 isNull, 18 getValue, 19 Log 20} from '../../../utils/index'; 21 22interface I18nConstructor { 23 new(options: object): I18n; 24} 25 26export interface I18nInterface { 27 instance?: { I18n: I18nConstructor }; 28} 29 30interface VMInterface { 31 $t: Function; 32 $tc: Function; 33 _i18n: Function; 34} 35 36const instances = {}; 37 38/** 39 * This class provide internationalization support. 40 */ 41class I18n { 42 public locale: string; 43 public messages: any; 44 45 constructor(options) { 46 this.locale = options.locale || language; 47 this.messages = options.messages; 48 } 49 50 /** 51 * Provide the '$t' method to import simple resources. 52 * @param {string} path - The path of language resources which to be translated. 53 * @param {*} [params] - The values of placeholder. 54 * @return {*} The translated result. 55 */ 56 public $t(path: string, params?: any): any { 57 if (typeof path !== 'string') { 58 Log.warn(`Invalid parameter type: The type of 'path' should be string, not ${typeof path}.`); 59 return; 60 } 61 if (!this._hasMessage(this.messages)) { 62 return path; 63 } 64 let value = this._getMessage(this.messages, path); 65 if (isNull(value)) { 66 return path; 67 } 68 if (Object.prototype.toString.call(value) === '[object Object]' || 69 Object.prototype.toString.call(value) === '[object Array]') { 70 return value; 71 } 72 value = this._translate(path, value, params); 73 return value; 74 } 75 76 /** 77 * Provide the '$tc' method to import singular and plural resources. 78 * @param {string} path - The path of language resources which to be translated. 79 * @param {number} [count] - The number which to be translated. 80 * @return {*} The translated result. 81 */ 82 public $tc(path: string, count?: number): any { 83 if (typeof path !== 'string') { 84 Log.warn(`Invalid parameter type: The type of 'path' should be string, not ${typeof path}.`); 85 return; 86 } 87 if (typeof count !== 'number' && !isNull(count)) { 88 Log.warn(`Invalid parameter type: The type of 'count' should be number, not ${typeof count}.`); 89 return; 90 } 91 if (!this._hasMessage(this.messages)) { 92 return path; 93 } 94 let value = this._getMessage(this.messages, path); 95 if (isNull(value)) { 96 return path; 97 } 98 if (isNull(count)) { 99 count = 1; 100 } 101 value = this._getChoice(count, path, value); 102 value = this._translate(path, value, count); 103 return value; 104 } 105 106 /** 107 * Extend _i18n to Vm. 108 * @param {VMInterface} Vm - The Vm. 109 */ 110 public extend(Vm: VMInterface): void { 111 Object.defineProperty(Vm, '_i18n', { 112 configurable: true, 113 enumerable: true, 114 get: function proxyGetter() { 115 return this.i18n ? this.i18n : global.aceapp.i18n; 116 } 117 }); 118 Vm.$t = function(path, params) { 119 const i18n = this._i18n; 120 return i18n.$t(path, params); 121 }; 122 Vm.$tc = function(path, count) { 123 const i18n = this._i18n; 124 return i18n.$tc(path, count); 125 }; 126 } 127 128 private _hasMessage(message: object[]): boolean { 129 if (!message || message.length === 0) { 130 Log.debug('I18n message is null.'); 131 return false; 132 } 133 return true; 134 } 135 136 private _getMessage(messages: any[], path: string): any { 137 for (const i in messages) { 138 const value = getValue(path, messages[i]); 139 if (!isNull(value)) { 140 return value; 141 } 142 } 143 return null; 144 } 145 146 private _getChoice(count: number, path: string, message: any): any { 147 const pluralChoice = i18nPluralRules.select(count); 148 if (!pluralChoice) { 149 Log.debug('PluralChoice is null.'); 150 return path; 151 } 152 return getValue(pluralChoice, message); 153 } 154 155 private _translate(path: string, value: any, params: any): any { 156 if (isNull(value)) { 157 return path; 158 } 159 if (Object.prototype.toString.call(params) === '[object Array]') { 160 value = value.replace(/\{(\d+)\}/g, (_, index) => { 161 if (index > params.length - 1 || index < 0) { 162 return ''; 163 } 164 return params[index]; 165 }); 166 } else if (Object.prototype.toString.call(params) === '[object Object]') { 167 value = value.replace(/\{(\w+)\}/g, (_, name) => { 168 if (name in params) { 169 return params[name]; 170 } 171 return ''; 172 }); 173 } else if (Object.prototype.toString.call(params) === '[object Number]') { 174 value = value.replace(/\{count\}/g, params.toLocaleString(this.locale.replace('_', '-'))); 175 } else { 176 return value; 177 } 178 return value; 179 } 180} 181 182/** 183 * Init the i18n object. 184 */ 185export default { 186 create: (id: number): I18nInterface | null => { 187 instances[id] = []; 188 if (typeof global.I18n === 'function') { 189 return {}; 190 } 191 const i18nObject = { 192 I18n: class extends I18n { 193 constructor(options) { 194 super(options); 195 instances[id].push(this); 196 } 197 } 198 }; 199 return { 200 instance: i18nObject 201 }; 202 }, 203 destroy: (id: number): void => { 204 delete instances[id]; 205 } 206}; 207