rust_icu_uloc/
lib.rs

1// Copyright 2019 Google LLC
2//
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
15use {
16    anyhow::anyhow,
17    rust_icu_common as common,
18    rust_icu_common::buffered_string_method_with_retry,
19    rust_icu_sys as sys,
20    rust_icu_sys::versioned_function,
21    rust_icu_sys::*,
22    rust_icu_uenum::Enumeration,
23    rust_icu_ustring as ustring,
24    rust_icu_ustring::buffered_uchar_method_with_retry,
25    std::{
26        cmp::Ordering,
27        collections::HashMap,
28        convert::{From, TryFrom, TryInto},
29        ffi, fmt,
30        os::raw,
31    },
32};
33
34/// Maximum length of locale supported by uloc.h.
35/// See `ULOC_FULLNAME_CAPACITY`.
36const LOCALE_CAPACITY: usize = 158;
37
38/// [ULocMut] is a mutable companion to [ULoc].
39///
40/// It has methods that allow one to create a different [ULoc] by adding and
41/// removing keywords to the locale identifier.  You can only create a `ULocMut`
42/// by converting from an existing `ULoc` by calling `ULocMut::from`.  And once
43/// you are done changing it, you can only convert it back with `ULoc::from`.
44///
45/// [ULocMut] is not meant to have comprehensive coverage of mutation options.
46/// They may be added as necessary.
47#[derive(Debug, Clone)]
48pub struct ULocMut {
49    base: ULoc,
50    unicode_keyvalues: HashMap<String, String>,
51    other_keyvalues: HashMap<String, String>,
52}
53
54impl From<ULoc> for ULocMut {
55    /// Turns [ULoc] into [ULocMut], which can be mutated.
56    fn from(l: ULoc) -> Self {
57        let all_keywords = l.keywords();
58        let mut unicode_keyvalues: HashMap<String, String> = HashMap::new();
59        let mut other_keyvalues: HashMap<String, String> = HashMap::new();
60        for kw in all_keywords {
61            // Despite the many unwraps below, none should be triggered, since we know
62            // that the keywords come from the list of keywords that already exist.
63            let ukw = to_unicode_locale_key(&kw);
64            match ukw {
65                None => {
66                    let v = l.keyword_value(&kw).unwrap().unwrap();
67                    other_keyvalues.insert(kw, v);
68                }
69                Some(u) => {
70                    let v = l.unicode_keyword_value(&u).unwrap().unwrap();
71                    unicode_keyvalues.insert(u, v);
72                }
73            }
74        }
75        // base_name may return an invalid language tag, so convert here.
76        let locmut = ULocMut {
77            base: l.base_name(),
78            unicode_keyvalues,
79            other_keyvalues,
80        };
81        locmut
82    }
83}
84
85impl From<ULocMut> for ULoc {
86    // Creates an [ULoc] from [ULocMut].
87    fn from(lm: ULocMut) -> Self {
88        // Assemble the unicode extension.
89        let mut unicode_extensions_vec = lm
90            .unicode_keyvalues
91            .iter()
92            .map(|(k, v)| format!("{}-{}", k, v))
93            .collect::<Vec<String>>();
94        unicode_extensions_vec.sort();
95        let unicode_extensions: String = unicode_extensions_vec
96            .join("-");
97        let unicode_extension: String = if unicode_extensions.len() > 0 {
98            vec!["u-".to_string(), unicode_extensions]
99                .into_iter()
100                .collect()
101        } else {
102            "".to_string()
103        };
104        // Assemble all other extensions.
105        let mut all_extensions: Vec<String> = lm
106            .other_keyvalues
107            .iter()
108            .map(|(k, v)| format!("{}-{}", k, v))
109            .collect();
110        if unicode_extension.len() > 0 {
111            all_extensions.push(unicode_extension);
112        }
113        // The base language must be in the form of BCP47 language tag to
114        // be usable in the code below.
115        let base_tag = lm.base.to_language_tag(true)
116            .expect("should be known-good");
117        let mut everything_vec: Vec<String> = vec![base_tag];
118        if !all_extensions.is_empty() {
119            all_extensions.sort();
120            let extension_string = all_extensions.join("-");
121            everything_vec.push(extension_string);
122        }
123        let everything = everything_vec.join("-").to_lowercase();
124        ULoc::for_language_tag(&everything).unwrap()
125    }
126}
127
128impl ULocMut {
129    /// Sets the specified unicode extension keyvalue.  Only valid keys can be set,
130    /// inserting an invalid extension key does not change [ULocMut].
131    pub fn set_unicode_keyvalue(&mut self, key: &str, value: &str) -> Option<String> {
132        if let None = to_unicode_locale_key(key) {
133            return None;
134        }
135        self.unicode_keyvalues
136            .insert(key.to_string(), value.to_string())
137    }
138
139    /// Removes the specified unicode extension keyvalue.  Only valid keys can
140    /// be removed, attempting to remove an invalid extension key does not
141    /// change [ULocMut].
142    pub fn remove_unicode_keyvalue(&mut self, key: &str) -> Option<String> {
143        if let None = to_unicode_locale_key(key) {
144            return None;
145        }
146        self.unicode_keyvalues.remove(key)
147    }
148}
149
150/// A representation of a Unicode locale.
151///
152/// For the time being, only basic conversion and methods are in fact implemented.
153///
154/// To get basic validation when creating a locale, use
155/// [`for_language_tag`](ULoc::for_language_tag) with a Unicode BCP-47 locale ID.
156#[derive(Debug, Clone, Eq, PartialEq, Hash)]
157pub struct ULoc {
158    // A locale's representation in C is really just a string.
159    repr: String,
160}
161
162/// Implement the Display trait to convert the ULoc into string for display.
163///
164/// The string for display and string serialization happen to be the same for [ULoc].
165impl fmt::Display for ULoc {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        write!(f, "{}", self.repr)
168    }
169}
170
171impl TryFrom<&str> for ULoc {
172    type Error = common::Error;
173    /// Creates a new ULoc from a string slice.
174    ///
175    /// The creation wil fail if the locale is nonexistent.
176    fn try_from(s: &str) -> Result<Self, Self::Error> {
177        let s = String::from(s);
178        ULoc { repr: s }.canonicalize()
179    }
180}
181
182impl TryFrom<&ffi::CStr> for ULoc {
183    type Error = common::Error;
184
185    /// Creates a new `ULoc` from a borrowed C string.
186    fn try_from(s: &ffi::CStr) -> Result<Self, Self::Error> {
187        let repr = s.to_str()?;
188        ULoc {
189            repr: String::from(repr),
190        }
191        .canonicalize()
192    }
193}
194
195impl ULoc {
196    /// Implements `uloc_getLanguage`.
197    pub fn language(&self) -> Option<String> {
198        self.call_buffered_string_method_to_option(versioned_function!(uloc_getLanguage))
199    }
200
201    /// Implements `uloc_getScript`.
202    pub fn script(&self) -> Option<String> {
203        self.call_buffered_string_method_to_option(versioned_function!(uloc_getScript))
204    }
205
206    /// Implements `uloc_getCountry`.
207    pub fn country(&self) -> Option<String> {
208        self.call_buffered_string_method_to_option(versioned_function!(uloc_getCountry))
209    }
210
211    /// Implements `uloc_getVariant`.
212    pub fn variant(&self) -> Option<String> {
213        self.call_buffered_string_method_to_option(versioned_function!(uloc_getVariant))
214    }
215
216    /// Implements `uloc_getName`.
217    pub fn name(&self) -> Option<String> {
218        self.call_buffered_string_method_to_option(versioned_function!(uloc_getName))
219    }
220
221    /// Implements `uloc_canonicalize` from ICU4C.
222    pub fn canonicalize(&self) -> Result<ULoc, common::Error> {
223        self.call_buffered_string_method(versioned_function!(uloc_canonicalize))
224            .map(|repr| ULoc { repr })
225    }
226
227    /// Implements 'uloc_getISO3Language' from ICU4C.
228    pub fn iso3_language(&self) -> Option<String> {
229        let lang = unsafe {
230            ffi::CStr::from_ptr(
231                versioned_function!(uloc_getISO3Language)(self.as_c_str().as_ptr())
232            ).to_str()
233        };
234        let value = lang.unwrap();
235        if value.is_empty() {
236            None
237        } else {
238            Some(value.to_string())
239        }
240    }
241
242    /// Implements 'uloc_getISO3Country' from ICU4C.
243    pub fn iso3_country(&self) -> Option<String> {
244        let country = unsafe {
245            ffi::CStr::from_ptr(
246                versioned_function!(uloc_getISO3Country)(self.as_c_str().as_ptr())
247            ).to_str()
248        };
249        let value = country.unwrap();
250        if value.is_empty() {
251            None
252        } else {
253            Some(value.to_string())
254        }
255    }
256
257    /// Implements `uloc_getDisplayLanguage`.
258    pub fn display_language(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
259        buffered_uchar_method_with_retry!(
260            display_language_impl,
261            LOCALE_CAPACITY,
262            [locale: *const raw::c_char, display_locale: *const raw::c_char,],
263            []
264        );
265        display_language_impl(
266            versioned_function!(uloc_getDisplayLanguage),
267            self.as_c_str().as_ptr(),
268            display_locale.as_c_str().as_ptr(),
269        )
270    }
271
272    /// Implements `uloc_getDisplayScript`.
273    pub fn display_script(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
274        buffered_uchar_method_with_retry!(
275            display_script_impl,
276            LOCALE_CAPACITY,
277            [locale: *const raw::c_char, display_locale: *const raw::c_char,],
278            []
279        );
280        display_script_impl(
281            versioned_function!(uloc_getDisplayScript),
282            self.as_c_str().as_ptr(),
283            display_locale.as_c_str().as_ptr(),
284        )
285    }
286
287    /// Implements `uloc_getDisplayCountry`.
288    pub fn display_country(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
289        buffered_uchar_method_with_retry!(
290            display_country_impl,
291            LOCALE_CAPACITY,
292            [locale: *const raw::c_char, display_locale: *const raw::c_char,],
293            []
294        );
295        display_country_impl(
296            versioned_function!(uloc_getDisplayCountry),
297            self.as_c_str().as_ptr(),
298            display_locale.as_c_str().as_ptr(),
299        )
300    }
301
302    /// Implements `uloc_getDisplayVariant`.
303    pub fn display_variant(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
304        buffered_uchar_method_with_retry!(
305            display_variant_impl,
306            LOCALE_CAPACITY,
307            [locale: *const raw::c_char, display_locale: *const raw::c_char,],
308            []
309        );
310        display_variant_impl(
311            versioned_function!(uloc_getDisplayCountry),
312            self.as_c_str().as_ptr(),
313            display_locale.as_c_str().as_ptr(),
314        )
315    }
316
317    /// Implements `uloc_getDisplayKeyword`.
318    pub fn display_keyword(keyword: &String, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
319        buffered_uchar_method_with_retry!(
320            display_keyword_impl,
321            LOCALE_CAPACITY,
322            [keyword: *const raw::c_char, display_locale: *const raw::c_char,],
323            []
324        );
325        let keyword = ffi::CString::new(keyword.as_str()).unwrap();
326        display_keyword_impl(
327            versioned_function!(uloc_getDisplayKeyword),
328            keyword.as_ptr(),
329            display_locale.as_c_str().as_ptr(),
330        )
331    }
332
333    /// Implements `uloc_getDisplayKeywordValue`.
334    pub fn display_keyword_value(&self, keyword: &String, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
335        buffered_uchar_method_with_retry!(
336            display_keyword_value_impl,
337            LOCALE_CAPACITY,
338            [keyword: *const raw::c_char, value: *const raw::c_char, display_locale: *const raw::c_char,],
339            []
340        );
341        let keyword = ffi::CString::new(keyword.as_str()).unwrap();
342        display_keyword_value_impl(
343            versioned_function!(uloc_getDisplayKeywordValue),
344            self.as_c_str().as_ptr(),
345            keyword.as_ptr(),
346            display_locale.as_c_str().as_ptr(),
347        )
348    }
349
350    /// Implements `uloc_getDisplayName`.
351    pub fn display_name(&self, display_locale: &ULoc) -> Result<ustring::UChar, common::Error> {
352        buffered_uchar_method_with_retry!(
353            display_name_impl,
354            LOCALE_CAPACITY,
355            [locale: *const raw::c_char, display_locale: *const raw::c_char,],
356            []
357        );
358        display_name_impl(
359            versioned_function!(uloc_getDisplayName),
360            self.as_c_str().as_ptr(),
361            display_locale.as_c_str().as_ptr(),
362        )
363    }
364
365    /// Implements `uloc_countAvailable`.
366    pub fn count_available() -> i32 {
367        let count = unsafe { versioned_function!(uloc_countAvailable)() };
368        count
369    }
370
371    /// Implements `uloc_getAvailable`.
372    pub fn get_available(n: i32) -> Result<ULoc, common::Error> {
373        if (0 > n) || (n >= Self::count_available()) {
374            panic!("{} is negative or greater than or equal to the value returned from count_available()", n);
375        }
376        let ptr = unsafe { versioned_function!(uloc_getAvailable)(n) };
377        if ptr == std::ptr::null() {
378            return Err(common::Error::Wrapper(anyhow!("uloc_getAvailable() returned a null pointer")));
379        }
380        let label = unsafe { ffi::CStr::from_ptr(ptr).to_str().unwrap() };
381        ULoc::try_from(label)
382    }
383
384    /// Returns a vector of available locales
385    pub fn get_available_locales() -> Vec<ULoc> {
386        let count = ULoc::count_available();
387        let mut vec = Vec::with_capacity(count as usize);
388        let mut index: i32 = 0;
389        while index < count {
390            let locale = Self::get_available(index).unwrap();
391            vec.push(locale);
392            index += 1;
393        }
394        vec
395    }
396
397    /// Implements `uloc_openAvailableByType`.
398    #[cfg(feature = "icu_version_67_plus")]
399    pub fn open_available_by_type(locale_type: ULocAvailableType) -> Result<Enumeration, common::Error> {
400        let mut status = common::Error::OK_CODE;
401        unsafe {
402            let iter = Enumeration::from_raw_parts(None, versioned_function!(uloc_openAvailableByType)(locale_type, &mut status));
403            common::Error::ok_or_warning(status)?;
404            Ok(iter)
405        }
406    }
407
408    /// Returns a vector of locales of the requested type.
409    #[cfg(feature = "icu_version_67_plus")]
410    pub fn get_available_locales_by_type(locale_type: ULocAvailableType) -> Vec<ULoc> {
411        if locale_type == ULocAvailableType::ULOC_AVAILABLE_COUNT {
412            panic!("ULOC_AVAILABLE_COUNT is for internal use only");
413        }
414        Self::open_available_by_type(locale_type).unwrap().map(|x| ULoc::try_from(x.unwrap().as_str()).unwrap()).collect()
415    }
416
417    /// Implements `uloc_addLikelySubtags` from ICU4C.
418    pub fn add_likely_subtags(&self) -> Result<ULoc, common::Error> {
419        self.call_buffered_string_method(versioned_function!(uloc_addLikelySubtags))
420            .map(|repr| ULoc { repr })
421    }
422
423    /// Implements `uloc_minimizeSubtags` from ICU4C.
424    pub fn minimize_subtags(&self) -> Result<ULoc, common::Error> {
425        self.call_buffered_string_method(versioned_function!(uloc_minimizeSubtags))
426            .map(|repr| ULoc { repr })
427    }
428
429    /// Implements `uloc_toLanguageTag` from ICU4C.
430    pub fn to_language_tag(&self, strict: bool) -> Result<String, common::Error> {
431        let locale_id = self.as_c_str();
432        // No `UBool` constants available in rust_icu_sys, unfortunately.
433        let strict = if strict { 1 } else { 0 };
434        buffered_string_method_with_retry(
435            |buf, len, error| unsafe {
436                versioned_function!(uloc_toLanguageTag)(
437                    locale_id.as_ptr(),
438                    buf,
439                    len,
440                    strict,
441                    error,
442                )
443            },
444            LOCALE_CAPACITY,
445        )
446    }
447
448    pub fn open_keywords(&self) -> Result<Enumeration, common::Error> {
449        let mut status = common::Error::OK_CODE;
450        let asciiz_locale = self.as_c_str();
451        let raw_enum = unsafe {
452            assert!(common::Error::is_ok(status));
453            versioned_function!(uloc_openKeywords)(asciiz_locale.as_ptr(), &mut status)
454        };
455        common::Error::ok_or_warning(status)?;
456        // "No error but null" means that there are no keywords
457        if raw_enum.is_null() {
458            Ok(Enumeration::empty())
459        } else {
460            Ok(unsafe { Enumeration::from_raw_parts(None, raw_enum) })
461        }
462    }
463
464    /// Implements `uloc_openKeywords()` from ICU4C.
465    pub fn keywords(&self) -> impl Iterator<Item = String> {
466        self.open_keywords()
467            .unwrap()
468            .map(|result| result.unwrap())
469    }
470
471    /// Implements `icu::Locale::getUnicodeKeywords()` from the C++ API.
472    pub fn unicode_keywords(&self) -> impl Iterator<Item = String> {
473        self.keywords().filter_map(|s| to_unicode_locale_key(&s))
474    }
475
476    /// Implements `uloc_getKeywordValue()` from ICU4C.
477    pub fn keyword_value(&self, keyword: &str) -> Result<Option<String>, common::Error> {
478        let locale_id = self.as_c_str();
479        let keyword_name = str_to_cstring(keyword);
480        buffered_string_method_with_retry(
481            |buf, len, error| unsafe {
482                versioned_function!(uloc_getKeywordValue)(
483                    locale_id.as_ptr(),
484                    keyword_name.as_ptr(),
485                    buf,
486                    len,
487                    error,
488                )
489            },
490            LOCALE_CAPACITY,
491        )
492        .map(|value| if value.is_empty() { None } else { Some(value) })
493    }
494
495    /// Implements `icu::Locale::getUnicodeKeywordValue()` from the C++ API.
496    pub fn unicode_keyword_value(
497        &self,
498        unicode_keyword: &str,
499    ) -> Result<Option<String>, common::Error> {
500        let legacy_keyword = to_legacy_key(unicode_keyword);
501        match legacy_keyword {
502            Some(legacy_keyword) => match self.keyword_value(&legacy_keyword) {
503                Ok(Some(legacy_value)) => {
504                    Ok(to_unicode_locale_type(&legacy_keyword, &legacy_value))
505                }
506                Ok(None) => Ok(None),
507                Err(e) => Err(e),
508            },
509            None => Ok(None),
510        }
511    }
512
513    /// Returns the current label of this locale.
514    pub fn label(&self) -> &str {
515        &self.repr
516    }
517
518    /// Returns the current locale name as a C string.
519    pub fn as_c_str(&self) -> ffi::CString {
520        ffi::CString::new(self.repr.clone()).expect("ULoc contained interior NUL bytes")
521    }
522
523    /// Implements `uloc_forLanguageTag` from ICU4C.
524    ///
525    /// Note that an invalid tag will cause that tag and all others to be
526    /// ignored.  For example `en-us` will work but `en_US` will not.
527    pub fn for_language_tag(tag: &str) -> Result<ULoc, common::Error> {
528        let tag = str_to_cstring(tag);
529        let locale_id = buffered_string_method_with_retry(
530            |buf, len, error| unsafe {
531                versioned_function!(uloc_forLanguageTag)(
532                    tag.as_ptr(),
533                    buf,
534                    len,
535                    std::ptr::null_mut(),
536                    error
537                )
538            },
539            LOCALE_CAPACITY,
540        )?;
541        ULoc::try_from(&locale_id[..])
542    }
543
544    /// Call a `uloc` method that takes this locale's ID and returns a string.
545    fn call_buffered_string_method(
546        &self,
547        uloc_method: unsafe extern "C" fn(
548            *const raw::c_char,
549            *mut raw::c_char,
550            i32,
551            *mut UErrorCode,
552        ) -> i32,
553    ) -> Result<String, common::Error> {
554        let asciiz = self.as_c_str();
555        buffered_string_method_with_retry(
556            |buf, len, error| unsafe { uloc_method(asciiz.as_ptr(), buf, len, error) },
557            LOCALE_CAPACITY,
558        )
559    }
560
561    /// Call a `uloc` method that takes this locale's ID, panics on any errors, and returns
562    /// `Some(result)` if the resulting string is non-empty, or `None` otherwise.
563    fn call_buffered_string_method_to_option(
564        &self,
565        uloc_method: unsafe extern "C" fn(
566            *const raw::c_char,
567            *mut raw::c_char,
568            i32,
569            *mut UErrorCode,
570        ) -> i32,
571    ) -> Option<String> {
572        let value: String = self.call_buffered_string_method(uloc_method).unwrap();
573        if value.is_empty() {
574            None
575        } else {
576            Some(value)
577        }
578    }
579
580    /// Implements `uloc_getBaseName` from ICU4C.
581    pub fn base_name(self) -> Self {
582        let result = self
583            .call_buffered_string_method(versioned_function!(uloc_getBaseName))
584            .expect("should be able to produce a shorter locale");
585        ULoc::try_from(&result[..]).expect("should be able to convert to locale")
586    }
587}
588
589/// This implementation is based on ULocale.compareTo from ICU4J.
590/// See 
591/// <https://github.com/unicode-org/icu/blob/%6d%61%73%74%65%72/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java>
592impl Ord for ULoc {
593    fn cmp(&self, other: &Self) -> Ordering {
594        /// Compare corresponding keywords from two `ULoc`s. If the keywords match, compare the
595        /// keyword values.
596        fn compare_keywords(
597            this: &ULoc,
598            self_keyword: &Option<String>,
599            other: &ULoc,
600            other_keyword: &Option<String>,
601        ) -> Option<Ordering> {
602            match (self_keyword, other_keyword) {
603                (Some(self_keyword), Some(other_keyword)) => {
604                    // Compare the two keywords
605                    match self_keyword.cmp(&other_keyword) {
606                        Ordering::Equal => {
607                            // Compare the two keyword values
608                            let self_val = this.keyword_value(&self_keyword[..]).unwrap();
609                            let other_val = other.keyword_value(&other_keyword[..]).unwrap();
610                            Some(self_val.cmp(&other_val))
611                        }
612                        unequal_ordering => Some(unequal_ordering),
613                    }
614                }
615                // `other` has run out of keywords
616                (Some(_), _) => Some(Ordering::Greater),
617                // `this` has run out of keywords
618                (_, Some(_)) => Some(Ordering::Less),
619                // Both iterators have run out
620                (_, _) => None,
621            }
622        }
623
624        self.language()
625            .cmp(&other.language())
626            .then_with(|| self.script().cmp(&other.script()))
627            .then_with(|| self.country().cmp(&other.country()))
628            .then_with(|| self.variant().cmp(&other.variant()))
629            .then_with(|| {
630                let mut self_keywords = self.keywords();
631                let mut other_keywords = other.keywords();
632
633                while let Some(keyword_ordering) =
634                    compare_keywords(self, &self_keywords.next(), other, &other_keywords.next())
635                {
636                    match keyword_ordering {
637                        Ordering::Equal => {}
638                        unequal_ordering => {
639                            return unequal_ordering;
640                        }
641                    }
642                }
643
644                // All keywords and values were identical (or there were none)
645                Ordering::Equal
646            })
647    }
648}
649
650impl PartialOrd for ULoc {
651    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
652        Some(self.cmp(other))
653    }
654}
655
656/// Gets the current system default locale.
657///
658/// Implements `uloc_getDefault` from ICU4C.
659pub fn get_default() -> ULoc {
660    let loc = unsafe { versioned_function!(uloc_getDefault)() };
661    let uloc_cstr = unsafe { ffi::CStr::from_ptr(loc) };
662    crate::ULoc::try_from(uloc_cstr).expect("could not convert default locale to ULoc")
663}
664
665/// Sets the current default system locale.
666///
667/// Implements `uloc_setDefault` from ICU4C.
668pub fn set_default(loc: &ULoc) -> Result<(), common::Error> {
669    let mut status = common::Error::OK_CODE;
670    let asciiz = str_to_cstring(&loc.repr);
671    unsafe { versioned_function!(uloc_setDefault)(asciiz.as_ptr(), &mut status) };
672    common::Error::ok_or_warning(status)
673}
674
675/// Implements `uloc_acceptLanguage` from ICU4C.
676pub fn accept_language(
677    accept_list: impl IntoIterator<Item = impl Into<ULoc>>,
678    available_locales: impl IntoIterator<Item = impl Into<ULoc>>,
679) -> Result<(Option<ULoc>, UAcceptResult), common::Error> {
680    let mut accept_result: UAcceptResult = UAcceptResult::ULOC_ACCEPT_FAILED;
681    let mut accept_list_cstrings: Vec<ffi::CString> = vec![];
682    // This is mutable only to satisfy the missing `const`s in the ICU4C API.
683    let mut accept_list: Vec<*const raw::c_char> = accept_list
684        .into_iter()
685        .map(|item| {
686            let uloc: ULoc = item.into();
687            accept_list_cstrings.push(uloc.as_c_str());
688            accept_list_cstrings
689                .last()
690                .expect("non-empty list")
691                .as_ptr()
692        })
693        .collect();
694
695    let available_locales: Vec<ULoc> = available_locales
696        .into_iter()
697        .map(|item| item.into())
698        .collect();
699    let available_locales: Vec<&str> = available_locales.iter().map(|uloc| uloc.label()).collect();
700    let mut available_locales = Enumeration::try_from(&available_locales[..])?;
701
702    let matched_locale = buffered_string_method_with_retry(
703        |buf, len, error| unsafe {
704            versioned_function!(uloc_acceptLanguage)(
705                buf,
706                len,
707                &mut accept_result,
708                accept_list.as_mut_ptr(),
709                accept_list.len() as i32,
710                available_locales.repr(),
711                error,
712            )
713        },
714        LOCALE_CAPACITY,
715    );
716
717    // Having no match is a valid if disappointing result.
718    if accept_result == UAcceptResult::ULOC_ACCEPT_FAILED {
719        return Ok((None, accept_result));
720    }
721
722    matched_locale
723        .and_then(|s| ULoc::try_from(s.as_str()))
724        .map(|uloc| (Some(uloc), accept_result))
725}
726
727/// Implements `uloc_toUnicodeLocaleKey` from ICU4C.
728pub fn to_unicode_locale_key(legacy_keyword: &str) -> Option<String> {
729    let legacy_keyword = str_to_cstring(legacy_keyword);
730    let unicode_keyword: Option<ffi::CString> = unsafe {
731        let ptr = versioned_function!(uloc_toUnicodeLocaleKey)(legacy_keyword.as_ptr());
732        ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
733    };
734    unicode_keyword.map(|cstring| cstring_to_string(&cstring))
735}
736
737/// Implements `uloc_toUnicodeLocaleType` from ICU4C.
738pub fn to_unicode_locale_type(legacy_keyword: &str, legacy_value: &str) -> Option<String> {
739    let legacy_keyword = str_to_cstring(legacy_keyword);
740    let legacy_value = str_to_cstring(legacy_value);
741    let unicode_value: Option<ffi::CString> = unsafe {
742        let ptr = versioned_function!(uloc_toUnicodeLocaleType)(
743            legacy_keyword.as_ptr(),
744            legacy_value.as_ptr(),
745        );
746        ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
747    };
748    unicode_value.map(|cstring| cstring_to_string(&cstring))
749}
750
751/// Implements `uloc_toLegacyKey` from ICU4C.
752pub fn to_legacy_key(unicode_keyword: &str) -> Option<String> {
753    let unicode_keyword = str_to_cstring(unicode_keyword);
754    let legacy_keyword: Option<ffi::CString> = unsafe {
755        let ptr = versioned_function!(uloc_toLegacyKey)(unicode_keyword.as_ptr());
756        ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned())
757    };
758    legacy_keyword.map(|cstring| cstring_to_string(&cstring))
759}
760
761/// Infallibly converts a Rust string to a `CString`. If there's an interior NUL, the string is
762/// truncated up to that point.
763fn str_to_cstring(input: &str) -> ffi::CString {
764    ffi::CString::new(input)
765        .unwrap_or_else(|e| ffi::CString::new(&input[0..e.nul_position()]).unwrap())
766}
767
768/// Infallibly converts a `CString` to a Rust `String`. We can safely assume that any strings
769/// coming from ICU data are valid UTF-8.
770fn cstring_to_string(input: &ffi::CString) -> String {
771    input.to_string_lossy().to_string()
772}
773
774#[cfg(test)]
775mod tests {
776    use {super::*, anyhow::Error};
777
778    #[test]
779    fn test_language() -> Result<(), Error> {
780        let loc = ULoc::try_from("es-CO")?;
781        assert_eq!(loc.language(), Some("es".to_string()));
782        Ok(())
783    }
784
785    #[test]
786    fn test_language_absent() -> Result<(), Error> {
787        let loc = ULoc::for_language_tag("und-CO")?;
788        assert_eq!(loc.language(), None);
789        Ok(())
790    }
791
792    // https://github.com/google/rust_icu/issues/244
793    #[test]
794    fn test_long_language_tag() -> Result<(), Error> {
795        let mut language_tag: String = "und-CO".to_owned();
796        let language_tag_rest = (0..500).map(|_| " ").collect::<String>();
797        language_tag.push_str(&language_tag_rest);
798        let _loc = ULoc::for_language_tag(&language_tag)?;
799        Ok(())
800    }
801
802    #[test]
803    fn test_script() -> Result<(), Error> {
804        let loc = ULoc::try_from("sr-Cyrl")?;
805        assert_eq!(loc.script(), Some("Cyrl".to_string()));
806        Ok(())
807    }
808
809    #[test]
810    fn test_script_absent() -> Result<(), Error> {
811        let loc = ULoc::try_from("sr")?;
812        assert_eq!(loc.script(), None);
813        Ok(())
814    }
815
816    #[test]
817    fn test_country() -> Result<(), Error> {
818        let loc = ULoc::try_from("es-CO")?;
819        assert_eq!(loc.country(), Some("CO".to_string()));
820        Ok(())
821    }
822
823    #[test]
824    fn test_country_absent() -> Result<(), Error> {
825        let loc = ULoc::try_from("es")?;
826        assert_eq!(loc.country(), None);
827        Ok(())
828    }
829
830    // This test yields a different result in ICU versions prior to 64:
831    // "zh-Latn@collation=pinyin".
832    #[cfg(feature = "icu_version_64_plus")]
833    #[test]
834    fn test_variant() -> Result<(), Error> {
835        let loc = ULoc::try_from("zh-Latn-pinyin")?;
836        assert_eq!(
837            loc.variant(),
838            Some("PINYIN".to_string()),
839            "locale was: {:?}",
840            loc
841        );
842        Ok(())
843    }
844
845    #[test]
846    fn test_variant_absent() -> Result<(), Error> {
847        let loc = ULoc::try_from("zh-Latn")?;
848        assert_eq!(loc.variant(), None);
849        Ok(())
850    }
851
852    #[cfg(feature = "icu_version_64_plus")]
853    #[test]
854    fn test_name() -> Result<(), Error> {
855        let loc = ULoc::try_from("en-US")?;
856        match loc.name() {
857            None => assert!(false),
858            Some(name) => assert_eq!(name, "en_US"),
859        }
860        let loc = ULoc::try_from("und")?;
861        assert_eq!(loc.name(), None);
862        let loc = ULoc::try_from("")?;
863        assert_eq!(loc.name(), None);
864        Ok(())
865    }
866
867    #[test]
868    fn test_default_locale() {
869        let loc = ULoc::try_from("fr-fr").expect("get fr_FR locale");
870        set_default(&loc).expect("successful set of locale");
871        assert_eq!(get_default().label(), loc.label());
872        assert_eq!(loc.label(), "fr_FR", "The locale should get canonicalized");
873        let loc = ULoc::try_from("en-us").expect("get en_US locale");
874        set_default(&loc).expect("successful set of locale");
875        assert_eq!(get_default().label(), loc.label());
876    }
877
878    #[test]
879    fn test_add_likely_subtags() {
880        let loc = ULoc::try_from("en-US").expect("get en_US locale");
881        let with_likely_subtags = loc.add_likely_subtags().expect("should add likely subtags");
882        let expected = ULoc::try_from("en_Latn_US").expect("get en_Latn_US locale");
883        assert_eq!(with_likely_subtags.label(), expected.label());
884    }
885
886    #[test]
887    fn test_minimize_subtags() {
888        let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale");
889        let minimized_subtags = loc.minimize_subtags().expect("should minimize subtags");
890        let expected = ULoc::try_from("sr").expect("get sr locale");
891        assert_eq!(minimized_subtags.label(), expected.label());
892    }
893
894    #[test]
895    fn test_to_language_tag() {
896        let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale");
897        let language_tag = loc
898            .to_language_tag(true)
899            .expect("should convert to language tag");
900        assert_eq!(language_tag, "sr-Cyrl-RS".to_string());
901    }
902
903    #[test]
904    fn test_keywords() -> Result<(), Error> {
905        let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
906        let keywords: Vec<String> = loc.keywords().collect();
907        assert_eq!(
908            keywords,
909            vec![
910                "calendar".to_string(),
911                "fw".to_string(),
912                "numbers".to_string(),
913                "timezone".to_string()
914            ]
915        );
916        Ok(())
917    }
918
919    #[test]
920    fn test_keywords_nounicode() -> Result<(), Error> {
921        let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-t-it-x-whatever")?;
922        let keywords: Vec<String> = loc.keywords().collect();
923        assert_eq!(
924            keywords,
925            vec!["calendar".to_string(), "t".to_string(), "x".to_string(),]
926        );
927        assert_eq!(loc.keyword_value("t")?.unwrap(), "it");
928        assert_eq!(loc.keyword_value("x")?.unwrap(), "whatever");
929        Ok(())
930    }
931
932    #[test]
933    fn test_keywords_empty() -> Result<(), Error> {
934        let loc = ULoc::for_language_tag("az-Cyrl-AZ")?;
935        let keywords: Vec<String> = loc.keywords().collect();
936        assert!(keywords.is_empty());
937        Ok(())
938    }
939
940    #[test]
941    fn test_unicode_keywords() -> Result<(), Error> {
942        let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
943        let keywords: Vec<String> = loc.unicode_keywords().collect();
944        assert_eq!(
945            keywords,
946            vec![
947                "ca".to_string(),
948                "fw".to_string(),
949                "nu".to_string(),
950                "tz".to_string()
951            ]
952        );
953        Ok(())
954    }
955
956    #[test]
957    fn test_unicode_keywords_empty() -> Result<(), Error> {
958        let loc = ULoc::for_language_tag("az-Cyrl-AZ")?;
959        let keywords: Vec<String> = loc.unicode_keywords().collect();
960        assert!(keywords.is_empty());
961        Ok(())
962    }
963
964    #[test]
965    fn test_keyword_value() -> Result<(), Error> {
966        let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
967        assert_eq!(loc.keyword_value("calendar")?, Some("hebrew".to_string()));
968        assert_eq!(loc.keyword_value("collation")?, None);
969        Ok(())
970    }
971
972    #[test]
973    fn test_unicode_keyword_value() -> Result<(), Error> {
974        let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?;
975        assert_eq!(loc.unicode_keyword_value("ca")?, Some("hebrew".to_string()));
976        assert_eq!(loc.unicode_keyword_value("fw")?, Some("sunday".to_string()));
977        assert_eq!(loc.unicode_keyword_value("co")?, None);
978        Ok(())
979    }
980
981    #[test]
982    fn test_order() -> Result<(), Error> {
983        assert!(ULoc::for_language_tag("az")? < ULoc::for_language_tag("az-Cyrl")?);
984        assert!(ULoc::for_language_tag("az-Cyrl")? < ULoc::for_language_tag("az-Cyrl-AZ")?);
985        assert!(
986            ULoc::for_language_tag("az-Cyrl-AZ")? < ULoc::for_language_tag("az-Cyrl-AZ-variant")?
987        );
988        assert!(
989            ULoc::for_language_tag("az-Cyrl-AZ-variant")?
990                < ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?
991        );
992        assert!(
993            ULoc::for_language_tag("az-u-ca-gregory")? < ULoc::for_language_tag("az-u-fw-fri")?
994        );
995        assert!(
996            ULoc::for_language_tag("az-u-ca-buddhist")?
997                < ULoc::for_language_tag("az-u-ca-chinese")?
998        );
999        assert!(ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-tue")?);
1000        assert!(
1001            ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-mon-nu-arab")?
1002        );
1003        assert!(
1004            ULoc::for_language_tag("az-u-fw-mon-nu-arab")? > ULoc::for_language_tag("az-u-fw-mon")?
1005        );
1006
1007        let loc = ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?;
1008        assert_eq!(loc.cmp(&loc), Ordering::Equal,);
1009        Ok(())
1010    }
1011
1012    #[test]
1013    fn test_accept_language_fallback() {
1014        let accept_list: Result<Vec<_>, _> = vec!["es_MX", "ar_EG", "fr_FR"]
1015            .into_iter()
1016            .map(ULoc::try_from)
1017            .collect();
1018        let accept_list = accept_list.expect("make accept_list");
1019
1020        let available_locales: Result<Vec<_>, _> =
1021            vec!["de_DE", "en_US", "es", "nl_NL", "sr_RS_Cyrl"]
1022                .into_iter()
1023                .map(ULoc::try_from)
1024                .collect();
1025        let available_locales = available_locales.expect("make available_locales");
1026
1027        let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1028        assert_eq!(
1029            actual,
1030            (
1031                ULoc::try_from("es").ok(),
1032                UAcceptResult::ULOC_ACCEPT_FALLBACK
1033            )
1034        );
1035    }
1036
1037    // This tests verifies buggy behavior which is fixed since ICU version 67.1
1038    #[cfg(not(feature = "icu_version_67_plus"))]
1039    #[test]
1040    fn test_accept_language_exact_match() {
1041        let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
1042            .into_iter()
1043            .map(ULoc::try_from)
1044            .collect();
1045        let accept_list = accept_list.expect("make accept_list");
1046
1047        let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"]
1048            .into_iter()
1049            .map(ULoc::try_from)
1050            .collect();
1051        let available_locales = available_locales.expect("make available_locales");
1052
1053        let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1054        assert_eq!(
1055            actual,
1056            (
1057                // "es_MX" should be preferred as a fallback over exact match "ar_EG".
1058                ULoc::try_from("ar_EG").ok(),
1059                UAcceptResult::ULOC_ACCEPT_VALID
1060            )
1061        );
1062    }
1063
1064    #[cfg(feature = "icu_version_67_plus")]
1065    #[test]
1066    fn test_accept_language_exact_match() {
1067        let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
1068            .into_iter()
1069            .map(ULoc::try_from)
1070            .collect();
1071        let accept_list = accept_list.expect("make accept_list");
1072
1073        let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"]
1074            .into_iter()
1075            .map(ULoc::try_from)
1076            .collect();
1077        let available_locales = available_locales.expect("make available_locales");
1078
1079        let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1080        assert_eq!(
1081            actual,
1082            (
1083                ULoc::try_from("es_MX").ok(),
1084                UAcceptResult::ULOC_ACCEPT_FALLBACK,
1085            )
1086        );
1087    }
1088
1089    #[test]
1090    fn test_accept_language_no_match() {
1091        let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"]
1092            .into_iter()
1093            .map(ULoc::try_from)
1094            .collect();
1095        let accept_list = accept_list.expect("make accept_list");
1096
1097        let available_locales: Result<Vec<_>, _> =
1098            vec!["el_GR"].into_iter().map(ULoc::try_from).collect();
1099        let available_locales = available_locales.expect("make available_locales");
1100
1101        let actual = accept_language(accept_list, available_locales).expect("call accept_language");
1102        assert_eq!(actual, (None, UAcceptResult::ULOC_ACCEPT_FAILED))
1103    }
1104
1105    #[test]
1106    fn test_to_unicode_locale_key() -> Result<(), Error> {
1107        let actual = to_unicode_locale_key("calendar");
1108        assert_eq!(actual, Some("ca".to_string()));
1109        Ok(())
1110    }
1111
1112    #[test]
1113    fn test_to_unicode_locale_type() -> Result<(), Error> {
1114        let actual = to_unicode_locale_type("co", "phonebook");
1115        assert_eq!(actual, Some("phonebk".to_string()));
1116        Ok(())
1117    }
1118
1119    #[test]
1120    fn test_to_legacy_key() -> Result<(), Error> {
1121        let actual = to_legacy_key("ca");
1122        assert_eq!(actual, Some("calendar".to_string()));
1123        Ok(())
1124    }
1125
1126    #[test]
1127    fn test_str_to_cstring() -> Result<(), Error> {
1128        assert_eq!(str_to_cstring("abc"), ffi::CString::new("abc")?);
1129        assert_eq!(str_to_cstring("abc\0def"), ffi::CString::new("abc")?);
1130
1131        Ok(())
1132    }
1133
1134    #[test]
1135    fn test_base_name() -> Result<(), Error> {
1136        assert_eq!(
1137            ULoc::try_from("en-u-tz-uslax-x-foo")?.base_name(),
1138            ULoc::try_from("en")?
1139        );
1140        Ok(())
1141    }
1142
1143    #[test]
1144    fn test_uloc_mut() -> Result<(), Error> {
1145        let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1146        let loc_mut = ULocMut::from(loc);
1147        let loc = ULoc::from(loc_mut);
1148        assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?, loc);
1149        Ok(())
1150    }
1151
1152    #[test]
1153    fn test_uloc_mut_changes() -> Result<(), Error> {
1154        let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1155        let mut loc_mut = ULocMut::from(loc);
1156        loc_mut.remove_unicode_keyvalue("tz");
1157        let loc = ULoc::from(loc_mut);
1158        assert_eq!(ULoc::for_language_tag("en-t-it-x-foo")?, loc);
1159
1160        let loc = ULoc::for_language_tag("en-u-tz-uslax")?;
1161        let mut loc_mut = ULocMut::from(loc);
1162        loc_mut.remove_unicode_keyvalue("tz");
1163        let loc = ULoc::from(loc_mut);
1164        assert_eq!(ULoc::for_language_tag("en")?, loc);
1165        Ok(())
1166    }
1167
1168    #[test]
1169    fn test_uloc_mut_overrides() -> Result<(), Error> {
1170        let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1171        let mut loc_mut = ULocMut::from(loc);
1172        loc_mut.set_unicode_keyvalue("tz", "usnyc");
1173        let loc = ULoc::from(loc_mut);
1174        assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-usnyc-x-foo")?, loc);
1175
1176        let loc = ULoc::for_language_tag("en-t-it-u-tz-uslax-x-foo")?;
1177        let mut loc_mut = ULocMut::from(loc);
1178        loc_mut.set_unicode_keyvalue("tz", "usnyc");
1179        loc_mut.set_unicode_keyvalue("nu", "arabic");
1180        let loc = ULoc::from(loc_mut);
1181        assert_eq!(ULoc::for_language_tag("en-t-it-u-nu-arabic-tz-usnyc-x-foo")?, loc);
1182        assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-usnyc-nu-arabic-x-foo")?, loc);
1183        Ok(())
1184    }
1185
1186    #[test]
1187    fn test_uloc_mut_add_unicode_extension() -> Result<(), Error> {
1188        let loc = ULoc::for_language_tag("en-t-it-x-foo")?;
1189        let mut loc_mut = ULocMut::from(loc);
1190        loc_mut.set_unicode_keyvalue("tz", "usnyc");
1191        let loc = ULoc::from(loc_mut);
1192        assert_eq!(ULoc::for_language_tag("en-t-it-u-tz-usnyc-x-foo")?, loc);
1193        Ok(())
1194    }
1195
1196    #[test]
1197    fn test_round_trip_from_uloc_plain() -> Result<(), Error> {
1198        let loc = ULoc::for_language_tag("sr")?;
1199        let loc = ULocMut::from(loc);
1200        let loc = ULoc::from(loc);
1201        assert_eq!(ULoc::try_from("sr")?, loc);
1202        Ok(())
1203    }
1204
1205    #[test]
1206    fn test_round_trip_from_uloc_with_country() -> Result<(), Error> {
1207        let loc = ULoc::for_language_tag("sr-rs")?;
1208        let loc = ULoc::from(ULocMut::from(loc));
1209        assert_eq!(ULoc::try_from("sr-rs")?, loc);
1210        Ok(())
1211    }
1212
1213    #[test]
1214    fn test_equivalence() {
1215        let loc = ULoc::try_from("sr@timezone=America/Los_Angeles").unwrap();
1216        assert_eq!(ULoc::for_language_tag("sr-u-tz-uslax").unwrap(), loc);
1217    }
1218
1219    #[cfg(feature = "icu_version_64_plus")]
1220    #[test]
1221    fn test_for_language_error() {
1222        let loc = ULoc::for_language_tag("en_US").unwrap();
1223        assert_eq!(loc.language(), None);
1224        assert_eq!(loc.country(), None);
1225    }
1226
1227    #[cfg(feature = "icu_version_64_plus")]
1228    #[test]
1229    fn test_iso3_language() {
1230        let loc = ULoc::for_language_tag("en-US").unwrap();
1231        let iso_lang = loc.iso3_language();
1232        assert_eq!(iso_lang, Some("eng".to_string()));
1233        let loc = ULoc::for_language_tag("und").unwrap();
1234        let iso_lang = loc.iso3_language();
1235        assert_eq!(iso_lang, None);
1236    }
1237
1238    #[cfg(feature = "icu_version_64_plus")]
1239    #[test]
1240    fn test_iso3_country() {
1241        let loc = ULoc::for_language_tag("en-US").unwrap();
1242        let iso_country = loc.iso3_country();
1243        assert_eq!(iso_country, Some("USA".to_string()));
1244        let loc = ULoc::for_language_tag("und").unwrap();
1245        let iso_country = loc.iso3_country();
1246        assert_eq!(iso_country, None);
1247    }
1248
1249    #[cfg(feature = "icu_version_64_plus")]
1250    #[test]
1251    fn test_display_language() {
1252        let english_locale = ULoc::for_language_tag("en").unwrap();
1253        let french_locale = ULoc::for_language_tag("fr").unwrap();
1254        let english_in_french = english_locale.display_language(&french_locale);
1255        assert!(english_in_french.is_ok());
1256        assert_eq!(english_in_french.unwrap().as_string_debug(), "anglais");
1257        let root_locale = ULoc::for_language_tag("und").unwrap();
1258        assert_eq!(root_locale.display_language(&french_locale).unwrap().as_string_debug(), "langue indéterminée");
1259        let root_locale = ULoc::for_language_tag("en_US").unwrap();
1260        assert_eq!(root_locale.display_language(&french_locale).unwrap().as_string_debug(), "langue indéterminée");
1261    }
1262
1263    #[cfg(feature = "icu_version_64_plus")]
1264    #[test]
1265    fn test_display_script() {
1266        let english_latin_locale = ULoc::for_language_tag("en-latg").unwrap();
1267        let french_locale = ULoc::for_language_tag("fr").unwrap();
1268        let latin_script_in_french = english_latin_locale.display_script(&french_locale);
1269        assert!(latin_script_in_french.is_ok());
1270        assert_eq!(latin_script_in_french.unwrap().as_string_debug(), "latin (variante gaélique)");
1271        let english_locale = ULoc::for_language_tag("en").unwrap();
1272        assert_eq!(english_locale.display_script(&french_locale).unwrap().as_string_debug(), "");
1273    }
1274
1275    #[test]
1276    fn test_display_country() {
1277        let usa_locale = ULoc::for_language_tag("en-US").unwrap();
1278        let french_locale = ULoc::for_language_tag("fr").unwrap();
1279        let usa_in_french = usa_locale.display_country(&french_locale);
1280        assert!(usa_in_french.is_ok());
1281        assert_eq!(usa_in_french.unwrap().as_string_debug(), "États-Unis");
1282        let english_locale = ULoc::for_language_tag("en").unwrap();
1283        assert_eq!(english_locale.display_script(&french_locale).unwrap().as_string_debug(), "");
1284    }
1285
1286    #[test]
1287    fn test_display_keyword() {
1288        let french_locale = ULoc::for_language_tag("fr").unwrap();
1289        let currency_in_french = ULoc::display_keyword(&"currency".to_string(), &french_locale);
1290        assert!(currency_in_french.is_ok());
1291        assert_eq!(currency_in_french.unwrap().as_string_debug(), "devise");
1292    }
1293
1294    #[test]
1295    fn test_display_keyword_value() {
1296        let locale_w_calendar = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-t-it-x-whatever").unwrap();
1297        let french_locale = ULoc::for_language_tag("fr").unwrap();
1298        let calendar_value_in_french = locale_w_calendar.display_keyword_value(
1299            &"calendar".to_string(), &french_locale);
1300        assert!(calendar_value_in_french.is_ok());
1301        assert_eq!(calendar_value_in_french.unwrap().as_string_debug(), "calendrier hébraïque");
1302    }
1303
1304    #[cfg(feature = "icu_version_64_plus")]
1305    #[test]
1306    fn test_display_name() {
1307        let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-t-it-x-whatever").unwrap();
1308        let french_locale = ULoc::for_language_tag("fr").unwrap();
1309        let display_name_in_french = loc.display_name(&french_locale);
1310        assert!(display_name_in_french.is_ok());
1311        assert_eq!(
1312            display_name_in_french.unwrap().as_string_debug(),
1313            "azerbaïdjanais (cyrillique, Azerbaïdjan, calendrier=calendrier hébraïque, t=it, usage privé=whatever)"
1314        );
1315    }
1316
1317    #[test]
1318    #[should_panic(expected = "-1 is negative or greater than or equal to the value returned from count_available()")]
1319    fn test_get_available_negative() {
1320        let _ = ULoc::get_available(-1);
1321    }
1322
1323    #[test]
1324    fn test_get_available_overrun() {
1325        let index = ULoc::count_available();
1326        let result = std::panic::catch_unwind(|| ULoc::get_available(index));
1327        assert!(result.is_err());
1328    }
1329
1330    #[test]
1331    fn test_get_available_locales() {
1332        let locales = ULoc::get_available_locales();
1333        assert!(locales.contains(&ULoc::try_from("en").unwrap()));
1334        assert!(locales.contains(&ULoc::try_from("en-US").unwrap()));
1335        assert!(locales.contains(&ULoc::try_from("fr").unwrap()));
1336        assert!(locales.contains(&ULoc::try_from("fr-FR").unwrap()));
1337        assert_eq!(ULoc::count_available() as usize, locales.capacity());
1338        assert_eq!(locales.len(), locales.capacity());
1339    }
1340
1341    #[cfg(feature = "icu_version_67_plus")]
1342    #[test]
1343    fn test_get_available_locales_by_type() {
1344        let locales1 = ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_DEFAULT);
1345        let locales2 = ULoc::get_available_locales();
1346        assert_eq!(locales1, locales2);
1347        let alias_locales = ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_ONLY_LEGACY_ALIASES);
1348        let all_locales = ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_WITH_LEGACY_ALIASES);
1349        for locale in &alias_locales {
1350            assert!(all_locales.contains(&locale));
1351            assert!(!locales1.contains(&locale));
1352        }
1353        for locale in &locales1 {
1354            assert!(all_locales.contains(&locale));
1355            assert!(!alias_locales.contains(&locale));
1356        }
1357        assert!(alias_locales.len() > 0);
1358        assert!(locales1.len() > alias_locales.len());
1359        assert!(all_locales.len() > locales1.len());
1360        assert!(alias_locales.contains(&ULoc::try_from("iw").unwrap()));
1361        assert!(alias_locales.contains(&ULoc::try_from("mo").unwrap()));
1362        assert!(alias_locales.contains(&ULoc::try_from("zh-CN").unwrap()));
1363        assert!(alias_locales.contains(&ULoc::try_from("sr-BA").unwrap()));
1364        assert!(alias_locales.contains(&ULoc::try_from("ars").unwrap()));
1365    }
1366
1367    #[cfg(feature = "icu_version_67_plus")]
1368    #[test]
1369    #[should_panic(expected = "ULOC_AVAILABLE_COUNT is for internal use only")]
1370    fn test_get_available_locales_by_type_panic() {
1371        ULoc::get_available_locales_by_type(ULocAvailableType::ULOC_AVAILABLE_COUNT);
1372    }
1373
1374    #[cfg(feature = "icu_version_67_plus")]
1375    #[test]
1376    fn test_get_available_locales_by_type_error() {
1377        assert!(!ULoc::open_available_by_type(ULocAvailableType::ULOC_AVAILABLE_COUNT).is_ok());
1378    }
1379}