rustc_errors/
translation.rs

1use std::borrow::Cow;
2use std::env;
3use std::error::Report;
4
5pub use rustc_error_messages::FluentArgs;
6use tracing::{debug, trace};
7
8use crate::error::{TranslateError, TranslateErrorKind};
9use crate::snippet::Style;
10use crate::{DiagArg, DiagMessage, FluentBundle};
11
12/// Convert diagnostic arguments (a rustc internal type that exists to implement
13/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
14///
15/// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
16/// passed around as a reference thereafter.
17pub fn to_fluent_args<'iter>(iter: impl Iterator<Item = DiagArg<'iter>>) -> FluentArgs<'static> {
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
31pub 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.
35    fn fluent_bundle(&self) -> Option<&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.
40    fn fallback_fluent_bundle(&self) -> &FluentBundle;
41
42    /// Convert `DiagMessage`s to a string, performing translation if necessary.
43    fn translate_messages(
44        &self,
45        messages: &[(DiagMessage, 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 `DiagMessage` to a string, performing translation if necessary.
57    fn translate_message<'a>(
58        &'a self,
59        message: &'a DiagMessage,
60        args: &'a FluentArgs<'_>,
61    ) -> Result<Cow<'a, str>, TranslateError<'a>> {
62        trace!(?message, ?args);
63        let (identifier, attr) = match message {
64            DiagMessage::Str(msg) | DiagMessage::Translated(msg) => {
65                return Ok(Cow::Borrowed(msg));
66            }
67            DiagMessage::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}