• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::error::{TranslateError, TranslateErrorKind};
2 use crate::snippet::Style;
3 use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
4 use rustc_data_structures::sync::Lrc;
5 use rustc_error_messages::FluentArgs;
6 use std::borrow::Cow;
7 use std::env;
8 use std::error::Report;
9 
10 /// Convert diagnostic arguments (a rustc internal type that exists to implement
11 /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
12 ///
13 /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
14 /// passed around as a reference thereafter.
to_fluent_args<'iter, 'arg: 'iter>( iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>, ) -> FluentArgs<'arg>15 pub fn to_fluent_args<'iter, 'arg: 'iter>(
16     iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
17 ) -> FluentArgs<'arg> {
18     let mut args = if let Some(size) = iter.size_hint().1 {
19         FluentArgs::with_capacity(size)
20     } else {
21         FluentArgs::new()
22     };
23 
24     for (k, v) in iter {
25         args.set(k.clone(), v.clone());
26     }
27 
28     args
29 }
30 
31 pub trait Translate {
32     /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
33     /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
34     /// should be used.
fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>35     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
36 
37     /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
38     /// Used when the user has not requested a specific language or when a localized diagnostic is
39     /// unavailable for the requested locale.
fallback_fluent_bundle(&self) -> &FluentBundle40     fn fallback_fluent_bundle(&self) -> &FluentBundle;
41 
42     /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
translate_messages( &self, messages: &[(DiagnosticMessage, Style)], args: &FluentArgs<'_>, ) -> Cow<'_, str>43     fn translate_messages(
44         &self,
45         messages: &[(DiagnosticMessage, Style)],
46         args: &FluentArgs<'_>,
47     ) -> Cow<'_, str> {
48         Cow::Owned(
49             messages
50                 .iter()
51                 .map(|(m, _)| self.translate_message(m, args).map_err(Report::new).unwrap())
52                 .collect::<String>(),
53         )
54     }
55 
56     /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
translate_message<'a>( &'a self, message: &'a DiagnosticMessage, args: &'a FluentArgs<'_>, ) -> Result<Cow<'_, str>, TranslateError<'_>>57     fn translate_message<'a>(
58         &'a self,
59         message: &'a DiagnosticMessage,
60         args: &'a FluentArgs<'_>,
61     ) -> Result<Cow<'_, str>, TranslateError<'_>> {
62         trace!(?message, ?args);
63         let (identifier, attr) = match message {
64             DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
65                 return Ok(Cow::Borrowed(msg));
66             }
67             DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
68         };
69         let translate_with_bundle =
70             |bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
71                 let message = bundle
72                     .get_message(identifier)
73                     .ok_or(TranslateError::message(identifier, args))?;
74                 let value = match attr {
75                     Some(attr) => message
76                         .get_attribute(attr)
77                         .ok_or(TranslateError::attribute(identifier, args, attr))?
78                         .value(),
79                     None => message.value().ok_or(TranslateError::value(identifier, args))?,
80                 };
81                 debug!(?message, ?value);
82 
83                 let mut errs = vec![];
84                 let translated = bundle.format_pattern(value, Some(args), &mut errs);
85                 debug!(?translated, ?errs);
86                 if errs.is_empty() {
87                     Ok(translated)
88                 } else {
89                     Err(TranslateError::fluent(identifier, args, errs))
90                 }
91             };
92 
93         try {
94             match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
95                 // The primary bundle was present and translation succeeded
96                 Some(Ok(t)) => t,
97 
98                 // If `translate_with_bundle` returns `Err` with the primary bundle, this is likely
99                 // just that the primary bundle doesn't contain the message being translated, so
100                 // proceed to the fallback bundle.
101                 Some(Err(
102                     primary @ TranslateError::One {
103                         kind: TranslateErrorKind::MessageMissing, ..
104                     },
105                 )) => translate_with_bundle(self.fallback_fluent_bundle())
106                     .map_err(|fallback| primary.and(fallback))?,
107 
108                 // Always yeet out for errors on debug (unless
109                 // `RUSTC_TRANSLATION_NO_DEBUG_ASSERT` is set in the environment - this allows
110                 // local runs of the test suites, of builds with debug assertions, to test the
111                 // behaviour in a normal build).
112                 Some(Err(primary))
113                     if cfg!(debug_assertions)
114                         && env::var("RUSTC_TRANSLATION_NO_DEBUG_ASSERT").is_err() =>
115                 {
116                     do yeet primary
117                 }
118 
119                 // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
120                 // just hide it and try with the fallback bundle.
121                 Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
122                     .map_err(|fallback| primary.and(fallback))?,
123 
124                 // The primary bundle is missing, proceed to the fallback bundle
125                 None => translate_with_bundle(self.fallback_fluent_bundle())
126                     .map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
127             }
128         }
129     }
130 }
131