]>
Commit | Line | Data |
---|---|---|
9ffffee4 | 1 | use crate::error::{TranslateError, TranslateErrorKind}; |
f2b60f7d FG |
2 | use crate::snippet::Style; |
3 | use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle}; | |
4 | use rustc_data_structures::sync::Lrc; | |
9c376795 | 5 | use rustc_error_messages::FluentArgs; |
f2b60f7d | 6 | use std::borrow::Cow; |
9ffffee4 | 7 | use std::env; |
9c376795 | 8 | use std::error::Report; |
f2b60f7d | 9 | |
2b03887a FG |
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. | |
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 | ||
f2b60f7d FG |
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. | |
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. | |
40 | fn fallback_fluent_bundle(&self) -> &FluentBundle; | |
41 | ||
f2b60f7d FG |
42 | /// Convert `DiagnosticMessage`s to a string, performing translation if necessary. |
43 | fn translate_messages( | |
44 | &self, | |
45 | messages: &[(DiagnosticMessage, Style)], | |
46 | args: &FluentArgs<'_>, | |
47 | ) -> Cow<'_, str> { | |
48 | Cow::Owned( | |
9c376795 FG |
49 | messages |
50 | .iter() | |
51 | .map(|(m, _)| self.translate_message(m, args).map_err(Report::new).unwrap()) | |
52 | .collect::<String>(), | |
f2b60f7d FG |
53 | ) |
54 | } | |
55 | ||
56 | /// Convert a `DiagnosticMessage` to a string, performing translation if necessary. | |
57 | fn translate_message<'a>( | |
58 | &'a self, | |
59 | message: &'a DiagnosticMessage, | |
60 | args: &'a FluentArgs<'_>, | |
9c376795 | 61 | ) -> Result<Cow<'_, str>, TranslateError<'_>> { |
f2b60f7d FG |
62 | trace!(?message, ?args); |
63 | let (identifier, attr) = match message { | |
2b03887a | 64 | DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => { |
9c376795 | 65 | return Ok(Cow::Borrowed(msg)); |
2b03887a | 66 | } |
f2b60f7d FG |
67 | DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr), |
68 | }; | |
9c376795 FG |
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); | |
f2b60f7d | 82 | |
9c376795 FG |
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 | } | |
f2b60f7d | 91 | }; |
f2b60f7d | 92 | |
9c376795 FG |
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, | |
487cf647 | 97 | |
9c376795 | 98 | // If `translate_with_bundle` returns `Err` with the primary bundle, this is likely |
9ffffee4 FG |
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. | |
9c376795 FG |
121 | Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle()) |
122 | .map_err(|fallback| primary.and(fallback))?, | |
487cf647 | 123 | |
9c376795 FG |
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 | } | |
f2b60f7d FG |
129 | } |
130 | } |