2022-08-10 16:30:47 +00:00
|
|
|
use crate::snippet::Style;
|
|
|
|
use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
|
|
|
|
use rustc_data_structures::sync::Lrc;
|
2022-11-09 21:37:17 +00:00
|
|
|
use rustc_error_messages::{
|
|
|
|
fluent_bundle::resolver::errors::{ReferenceKind, ResolverError},
|
|
|
|
FluentArgs, FluentError,
|
|
|
|
};
|
2022-08-10 16:30:47 +00:00
|
|
|
use std::borrow::Cow;
|
|
|
|
|
2022-10-03 13:02:49 +00:00
|
|
|
/// Convert diagnostic arguments (a rustc internal type that exists to implement
|
|
|
|
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
|
|
|
|
///
|
|
|
|
/// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
|
|
|
|
/// passed around as a reference thereafter.
|
|
|
|
pub fn to_fluent_args<'iter, 'arg: 'iter>(
|
|
|
|
iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
|
|
|
|
) -> FluentArgs<'arg> {
|
|
|
|
let mut args = if let Some(size) = iter.size_hint().1 {
|
|
|
|
FluentArgs::with_capacity(size)
|
|
|
|
} else {
|
|
|
|
FluentArgs::new()
|
|
|
|
};
|
|
|
|
|
|
|
|
for (k, v) in iter {
|
|
|
|
args.set(k.clone(), v.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
args
|
|
|
|
}
|
|
|
|
|
2022-08-10 16:30:47 +00:00
|
|
|
pub trait Translate {
|
|
|
|
/// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
|
|
|
|
/// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
|
|
|
|
/// should be used.
|
|
|
|
fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
|
|
|
|
|
|
|
|
/// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
|
|
|
|
/// Used when the user has not requested a specific language or when a localized diagnostic is
|
|
|
|
/// unavailable for the requested locale.
|
|
|
|
fn fallback_fluent_bundle(&self) -> &FluentBundle;
|
|
|
|
|
|
|
|
/// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
|
|
|
|
fn translate_messages(
|
|
|
|
&self,
|
|
|
|
messages: &[(DiagnosticMessage, Style)],
|
|
|
|
args: &FluentArgs<'_>,
|
|
|
|
) -> Cow<'_, str> {
|
|
|
|
Cow::Owned(
|
|
|
|
messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
|
|
|
|
fn translate_message<'a>(
|
|
|
|
&'a self,
|
|
|
|
message: &'a DiagnosticMessage,
|
|
|
|
args: &'a FluentArgs<'_>,
|
|
|
|
) -> Cow<'_, str> {
|
|
|
|
trace!(?message, ?args);
|
|
|
|
let (identifier, attr) = match message {
|
2022-10-03 13:14:51 +00:00
|
|
|
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
|
|
|
|
return Cow::Borrowed(&msg);
|
|
|
|
}
|
2022-08-10 16:30:47 +00:00
|
|
|
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
|
|
|
|
};
|
|
|
|
|
|
|
|
let translate_with_bundle = |bundle: &'a FluentBundle| -> Option<(Cow<'_, str>, Vec<_>)> {
|
|
|
|
let message = bundle.get_message(&identifier)?;
|
|
|
|
let value = match attr {
|
|
|
|
Some(attr) => message.get_attribute(attr)?.value(),
|
|
|
|
None => message.value()?,
|
|
|
|
};
|
|
|
|
debug!(?message, ?value);
|
|
|
|
|
|
|
|
let mut errs = vec![];
|
|
|
|
let translated = bundle.format_pattern(value, Some(&args), &mut errs);
|
|
|
|
debug!(?translated, ?errs);
|
|
|
|
Some((translated, errs))
|
|
|
|
};
|
|
|
|
|
|
|
|
self.fluent_bundle()
|
|
|
|
.and_then(|bundle| translate_with_bundle(bundle))
|
|
|
|
// If `translate_with_bundle` returns `None` with the primary bundle, this is likely
|
|
|
|
// just that the primary bundle doesn't contain the message being translated, so
|
|
|
|
// proceed to the fallback bundle.
|
|
|
|
//
|
|
|
|
// However, when errors are produced from translation, then that means the translation
|
|
|
|
// is broken (e.g. `{$foo}` exists in a translation but `foo` isn't provided).
|
|
|
|
//
|
|
|
|
// In debug builds, assert so that compiler devs can spot the broken translation and
|
|
|
|
// fix it..
|
|
|
|
.inspect(|(_, errs)| {
|
|
|
|
debug_assert!(
|
|
|
|
errs.is_empty(),
|
|
|
|
"identifier: {:?}, attr: {:?}, args: {:?}, errors: {:?}",
|
|
|
|
identifier,
|
|
|
|
attr,
|
|
|
|
args,
|
|
|
|
errs
|
|
|
|
);
|
|
|
|
})
|
|
|
|
// ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
|
|
|
|
// just hide it and try with the fallback bundle.
|
|
|
|
.filter(|(_, errs)| errs.is_empty())
|
|
|
|
.or_else(|| translate_with_bundle(self.fallback_fluent_bundle()))
|
|
|
|
.map(|(translated, errs)| {
|
|
|
|
// Always bail out for errors with the fallback bundle.
|
2022-11-09 21:37:17 +00:00
|
|
|
|
|
|
|
let mut help_messages = vec![];
|
|
|
|
|
|
|
|
if !errs.is_empty() {
|
|
|
|
for error in &errs {
|
|
|
|
match error {
|
|
|
|
FluentError::ResolverError(ResolverError::Reference(
|
|
|
|
ReferenceKind::Message { id, .. },
|
|
|
|
)) if args.iter().any(|(arg_id, _)| arg_id == id) => {
|
|
|
|
help_messages.push(format!("Argument `{id}` exists but was not referenced correctly. Try using `{{${id}}}` instead"));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"Encountered errors while formatting message for `{identifier}`\n\
|
|
|
|
help: {}\n\
|
|
|
|
attr: `{attr:?}`\n\
|
|
|
|
args: `{args:?}`\n\
|
|
|
|
errors: `{errs:?}`",
|
|
|
|
help_messages.join("\nhelp: ")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-10 16:30:47 +00:00
|
|
|
translated
|
|
|
|
})
|
|
|
|
.expect("failed to find message in primary or fallback fluent bundles")
|
|
|
|
}
|
|
|
|
}
|