| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use { |
| rust_icu_common as common, |
| rust_icu_common::buffered_string_method_with_retry, |
| rust_icu_sys::versioned_function, |
| rust_icu_sys::*, |
| rust_icu_sys as sys, |
| rust_icu_uenum::Enumeration, |
| std::{ |
| cmp::Ordering, |
| convert::{From, TryFrom, TryInto}, |
| ffi, |
| os::raw, |
| fmt, |
| }, |
| }; |
| |
| /// Maximum length of locale supported by uloc.h. |
| /// See `ULOC_FULLNAME_CAPACITY`. |
| const LOCALE_CAPACITY: usize = 158; |
| |
| /// A representation of a Unicode locale. |
| /// |
| /// For the time being, only basic conversion and methods are in fact implemented. |
| /// |
| /// To get basic validation when creating a locale, use |
| /// [`for_language_tag`](ULoc::for_language_tag) with a Unicode BCP-47 locale ID. |
| #[derive(Debug, Clone, Eq, PartialEq, Hash)] |
| pub struct ULoc { |
| // A locale's representation in C is really just a string. |
| repr: String, |
| } |
| |
| /// Implement the Display trait to convert the ULoc into string for display. |
| /// |
| /// The string for display and string serialization happen to be the same for [ULoc]. |
| impl fmt::Display for ULoc { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{}", self.repr) |
| } |
| } |
| |
| impl TryFrom<&str> for ULoc { |
| type Error = common::Error; |
| /// Creates a new ULoc from a string slice. |
| /// |
| /// The creation wil fail if the locale is nonexistent. |
| fn try_from(s: &str) -> Result<Self, Self::Error> { |
| let s = String::from(s); |
| ULoc { repr: s }.canonicalize() |
| } |
| } |
| |
| impl TryFrom<&ffi::CStr> for ULoc { |
| type Error = common::Error; |
| |
| /// Creates a new `ULoc` from a borrowed C string. |
| fn try_from(s: &ffi::CStr) -> Result<Self, Self::Error> { |
| let repr = s.to_str()?; |
| ULoc { |
| repr: String::from(repr), |
| } |
| .canonicalize() |
| } |
| } |
| |
| impl ULoc { |
| /// Implements `uloc_getLanguage`. |
| pub fn language(&self) -> Option<String> { |
| self.call_buffered_string_method_to_option(versioned_function!(uloc_getLanguage)) |
| } |
| |
| /// Implements `uloc_getScript`. |
| pub fn script(&self) -> Option<String> { |
| self.call_buffered_string_method_to_option(versioned_function!(uloc_getScript)) |
| } |
| |
| /// Implements `uloc_getCountry`. |
| pub fn country(&self) -> Option<String> { |
| self.call_buffered_string_method_to_option(versioned_function!(uloc_getCountry)) |
| } |
| |
| /// Implements `uloc_getVariant`. |
| pub fn variant(&self) -> Option<String> { |
| self.call_buffered_string_method_to_option(versioned_function!(uloc_getVariant)) |
| } |
| |
| /// Implements `uloc_canonicalize` from ICU4C. |
| pub fn canonicalize(&self) -> Result<ULoc, common::Error> { |
| self.call_buffered_string_method(versioned_function!(uloc_canonicalize)) |
| .map(|repr| ULoc { repr }) |
| } |
| |
| /// Implements `uloc_addLikelySubtags` from ICU4C. |
| pub fn add_likely_subtags(&self) -> Result<ULoc, common::Error> { |
| self.call_buffered_string_method(versioned_function!(uloc_addLikelySubtags)) |
| .map(|repr| ULoc { repr }) |
| } |
| |
| /// Implements `uloc_minimizeSubtags` from ICU4C. |
| pub fn minimize_subtags(&self) -> Result<ULoc, common::Error> { |
| self.call_buffered_string_method(versioned_function!(uloc_minimizeSubtags)) |
| .map(|repr| ULoc { repr }) |
| } |
| |
| /// Implements `uloc_toLanguageTag` from ICU4C. |
| pub fn to_language_tag(&self, strict: bool) -> Result<String, common::Error> { |
| buffered_string_method_with_retry!( |
| buffered_string_to_language_tag, |
| LOCALE_CAPACITY, |
| [locale_id: *const raw::c_char,], |
| [strict: rust_icu_sys::UBool,] |
| ); |
| |
| let locale_id = self.as_c_str(); |
| // No `UBool` constants available in rust_icu_sys, unfortunately. |
| let strict = if strict { 1 } else { 0 }; |
| buffered_string_to_language_tag( |
| versioned_function!(uloc_toLanguageTag), |
| locale_id.as_ptr(), |
| strict, |
| ) |
| } |
| |
| /// Implements `uloc_openKeywords()` from ICU4C. |
| pub fn keywords(&self) -> impl Iterator<Item = String> { |
| rust_icu_uenum::uloc_open_keywords(&self.repr) |
| .unwrap() |
| .map(|result| result.unwrap()) |
| } |
| |
| /// Implements `icu::Locale::getUnicodeKeywords()` from the C++ API. |
| pub fn unicode_keywords(&self) -> impl Iterator<Item = String> { |
| self.keywords().filter_map(|s| to_unicode_locale_key(&s)) |
| } |
| |
| /// Implements `uloc_getKeywordValue()` from ICU4C. |
| pub fn keyword_value(&self, keyword: &str) -> Result<Option<String>, common::Error> { |
| buffered_string_method_with_retry!( |
| buffered_string_keyword_value, |
| LOCALE_CAPACITY, |
| [ |
| locale_id: *const raw::c_char, |
| keyword_name: *const raw::c_char, |
| ], |
| [] |
| ); |
| let locale_id = self.as_c_str(); |
| let keyword_name = str_to_cstring(keyword); |
| buffered_string_keyword_value( |
| versioned_function!(uloc_getKeywordValue), |
| locale_id.as_ptr(), |
| keyword_name.as_ptr(), |
| ) |
| .map(|value| if value.is_empty() { None } else { Some(value) }) |
| } |
| |
| /// Implements `icu::Locale::getUnicodeKeywordValue()` from ICU4C. |
| pub fn unicode_keyword_value( |
| &self, |
| unicode_keyword: &str, |
| ) -> Result<Option<String>, common::Error> { |
| let legacy_keyword = to_legacy_key(unicode_keyword); |
| match legacy_keyword { |
| Some(legacy_keyword) => match self.keyword_value(&legacy_keyword) { |
| Ok(Some(legacy_value)) => { |
| Ok(to_unicode_locale_type(&legacy_keyword, &legacy_value)) |
| } |
| Ok(None) => Ok(None), |
| Err(e) => Err(e), |
| }, |
| None => Ok(None), |
| } |
| } |
| |
| /// Returns the current label of this locale. |
| pub fn label(&self) -> &str { |
| &self.repr |
| } |
| |
| /// Returns the current locale name as a C string. |
| pub fn as_c_str(&self) -> ffi::CString { |
| ffi::CString::new(self.repr.clone()).expect("ULoc contained interior NUL bytes") |
| } |
| |
| /// Implements `uloc_forLanguageTag` from ICU4C. |
| pub fn for_language_tag(tag: &str) -> Result<ULoc, common::Error> { |
| buffered_string_method_with_retry!( |
| buffered_string_for_language_tag, |
| LOCALE_CAPACITY, |
| [tag: *const raw::c_char,], |
| [parsed_length: *mut i32,] |
| ); |
| |
| let tag = str_to_cstring(tag); |
| let locale_id = buffered_string_for_language_tag( |
| versioned_function!(uloc_forLanguageTag), |
| tag.as_ptr(), |
| std::ptr::null_mut(), |
| )?; |
| ULoc::try_from(&locale_id[..]) |
| } |
| |
| /// Call a `uloc` method that takes this locale's ID and returns a string. |
| fn call_buffered_string_method( |
| &self, |
| uloc_method: unsafe extern "C" fn( |
| *const raw::c_char, |
| *mut raw::c_char, |
| i32, |
| *mut UErrorCode, |
| ) -> i32, |
| ) -> Result<String, common::Error> { |
| buffered_string_method_with_retry!( |
| buffered_string_char_star, |
| LOCALE_CAPACITY, |
| [char_star: *const raw::c_char,], |
| [] |
| ); |
| let asciiz = self.as_c_str(); |
| buffered_string_char_star(uloc_method, asciiz.as_ptr()) |
| } |
| |
| /// Call a `uloc` method that takes this locale's ID, panics on any errors, and returns |
| /// `Some(result)` if the resulting string is non-empty, or `None` otherwise. |
| fn call_buffered_string_method_to_option( |
| &self, |
| uloc_method: unsafe extern "C" fn( |
| *const raw::c_char, |
| *mut raw::c_char, |
| i32, |
| *mut UErrorCode, |
| ) -> i32, |
| ) -> Option<String> { |
| let value: String = self.call_buffered_string_method(uloc_method).unwrap(); |
| if value.is_empty() { |
| None |
| } else { |
| Some(value) |
| } |
| } |
| } |
| |
| /// This implementation is based on ULocale.compareTo from ICU4J. |
| /// See https://github.com/unicode-org/icu/blob/master/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java |
| impl Ord for ULoc { |
| fn cmp(&self, other: &Self) -> Ordering { |
| /// Compare corresponding keywords from two `ULoc`s. If the keywords match, compare the |
| /// keyword values. |
| fn compare_keywords( |
| this: &ULoc, |
| self_keyword: &Option<String>, |
| other: &ULoc, |
| other_keyword: &Option<String>, |
| ) -> Option<Ordering> { |
| match (self_keyword, other_keyword) { |
| (Some(self_keyword), Some(other_keyword)) => { |
| // Compare the two keywords |
| match self_keyword.cmp(&other_keyword) { |
| Ordering::Equal => { |
| // Compare the two keyword values |
| let self_val = this.keyword_value(&self_keyword[..]).unwrap(); |
| let other_val = other.keyword_value(&other_keyword[..]).unwrap(); |
| Some(self_val.cmp(&other_val)) |
| } |
| unequal_ordering => Some(unequal_ordering), |
| } |
| } |
| // `other` has run out of keywords |
| (Some(_), _) => Some(Ordering::Greater), |
| // `this` has run out of keywords |
| (_, Some(_)) => Some(Ordering::Less), |
| // Both iterators have run out |
| (_, _) => None, |
| } |
| } |
| |
| self.language() |
| .cmp(&other.language()) |
| .then_with(|| self.script().cmp(&other.script())) |
| .then_with(|| self.country().cmp(&other.country())) |
| .then_with(|| self.variant().cmp(&other.variant())) |
| .then_with(|| { |
| let mut self_keywords = self.keywords(); |
| let mut other_keywords = other.keywords(); |
| |
| while let Some(keyword_ordering) = |
| compare_keywords(self, &self_keywords.next(), other, &other_keywords.next()) |
| { |
| match keyword_ordering { |
| Ordering::Equal => {} |
| unequal_ordering => { |
| return unequal_ordering; |
| } |
| } |
| } |
| |
| // All keywords and values were identical (or there were none) |
| Ordering::Equal |
| }) |
| } |
| } |
| |
| impl PartialOrd for ULoc { |
| fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| Some(self.cmp(other)) |
| } |
| } |
| |
| /// Gets the current system default locale. |
| /// |
| /// Implements `uloc_getDefault` from ICU4C. |
| pub fn get_default() -> ULoc { |
| let loc = unsafe { versioned_function!(uloc_getDefault)() }; |
| let uloc_cstr = unsafe { ffi::CStr::from_ptr(loc) }; |
| crate::ULoc::try_from(uloc_cstr).expect("could not convert default locale to ULoc") |
| } |
| |
| /// Sets the current default system locale. |
| /// |
| /// Implements `uloc_setDefault` from ICU4C. |
| pub fn set_default(loc: &ULoc) -> Result<(), common::Error> { |
| let mut status = common::Error::OK_CODE; |
| let asciiz = str_to_cstring(&loc.repr); |
| unsafe { versioned_function!(uloc_setDefault)(asciiz.as_ptr(), &mut status) }; |
| common::Error::ok_or_warning(status) |
| } |
| |
| /// Implements `uloc_acceptLanguage` from ICU4C. |
| pub fn accept_language( |
| accept_list: impl IntoIterator<Item = impl Into<ULoc>>, |
| available_locales: impl IntoIterator<Item = impl Into<ULoc>>, |
| ) -> Result<(Option<ULoc>, UAcceptResult), common::Error> { |
| buffered_string_method_with_retry!( |
| buffered_string_uloc_accept_language, |
| LOCALE_CAPACITY, |
| [], |
| [ |
| out_result: *mut UAcceptResult, |
| accept_list: *mut *const ::std::os::raw::c_char, |
| accept_list_count: i32, |
| available_locales: *mut UEnumeration, |
| ] |
| ); |
| |
| let mut accept_result: UAcceptResult = UAcceptResult::ULOC_ACCEPT_FAILED; |
| let mut accept_list_cstrings: Vec<ffi::CString> = vec![]; |
| // This is mutable only to satisfy the missing `const`s in the ICU4C API. |
| let mut accept_list: Vec<*const raw::c_char> = accept_list |
| .into_iter() |
| .map(|item| { |
| let uloc: ULoc = item.into(); |
| accept_list_cstrings.push(uloc.as_c_str()); |
| accept_list_cstrings |
| .last() |
| .expect("non-empty list") |
| .as_ptr() |
| }) |
| .collect(); |
| |
| let available_locales: Vec<ULoc> = available_locales |
| .into_iter() |
| .map(|item| item.into()) |
| .collect(); |
| let available_locales: Vec<&str> = available_locales.iter().map(|uloc| uloc.label()).collect(); |
| let mut available_locales = Enumeration::try_from(&available_locales[..])?; |
| |
| let matched_locale = buffered_string_uloc_accept_language( |
| versioned_function!(uloc_acceptLanguage), |
| &mut accept_result, |
| accept_list.as_mut_ptr(), |
| accept_list.len() as i32, |
| available_locales.repr(), |
| ); |
| |
| // Having no match is a valid if disappointing result. |
| if accept_result == UAcceptResult::ULOC_ACCEPT_FAILED { |
| return Ok((None, accept_result)); |
| } |
| |
| matched_locale |
| .and_then(|s| ULoc::try_from(s.as_str())) |
| .map(|uloc| (Some(uloc), accept_result)) |
| } |
| |
| /// Implements `uloc_toUnicodeLocaleKey` from ICU4C. |
| pub fn to_unicode_locale_key(legacy_keyword: &str) -> Option<String> { |
| let legacy_keyword = str_to_cstring(legacy_keyword); |
| let unicode_keyword: Option<ffi::CString> = unsafe { |
| let ptr = versioned_function!(uloc_toUnicodeLocaleKey)(legacy_keyword.as_ptr()); |
| ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned()) |
| }; |
| unicode_keyword.map(|cstring| cstring_to_string(&cstring)) |
| } |
| |
| /// Implements `uloc_toUnicodeLocaleType` from ICU4C. |
| pub fn to_unicode_locale_type(legacy_keyword: &str, legacy_value: &str) -> Option<String> { |
| let legacy_keyword = str_to_cstring(legacy_keyword); |
| let legacy_value = str_to_cstring(legacy_value); |
| let unicode_value: Option<ffi::CString> = unsafe { |
| let ptr = versioned_function!(uloc_toUnicodeLocaleType)( |
| legacy_keyword.as_ptr(), |
| legacy_value.as_ptr(), |
| ); |
| ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned()) |
| }; |
| unicode_value.map(|cstring| cstring_to_string(&cstring)) |
| } |
| |
| /// Implements `uloc_toLegacyKey` from ICU4C. |
| pub fn to_legacy_key(unicode_keyword: &str) -> Option<String> { |
| let unicode_keyword = str_to_cstring(unicode_keyword); |
| let legacy_keyword: Option<ffi::CString> = unsafe { |
| let ptr = versioned_function!(uloc_toLegacyKey)(unicode_keyword.as_ptr()); |
| ptr.as_ref().map(|ptr| ffi::CStr::from_ptr(ptr).to_owned()) |
| }; |
| legacy_keyword.map(|cstring| cstring_to_string(&cstring)) |
| } |
| |
| /// Infallibly converts a Rust string to a `CString`. If there's an interior NUL, the string is |
| /// truncated up to that point. |
| fn str_to_cstring(input: &str) -> ffi::CString { |
| ffi::CString::new(input) |
| .unwrap_or_else(|e| ffi::CString::new(&input[0..e.nul_position()]).unwrap()) |
| } |
| |
| /// Infallibly converts a `CString` to a Rust `String`. We can safely assume that any strings |
| /// coming from ICU data are valid UTF-8. |
| fn cstring_to_string(input: &ffi::CString) -> String { |
| input.to_string_lossy().to_string() |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use {super::*, anyhow::Error}; |
| |
| #[test] |
| fn test_language() -> Result<(), Error> { |
| let loc = ULoc::try_from("es-CO")?; |
| assert_eq!(loc.language(), Some("es".to_string())); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_language_absent() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("und-CO")?; |
| assert_eq!(loc.language(), None); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_script() -> Result<(), Error> { |
| let loc = ULoc::try_from("sr-Cyrl")?; |
| assert_eq!(loc.script(), Some("Cyrl".to_string())); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_script_absent() -> Result<(), Error> { |
| let loc = ULoc::try_from("sr")?; |
| assert_eq!(loc.script(), None); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_country() -> Result<(), Error> { |
| let loc = ULoc::try_from("es-CO")?; |
| assert_eq!(loc.country(), Some("CO".to_string())); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_country_absent() -> Result<(), Error> { |
| let loc = ULoc::try_from("es")?; |
| assert_eq!(loc.country(), None); |
| Ok(()) |
| } |
| |
| // This test yields a different result in ICU versions prior to 64: |
| // "zh-Latn@collation=pinyin". |
| #[cfg(features = "icu_version_64_plus")] |
| #[test] |
| fn test_variant() -> Result<(), Error> { |
| let loc = ULoc::try_from("zh-Latn-pinyin")?; |
| assert_eq!( |
| loc.variant(), |
| Some("PINYIN".to_string()), |
| "locale was: {:?}", |
| loc |
| ); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_variant_absent() -> Result<(), Error> { |
| let loc = ULoc::try_from("zh-Latn")?; |
| assert_eq!(loc.variant(), None); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_default_locale() { |
| let loc = ULoc::try_from("fr-fr").expect("get fr_FR locale"); |
| set_default(&loc).expect("successful set of locale"); |
| assert_eq!(get_default().label(), loc.label()); |
| assert_eq!(loc.label(), "fr_FR", "The locale should get canonicalized"); |
| let loc = ULoc::try_from("en-us").expect("get en_US locale"); |
| set_default(&loc).expect("successful set of locale"); |
| assert_eq!(get_default().label(), loc.label()); |
| } |
| |
| #[test] |
| fn test_add_likely_subtags() { |
| let loc = ULoc::try_from("en-US").expect("get en_US locale"); |
| let with_likely_subtags = loc.add_likely_subtags().expect("should add likely subtags"); |
| let expected = ULoc::try_from("en_Latn_US").expect("get en_Latn_US locale"); |
| assert_eq!(with_likely_subtags.label(), expected.label()); |
| } |
| |
| #[test] |
| fn test_minimize_subtags() { |
| let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale"); |
| let minimized_subtags = loc.minimize_subtags().expect("should minimize subtags"); |
| let expected = ULoc::try_from("sr").expect("get sr locale"); |
| assert_eq!(minimized_subtags.label(), expected.label()); |
| } |
| |
| #[test] |
| fn test_to_language_tag() { |
| let loc = ULoc::try_from("sr_Cyrl_RS").expect("get sr_Cyrl_RS locale"); |
| let language_tag = loc |
| .to_language_tag(true) |
| .expect("should convert to language tag"); |
| assert_eq!(language_tag, "sr-Cyrl-RS".to_string()); |
| } |
| |
| #[test] |
| fn test_keywords() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?; |
| let keywords: Vec<String> = loc.keywords().collect(); |
| assert_eq!( |
| keywords, |
| vec![ |
| "calendar".to_string(), |
| "fw".to_string(), |
| "numbers".to_string(), |
| "timezone".to_string() |
| ] |
| ); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_keywords_empty() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ")?; |
| let keywords: Vec<String> = loc.keywords().collect(); |
| assert!(keywords.is_empty()); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_unicode_keywords() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?; |
| let keywords: Vec<String> = loc.unicode_keywords().collect(); |
| assert_eq!( |
| keywords, |
| vec![ |
| "ca".to_string(), |
| "fw".to_string(), |
| "nu".to_string(), |
| "tz".to_string() |
| ] |
| ); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_unicode_keywords_empty() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ")?; |
| let keywords: Vec<String> = loc.unicode_keywords().collect(); |
| assert!(keywords.is_empty()); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_keyword_value() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?; |
| assert_eq!(loc.keyword_value("calendar")?, Some("hebrew".to_string())); |
| assert_eq!(loc.keyword_value("collation")?, None); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_unicode_keyword_value() -> Result<(), Error> { |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc")?; |
| assert_eq!(loc.unicode_keyword_value("ca")?, Some("hebrew".to_string())); |
| assert_eq!(loc.unicode_keyword_value("fw")?, Some("sunday".to_string())); |
| assert_eq!(loc.unicode_keyword_value("co")?, None); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_order() -> Result<(), Error> { |
| assert!(ULoc::for_language_tag("az")? < ULoc::for_language_tag("az-Cyrl")?); |
| assert!(ULoc::for_language_tag("az-Cyrl")? < ULoc::for_language_tag("az-Cyrl-AZ")?); |
| assert!( |
| ULoc::for_language_tag("az-Cyrl-AZ")? < ULoc::for_language_tag("az-Cyrl-AZ-variant")? |
| ); |
| assert!( |
| ULoc::for_language_tag("az-Cyrl-AZ-variant")? |
| < ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")? |
| ); |
| assert!( |
| ULoc::for_language_tag("az-u-ca-gregory")? < ULoc::for_language_tag("az-u-fw-fri")? |
| ); |
| assert!( |
| ULoc::for_language_tag("az-u-ca-buddhist")? |
| < ULoc::for_language_tag("az-u-ca-chinese")? |
| ); |
| assert!(ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-tue")?); |
| assert!( |
| ULoc::for_language_tag("az-u-fw-mon")? < ULoc::for_language_tag("az-u-fw-mon-nu-arab")? |
| ); |
| assert!( |
| ULoc::for_language_tag("az-u-fw-mon-nu-arab")? > ULoc::for_language_tag("az-u-fw-mon")? |
| ); |
| |
| let loc = ULoc::for_language_tag("az-Cyrl-AZ-variant-u-nu-arab")?; |
| assert_eq!(loc.cmp(&loc), Ordering::Equal,); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_accept_language_fallback() { |
| let accept_list: Result<Vec<_>, _> = vec!["es_MX", "ar_EG", "fr_FR"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let accept_list = accept_list.expect("make accept_list"); |
| |
| let available_locales: Result<Vec<_>, _> = |
| vec!["de_DE", "en_US", "es", "nl_NL", "sr_RS_Cyrl"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let available_locales = available_locales.expect("make available_locales"); |
| |
| let actual = accept_language(accept_list, available_locales).expect("call accept_language"); |
| assert_eq!( |
| actual, |
| ( |
| ULoc::try_from("es").ok(), |
| UAcceptResult::ULOC_ACCEPT_FALLBACK |
| ) |
| ); |
| } |
| |
| // This tests verifies buggy behavior which is fixed since ICU version 67.1 |
| #[cfg(not(feature="icu_version_67_plus"))] |
| #[test] |
| fn test_accept_language_exact_match() { |
| let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let accept_list = accept_list.expect("make accept_list"); |
| |
| let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let available_locales = available_locales.expect("make available_locales"); |
| |
| let actual = accept_language(accept_list, available_locales).expect("call accept_language"); |
| assert_eq!( |
| actual, |
| ( |
| // "es_MX" should be preferred as a fallback over exact match "ar_EG". |
| ULoc::try_from("ar_EG").ok(), |
| UAcceptResult::ULOC_ACCEPT_VALID |
| ) |
| ); |
| } |
| |
| #[cfg(feature="icu_version_67_plus")] |
| #[test] |
| fn test_accept_language_exact_match() { |
| let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let accept_list = accept_list.expect("make accept_list"); |
| |
| let available_locales: Result<Vec<_>, _> = vec!["de_DE", "en_US", "es_MX", "ar_EG"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let available_locales = available_locales.expect("make available_locales"); |
| |
| let actual = accept_language(accept_list, available_locales).expect("call accept_language"); |
| assert_eq!( |
| actual, |
| ( |
| ULoc::try_from("es_MX").ok(), |
| UAcceptResult::ULOC_ACCEPT_FALLBACK, |
| ) |
| ); |
| } |
| |
| #[test] |
| fn test_accept_language_no_match() { |
| let accept_list: Result<Vec<_>, _> = vec!["es_ES", "ar_EG", "fr_FR"] |
| .into_iter() |
| .map(ULoc::try_from) |
| .collect(); |
| let accept_list = accept_list.expect("make accept_list"); |
| |
| let available_locales: Result<Vec<_>, _> = |
| vec!["el_GR"].into_iter().map(ULoc::try_from).collect(); |
| let available_locales = available_locales.expect("make available_locales"); |
| |
| let actual = accept_language(accept_list, available_locales).expect("call accept_language"); |
| assert_eq!(actual, (None, UAcceptResult::ULOC_ACCEPT_FAILED)) |
| } |
| |
| #[test] |
| fn test_to_unicode_locale_key() -> Result<(), Error> { |
| let actual = to_unicode_locale_key("calendar"); |
| assert_eq!(actual, Some("ca".to_string())); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_to_unicode_locale_type() -> Result<(), Error> { |
| let actual = to_unicode_locale_type("co", "phonebook"); |
| assert_eq!(actual, Some("phonebk".to_string())); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_to_legacy_key() -> Result<(), Error> { |
| let actual = to_legacy_key("ca"); |
| assert_eq!(actual, Some("calendar".to_string())); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_str_to_cstring() -> Result<(), Error> { |
| assert_eq!(str_to_cstring("abc"), ffi::CString::new("abc")?); |
| assert_eq!(str_to_cstring("abc\0def"), ffi::CString::new("abc")?); |
| |
| Ok(()) |
| } |
| } |