| // tidy-alphabetical-start | |
| #![allow(internal_features)] | |
| #![doc(rust_logo)] | |
| #![feature(rustc_attrs)] | |
| #![feature(rustdoc_internals)] | |
| #![feature(type_alias_impl_trait)] | |
| #![warn(unreachable_pub)] | |
| // tidy-alphabetical-end | |
| use std::borrow::Cow; | |
| #[cfg(not(parallel_compiler))] | |
| use std::cell::LazyCell as Lazy; | |
| use std::error::Error; | |
| use std::path::{Path, PathBuf}; | |
| #[cfg(parallel_compiler)] | |
| use std::sync::LazyLock as Lazy; | |
| use std::{fmt, fs, io}; | |
| use fluent_bundle::FluentResource; | |
| pub use fluent_bundle::types::FluentType; | |
| pub use fluent_bundle::{self, FluentArgs, FluentError, FluentValue}; | |
| use fluent_syntax::parser::ParserError; | |
| use icu_provider_adapters::fallback::{LocaleFallbackProvider, LocaleFallbacker}; | |
| #[cfg(not(parallel_compiler))] | |
| use intl_memoizer::IntlLangMemoizer; | |
| #[cfg(parallel_compiler)] | |
| use intl_memoizer::concurrent::IntlLangMemoizer; | |
| use rustc_data_structures::sync::{IntoDynSyncSend, Lrc}; | |
| use rustc_macros::{Decodable, Encodable}; | |
| use rustc_span::Span; | |
| use tracing::{instrument, trace}; | |
| pub use unic_langid::{LanguageIdentifier, langid}; | |
| pub type FluentBundle = | |
| IntoDynSyncSend<fluent_bundle::bundle::FluentBundle<FluentResource, IntlLangMemoizer>>; | |
| #[cfg(not(parallel_compiler))] | |
| fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle { | |
| IntoDynSyncSend(fluent_bundle::bundle::FluentBundle::new(locales)) | |
| } | |
| #[cfg(parallel_compiler)] | |
| fn new_bundle(locales: Vec<LanguageIdentifier>) -> FluentBundle { | |
| IntoDynSyncSend(fluent_bundle::bundle::FluentBundle::new_concurrent(locales)) | |
| } | |
| #[derive(Debug)] | |
| pub enum TranslationBundleError { | |
| /// Failed to read from `.ftl` file. | |
| ReadFtl(io::Error), | |
| /// Failed to parse contents of `.ftl` file. | |
| ParseFtl(ParserError), | |
| /// Failed to add `FluentResource` to `FluentBundle`. | |
| AddResource(FluentError), | |
| /// `$sysroot/share/locale/$locale` does not exist. | |
| MissingLocale, | |
| /// Cannot read directory entries of `$sysroot/share/locale/$locale`. | |
| ReadLocalesDir(io::Error), | |
| /// Cannot read directory entry of `$sysroot/share/locale/$locale`. | |
| ReadLocalesDirEntry(io::Error), | |
| /// `$sysroot/share/locale/$locale` is not a directory. | |
| LocaleIsNotDir, | |
| } | |
| impl fmt::Display for TranslationBundleError { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| TranslationBundleError::ReadFtl(e) => write!(f, "could not read ftl file: {e}"), | |
| TranslationBundleError::ParseFtl(e) => { | |
| write!(f, "could not parse ftl file: {e}") | |
| } | |
| TranslationBundleError::AddResource(e) => write!(f, "failed to add resource: {e}"), | |
| TranslationBundleError::MissingLocale => write!(f, "missing locale directory"), | |
| TranslationBundleError::ReadLocalesDir(e) => { | |
| write!(f, "could not read locales dir: {e}") | |
| } | |
| TranslationBundleError::ReadLocalesDirEntry(e) => { | |
| write!(f, "could not read locales dir entry: {e}") | |
| } | |
| TranslationBundleError::LocaleIsNotDir => { | |
| write!(f, "`$sysroot/share/locales/$locale` is not a directory") | |
| } | |
| } | |
| } | |
| } | |
| impl Error for TranslationBundleError { | |
| fn source(&self) -> Option<&(dyn Error + 'static)> { | |
| match self { | |
| TranslationBundleError::ReadFtl(e) => Some(e), | |
| TranslationBundleError::ParseFtl(e) => Some(e), | |
| TranslationBundleError::AddResource(e) => Some(e), | |
| TranslationBundleError::MissingLocale => None, | |
| TranslationBundleError::ReadLocalesDir(e) => Some(e), | |
| TranslationBundleError::ReadLocalesDirEntry(e) => Some(e), | |
| TranslationBundleError::LocaleIsNotDir => None, | |
| } | |
| } | |
| } | |
| impl From<(FluentResource, Vec<ParserError>)> for TranslationBundleError { | |
| fn from((_, mut errs): (FluentResource, Vec<ParserError>)) -> Self { | |
| TranslationBundleError::ParseFtl(errs.pop().expect("failed ftl parse with no errors")) | |
| } | |
| } | |
| impl From<Vec<FluentError>> for TranslationBundleError { | |
| fn from(mut errs: Vec<FluentError>) -> Self { | |
| TranslationBundleError::AddResource( | |
| errs.pop().expect("failed adding resource to bundle with no errors"), | |
| ) | |
| } | |
| } | |
| /// Returns Fluent bundle with the user's locale resources from | |
| /// `$sysroot/share/locale/$requested_locale/*.ftl`. | |
| /// | |
| /// If `-Z additional-ftl-path` was provided, load that resource and add it to the bundle | |
| /// (overriding any conflicting messages). | |
| #[instrument(level = "trace")] | |
| pub fn fluent_bundle( | |
| mut user_provided_sysroot: Option<PathBuf>, | |
| mut sysroot_candidates: Vec<PathBuf>, | |
| requested_locale: Option<LanguageIdentifier>, | |
| additional_ftl_path: Option<&Path>, | |
| with_directionality_markers: bool, | |
| ) -> Result<Option<Lrc<FluentBundle>>, TranslationBundleError> { | |
| if requested_locale.is_none() && additional_ftl_path.is_none() { | |
| return Ok(None); | |
| } | |
| let fallback_locale = langid!("en-US"); | |
| let requested_fallback_locale = requested_locale.as_ref() == Some(&fallback_locale); | |
| trace!(?requested_fallback_locale); | |
| if requested_fallback_locale && additional_ftl_path.is_none() { | |
| return Ok(None); | |
| } | |
| // If there is only `-Z additional-ftl-path`, assume locale is "en-US", otherwise use user | |
| // provided locale. | |
| let locale = requested_locale.clone().unwrap_or(fallback_locale); | |
| trace!(?locale); | |
| let mut bundle = new_bundle(vec![locale]); | |
| // Add convenience functions available to ftl authors. | |
| register_functions(&mut bundle); | |
| // Fluent diagnostics can insert directionality isolation markers around interpolated variables | |
| // indicating that there may be a shift from right-to-left to left-to-right text (or | |
| // vice-versa). These are disabled because they are sometimes visible in the error output, but | |
| // may be worth investigating in future (for example: if type names are left-to-right and the | |
| // surrounding diagnostic messages are right-to-left, then these might be helpful). | |
| bundle.set_use_isolating(with_directionality_markers); | |
| // If the user requests the default locale then don't try to load anything. | |
| if let Some(requested_locale) = requested_locale { | |
| let mut found_resources = false; | |
| for sysroot in user_provided_sysroot.iter_mut().chain(sysroot_candidates.iter_mut()) { | |
| sysroot.push("share"); | |
| sysroot.push("locale"); | |
| sysroot.push(requested_locale.to_string()); | |
| trace!(?sysroot); | |
| if !sysroot.exists() { | |
| trace!("skipping"); | |
| continue; | |
| } | |
| if !sysroot.is_dir() { | |
| return Err(TranslationBundleError::LocaleIsNotDir); | |
| } | |
| for entry in sysroot.read_dir().map_err(TranslationBundleError::ReadLocalesDir)? { | |
| let entry = entry.map_err(TranslationBundleError::ReadLocalesDirEntry)?; | |
| let path = entry.path(); | |
| trace!(?path); | |
| if path.extension().and_then(|s| s.to_str()) != Some("ftl") { | |
| trace!("skipping"); | |
| continue; | |
| } | |
| let resource_str = | |
| fs::read_to_string(path).map_err(TranslationBundleError::ReadFtl)?; | |
| let resource = | |
| FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?; | |
| trace!(?resource); | |
| bundle.add_resource(resource).map_err(TranslationBundleError::from)?; | |
| found_resources = true; | |
| } | |
| } | |
| if !found_resources { | |
| return Err(TranslationBundleError::MissingLocale); | |
| } | |
| } | |
| if let Some(additional_ftl_path) = additional_ftl_path { | |
| let resource_str = | |
| fs::read_to_string(additional_ftl_path).map_err(TranslationBundleError::ReadFtl)?; | |
| let resource = | |
| FluentResource::try_new(resource_str).map_err(TranslationBundleError::from)?; | |
| trace!(?resource); | |
| bundle.add_resource_overriding(resource); | |
| } | |
| let bundle = Lrc::new(bundle); | |
| Ok(Some(bundle)) | |
| } | |
| fn register_functions(bundle: &mut FluentBundle) { | |
| bundle | |
| .add_function("STREQ", |positional, _named| match positional { | |
| [FluentValue::String(a), FluentValue::String(b)] => format!("{}", (a == b)).into(), | |
| _ => FluentValue::Error, | |
| }) | |
| .expect("Failed to add a function to the bundle."); | |
| } | |
| /// Type alias for the result of `fallback_fluent_bundle` - a reference-counted pointer to a lazily | |
| /// evaluated fluent bundle. | |
| pub type LazyFallbackBundle = Lrc<Lazy<FluentBundle, impl FnOnce() -> FluentBundle>>; | |
| /// Return the default `FluentBundle` with standard "en-US" diagnostic messages. | |
| #[instrument(level = "trace", skip(resources))] | |
| pub fn fallback_fluent_bundle( | |
| resources: Vec<&'static str>, | |
| with_directionality_markers: bool, | |
| ) -> LazyFallbackBundle { | |
| Lrc::new(Lazy::new(move || { | |
| let mut fallback_bundle = new_bundle(vec![langid!("en-US")]); | |
| register_functions(&mut fallback_bundle); | |
| // See comment in `fluent_bundle`. | |
| fallback_bundle.set_use_isolating(with_directionality_markers); | |
| for resource in resources { | |
| let resource = FluentResource::try_new(resource.to_string()) | |
| .expect("failed to parse fallback fluent resource"); | |
| fallback_bundle.add_resource_overriding(resource); | |
| } | |
| fallback_bundle | |
| })) | |
| } | |
| /// Identifier for the Fluent message/attribute corresponding to a diagnostic message. | |
| type FluentId = Cow<'static, str>; | |
| /// Abstraction over a message in a subdiagnostic (i.e. label, note, help, etc) to support both | |
| /// translatable and non-translatable diagnostic messages. | |
| /// | |
| /// Translatable messages for subdiagnostics are typically attributes attached to a larger Fluent | |
| /// message so messages of this type must be combined with a `DiagMessage` (using | |
| /// `DiagMessage::with_subdiagnostic_message`) before rendering. However, subdiagnostics from | |
| /// the `Subdiagnostic` derive refer to Fluent identifiers directly. | |
| #[rustc_diagnostic_item = "SubdiagMessage"] | |
| pub enum SubdiagMessage { | |
| /// Non-translatable diagnostic message. | |
| Str(Cow<'static, str>), | |
| /// Translatable message which has already been translated eagerly. | |
| /// | |
| /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would | |
| /// be instantiated multiple times with different values. These subdiagnostics' messages | |
| /// are translated when they are added to the parent diagnostic, producing this variant of | |
| /// `DiagMessage`. | |
| Translated(Cow<'static, str>), | |
| /// Identifier of a Fluent message. Instances of this variant are generated by the | |
| /// `Subdiagnostic` derive. | |
| FluentIdentifier(FluentId), | |
| /// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an | |
| /// actual translated message. Instances of this variant are generated by the `fluent_messages` | |
| /// macro. | |
| /// | |
| /// <https://projectfluent.org/fluent/guide/attributes.html> | |
| FluentAttr(FluentId), | |
| } | |
| impl From<String> for SubdiagMessage { | |
| fn from(s: String) -> Self { | |
| SubdiagMessage::Str(Cow::Owned(s)) | |
| } | |
| } | |
| impl From<&'static str> for SubdiagMessage { | |
| fn from(s: &'static str) -> Self { | |
| SubdiagMessage::Str(Cow::Borrowed(s)) | |
| } | |
| } | |
| impl From<Cow<'static, str>> for SubdiagMessage { | |
| fn from(s: Cow<'static, str>) -> Self { | |
| SubdiagMessage::Str(s) | |
| } | |
| } | |
| /// Abstraction over a message in a diagnostic to support both translatable and non-translatable | |
| /// diagnostic messages. | |
| /// | |
| /// Intended to be removed once diagnostics are entirely translatable. | |
| #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] | |
| #[rustc_diagnostic_item = "DiagMessage"] | |
| pub enum DiagMessage { | |
| /// Non-translatable diagnostic message. | |
| Str(Cow<'static, str>), | |
| /// Translatable message which has been already translated. | |
| /// | |
| /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would | |
| /// be instantiated multiple times with different values. These subdiagnostics' messages | |
| /// are translated when they are added to the parent diagnostic, producing this variant of | |
| /// `DiagMessage`. | |
| Translated(Cow<'static, str>), | |
| /// Identifier for a Fluent message (with optional attribute) corresponding to the diagnostic | |
| /// message. Yet to be translated. | |
| /// | |
| /// <https://projectfluent.org/fluent/guide/hello.html> | |
| /// <https://projectfluent.org/fluent/guide/attributes.html> | |
| FluentIdentifier(FluentId, Option<FluentId>), | |
| } | |
| impl DiagMessage { | |
| /// Given a `SubdiagMessage` which may contain a Fluent attribute, create a new | |
| /// `DiagMessage` that combines that attribute with the Fluent identifier of `self`. | |
| /// | |
| /// - If the `SubdiagMessage` is non-translatable then return the message as a `DiagMessage`. | |
| /// - If `self` is non-translatable then return `self`'s message. | |
| pub fn with_subdiagnostic_message(&self, sub: SubdiagMessage) -> Self { | |
| let attr = match sub { | |
| SubdiagMessage::Str(s) => return DiagMessage::Str(s), | |
| SubdiagMessage::Translated(s) => return DiagMessage::Translated(s), | |
| SubdiagMessage::FluentIdentifier(id) => { | |
| return DiagMessage::FluentIdentifier(id, None); | |
| } | |
| SubdiagMessage::FluentAttr(attr) => attr, | |
| }; | |
| match self { | |
| DiagMessage::Str(s) => DiagMessage::Str(s.clone()), | |
| DiagMessage::Translated(s) => DiagMessage::Translated(s.clone()), | |
| DiagMessage::FluentIdentifier(id, _) => { | |
| DiagMessage::FluentIdentifier(id.clone(), Some(attr)) | |
| } | |
| } | |
| } | |
| pub fn as_str(&self) -> Option<&str> { | |
| match self { | |
| DiagMessage::Translated(s) | DiagMessage::Str(s) => Some(s), | |
| DiagMessage::FluentIdentifier(_, _) => None, | |
| } | |
| } | |
| } | |
| impl From<String> for DiagMessage { | |
| fn from(s: String) -> Self { | |
| DiagMessage::Str(Cow::Owned(s)) | |
| } | |
| } | |
| impl From<&'static str> for DiagMessage { | |
| fn from(s: &'static str) -> Self { | |
| DiagMessage::Str(Cow::Borrowed(s)) | |
| } | |
| } | |
| impl From<Cow<'static, str>> for DiagMessage { | |
| fn from(s: Cow<'static, str>) -> Self { | |
| DiagMessage::Str(s) | |
| } | |
| } | |
| /// Translating *into* a subdiagnostic message from a diagnostic message is a little strange - but | |
| /// the subdiagnostic functions (e.g. `span_label`) take a `SubdiagMessage` and the | |
| /// subdiagnostic derive refers to typed identifiers that are `DiagMessage`s, so need to be | |
| /// able to convert between these, as much as they'll be converted back into `DiagMessage` | |
| /// using `with_subdiagnostic_message` eventually. Don't use this other than for the derive. | |
| impl Into<SubdiagMessage> for DiagMessage { | |
| fn into(self) -> SubdiagMessage { | |
| match self { | |
| DiagMessage::Str(s) => SubdiagMessage::Str(s), | |
| DiagMessage::Translated(s) => SubdiagMessage::Translated(s), | |
| DiagMessage::FluentIdentifier(id, None) => SubdiagMessage::FluentIdentifier(id), | |
| // There isn't really a sensible behaviour for this because it loses information but | |
| // this is the most sensible of the behaviours. | |
| DiagMessage::FluentIdentifier(_, Some(attr)) => SubdiagMessage::FluentAttr(attr), | |
| } | |
| } | |
| } | |
| /// A span together with some additional data. | |
| #[derive(Clone, Debug)] | |
| pub struct SpanLabel { | |
| /// The span we are going to include in the final snippet. | |
| pub span: Span, | |
| /// Is this a primary span? This is the "locus" of the message, | |
| /// and is indicated with a `^^^^` underline, versus `----`. | |
| pub is_primary: bool, | |
| /// What label should we attach to this span (if any)? | |
| pub label: Option<DiagMessage>, | |
| } | |
| /// A collection of `Span`s. | |
| /// | |
| /// Spans have two orthogonal attributes: | |
| /// | |
| /// - They can be *primary spans*. In this case they are the locus of | |
| /// the error, and would be rendered with `^^^`. | |
| /// - They can have a *label*. In this case, the label is written next | |
| /// to the mark in the snippet when we render. | |
| #[derive(Clone, Debug, Hash, PartialEq, Eq, Encodable, Decodable)] | |
| pub struct MultiSpan { | |
| primary_spans: Vec<Span>, | |
| span_labels: Vec<(Span, DiagMessage)>, | |
| } | |
| impl MultiSpan { | |
| #[inline] | |
| pub fn new() -> MultiSpan { | |
| MultiSpan { primary_spans: vec![], span_labels: vec![] } | |
| } | |
| pub fn from_span(primary_span: Span) -> MultiSpan { | |
| MultiSpan { primary_spans: vec![primary_span], span_labels: vec![] } | |
| } | |
| pub fn from_spans(mut vec: Vec<Span>) -> MultiSpan { | |
| vec.sort(); | |
| MultiSpan { primary_spans: vec, span_labels: vec![] } | |
| } | |
| pub fn push_span_label(&mut self, span: Span, label: impl Into<DiagMessage>) { | |
| self.span_labels.push((span, label.into())); | |
| } | |
| /// Selects the first primary span (if any). | |
| pub fn primary_span(&self) -> Option<Span> { | |
| self.primary_spans.first().cloned() | |
| } | |
| /// Returns all primary spans. | |
| pub fn primary_spans(&self) -> &[Span] { | |
| &self.primary_spans | |
| } | |
| /// Returns `true` if any of the primary spans are displayable. | |
| pub fn has_primary_spans(&self) -> bool { | |
| !self.is_dummy() | |
| } | |
| /// Returns `true` if this contains only a dummy primary span with any hygienic context. | |
| pub fn is_dummy(&self) -> bool { | |
| self.primary_spans.iter().all(|sp| sp.is_dummy()) | |
| } | |
| /// Replaces all occurrences of one Span with another. Used to move `Span`s in areas that don't | |
| /// display well (like std macros). Returns whether replacements occurred. | |
| pub fn replace(&mut self, before: Span, after: Span) -> bool { | |
| let mut replacements_occurred = false; | |
| for primary_span in &mut self.primary_spans { | |
| if *primary_span == before { | |
| *primary_span = after; | |
| replacements_occurred = true; | |
| } | |
| } | |
| for span_label in &mut self.span_labels { | |
| if span_label.0 == before { | |
| span_label.0 = after; | |
| replacements_occurred = true; | |
| } | |
| } | |
| replacements_occurred | |
| } | |
| pub fn pop_span_label(&mut self) -> Option<(Span, DiagMessage)> { | |
| self.span_labels.pop() | |
| } | |
| /// Returns the strings to highlight. We always ensure that there | |
| /// is an entry for each of the primary spans -- for each primary | |
| /// span `P`, if there is at least one label with span `P`, we return | |
| /// those labels (marked as primary). But otherwise we return | |
| /// `SpanLabel` instances with empty labels. | |
| pub fn span_labels(&self) -> Vec<SpanLabel> { | |
| let is_primary = |span| self.primary_spans.contains(&span); | |
| let mut span_labels = self | |
| .span_labels | |
| .iter() | |
| .map(|&(span, ref label)| SpanLabel { | |
| span, | |
| is_primary: is_primary(span), | |
| label: Some(label.clone()), | |
| }) | |
| .collect::<Vec<_>>(); | |
| for &span in &self.primary_spans { | |
| if !span_labels.iter().any(|sl| sl.span == span) { | |
| span_labels.push(SpanLabel { span, is_primary: true, label: None }); | |
| } | |
| } | |
| span_labels | |
| } | |
| /// Returns `true` if any of the span labels is displayable. | |
| pub fn has_span_labels(&self) -> bool { | |
| self.span_labels.iter().any(|(sp, _)| !sp.is_dummy()) | |
| } | |
| /// Clone this `MultiSpan` without keeping any of the span labels - sometimes a `MultiSpan` is | |
| /// to be re-used in another diagnostic, but includes `span_labels` which have translated | |
| /// messages. These translated messages would fail to translate without their diagnostic | |
| /// arguments which are unlikely to be cloned alongside the `Span`. | |
| pub fn clone_ignoring_labels(&self) -> Self { | |
| Self { primary_spans: self.primary_spans.clone(), ..MultiSpan::new() } | |
| } | |
| } | |
| impl From<Span> for MultiSpan { | |
| fn from(span: Span) -> MultiSpan { | |
| MultiSpan::from_span(span) | |
| } | |
| } | |
| impl From<Vec<Span>> for MultiSpan { | |
| fn from(spans: Vec<Span>) -> MultiSpan { | |
| MultiSpan::from_spans(spans) | |
| } | |
| } | |
| fn icu_locale_from_unic_langid(lang: LanguageIdentifier) -> Option<icu_locid::Locale> { | |
| icu_locid::Locale::try_from_bytes(lang.to_string().as_bytes()).ok() | |
| } | |
| pub fn fluent_value_from_str_list_sep_by_and(l: Vec<Cow<'_, str>>) -> FluentValue<'_> { | |
| // Fluent requires 'static value here for its AnyEq usages. | |
| #[derive(Clone, PartialEq, Debug)] | |
| struct FluentStrListSepByAnd(Vec<String>); | |
| impl FluentType for FluentStrListSepByAnd { | |
| fn duplicate(&self) -> Box<dyn FluentType + Send> { | |
| Box::new(self.clone()) | |
| } | |
| fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str> { | |
| let result = intls | |
| .with_try_get::<MemoizableListFormatter, _, _>((), |list_formatter| { | |
| list_formatter.format_to_string(self.0.iter()) | |
| }) | |
| .unwrap(); | |
| Cow::Owned(result) | |
| } | |
| #[cfg(not(parallel_compiler))] | |
| fn as_string_threadsafe( | |
| &self, | |
| _intls: &intl_memoizer::concurrent::IntlLangMemoizer, | |
| ) -> Cow<'static, str> { | |
| unreachable!("`as_string_threadsafe` is not used in non-parallel rustc") | |
| } | |
| #[cfg(parallel_compiler)] | |
| fn as_string_threadsafe( | |
| &self, | |
| intls: &intl_memoizer::concurrent::IntlLangMemoizer, | |
| ) -> Cow<'static, str> { | |
| let result = intls | |
| .with_try_get::<MemoizableListFormatter, _, _>((), |list_formatter| { | |
| list_formatter.format_to_string(self.0.iter()) | |
| }) | |
| .unwrap(); | |
| Cow::Owned(result) | |
| } | |
| } | |
| struct MemoizableListFormatter(icu_list::ListFormatter); | |
| impl std::ops::Deref for MemoizableListFormatter { | |
| type Target = icu_list::ListFormatter; | |
| fn deref(&self) -> &Self::Target { | |
| &self.0 | |
| } | |
| } | |
| impl intl_memoizer::Memoizable for MemoizableListFormatter { | |
| type Args = (); | |
| type Error = (); | |
| fn construct(lang: LanguageIdentifier, _args: Self::Args) -> Result<Self, Self::Error> | |
| where | |
| Self: Sized, | |
| { | |
| let baked_data_provider = rustc_baked_icu_data::baked_data_provider(); | |
| let locale_fallbacker = | |
| LocaleFallbacker::try_new_with_any_provider(&baked_data_provider) | |
| .expect("Failed to create fallback provider"); | |
| let data_provider = | |
| LocaleFallbackProvider::new_with_fallbacker(baked_data_provider, locale_fallbacker); | |
| let locale = icu_locale_from_unic_langid(lang) | |
| .unwrap_or_else(|| rustc_baked_icu_data::supported_locales::EN); | |
| let list_formatter = | |
| icu_list::ListFormatter::try_new_and_with_length_with_any_provider( | |
| &data_provider, | |
| &locale.into(), | |
| icu_list::ListLength::Wide, | |
| ) | |
| .expect("Failed to create list formatter"); | |
| Ok(MemoizableListFormatter(list_formatter)) | |
| } | |
| } | |
| let l = l.into_iter().map(|x| x.into_owned()).collect(); | |
| FluentValue::Custom(Box::new(FluentStrListSepByAnd(l))) | |
| } |