mirror of
https://github.com/ankitects/anki.git
synced 2025-12-15 07:40:58 -05:00
make template errors translatable
This commit is contained in:
parent
5c8e3df612
commit
33367c8edf
8 changed files with 93 additions and 51 deletions
|
|
@ -81,7 +81,6 @@ message StringError {
|
|||
|
||||
message TemplateParseError {
|
||||
string info = 1;
|
||||
bool q_side = 2;
|
||||
}
|
||||
|
||||
message NetworkError {
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ class DBError(StringError):
|
|||
|
||||
|
||||
class TemplateError(StringError):
|
||||
def q_side(self) -> bool:
|
||||
return self.args[1]
|
||||
pass
|
||||
|
||||
|
||||
SyncErrorKind = pb.SyncError.SyncErrorKind
|
||||
|
|
@ -70,7 +69,7 @@ def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
|||
elif val == "db_error":
|
||||
return DBError(err.db_error.info)
|
||||
elif val == "template_parse":
|
||||
return TemplateError(err.template_parse.info, err.template_parse.q_side)
|
||||
return TemplateError(err.template_parse.info)
|
||||
elif val == "invalid_input":
|
||||
return StringError(err.invalid_input.info)
|
||||
elif val == "sync_error":
|
||||
|
|
|
|||
|
|
@ -121,17 +121,9 @@ def render_card(
|
|||
try:
|
||||
output = render_card_from_context(ctx)
|
||||
except anki.rsbackend.TemplateError as e:
|
||||
if e.q_side():
|
||||
side = _("Front")
|
||||
else:
|
||||
side = _("Back")
|
||||
errmsg = _("{} template has a problem:").format(side) + f"<br>{e}"
|
||||
errmsg += "<br><a href=https://anki.tenderapp.com/kb/problems/card-template-has-a-problem>{}</a>".format(
|
||||
_("More info")
|
||||
)
|
||||
output = TemplateRenderOutput(
|
||||
question_text=errmsg,
|
||||
answer_text=errmsg,
|
||||
question_text=str(e),
|
||||
answer_text=str(e),
|
||||
question_av_tags=[],
|
||||
answer_av_tags=[],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,9 +43,7 @@ impl std::convert::From<AnkiError> for pb::BackendError {
|
|||
use pb::backend_error::Value as V;
|
||||
let value = match err {
|
||||
AnkiError::InvalidInput { info } => V::InvalidInput(pb::StringError { info }),
|
||||
AnkiError::TemplateError { info, q_side } => {
|
||||
V::TemplateParse(pb::TemplateParseError { info, q_side })
|
||||
}
|
||||
AnkiError::TemplateError { info } => V::TemplateParse(pb::TemplateParseError { info }),
|
||||
AnkiError::IOError { info } => V::IoError(pb::StringError { info }),
|
||||
AnkiError::DBError { info } => V::DbError(pb::StringError { info }),
|
||||
AnkiError::NetworkError { info, kind } => V::NetworkError(pb::NetworkError {
|
||||
|
|
@ -280,6 +278,7 @@ impl Backend {
|
|||
&input.answer_template,
|
||||
&fields,
|
||||
input.card_ordinal as u16,
|
||||
&self.i18n,
|
||||
)?;
|
||||
|
||||
// return
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub enum AnkiError {
|
|||
InvalidInput { info: String },
|
||||
|
||||
#[fail(display = "invalid card template: {}", info)]
|
||||
TemplateError { info: String, q_side: bool },
|
||||
TemplateError { info: String },
|
||||
|
||||
#[fail(display = "I/O error: {}", info)]
|
||||
IOError { info: String },
|
||||
|
|
|
|||
15
rslib/src/i18n/card-templates.ftl
Normal file
15
rslib/src/i18n/card-templates.ftl
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
front-side-problem = Front template has a problem:
|
||||
back-side-problem = Back template has a problem:
|
||||
|
||||
no-closing-brackets =
|
||||
Missing '{$missing}' in '{$tag}'
|
||||
conditional-not-closed =
|
||||
Missing '{$missing}'
|
||||
wrong-conditional-closed =
|
||||
Found '{$found}', but expected '{$expected}'
|
||||
conditional-not-open =
|
||||
Found '{$found}', but missing '{$missing1}' or '{$missing2}'
|
||||
no-such-field =
|
||||
Found '{$found}', but there is no field called '{$field}'
|
||||
|
||||
more-info = More information
|
||||
|
|
@ -34,6 +34,13 @@ pub enum LanguageDialect {
|
|||
ChineseTaiwan,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum TranslationFile {
|
||||
Test,
|
||||
MediaCheck,
|
||||
CardTemplates,
|
||||
}
|
||||
|
||||
fn lang_dialect(lang: LanguageIdentifier) -> Option<LanguageDialect> {
|
||||
use LanguageDialect as L;
|
||||
Some(match lang.get_language() {
|
||||
|
|
@ -54,16 +61,11 @@ fn dialect_file_locale(dialect: LanguageDialect) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum TranslationFile {
|
||||
Test,
|
||||
MediaCheck,
|
||||
}
|
||||
|
||||
fn data_for_fallback(file: TranslationFile) -> String {
|
||||
match file {
|
||||
TranslationFile::MediaCheck => include_str!("media-check.ftl"),
|
||||
TranslationFile::Test => include_str!("../../tests/support/test.ftl"),
|
||||
TranslationFile::MediaCheck => include_str!("media-check.ftl"),
|
||||
TranslationFile::CardTemplates => include_str!("card-templates.ftl"),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
|
@ -74,8 +76,9 @@ fn data_for_lang_and_file(
|
|||
locales: &Path,
|
||||
) -> Option<String> {
|
||||
let path = locales.join(dialect_file_locale(dialect)).join(match file {
|
||||
TranslationFile::MediaCheck => "media-check.ftl",
|
||||
TranslationFile::Test => "test.ftl",
|
||||
TranslationFile::MediaCheck => "media-check.ftl",
|
||||
TranslationFile::CardTemplates => "card-templates.ftl",
|
||||
});
|
||||
fs::read_to_string(&path)
|
||||
.map_err(|e| {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::err::{AnkiError, Result, TemplateError};
|
||||
use crate::i18n::{tr_strs, I18n, I18nCategory, TranslationFile};
|
||||
use crate::template_filters::apply_filters;
|
||||
use lazy_static::lazy_static;
|
||||
use nom;
|
||||
|
|
@ -17,6 +18,9 @@ use std::iter;
|
|||
pub type FieldMap<'a> = HashMap<&'a str, u16>;
|
||||
type TemplateResult<T> = std::result::Result<T, TemplateError>;
|
||||
|
||||
static TEMPLATE_ERROR_LINK: &str =
|
||||
"https://anki.tenderapp.com/kb/problems/card-template-has-a-problem";
|
||||
|
||||
// Lexing
|
||||
//----------------------------------------
|
||||
|
||||
|
|
@ -189,30 +193,60 @@ fn parse_inner<'a, I: Iterator<Item = TemplateResult<Token<'a>>>>(
|
|||
}
|
||||
}
|
||||
|
||||
fn template_error_to_anki_error(err: TemplateError, q_side: bool) -> AnkiError {
|
||||
AnkiError::TemplateError {
|
||||
info: match err {
|
||||
TemplateError::NoClosingBrackets(context) => format!("Missing '}}}}' in '{}'", context),
|
||||
TemplateError::ConditionalNotClosed(tag) => format!("Missing '{{{{/{}}}}}'", tag),
|
||||
fn template_error_to_anki_error(err: TemplateError, q_side: bool, i18n: &I18n) -> AnkiError {
|
||||
let cat = i18n.get(TranslationFile::CardTemplates);
|
||||
let header = cat.tr(if q_side {
|
||||
"front-side-problem"
|
||||
} else {
|
||||
"back-side-problem"
|
||||
});
|
||||
let details = localized_template_error(&cat, err);
|
||||
let more_info = cat.tr("more-info");
|
||||
let info = format!(
|
||||
"{}<br>{}<br><a href='{}'>{}</a>",
|
||||
header, details, TEMPLATE_ERROR_LINK, more_info
|
||||
);
|
||||
|
||||
AnkiError::TemplateError { info }
|
||||
}
|
||||
|
||||
fn localized_template_error(cat: &I18nCategory, err: TemplateError) -> String {
|
||||
match err {
|
||||
TemplateError::NoClosingBrackets(tag) => {
|
||||
cat.trn("no-closing-brackets", tr_strs!("tag"=>tag, "missing"=>"}}"))
|
||||
}
|
||||
TemplateError::ConditionalNotClosed(tag) => cat.trn(
|
||||
"conditional-not-closed",
|
||||
tr_strs!("missing"=>format!("{{{{/{}}}}}", tag)),
|
||||
),
|
||||
TemplateError::ConditionalNotOpen {
|
||||
closed,
|
||||
currently_open,
|
||||
} => {
|
||||
if let Some(open) = currently_open {
|
||||
format!("Found {{{{/{}}}}}, but expected {{{{/{}}}}}", closed, open)
|
||||
cat.trn(
|
||||
"wrong-conditional-closed",
|
||||
tr_strs!(
|
||||
"found"=>format!("{{{{/{}}}}}", closed),
|
||||
"expected"=>format!("{{{{/{}}}}}", open)),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Found {{{{/{}}}}}, but missing '{{{{#{}}}}}' or '{{{{^{}}}}}'",
|
||||
closed, closed, closed
|
||||
cat.trn(
|
||||
"conditional-not-open",
|
||||
tr_strs!(
|
||||
"found"=>format!("{{{{/{}}}}}", closed),
|
||||
"missing1"=>format!("{{{{#{}}}}}", closed),
|
||||
"missing2"=>format!("{{{{^{}}}}}", closed)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
TemplateError::FieldNotFound { field, filters } => format!(
|
||||
"Found '{{{{{}{}}}}}', but there is no field called '{}'",
|
||||
filters, field, field
|
||||
TemplateError::FieldNotFound { field, filters } => cat.trn(
|
||||
"no-such-field",
|
||||
tr_strs!(
|
||||
"found"=>format!("{{{{{}{}}}}}", filters, field),
|
||||
"field"=>field),
|
||||
),
|
||||
},
|
||||
q_side,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -454,6 +488,7 @@ pub fn render_card(
|
|||
afmt: &str,
|
||||
field_map: &HashMap<&str, &str>,
|
||||
card_ord: u16,
|
||||
i18n: &I18n,
|
||||
) -> Result<(Vec<RenderedNode>, Vec<RenderedNode>)> {
|
||||
// prepare context
|
||||
let mut context = RenderContext {
|
||||
|
|
@ -467,14 +502,14 @@ pub fn render_card(
|
|||
let qnorm = without_legacy_template_directives(qfmt);
|
||||
let qnodes = ParsedTemplate::from_text(qnorm.as_ref())
|
||||
.and_then(|tmpl| tmpl.render(&context))
|
||||
.map_err(|e| template_error_to_anki_error(e, true))?;
|
||||
.map_err(|e| template_error_to_anki_error(e, true, i18n))?;
|
||||
|
||||
// answer side
|
||||
context.question_side = false;
|
||||
let anorm = without_legacy_template_directives(afmt);
|
||||
let anodes = ParsedTemplate::from_text(anorm.as_ref())
|
||||
.and_then(|tmpl| tmpl.render(&context))
|
||||
.map_err(|e| template_error_to_anki_error(e, false))?;
|
||||
.map_err(|e| template_error_to_anki_error(e, false, i18n))?;
|
||||
|
||||
Ok((qnodes, anodes))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue