forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtranslation.rs
196 lines (179 loc) · 9.06 KB
/
translation.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use crate::error::{TranslateError, TranslateErrorKind};
use crate::fluent_bundle::FluentResource;
use crate::snippet::Style;
use crate::{DiagnosticArg, DiagnosticMessage, FluentBundle};
use fluent::FluentBundle as RawFluentBundle;
use md5::{Digest, Md5};
use rustc_data_structures::sync::Lrc;
use rustc_error_messages::FluentArgs;
use std::borrow::Cow;
use std::env;
use std::error::Report;
use unic_langid::langid;
/// 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
}
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).map_err(Report::new).unwrap())
.collect::<String>(),
)
}
/// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
fn translate_message<'a>(
&'a self,
message: &'a DiagnosticMessage,
args: &'a FluentArgs<'_>,
) -> Result<Cow<'_, str>, TranslateError<'_>> {
trace!(?message, ?args);
let (identifier, attr) = match message {
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
return Ok(Cow::Borrowed(msg));
}
DiagnosticMessage::FluentRaw(msg) => {
// FIXME(yukang): calculate the `slug` from the raw fluent content,
// The fluent resources are generated by a simple standalone visitor:
// https://github.com/chenyukang/fluent-utils/blob/main/src/visitor.rs#L13-L97
// we may need to add fluent-utils into the tools directory of rustc
let mut hasher = Md5::new();
hasher.update(msg.to_string());
let digest = hasher.finalize();
let id = format!("{:x}", digest);
let identifier = format!("slug-{}", id[0..8].to_string());
let id = Cow::Borrowed(identifier.as_str());
let translate_with_bundle =
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
let message = bundle
.get_message(&identifier)
.ok_or(TranslateError::message(&id, args))?;
let value = message.value().ok_or(TranslateError::value(&id, args))?;
debug!(?message, ?value);
let mut errs = vec![];
let translated = bundle.format_pattern(value, Some(args), &mut errs);
debug!(?translated, ?errs);
if errs.is_empty() {
Ok(translated)
} else {
Err(TranslateError::fluent(&id, args, errs))
}
};
return {
match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
// The primary bundle was present and translation succeeded
Some(Ok(t)) => {
// eprintln!("translated id OK: {} => {}", identifier, t);
Ok(t)
}
// If `translate_with_bundle` returns `Err` 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.
_ => {
// fallback to en-US, we don't need fluent bundle for raw fluent content in English
// here we just interpret the variables in the fluent content.
let fluent_text = format!("dummy = {}", msg);
if let Ok(resource) = FluentResource::try_new(fluent_text) {
let mut bundle = RawFluentBundle::new(vec![langid!("en-US")]);
bundle.add_resource(resource).unwrap();
let mut errors = vec![];
let pattern = bundle.get_message("dummy").unwrap().value().unwrap();
let res = bundle.format_pattern(&pattern, Some(args), &mut errors);
Ok(Cow::Owned(
res.to_string().replace("\u{2068}", "").replace("\u{2069}", ""),
))
} else {
Ok(Cow::Owned(msg.to_string()))
}
}
}
};
}
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
};
// FIXME(yukang): remove this part for fluent resource id after all diagnostics are migrated to Fluent
let translate_with_bundle =
|bundle: &'a FluentBundle| -> Result<Cow<'_, str>, TranslateError<'_>> {
let message = bundle
.get_message(identifier)
.ok_or(TranslateError::message(identifier, args))?;
let value = match attr {
Some(attr) => message
.get_attribute(attr)
.ok_or(TranslateError::attribute(identifier, args, attr))?
.value(),
None => message.value().ok_or(TranslateError::value(identifier, args))?,
};
debug!(?message, ?value);
let mut errs = vec![];
let translated = bundle.format_pattern(value, Some(args), &mut errs);
debug!(?translated, ?errs);
if errs.is_empty() {
Ok(translated)
} else {
Err(TranslateError::fluent(identifier, args, errs))
}
};
try {
match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
// The primary bundle was present and translation succeeded
Some(Ok(t)) => t,
// If `translate_with_bundle` returns `Err` 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.
Some(Err(
primary @ TranslateError::One {
kind: TranslateErrorKind::MessageMissing, ..
},
)) => translate_with_bundle(self.fallback_fluent_bundle())
.map_err(|fallback| primary.and(fallback))?,
// Always yeet out for errors on debug (unless
// `RUSTC_TRANSLATION_NO_DEBUG_ASSERT` is set in the environment - this allows
// local runs of the test suites, of builds with debug assertions, to test the
// behaviour in a normal build).
Some(Err(primary))
if cfg!(debug_assertions)
&& env::var("RUSTC_TRANSLATION_NO_DEBUG_ASSERT").is_err() =>
{
do yeet primary
}
// ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
// just hide it and try with the fallback bundle.
Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
.map_err(|fallback| primary.and(fallback))?,
// The primary bundle is missing, proceed to the fallback bundle
None => translate_with_bundle(self.fallback_fluent_bundle())
.map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
}
}
}
}