1/* 2 * Copyright (c) 2025 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 16 package std.core; 17 18 export namespace Intl { 19 20 const calendarValues: ReadonlyArray<string> = Array.of<string> ( 21 "buddhist", 22 "chinese", 23 "coptic", 24 "dangi", 25 "ethioaa", 26 "ethiopic", 27 "gregory", 28 "hebrew", 29 "indian", 30 "islamic", 31 "islamic-umalqura", 32 "islamic-tbla", 33 "islamic-civil", 34 "islamic-rgsa", 35 "iso8601", 36 "japanese", 37 "persian", 38 "roc", 39 "islamicc" 40 ) 41 42 const extensionValues = [ 43 "ca", // calendar 44 "kf", // caseFirst 45 "co", // collation 46 "hc", // hourCycle 47 "nu", // numberingSystem 48 "kn" // numeric 49 ] 50 51 const caseFirstValues: ReadonlyArray<string> = Array.of<string>("upper", "lower", "false") 52 const hourCycleValues: ReadonlyArray<string> = Array.of<string>("h12", "h23", "h11", "h24") 53 54 const collationValues: ReadonlyArray<string> = Array.of<string>( 55 "big5han", 56 "compat", 57 "dict", 58 "direct", 59 "ducet", 60 "emoji", 61 "eor", 62 "gb2312", 63 "phonebk", 64 "phonetic", 65 "pinyin", 66 "reformed", 67 "search", 68 "searchjl", 69 "standard", 70 "stroke", 71 "trad", 72 "unihan", 73 "zhuyin" 74 ) 75 76 enum LocaleInfoKeys { 77 LANG, 78 SCRIPT, 79 COUNTRY, 80 U_CA, 81 U_KF, 82 U_CO, 83 U_HC, 84 U_NU, 85 U_KN, 86 COUNT 87 } 88 89 export interface LocaleOptions { 90 baseName?: string 91 language?: string 92 calendar?: string 93 caseFirst?: string // LocaleCollationCaseFirst 94 numeric?: boolean 95 collation?: string 96 hourCycle?: string // LocaleHourCycleKey 97 numberingSystem?: string 98 region?: string 99 script?: string 100 } 101 102 export class Locale implements LocaleOptions { 103 private _baseName?: string; 104 private _language?: string; 105 private _calendar?: string; 106 private _caseFirst?: string; 107 private _numeric?: boolean; 108 private _collation?: string; 109 private _hourCycle?: string; 110 private _numberingSystem?: string; 111 private _region?: string; 112 private _script?: string; 113 114 set baseName(value: string | undefined) { 115 this._baseName = value 116 } 117 118 get baseName(): string | undefined { 119 return this._baseName 120 } 121 122 set language(value: string | undefined) { 123 this._language = value 124 } 125 126 get language(): string | undefined { 127 return this._language 128 } 129 130 set calendar(value: string | undefined) { 131 this._calendar = value 132 } 133 134 get calendar(): string | undefined { 135 return this._calendar 136 } 137 138 set caseFirst(value: string | undefined) { 139 this._caseFirst = value 140 } 141 142 get caseFirst(): string | undefined { 143 return this._caseFirst 144 } 145 146 set numeric(value: boolean | undefined) { 147 this._numeric = value 148 } 149 150 get numeric(): boolean | undefined { 151 return this._numeric 152 } 153 154 set collation(value: string | undefined) { 155 this._collation = value 156 } 157 158 get collation(): string | undefined { 159 return this._collation 160 } 161 162 set hourCycle(value: string | undefined) { 163 this._hourCycle = value 164 } 165 166 get hourCycle(): string | undefined { 167 return this._hourCycle 168 } 169 170 set numberingSystem(value: string | undefined) { 171 this._numberingSystem = value 172 } 173 174 get numberingSystem(): string | undefined { 175 return this._numberingSystem 176 } 177 178 set region(value: string | undefined) { 179 this._region = value 180 } 181 182 get region(): string | undefined { 183 return this._region 184 } 185 186 set script(value: string | undefined) { 187 this._script = value 188 } 189 190 get script(): string | undefined { 191 return this._script 192 } 193 194 /** 195 * The Intl.Locale() constructor creates Intl.Locale objects. 196 * 197 * @param tag BCP47LanguageTag or other Locale 198 * @param options options 199 */ 200 public constructor(tag: BCP47LanguageTag | Locale, options?: LocaleOptions) { 201 this.initLocale() 202 this.language = this.defaultLang() 203 if (tag instanceof BCP47LanguageTag) { 204 this.tag = tag 205 this.parseTag() 206 } 207 else { 208 this.language = (tag.language != undefined) ? tag.language : this.defaultLang() 209 this.baseName = (tag.baseName != undefined) ? tag.baseName : this.defaultLang() 210 this.calendar = (tag.calendar != undefined) ? tag.calendar : "" 211 this.caseFirst = (tag.caseFirst != undefined) ? tag.caseFirst : undefined 212 this.numeric = (tag.numeric != undefined) ? tag.numeric : false 213 this.collation = tag.collation 214 this.hourCycle = tag.hourCycle 215 this.numberingSystem = tag.numberingSystem 216 this.region = tag.region 217 this.script = tag.script 218 this.tag = tag.tag 219 } 220 if (options != undefined) { 221 this.checkOptions(options) 222 this.language = (options.language != undefined) ? options.language : this.defaultLang() 223 this.baseName = (options.baseName != undefined) ? options.baseName : this.defaultLang() 224 this.calendar = (options.calendar != undefined) ? options.calendar : "" 225 this.caseFirst = (options.caseFirst != undefined) ? options.caseFirst : undefined 226 this.numeric = (options.numeric != undefined) ? options.numeric : false 227 this.collation = (options.collation != undefined) ? options.collation : this.collation 228 this.hourCycle = (options.hourCycle != undefined) ? options.hourCycle : this.hourCycle 229 this.numberingSystem = (options.numberingSystem != undefined) ? options.numberingSystem : this.numberingSystem 230 this.region = (options.region != undefined) ? (options.region as string).toUpperCase() : this.region 231 this.script = (options.script != undefined) ? options.script : this.script 232 } 233 234 if (this.region != undefined && !this.regionList().includes((this.region as string).toUpperCase())) { 235 throw new RangeError(this.errMsg) 236 } 237 238 if (this.script != undefined && !this.scriptList().includes(this.script as string)) { 239 throw new RangeError(this.errMsg) 240 } 241 242 this.tag = this.createTag() 243 } 244 245 native langList() : string 246 native regionList() : string 247 native scriptList() : string 248 native numberingSystemList() : string 249 native initLocale() : void 250 native maximizeInfo(lang: string) : string 251 native isTagValid(tag: string) : int 252 native parseTagImpl(tag:string) : string 253 private native defaultLang() : string 254 255 static native defaultTag(): string 256 257 private fillBaseName() { 258 let sb = new StringBuilder() 259 sb.append(this.language as string) 260 if (this.script != undefined && this.script != "" && this.script != " ") { 261 sb.append("-") 262 sb.append(this.script as string) 263 } 264 if (this.region != undefined && this.region != "" && this.region != " ") { 265 sb.append("-") 266 sb.append(this.region as string) 267 } 268 this.baseName = sb.toString() 269 } 270 271 /** 272 * The maximize() method of Intl.Locale instances gets the most likely values for the language, 273 * script, and region of this locale based on existing values. 274 * @return A Intl.Locale instance whose baseName property returns the result of the 275 * Add Likely Subtags algorithm executed against locale.baseName. 276 */ 277 public maximize() : Locale { 278 let res = new Locale(this) 279 let info = this.maximizeInfo(this.language as string).split(",") 280 res.region = info[0] 281 res.script = info.length > 1 ? info[1] : res.script 282 this.fillBaseName() 283 res.tag = res.createTag() 284 return res 285 } 286 287 /** 288 * The minimize() attempts to remove information about this locale that would be added by calling maximize(). 289 * @return A Intl.Locale instance whose baseName property returns the result of the 290 * Remove Likely Subtags algorithm executed against locale.baseName. 291 */ 292 public minimize() : Locale { 293 let res = new Locale(this) 294 res.region = undefined 295 res.script = undefined 296 res.baseName = res.language 297 res.tag = res.createTag() 298 return res 299 } 300 301 /** 302 * The toString() method of Intl.Locale instances returns this Locale's full locale identifier string. 303 * @return The locale's Unicode locale identifier string. 304 */ 305 public toString() : BCP47LanguageTag { 306 return this.tag 307 } 308 309 private tag: BCP47LanguageTag = "" 310 311 private uFlags() : BCP47LanguageTag { 312 let sb = new StringBuilder() 313 if ((this.numeric != undefined && this.numeric as boolean == true)) { 314 sb.append("-u-kn") 315 } 316 if (this.caseFirst != undefined && this.caseFirst != "") { 317 sb.append("-u-kf-" + this.caseFirst as string) 318 } 319 if (this.calendar != undefined && this.calendar != "") { 320 sb.append("-u-ca-" + this.calendar as string) 321 } 322 if (this.collation != undefined && this.collation != "") { 323 sb.append("-u-co-" + this.collation as string) 324 } 325 if (this.hourCycle != undefined && this.hourCycle != "" && (this.hourCycle instanceof LocaleHourCycleKey)) { 326 sb.append("-u-hc-" + this.hourCycle as string) 327 } 328 if (this.numberingSystem != undefined && this.numberingSystem != "") { 329 sb.append("-u-nu-" + this.numberingSystem as string) 330 } 331 return sb.toString() 332 } 333 334 private createTag() : BCP47LanguageTag { 335 let sb = new StringBuilder() 336 this.fillBaseName() 337 sb.append(this.baseName as string) 338 sb.append(this.uFlags()) 339 return sb.toString() 340 } 341 342 private errOptionMessage(val : string, name : string) : string { 343 return "Value " + val + " out of range for locale options property " + name 344 } 345 346 private errMsg = "Incorrect locale information provided" 347 private checkOptions(options: LocaleOptions) { 348 if ((options.calendar != undefined && !calendarValues.includes(options.calendar!)) || 349 (options.caseFirst != undefined && !caseFirstValues.includes(options.caseFirst!)) || 350 (options.collation != undefined && !collationValues.includes(options.collation!)) || 351 (options.hourCycle != undefined && !hourCycleValues.includes(options.hourCycle!)) || 352 (options.numberingSystem != undefined && (options.numberingSystem! == "" || 353 !this.numberingSystemList().includes(options.numberingSystem!))) || 354 (options.region != undefined && (options.region! == "" || !this.regionList().includes((options.region! as String).toUpperCase()))) || 355 (options.script != undefined && (options.script! == "" || !this.scriptList().includes(options.script!))) 356 ) { 357 throw new RangeError(this.errMsg) 358 } 359 } 360 361 private parseTag() { 362 if (this.tag == "") { 363 throw new RangeError("First argument to Intl.Locale constructor can't be empty or missing") 364 } 365 366 if (this.isTagValid(this.tag as string) == 1) { 367 throw new RangeError(this.errMsg) 368 } 369 370 let argsStr : string = this.parseTagImpl(this.tag) 371 let args = argsStr.split(" ") 372 let getVale = (val: string) : string|undefined => {return (val == "") ? undefined : val} 373 this.language = args[LocaleInfoKeys.LANG as int] 374 this.script = getVale(args[LocaleInfoKeys.SCRIPT as int]) 375 this.region = getVale(args[LocaleInfoKeys.COUNTRY as int]) 376 this.calendar = getVale(args[LocaleInfoKeys.U_CA as int]) 377 this.collation = getVale(args[LocaleInfoKeys.U_CO as int]) 378 this.numberingSystem = getVale(args[LocaleInfoKeys.U_NU as int]) 379 this.hourCycle = getVale(args[LocaleInfoKeys.U_HC as int]) 380 this.numeric = (args[LocaleInfoKeys.U_KN as int] == "true") ? true : false 381 this.caseFirst = getVale(args[LocaleInfoKeys.U_KF as int]) 382 383 if (this.tag.includes("-u")) { 384 if (this.language != undefined && !this.langList().includes(this.language as string)) { 385 throw new RangeError("Incorrect locale information provided") 386 } 387 } 388 this.fillBaseName() 389 } 390 391 private fillScriptOrRegion(val : string) { 392 if (val.length <= 3) { 393 this.region = val.toUpperCase() 394 } 395 else { 396 this.script = val 397 } 398 } 399 } 400} 401