rustc_errors/
translation.rs

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