]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_errors/src/translation.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / compiler / rustc_errors / src / translation.rs
1 use crate::error::TranslateError;
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::error::Report;
8
9 /// Convert diagnostic arguments (a rustc internal type that exists to implement
10 /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
11 ///
12 /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
13 /// passed around as a reference thereafter.
14 pub fn to_fluent_args<'iter, 'arg: 'iter>(
15     iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
16 ) -> FluentArgs<'arg> {
17     let mut args = if let Some(size) = iter.size_hint().1 {
18         FluentArgs::with_capacity(size)
19     } else {
20         FluentArgs::new()
21     };
22
23     for (k, v) in iter {
24         args.set(k.clone(), v.clone());
25     }
26
27     args
28 }
29
30 pub trait Translate {
31     /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
32     /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
33     /// should be used.
34     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
35
36     /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
37     /// Used when the user has not requested a specific language or when a localized diagnostic is
38     /// unavailable for the requested locale.
39     fn fallback_fluent_bundle(&self) -> &FluentBundle;
40
41     /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
42     fn translate_messages(
43         &self,
44         messages: &[(DiagnosticMessage, Style)],
45         args: &FluentArgs<'_>,
46     ) -> Cow<'_, str> {
47         Cow::Owned(
48             messages
49                 .iter()
50                 .map(|(m, _)| self.translate_message(m, args).map_err(Report::new).unwrap())
51                 .collect::<String>(),
52         )
53     }
54
55     /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
56     fn translate_message<'a>(
57         &'a self,
58         message: &'a DiagnosticMessage,
59         args: &'a FluentArgs<'_>,
60     ) -> Result<Cow<'_, str>, TranslateError<'_>> {
61         trace!(?message, ?args);
62         let (identifier, attr) = match message {
63             DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
64                 return Ok(Cow::Borrowed(msg));
65             }
66             DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
67         };
68         let translate_with_bundle =
69             |bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
70                 let message = bundle
71                     .get_message(identifier)
72                     .ok_or(TranslateError::message(identifier, args))?;
73                 let value = match attr {
74                     Some(attr) => message
75                         .get_attribute(attr)
76                         .ok_or(TranslateError::attribute(identifier, args, attr))?
77                         .value(),
78                     None => message.value().ok_or(TranslateError::value(identifier, args))?,
79                 };
80                 debug!(?message, ?value);
81
82                 let mut errs = vec![];
83                 let translated = bundle.format_pattern(value, Some(args), &mut errs);
84                 debug!(?translated, ?errs);
85                 if errs.is_empty() {
86                     Ok(translated)
87                 } else {
88                     Err(TranslateError::fluent(identifier, args, errs))
89                 }
90             };
91
92         try {
93             match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
94                 // The primary bundle was present and translation succeeded
95                 Some(Ok(t)) => t,
96
97                 // Always yeet out for errors on debug
98                 Some(Err(primary)) if cfg!(debug_assertions) => do yeet primary,
99
100                 // If `translate_with_bundle` returns `Err` with the primary bundle, this is likely
101                 // just that the primary bundle doesn't contain the message being translated or
102                 // something else went wrong) so proceed to the fallback bundle.
103                 Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
104                     .map_err(|fallback| primary.and(fallback))?,
105
106                 // The primary bundle is missing, proceed to the fallback bundle
107                 None => translate_with_bundle(self.fallback_fluent_bundle())
108                     .map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
109             }
110         }
111     }
112 }