Pronounce "[...]" as "blank" with TTS
This commit is contained in:
parent
363548e8a2
commit
b5c29fb498
@ -1,3 +1,5 @@
|
||||
# This word is used by TTS voices instead of the elided part of a cloze.
|
||||
card-templates-blank = blank
|
||||
card-templates-changes-will-affect-notes =
|
||||
{ $count ->
|
||||
[one] Changes below will affect the { $count } note that uses this card type.
|
||||
|
@ -385,10 +385,10 @@ impl ParsedTemplate {
|
||||
/// Replacements that use only standard filters will become part of
|
||||
/// a text node. If a non-standard filter is encountered, a partially
|
||||
/// rendered Replacement is returned for the calling code to complete.
|
||||
fn render(&self, context: &RenderContext) -> TemplateResult<Vec<RenderedNode>> {
|
||||
fn render(&self, context: &RenderContext, tr: &I18n) -> TemplateResult<Vec<RenderedNode>> {
|
||||
let mut rendered = vec![];
|
||||
|
||||
render_into(&mut rendered, self.0.as_ref(), context)?;
|
||||
render_into(&mut rendered, self.0.as_ref(), context, tr)?;
|
||||
|
||||
Ok(rendered)
|
||||
}
|
||||
@ -398,6 +398,7 @@ fn render_into(
|
||||
rendered_nodes: &mut Vec<RenderedNode>,
|
||||
nodes: &[ParsedNode],
|
||||
context: &RenderContext,
|
||||
tr: &I18n,
|
||||
) -> TemplateResult<()> {
|
||||
use ParsedNode::*;
|
||||
for node in nodes {
|
||||
@ -436,6 +437,7 @@ fn render_into(
|
||||
.as_slice(),
|
||||
key,
|
||||
context,
|
||||
tr,
|
||||
),
|
||||
None => {
|
||||
// unknown field encountered
|
||||
@ -466,12 +468,12 @@ fn render_into(
|
||||
}
|
||||
Conditional { key, children } => {
|
||||
if context.nonempty_fields.contains(key.as_str()) {
|
||||
render_into(rendered_nodes, children.as_ref(), context)?;
|
||||
render_into(rendered_nodes, children.as_ref(), context, tr)?;
|
||||
}
|
||||
}
|
||||
NegatedConditional { key, children } => {
|
||||
if !context.nonempty_fields.contains(key.as_str()) {
|
||||
render_into(rendered_nodes, children.as_ref(), context)?;
|
||||
render_into(rendered_nodes, children.as_ref(), context, tr)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -551,7 +553,7 @@ pub fn render_card(
|
||||
|
||||
// question side
|
||||
let (mut qnodes, qtmpl) = ParsedTemplate::from_text(qfmt)
|
||||
.and_then(|tmpl| Ok((tmpl.render(&context)?, tmpl)))
|
||||
.and_then(|tmpl| Ok((tmpl.render(&context, tr)?, tmpl)))
|
||||
.map_err(|e| template_error_to_anki_error(e, true, tr))?;
|
||||
|
||||
// check if the front side was empty
|
||||
@ -580,7 +582,7 @@ pub fn render_card(
|
||||
// answer side
|
||||
context.question_side = false;
|
||||
let anodes = ParsedTemplate::from_text(afmt)
|
||||
.and_then(|tmpl| tmpl.render(&context))
|
||||
.and_then(|tmpl| tmpl.render(&context, tr))
|
||||
.map_err(|e| template_error_to_anki_error(e, false, tr))?;
|
||||
|
||||
Ok((qnodes, anodes))
|
||||
@ -1021,8 +1023,9 @@ mod test {
|
||||
|
||||
use crate::template::RenderedNode as FN;
|
||||
let mut tmpl = PT::from_text("{{B}}A{{F}}").unwrap();
|
||||
let tr = I18n::template_only();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Text {
|
||||
text: "bAf".to_owned()
|
||||
},]
|
||||
@ -1030,12 +1033,12 @@ mod test {
|
||||
|
||||
// empty
|
||||
tmpl = PT::from_text("{{#E}}A{{/E}}").unwrap();
|
||||
assert_eq!(tmpl.render(&ctx).unwrap(), vec![]);
|
||||
assert_eq!(tmpl.render(&ctx, &tr).unwrap(), vec![]);
|
||||
|
||||
// missing
|
||||
tmpl = PT::from_text("{{^M}}A{{/M}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Text {
|
||||
text: "A".to_owned()
|
||||
},]
|
||||
@ -1044,7 +1047,7 @@ mod test {
|
||||
// nested
|
||||
tmpl = PT::from_text("{{^E}}1{{#F}}2{{#B}}{{F}}{{/B}}{{/F}}{{/E}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Text {
|
||||
text: "12f".to_owned()
|
||||
},]
|
||||
@ -1053,7 +1056,7 @@ mod test {
|
||||
// unknown filters
|
||||
tmpl = PT::from_text("{{one:two:B}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Replacement {
|
||||
field_name: "B".to_owned(),
|
||||
filters: vec!["two".to_string(), "one".to_string()],
|
||||
@ -1065,7 +1068,7 @@ mod test {
|
||||
// excess colons are ignored
|
||||
tmpl = PT::from_text("{{one::text:B}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Replacement {
|
||||
field_name: "B".to_owned(),
|
||||
filters: vec!["one".to_string()],
|
||||
@ -1076,7 +1079,7 @@ mod test {
|
||||
// known filter
|
||||
tmpl = PT::from_text("{{text:B}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Text {
|
||||
text: "b".to_owned()
|
||||
}]
|
||||
@ -1085,7 +1088,7 @@ mod test {
|
||||
// unknown field
|
||||
tmpl = PT::from_text("{{X}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap_err(),
|
||||
tmpl.render(&ctx, &tr).unwrap_err(),
|
||||
TemplateError::FieldNotFound {
|
||||
field: "X".to_owned(),
|
||||
filters: "".to_owned()
|
||||
@ -1095,7 +1098,7 @@ mod test {
|
||||
// unknown field with filters
|
||||
tmpl = PT::from_text("{{foo:text:X}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap_err(),
|
||||
tmpl.render(&ctx, &tr).unwrap_err(),
|
||||
TemplateError::FieldNotFound {
|
||||
field: "X".to_owned(),
|
||||
filters: "foo:text:".to_owned()
|
||||
@ -1105,7 +1108,7 @@ mod test {
|
||||
// a blank field is allowed if it has filters
|
||||
tmpl = PT::from_text("{{filter:}}").unwrap();
|
||||
assert_eq!(
|
||||
tmpl.render(&ctx).unwrap(),
|
||||
tmpl.render(&ctx, &tr).unwrap(),
|
||||
vec![FN::Replacement {
|
||||
field_name: "".to_string(),
|
||||
current_text: "".to_string(),
|
||||
|
@ -9,6 +9,7 @@ use regex::{Captures, Regex};
|
||||
|
||||
use crate::{
|
||||
cloze::{cloze_filter, cloze_only_filter},
|
||||
i18n::I18n,
|
||||
template::RenderContext,
|
||||
text::strip_html,
|
||||
};
|
||||
@ -25,6 +26,7 @@ pub(crate) fn apply_filters<'a>(
|
||||
filters: &[&str],
|
||||
field_name: &str,
|
||||
context: &RenderContext,
|
||||
tr: &I18n,
|
||||
) -> (Cow<'a, str>, Vec<String>) {
|
||||
let mut text: Cow<str> = text.into();
|
||||
|
||||
@ -36,7 +38,7 @@ pub(crate) fn apply_filters<'a>(
|
||||
};
|
||||
|
||||
for (idx, &filter_name) in filters.iter().enumerate() {
|
||||
match apply_filter(filter_name, text.as_ref(), field_name, context) {
|
||||
match apply_filter(filter_name, text.as_ref(), field_name, context, tr) {
|
||||
(true, None) => {
|
||||
// filter did not change text
|
||||
}
|
||||
@ -67,6 +69,7 @@ fn apply_filter<'a>(
|
||||
text: &'a str,
|
||||
field_name: &str,
|
||||
context: &RenderContext,
|
||||
tr: &I18n,
|
||||
) -> (bool, Option<String>) {
|
||||
let output_text = match filter_name {
|
||||
"text" => strip_html(text),
|
||||
@ -82,7 +85,7 @@ fn apply_filter<'a>(
|
||||
"" => text.into(),
|
||||
_ => {
|
||||
if filter_name.starts_with("tts ") {
|
||||
tts_filter(filter_name, text)
|
||||
tts_filter(filter_name, text, tr)
|
||||
} else {
|
||||
// unrecognized filter
|
||||
return (false, None);
|
||||
@ -191,8 +194,9 @@ return false;">
|
||||
.into()
|
||||
}
|
||||
|
||||
fn tts_filter(filter_name: &str, text: &str) -> Cow<'static, str> {
|
||||
fn tts_filter(filter_name: &str, text: &str, tr: &I18n) -> Cow<'static, str> {
|
||||
let args = filter_name.splitn(2, ' ').nth(1).unwrap_or("");
|
||||
let text = text.replace("[...]", &tr.card_templates_blank());
|
||||
|
||||
format!("[anki:tts][{}]{}[/anki:tts]", args, text).into()
|
||||
}
|
||||
@ -201,14 +205,7 @@ fn tts_filter(filter_name: &str, text: &str) -> Cow<'static, str> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
template::RenderContext,
|
||||
template_filters::{
|
||||
apply_filters, cloze_filter, furigana_filter, hint_filter, kana_filter, kanji_filter,
|
||||
tts_filter, type_cloze_filter, type_filter,
|
||||
},
|
||||
text::strip_html,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn furigana() {
|
||||
@ -238,6 +235,7 @@ field</a>
|
||||
|
||||
#[test]
|
||||
fn typing() {
|
||||
let tr = I18n::template_only();
|
||||
assert_eq!(type_filter("Front"), "[[type:Front]]");
|
||||
assert_eq!(type_cloze_filter("Front"), "[[type:cloze:Front]]");
|
||||
let ctx = RenderContext {
|
||||
@ -247,7 +245,7 @@ field</a>
|
||||
card_ord: 0,
|
||||
};
|
||||
assert_eq!(
|
||||
apply_filters("ignored", &["cloze", "type"], "Text", &ctx),
|
||||
apply_filters("ignored", &["cloze", "type"], "Text", &ctx, &tr),
|
||||
("[[type:cloze:Text]]".into(), vec![])
|
||||
);
|
||||
}
|
||||
@ -282,9 +280,17 @@ field</a>
|
||||
|
||||
#[test]
|
||||
fn tts() {
|
||||
let tr = I18n::template_only();
|
||||
assert_eq!(
|
||||
tts_filter("tts en_US voices=Bob,Jane", "foo"),
|
||||
tts_filter("tts en_US voices=Bob,Jane", "foo", &tr),
|
||||
"[anki:tts][en_US voices=Bob,Jane]foo[/anki:tts]"
|
||||
);
|
||||
assert_eq!(
|
||||
tts_filter("tts en_US", "foo [...]", &tr),
|
||||
format!(
|
||||
"[anki:tts][en_US]foo {}[/anki:tts]",
|
||||
tr.card_templates_blank()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user