]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_errors/src/translation.rs
Rollup merge of #103524 - petrochenkov:modchild4, r=cjgillot
[rust.git] / compiler / rustc_errors / src / translation.rs
1 use crate::snippet::Style;
2 use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
3 use rustc_data_structures::sync::Lrc;
4 use rustc_error_messages::FluentArgs;
5 use std::borrow::Cow;
6
7 /// Convert diagnostic arguments (a rustc internal type that exists to implement
8 /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
9 ///
10 /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
11 /// passed around as a reference thereafter.
12 pub fn to_fluent_args<'iter, 'arg: 'iter>(
13     iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
14 ) -> FluentArgs<'arg> {
15     let mut args = if let Some(size) = iter.size_hint().1 {
16         FluentArgs::with_capacity(size)
17     } else {
18         FluentArgs::new()
19     };
20
21     for (k, v) in iter {
22         args.set(k.clone(), v.clone());
23     }
24
25     args
26 }
27
28 pub trait Translate {
29     /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
30     /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
31     /// should be used.
32     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
33
34     /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
35     /// Used when the user has not requested a specific language or when a localized diagnostic is
36     /// unavailable for the requested locale.
37     fn fallback_fluent_bundle(&self) -> &FluentBundle;
38
39     /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
40     fn translate_messages(
41         &self,
42         messages: &[(DiagnosticMessage, Style)],
43         args: &FluentArgs<'_>,
44     ) -> Cow<'_, str> {
45         Cow::Owned(
46             messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
47         )
48     }
49
50     /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
51     fn translate_message<'a>(
52         &'a self,
53         message: &'a DiagnosticMessage,
54         args: &'a FluentArgs<'_>,
55     ) -> Cow<'_, str> {
56         trace!(?message, ?args);
57         let (identifier, attr) = match message {
58             DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
59                 return Cow::Borrowed(&msg);
60             }
61             DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
62         };
63
64         let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> {
65             let message = bundle.get_message(&identifier)?;
66             let value = match attr {
67                 Some(attr) => message.get_attribute(attr)?.value(),
68                 None => message.value()?,
69             };
70             debug!(?message, ?value);
71
72             let mut errs = vec![];
73             let translated = bundle.format_pattern(value, Some(&args), &mut errs);
74             debug!(?translated, ?errs);
75             Some((translated, errs))
76         };
77
78         self.fluent_bundle()
79             .and_then(|bundle| translate_with_bundle(bundle))
80             // If `translate_with_bundle` returns `None` with the primary bundle, this is likely
81             // just that the primary bundle doesn't contain the message being translated, so
82             // proceed to the fallback bundle.
83             //
84             // However, when errors are produced from translation, then that means the translation
85             // is broken (e.g. `{$foo}` exists in a translation but `foo` isn't provided).
86             //
87             // In debug builds, assert so that compiler devs can spot the broken translation and
88             // fix it..
89             .inspect(|(_, errs)| {
90                 debug_assert!(
91                     errs.is_empty(),
92                     "identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
93                     identifier,
94                     attr,
95                     args,
96                     errs
97                 );
98             })
99             // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
100             // just hide it and try with the fallback bundle.
101             .filter(|(_, errs)| errs.is_empty())
102             .or_else(|| translate_with_bundle(self.fallback_fluent_bundle()))
103             .map(|(translated, errs)| {
104                 // Always bail out for errors with the fallback bundle.
105                 assert!(
106                     errs.is_empty(),
107                     "identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
108                     identifier,
109                     attr,
110                     args,
111                     errs
112                 );
113                 translated
114             })
115             .expect("failed to find message in primary or fallback fluent bundles")
116     }
117 }