1use crate::parser::*;
6use crate::subtags::Subtag;
7use crate::{extensions, subtags, LanguageIdentifier};
8#[cfg(feature = "alloc")]
9use alloc::borrow::Cow;
10use core::cmp::Ordering;
11#[cfg(feature = "alloc")]
12use core::str::FromStr;
13
14#[derive(#[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::PartialEq for Locale {
#[inline]
fn eq(&self, other: &Locale) -> bool {
self.id == other.id && self.extensions == other.extensions
}
}PartialEq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::cmp::Eq for Locale {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) -> () {
let _: ::core::cmp::AssertParamIsEq<LanguageIdentifier>;
let _: ::core::cmp::AssertParamIsEq<extensions::Extensions>;
}
}Eq, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::clone::Clone for Locale {
#[inline]
fn clone(&self) -> Locale {
Locale {
id: ::core::clone::Clone::clone(&self.id),
extensions: ::core::clone::Clone::clone(&self.extensions),
}
}
}Clone, #[automatically_derived]
#[allow(clippy::exhaustive_structs)]
impl ::core::hash::Hash for Locale {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
::core::hash::Hash::hash(&self.id, state);
::core::hash::Hash::hash(&self.extensions, state)
}
}Hash)] #[allow(clippy::exhaustive_structs)] pub struct Locale {
103 pub id: LanguageIdentifier,
105 pub extensions: extensions::Extensions,
107}
108
109#[test]
110#[cfg(target_pointer_width = "64")]
112fn test_sizes() {
113 assert_eq!(core::mem::size_of::<subtags::Language>(), 3);
114 assert_eq!(core::mem::size_of::<subtags::Script>(), 4);
115 assert_eq!(core::mem::size_of::<subtags::Region>(), 3);
116 assert_eq!(core::mem::size_of::<subtags::Variant>(), 8);
117 assert_eq!(core::mem::size_of::<subtags::Variants>(), 16);
118 assert_eq!(core::mem::size_of::<LanguageIdentifier>(), 32);
119
120 assert_eq!(core::mem::size_of::<extensions::transform::Transform>(), 56);
121 assert_eq!(core::mem::size_of::<Option<LanguageIdentifier>>(), 32);
122 assert_eq!(core::mem::size_of::<extensions::transform::Fields>(), 24);
123
124 assert_eq!(core::mem::size_of::<extensions::unicode::Attributes>(), 16);
125 assert_eq!(core::mem::size_of::<extensions::unicode::Keywords>(), 24);
126 assert_eq!(core::mem::size_of::<Vec<extensions::other::Other>>(), 24);
127 assert_eq!(core::mem::size_of::<extensions::private::Private>(), 16);
128 assert_eq!(core::mem::size_of::<extensions::Extensions>(), 136);
129
130 assert_eq!(core::mem::size_of::<Locale>(), 168);
131}
132
133impl Locale {
134 pub const UNKNOWN: Self = const {
match crate::Locale::try_from_utf8_with_single_variant_single_keyword_unicode_extension("und".as_bytes())
{
Ok((language, script, region, variant, keyword)) =>
crate::Locale {
id: crate::LanguageIdentifier {
language,
script,
region,
variants: match variant {
Some(v) => crate::subtags::Variants::from_variant(v),
None => crate::subtags::Variants::new(),
},
},
extensions: match keyword {
Some(k) =>
crate::extensions::Extensions::from_unicode(crate::extensions::unicode::Unicode {
keywords: crate::extensions::unicode::Keywords::new_single(k.0,
crate::extensions::unicode::Value::from_subtag(k.1)),
attributes: crate::extensions::unicode::Attributes::new(),
}),
None => crate::extensions::Extensions::new(),
},
},
_ => {
::core::panicking::panic_fmt(format_args!("Invalid language code: und . Note the locale! macro only supports up to one variant tag; and one unicode keyword, other extension are not supported. Use runtime parsing instead."));
}
}
}crate::locale!("und");
136
137 #[inline]
150 #[cfg(feature = "alloc")]
151 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
152 Self::try_from_utf8(s.as_bytes())
153 }
154
155 #[cfg(feature = "alloc")]
159 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
160 parse_locale(code_units)
161 }
162
163 #[cfg(feature = "alloc")]
180 pub fn normalize_utf8(input: &[u8]) -> Result<Cow<'_, str>, ParseError> {
181 let locale = Self::try_from_utf8(input)?;
182 Ok(writeable::to_string_or_borrow(&locale, input))
183 }
184
185 #[cfg(feature = "alloc")]
202 pub fn normalize(input: &str) -> Result<Cow<'_, str>, ParseError> {
203 Self::normalize_utf8(input.as_bytes())
204 }
205
206 pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
261 writeable::cmp_utf8(self, other)
262 }
263
264 #[expect(clippy::type_complexity)]
265 pub(crate) fn as_tuple(
266 &self,
267 ) -> (
268 (
269 subtags::Language,
270 Option<subtags::Script>,
271 Option<subtags::Region>,
272 &subtags::Variants,
273 ),
274 (
275 (
276 &extensions::unicode::Attributes,
277 &extensions::unicode::Keywords,
278 ),
279 (
280 Option<(
281 subtags::Language,
282 Option<subtags::Script>,
283 Option<subtags::Region>,
284 &subtags::Variants,
285 )>,
286 &extensions::transform::Fields,
287 ),
288 &extensions::private::Private,
289 &[extensions::other::Other],
290 ),
291 ) {
292 (self.id.as_tuple(), self.extensions.as_tuple())
293 }
294
295 pub fn total_cmp(&self, other: &Self) -> Ordering {
371 self.as_tuple().cmp(&other.as_tuple())
372 }
373
374 #[cfg(feature = "alloc")]
400 pub fn normalizing_eq(&self, other: &str) -> bool {
401 macro_rules! subtag_matches {
402 ($T:ty, $iter:ident, $expected:expr) => {
403 $iter
404 .next()
405 .map(|b| <$T>::try_from_utf8(b) == Ok($expected))
406 .unwrap_or(false)
407 };
408 }
409
410 let mut iter = SubtagIterator::new(other.as_bytes());
411 if !subtag_matches!(subtags::Language, iter, self.id.language) {
412 return false;
413 }
414 if let Some(ref script) = self.id.script {
415 if !subtag_matches!(subtags::Script, iter, *script) {
416 return false;
417 }
418 }
419 if let Some(ref region) = self.id.region {
420 if !subtag_matches!(subtags::Region, iter, *region) {
421 return false;
422 }
423 }
424 for variant in self.id.variants.iter() {
425 if !subtag_matches!(subtags::Variant, iter, *variant) {
426 return false;
427 }
428 }
429 if !self.extensions.is_empty() {
430 match extensions::Extensions::try_from_iter(&mut iter) {
431 Ok(exts) => {
432 if self.extensions != exts {
433 return false;
434 }
435 }
436 Err(_) => {
437 return false;
438 }
439 }
440 }
441 iter.next().is_none()
442 }
443
444 #[doc(hidden)] #[expect(clippy::type_complexity)]
446 pub const fn try_from_utf8_with_single_variant_single_keyword_unicode_extension(
447 code_units: &[u8],
448 ) -> Result<
449 (
450 subtags::Language,
451 Option<subtags::Script>,
452 Option<subtags::Region>,
453 Option<subtags::Variant>,
454 Option<(extensions::unicode::Key, Option<Subtag>)>,
455 ),
456 ParseError,
457 > {
458 parse_locale_with_single_variant_single_keyword_unicode_keyword_extension(
459 code_units,
460 ParserMode::Locale,
461 )
462 }
463
464 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
465 where
466 F: FnMut(&str) -> Result<(), E>,
467 {
468 self.id.for_each_subtag_str(f)?;
469 self.extensions.for_each_subtag_str(f)?;
470 Ok(())
471 }
472}
473
474#[cfg(feature = "alloc")]
476impl FromStr for Locale {
477 type Err = ParseError;
478
479 #[inline]
480 fn from_str(s: &str) -> Result<Self, Self::Err> {
481 Self::try_from_str(s)
482 }
483}
484
485impl From<LanguageIdentifier> for Locale {
486 fn from(id: LanguageIdentifier) -> Self {
487 Self {
488 id,
489 extensions: extensions::Extensions::default(),
490 }
491 }
492}
493
494impl From<Locale> for LanguageIdentifier {
495 fn from(loc: Locale) -> Self {
496 loc.id
497 }
498}
499
500impl core::fmt::Debug for Locale {
501 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
502 writeable::Writeable::write_to(self, f)
503 }
504}
505
506impl writeable::Writeable for Locale {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
-> core::fmt::Result {
let mut initial = true;
self.for_each_subtag_str(&mut |subtag|
{
if initial {
initial = false;
} else { sink.write_char('-')?; }
sink.write_str(subtag)
})
}
#[inline]
fn writeable_length_hint(&self) -> writeable::LengthHint {
let mut result = writeable::LengthHint::exact(0);
let mut initial = true;
self.for_each_subtag_str::<core::convert::Infallible,
_>(&mut |subtag|
{
if initial { initial = false; } else { result += 1; }
result += subtag.len();
Ok(())
}).expect("infallible");
result
}
fn writeable_borrow(&self) -> Option<&str> {
let selff = self;
if selff.extensions.is_empty() {
selff.id.writeable_borrow()
} else { None }
}
}
impl core::fmt::Display for Locale {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}impl_writeable_for_each_subtag_str_no_test!(Locale, selff, selff.extensions.is_empty() => selff.id.writeable_borrow());
507
508#[test]
509fn test_writeable() {
510 use writeable::assert_writeable_eq;
511 assert_writeable_eq!(Locale::UNKNOWN, "und");
512 assert_writeable_eq!("und-001".parse::<Locale>().unwrap(), "und-001");
513 assert_writeable_eq!("und-Mymr".parse::<Locale>().unwrap(), "und-Mymr");
514 assert_writeable_eq!("my-Mymr-MM".parse::<Locale>().unwrap(), "my-Mymr-MM");
515 assert_writeable_eq!(
516 "my-Mymr-MM-posix".parse::<Locale>().unwrap(),
517 "my-Mymr-MM-posix",
518 );
519 assert_writeable_eq!(
520 "zh-macos-posix".parse::<Locale>().unwrap(),
521 "zh-macos-posix",
522 );
523 assert_writeable_eq!(
524 "my-t-my-d0-zawgyi".parse::<Locale>().unwrap(),
525 "my-t-my-d0-zawgyi",
526 );
527 assert_writeable_eq!(
528 "ar-SA-u-ca-islamic-civil".parse::<Locale>().unwrap(),
529 "ar-SA-u-ca-islamic-civil",
530 );
531 assert_writeable_eq!(
532 "en-001-x-foo-bar".parse::<Locale>().unwrap(),
533 "en-001-x-foo-bar",
534 );
535 assert_writeable_eq!("und-t-m0-true".parse::<Locale>().unwrap(), "und-t-m0-true",);
536}
537
538impl From<subtags::Language> for Locale {
547 fn from(language: subtags::Language) -> Self {
548 Self {
549 id: language.into(),
550 extensions: extensions::Extensions::new(),
551 }
552 }
553}
554
555impl From<Option<subtags::Script>> for Locale {
564 fn from(script: Option<subtags::Script>) -> Self {
565 Self {
566 id: script.into(),
567 extensions: extensions::Extensions::new(),
568 }
569 }
570}
571
572impl From<Option<subtags::Region>> for Locale {
581 fn from(region: Option<subtags::Region>) -> Self {
582 Self {
583 id: region.into(),
584 extensions: extensions::Extensions::new(),
585 }
586 }
587}
588
589impl
608 From<(
609 subtags::Language,
610 Option<subtags::Script>,
611 Option<subtags::Region>,
612 )> for Locale
613{
614 fn from(
615 lsr: (
616 subtags::Language,
617 Option<subtags::Script>,
618 Option<subtags::Region>,
619 ),
620 ) -> Self {
621 Self {
622 id: lsr.into(),
623 extensions: extensions::Extensions::new(),
624 }
625 }
626}