• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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