1 use crate::snippet::Style;
2 use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
3 use rustc_data_structures::sync::Lrc;
4 use rustc_error_messages::{
5 fluent_bundle::resolver::errors::{ReferenceKind, ResolverError},
6 FluentArgs, FluentError,
10 /// Convert diagnostic arguments (a rustc internal type that exists to implement
11 /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
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)
25 args.set(k.clone(), v.clone());
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`
35 fn fluent_bundle(&self) -> Option<&Lrc<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 fn fallback_fluent_bundle(&self) -> &FluentBundle;
42 /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
43 fn translate_messages(
45 messages: &[(DiagnosticMessage, Style)],
46 args: &FluentArgs<'_>,
49 messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
53 /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
54 fn translate_message<'a>(
56 message: &'a DiagnosticMessage,
57 args: &'a FluentArgs<'_>,
59 trace!(?message, ?args);
60 let (identifier, attr) = match message {
61 DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
62 return Cow::Borrowed(&msg);
64 DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
67 let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> {
68 let message = bundle.get_message(&identifier)?;
69 let value = match attr {
70 Some(attr) => message.get_attribute(attr)?.value(),
71 None => message.value()?,
73 debug!(?message, ?value);
75 let mut errs = vec![];
76 let translated = bundle.format_pattern(value, Some(&args), &mut errs);
77 debug!(?translated, ?errs);
78 Some((translated, errs))
82 .and_then(|bundle| translate_with_bundle(bundle))
83 // If `translate_with_bundle` returns `None` with the primary bundle, this is likely
84 // just that the primary bundle doesn't contain the message being translated, so
85 // proceed to the fallback bundle.
87 // However, when errors are produced from translation, then that means the translation
88 // is broken (e.g. `{$foo}` exists in a translation but `foo` isn't provided).
90 // In debug builds, assert so that compiler devs can spot the broken translation and
92 .inspect(|(_, errs)| {
95 "identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
102 // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
103 // just hide it and try with the fallback bundle.
104 .filter(|(_, errs)| errs.is_empty())
105 .or_else(|| translate_with_bundle(self.fallback_fluent_bundle()))
106 .map(|(translated, errs)| {
107 // Always bail out for errors with the fallback bundle.
109 let mut help_messages = vec![];
111 if !errs.is_empty() {
114 FluentError::ResolverError(ResolverError::Reference(
115 ReferenceKind::Message { id, .. },
116 )) if args.iter().any(|(arg_id, _)| arg_id == id) => {
117 help_messages.push(format!("Argument `{id}` exists but was not referenced correctly. Try using `{{${id}}}` instead"));
124 "Encountered errors while formatting message for `{identifier}`\n\
129 help_messages.join("\nhelp: ")
135 .expect("failed to find message in primary or fallback fluent bundles")