i18n->tr in rslib/ to match Python/TS code

This commit is contained in:
Damien Elmes 2021-03-27 12:09:51 +10:00
parent d6b9cc4d9b
commit 3433c02242
32 changed files with 310 additions and 310 deletions

View File

@ -426,50 +426,50 @@ mod test {
#[test]
fn i18n() {
// English template
let i18n = I18n::new(&["zz"]);
assert_eq!(i18n.translate("valid-key", None), "a valid key");
assert_eq!(i18n.translate("invalid-key", None), "invalid-key");
let tr = I18n::new(&["zz"]);
assert_eq!(tr.translate("valid-key", None), "a valid key");
assert_eq!(tr.translate("invalid-key", None), "invalid-key");
assert_eq!(
i18n.translate("two-args-key", Some(tr_args!["one"=>1.1, "two"=>"2"])),
tr.translate("two-args-key", Some(tr_args!["one"=>1.1, "two"=>"2"])),
"two args: 1.1 and 2"
);
assert_eq!(
i18n.translate("plural", Some(tr_args!["hats"=>1.0])),
tr.translate("plural", Some(tr_args!["hats"=>1.0])),
"You have 1 hat."
);
assert_eq!(
i18n.translate("plural", Some(tr_args!["hats"=>1.1])),
tr.translate("plural", Some(tr_args!["hats"=>1.1])),
"You have 1.1 hats."
);
assert_eq!(
i18n.translate("plural", Some(tr_args!["hats"=>3])),
tr.translate("plural", Some(tr_args!["hats"=>3])),
"You have 3 hats."
);
// Another language
let i18n = I18n::new(&["ja_JP"]);
assert_eq!(i18n.translate("valid-key", None), "キー");
assert_eq!(i18n.translate("only-in-english", None), "not translated");
assert_eq!(i18n.translate("invalid-key", None), "invalid-key");
let tr = I18n::new(&["ja_JP"]);
assert_eq!(tr.translate("valid-key", None), "キー");
assert_eq!(tr.translate("only-in-english", None), "not translated");
assert_eq!(tr.translate("invalid-key", None), "invalid-key");
assert_eq!(
i18n.translate("two-args-key", Some(tr_args!["one"=>1, "two"=>"2"])),
tr.translate("two-args-key", Some(tr_args!["one"=>1, "two"=>"2"])),
"1と2"
);
// Decimal separator
let i18n = I18n::new(&["pl-PL"]);
let tr = I18n::new(&["pl-PL"]);
// Polish will use a comma if the string is translated
assert_eq!(
i18n.translate("one-arg-key", Some(tr_args!["one"=>2.07])),
tr.translate("one-arg-key", Some(tr_args!["one"=>2.07])),
"fake Polish 2,07"
);
// but if it falls back on English, it will use an English separator
assert_eq!(
i18n.translate("two-args-key", Some(tr_args!["one"=>1, "two"=>2.07])),
tr.translate("two-args-key", Some(tr_args!["one"=>1, "two"=>2.07])),
"two args: 1 and 2.07"
);
}

View File

@ -15,7 +15,7 @@ use slog::error;
impl CollectionService for Backend {
fn latest_progress(&self, _input: pb::Empty) -> Result<pb::Progress> {
let progress = self.progress_state.lock().unwrap().last_progress;
Ok(progress_to_proto(progress, &self.i18n))
Ok(progress_to_proto(progress, &self.tr))
}
fn set_wants_abort(&self, _input: pb::Empty) -> Result<pb::Empty> {
@ -43,7 +43,7 @@ impl CollectionService for Backend {
input.media_folder_path,
input.media_db_path,
self.server,
self.i18n.clone(),
self.tr.clone(),
logger,
)?;
@ -79,26 +79,26 @@ impl CollectionService for Backend {
self.with_col(|col| {
col.check_database(progress_fn)
.map(|problems| pb::CheckDatabaseOut {
problems: problems.to_i18n_strings(&col.i18n),
problems: problems.to_i18n_strings(&col.tr),
})
})
}
fn get_undo_status(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
self.with_col(|col| Ok(col.undo_status().into_protobuf(&col.i18n)))
self.with_col(|col| Ok(col.undo_status().into_protobuf(&col.tr)))
}
fn undo(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
self.with_col(|col| {
col.undo()?;
Ok(col.undo_status().into_protobuf(&col.i18n))
Ok(col.undo_status().into_protobuf(&col.tr))
})
}
fn redo(&self, _input: pb::Empty) -> Result<pb::UndoStatus> {
self.with_col(|col| {
col.redo()?;
Ok(col.undo_status().into_protobuf(&col.i18n))
Ok(col.undo_status().into_protobuf(&col.tr))
})
}
}

View File

@ -8,9 +8,9 @@ use crate::{
};
/// Convert an Anki error to a protobuf error.
pub(super) fn anki_error_to_proto_error(err: AnkiError, i18n: &I18n) -> pb::BackendError {
pub(super) fn anki_error_to_proto_error(err: AnkiError, tr: &I18n) -> pb::BackendError {
use pb::backend_error::Value as V;
let localized = err.localized_description(i18n);
let localized = err.localized_description(tr);
let value = match err {
AnkiError::InvalidInput { .. } => V::InvalidInput(pb::Empty {}),
AnkiError::TemplateError { .. } => V::TemplateParse(pb::Empty {}),

View File

@ -19,7 +19,7 @@ impl I18nService for Backend {
.collect();
Ok(self
.i18n
.tr
.translate_via_index(
input.module_index as usize,
input.message_index as usize,
@ -31,15 +31,15 @@ impl I18nService for Backend {
fn format_timespan(&self, input: pb::FormatTimespanIn) -> Result<pb::String> {
use pb::format_timespan_in::Context;
Ok(match input.context() {
Context::Precise => time_span(input.seconds, &self.i18n, true),
Context::Intervals => time_span(input.seconds, &self.i18n, false),
Context::AnswerButtons => answer_button_time(input.seconds, &self.i18n),
Context::Precise => time_span(input.seconds, &self.tr, true),
Context::Intervals => time_span(input.seconds, &self.tr, false),
Context::AnswerButtons => answer_button_time(input.seconds, &self.tr),
}
.into())
}
fn i18n_resources(&self, input: pb::I18nResourcesIn) -> Result<pb::Json> {
serde_json::to_vec(&self.i18n.resources_for_js(&input.modules))
serde_json::to_vec(&self.tr.resources_for_js(&input.modules))
.map(Into::into)
.map_err(Into::into)
}

View File

@ -62,7 +62,7 @@ use self::err::anki_error_to_proto_error;
pub struct Backend {
col: Arc<Mutex<Option<Collection>>>,
i18n: I18n,
tr: I18n,
server: bool,
sync_abort: AbortHandleSlot,
progress_state: Arc<Mutex<ProgressState>>,
@ -81,16 +81,16 @@ pub fn init_backend(init_msg: &[u8]) -> std::result::Result<Backend, String> {
Err(_) => return Err("couldn't decode init request".into()),
};
let i18n = I18n::new(&input.preferred_langs);
let tr = I18n::new(&input.preferred_langs);
Ok(Backend::new(i18n, input.server))
Ok(Backend::new(tr, input.server))
}
impl Backend {
pub fn new(i18n: I18n, server: bool) -> Backend {
pub fn new(tr: I18n, server: bool) -> Backend {
Backend {
col: Arc::new(Mutex::new(None)),
i18n,
tr,
server,
sync_abort: Arc::new(Mutex::new(None)),
progress_state: Arc::new(Mutex::new(ProgressState {
@ -103,7 +103,7 @@ impl Backend {
}
pub fn i18n(&self) -> &I18n {
&self.i18n
&self.tr
}
pub fn run_method(
@ -134,7 +134,7 @@ impl Backend {
pb::ServiceIndex::Cards => CardsService::run_method(self, method, input),
})
.map_err(|err| {
let backend_err = anki_error_to_proto_error(err, &self.i18n);
let backend_err = anki_error_to_proto_error(err, &self.tr);
let mut bytes = Vec::new();
backend_err.encode(&mut bytes).unwrap();
bytes
@ -143,7 +143,7 @@ impl Backend {
pub fn run_db_command_bytes(&self, input: &[u8]) -> std::result::Result<Vec<u8>, Vec<u8>> {
self.db_command(input).map_err(|err| {
let backend_err = anki_error_to_proto_error(err, &self.i18n);
let backend_err = anki_error_to_proto_error(err, &self.tr);
let mut bytes = Vec::new();
backend_err.encode(&mut bytes).unwrap();
bytes

View File

@ -25,7 +25,7 @@ impl NoteTypesService for Backend {
fn get_stock_notetype_legacy(&self, input: pb::StockNoteType) -> Result<pb::Json> {
// fixme: use individual functions instead of full vec
let mut all = all_stock_notetypes(&self.i18n);
let mut all = all_stock_notetypes(&self.tr);
let idx = (input.kind as usize).min(all.len() - 1);
let nt = all.swap_remove(idx);
let schema11: NoteTypeSchema11 = nt.into();

View File

@ -31,10 +31,10 @@ impl From<OpChanges> for pb::OpChanges {
}
impl UndoStatus {
pub(crate) fn into_protobuf(self, i18n: &I18n) -> pb::UndoStatus {
pub(crate) fn into_protobuf(self, tr: &I18n) -> pb::UndoStatus {
pb::UndoStatus {
undo: self.undo.map(|op| op.describe(i18n)).unwrap_or_default(),
redo: self.redo.map(|op| op.describe(i18n)).unwrap_or_default(),
undo: self.undo.map(|op| op.describe(tr)).unwrap_or_default(),
redo: self.redo.map(|op| op.describe(tr)).unwrap_or_default(),
}
}
}

View File

@ -52,12 +52,12 @@ pub(super) enum Progress {
DatabaseCheck(DatabaseCheckProgress),
}
pub(super) fn progress_to_proto(progress: Option<Progress>, i18n: &I18n) -> pb::Progress {
pub(super) fn progress_to_proto(progress: Option<Progress>, tr: &I18n) -> pb::Progress {
let progress = if let Some(progress) = progress {
match progress {
Progress::MediaSync(p) => pb::progress::Value::MediaSync(media_sync_progress(p, i18n)),
Progress::MediaSync(p) => pb::progress::Value::MediaSync(media_sync_progress(p, tr)),
Progress::MediaCheck(n) => {
pb::progress::Value::MediaCheck(i18n.media_check_checked(n).into())
pb::progress::Value::MediaCheck(tr.media_check_checked(n).into())
}
Progress::FullSync(p) => pb::progress::Value::FullSync(pb::progress::FullSync {
transferred: p.transferred_bytes as u32,
@ -65,15 +65,15 @@ pub(super) fn progress_to_proto(progress: Option<Progress>, i18n: &I18n) -> pb::
}),
Progress::NormalSync(p) => {
let stage = match p.stage {
SyncStage::Connecting => i18n.sync_syncing(),
SyncStage::Syncing => i18n.sync_syncing(),
SyncStage::Finalizing => i18n.sync_checking(),
SyncStage::Connecting => tr.sync_syncing(),
SyncStage::Syncing => tr.sync_syncing(),
SyncStage::Finalizing => tr.sync_checking(),
}
.to_string();
let added = i18n
let added = tr
.sync_added_updated_count(p.local_update, p.remote_update)
.into();
let removed = i18n
let removed = tr
.sync_media_removed_count(p.local_remove, p.remote_remove)
.into();
pb::progress::Value::NormalSync(pb::progress::NormalSync {
@ -86,15 +86,15 @@ pub(super) fn progress_to_proto(progress: Option<Progress>, i18n: &I18n) -> pb::
let mut stage_total = 0;
let mut stage_current = 0;
let stage = match p {
DatabaseCheckProgress::Integrity => i18n.database_check_checking_integrity(),
DatabaseCheckProgress::Optimize => i18n.database_check_rebuilding(),
DatabaseCheckProgress::Cards => i18n.database_check_checking_cards(),
DatabaseCheckProgress::Integrity => tr.database_check_checking_integrity(),
DatabaseCheckProgress::Optimize => tr.database_check_rebuilding(),
DatabaseCheckProgress::Cards => tr.database_check_checking_cards(),
DatabaseCheckProgress::Notes { current, total } => {
stage_total = total;
stage_current = current;
i18n.database_check_checking_notes()
tr.database_check_checking_notes()
}
DatabaseCheckProgress::History => i18n.database_check_checking_history(),
DatabaseCheckProgress::History => tr.database_check_checking_history(),
}
.to_string();
pb::progress::Value::DatabaseCheck(pb::progress::DatabaseCheck {
@ -112,13 +112,13 @@ pub(super) fn progress_to_proto(progress: Option<Progress>, i18n: &I18n) -> pb::
}
}
fn media_sync_progress(p: MediaSyncProgress, i18n: &I18n) -> pb::progress::MediaSync {
fn media_sync_progress(p: MediaSyncProgress, tr: &I18n) -> pb::progress::MediaSync {
pb::progress::MediaSync {
checked: i18n.sync_media_checked_count(p.checked).into(),
added: i18n
checked: tr.sync_media_checked_count(p.checked).into(),
added: tr
.sync_media_added_count(p.uploaded_files, p.downloaded_files)
.into(),
removed: i18n
removed: tr
.sync_media_removed_count(p.uploaded_deletions, p.downloaded_deletions)
.into(),
}

View File

@ -34,7 +34,7 @@ impl SchedulingService for Backend {
/// Message rendering only, for old graphs.
fn studied_today_message(&self, input: pb::StudiedTodayMessageIn) -> Result<pb::String> {
Ok(studied_today(input.cards, input.seconds as f32, &self.i18n).into())
Ok(studied_today(input.cards, input.seconds as f32, &self.tr).into())
}
fn update_stats(&self, input: pb::UpdateStatsIn) -> Result<pb::Empty> {

View File

@ -356,7 +356,7 @@ impl Backend {
media_folder_path,
media_db_path,
self.server,
self.i18n.clone(),
self.tr.clone(),
logger,
)?);

View File

@ -58,7 +58,7 @@ struct RowContext<'a> {
notetype: Arc<NoteType>,
deck: Option<Deck>,
original_deck: Option<Option<Deck>>,
i18n: &'a I18n,
tr: &'a I18n,
timing: SchedTimingToday,
render_context: Option<RenderContext>,
}
@ -152,7 +152,7 @@ impl<'a> RowContext<'a> {
notetype,
deck: None,
original_deck: None,
i18n: &col.i18n,
tr: &col.tr,
timing,
render_context,
})
@ -236,9 +236,9 @@ impl<'a> RowContext<'a> {
fn card_due_str(&mut self) -> String {
let due = if self.card.original_deck_id != DeckID(0) {
self.i18n.browsing_filtered()
self.tr.browsing_filtered()
} else if self.card.queue == CardQueue::New || self.card.ctype == CardType::New {
self.i18n.statistics_due_for_new_card(self.card.due)
self.tr.statistics_due_for_new_card(self.card.due)
} else {
let date = if self.card.queue == CardQueue::Learn {
TimestampSecs(self.card.due as i64)
@ -262,16 +262,16 @@ impl<'a> RowContext<'a> {
fn card_ease_str(&self) -> String {
match self.card.ctype {
CardType::New => self.i18n.browsing_new().into(),
CardType::New => self.tr.browsing_new().into(),
_ => format!("{}%", self.card.ease_factor / 10),
}
}
fn card_interval_str(&self) -> String {
match self.card.ctype {
CardType::New => self.i18n.browsing_new().into(),
CardType::Learn => self.i18n.browsing_learning().into(),
_ => time_span((self.card.interval * 86400) as f32, self.i18n, false),
CardType::New => self.tr.browsing_new().into(),
CardType::Learn => self.tr.browsing_learning().into(),
_ => time_span((self.card.interval * 86400) as f32, self.tr, false),
}
}

View File

@ -19,18 +19,18 @@ pub fn open_collection<P: Into<PathBuf>>(
media_folder: P,
media_db: P,
server: bool,
i18n: I18n,
tr: I18n,
log: Logger,
) -> Result<Collection> {
let col_path = path.into();
let storage = SqliteStorage::open_or_create(&col_path, &i18n, server)?;
let storage = SqliteStorage::open_or_create(&col_path, &tr, server)?;
let col = Collection {
storage,
col_path,
media_folder: media_folder.into(),
media_db: media_db.into(),
i18n,
tr,
log,
server,
state: CollectionState::default(),
@ -55,8 +55,8 @@ pub fn open_test_collection() -> Collection {
#[cfg(test)]
pub fn open_test_collection_with_server(server: bool) -> Collection {
use crate::log;
let i18n = I18n::template_only();
open_collection(":memory:", "", "", server, i18n, log::terminal()).unwrap()
let tr = I18n::template_only();
open_collection(":memory:", "", "", server, tr, log::terminal()).unwrap()
}
#[derive(Debug, Default)]
@ -76,7 +76,7 @@ pub struct Collection {
pub(crate) col_path: PathBuf,
pub(crate) media_folder: PathBuf,
pub(crate) media_db: PathBuf,
pub(crate) i18n: I18n,
pub(crate) tr: I18n,
pub(crate) log: Logger,
pub(crate) server: bool,
pub(crate) state: CollectionState,

View File

@ -41,39 +41,39 @@ pub(crate) enum DatabaseCheckProgress {
}
impl CheckDatabaseOutput {
pub fn to_i18n_strings(&self, i18n: &I18n) -> Vec<String> {
pub fn to_i18n_strings(&self, tr: &I18n) -> Vec<String> {
let mut probs = Vec::new();
if self.notetypes_recovered > 0 {
probs.push(i18n.database_check_notetypes_recovered());
probs.push(tr.database_check_notetypes_recovered());
}
if self.card_position_too_high > 0 {
probs.push(i18n.database_check_new_card_high_due(self.card_position_too_high));
probs.push(tr.database_check_new_card_high_due(self.card_position_too_high));
}
if self.card_properties_invalid > 0 {
probs.push(i18n.database_check_card_properties(self.card_properties_invalid));
probs.push(tr.database_check_card_properties(self.card_properties_invalid));
}
if self.cards_missing_note > 0 {
probs.push(i18n.database_check_card_missing_note(self.cards_missing_note));
probs.push(tr.database_check_card_missing_note(self.cards_missing_note));
}
if self.decks_missing > 0 {
probs.push(i18n.database_check_missing_decks(self.decks_missing));
probs.push(tr.database_check_missing_decks(self.decks_missing));
}
if self.field_count_mismatch > 0 {
probs.push(i18n.database_check_field_count(self.field_count_mismatch));
probs.push(tr.database_check_field_count(self.field_count_mismatch));
}
if self.card_ords_duplicated > 0 {
probs.push(i18n.database_check_duplicate_card_ords(self.card_ords_duplicated));
probs.push(tr.database_check_duplicate_card_ords(self.card_ords_duplicated));
}
if self.templates_missing > 0 {
probs.push(i18n.database_check_missing_templates(self.templates_missing));
probs.push(tr.database_check_missing_templates(self.templates_missing));
}
if self.revlog_properties_invalid > 0 {
probs.push(i18n.database_check_revlog_properties(self.revlog_properties_invalid));
probs.push(tr.database_check_revlog_properties(self.revlog_properties_invalid));
}
if self.invalid_utf8 > 0 {
probs.push(i18n.database_check_notes_with_invalid_utf8(self.invalid_utf8));
probs.push(tr.database_check_notes_with_invalid_utf8(self.invalid_utf8));
}
probs.into_iter().map(Into::into).collect()
@ -91,7 +91,7 @@ impl Collection {
if self.storage.quick_check_corrupt() {
debug!(self.log, "quick check failed");
return Err(AnkiError::DBError {
info: self.i18n.database_check_corrupt().into(),
info: self.tr.database_check_corrupt().into(),
kind: DBErrorKind::Corrupt,
});
}
@ -266,7 +266,7 @@ impl Collection {
// if the collection is empty and the user has deleted all note types, ensure at least
// one note type exists
if self.storage.get_all_notetype_names()?.is_empty() {
let mut nt = all_stock_notetypes(&self.i18n).remove(0);
let mut nt = all_stock_notetypes(&self.tr).remove(0);
self.add_notetype_inner(&mut nt, usn)?;
}
@ -349,7 +349,7 @@ impl Collection {
let extra_cards_required = self
.storage
.highest_card_ordinal_for_notetype(previous_id)?;
let mut basic = all_stock_notetypes(&self.i18n).remove(0);
let mut basic = all_stock_notetypes(&self.tr).remove(0);
let mut field = 3;
while basic.fields.len() < field_count {
basic.add_field(format!("{}", field));

View File

@ -512,7 +512,7 @@ impl Collection {
if deck.id.0 == 1 {
// if deleting the default deck, ensure there's a new one, and avoid the grave
let mut deck = deck.to_owned();
deck.name = self.i18n.deck_config_default_name().into();
deck.name = self.tr.deck_config_default_name().into();
deck.set_modified(usn);
self.add_or_update_single_deck_with_existing_id(&mut deck, usn)?;
} else {

View File

@ -90,37 +90,37 @@ impl AnkiError {
}
}
pub fn localized_description(&self, i18n: &I18n) -> String {
pub fn localized_description(&self, tr: &I18n) -> String {
match self {
AnkiError::SyncError { info, kind } => match kind {
SyncErrorKind::ServerMessage => info.into(),
SyncErrorKind::Other => info.into(),
SyncErrorKind::Conflict => i18n.sync_conflict(),
SyncErrorKind::ServerError => i18n.sync_server_error(),
SyncErrorKind::ClientTooOld => i18n.sync_client_too_old(),
SyncErrorKind::AuthFailed => i18n.sync_wrong_pass(),
SyncErrorKind::ResyncRequired => i18n.sync_resync_required(),
SyncErrorKind::ClockIncorrect => i18n.sync_clock_off(),
SyncErrorKind::DatabaseCheckRequired => i18n.sync_sanity_check_failed(),
SyncErrorKind::Conflict => tr.sync_conflict(),
SyncErrorKind::ServerError => tr.sync_server_error(),
SyncErrorKind::ClientTooOld => tr.sync_client_too_old(),
SyncErrorKind::AuthFailed => tr.sync_wrong_pass(),
SyncErrorKind::ResyncRequired => tr.sync_resync_required(),
SyncErrorKind::ClockIncorrect => tr.sync_clock_off(),
SyncErrorKind::DatabaseCheckRequired => tr.sync_sanity_check_failed(),
// server message
SyncErrorKind::SyncNotStarted => "sync not started".into(),
}
.into(),
AnkiError::NetworkError { kind, info } => {
let summary = match kind {
NetworkErrorKind::Offline => i18n.network_offline(),
NetworkErrorKind::Timeout => i18n.network_timeout(),
NetworkErrorKind::ProxyAuth => i18n.network_proxy_auth(),
NetworkErrorKind::Other => i18n.network_other(),
NetworkErrorKind::Offline => tr.network_offline(),
NetworkErrorKind::Timeout => tr.network_timeout(),
NetworkErrorKind::ProxyAuth => tr.network_proxy_auth(),
NetworkErrorKind::Other => tr.network_other(),
};
let details = i18n.network_details(info.as_str());
let details = tr.network_details(info.as_str());
format!("{}\n\n{}", summary, details)
}
AnkiError::TemplateError { info } => {
// already localized
info.into()
}
AnkiError::TemplateSaveError { ordinal } => i18n
AnkiError::TemplateSaveError { ordinal } => tr
.card_templates_invalid_template_number(ordinal + 1)
.into(),
AnkiError::DBError { info, kind } => match kind {
@ -130,75 +130,75 @@ impl AnkiError {
},
AnkiError::SearchError(kind) => {
let reason = match kind {
SearchErrorKind::MisplacedAnd => i18n.search_misplaced_and(),
SearchErrorKind::MisplacedOr => i18n.search_misplaced_or(),
SearchErrorKind::EmptyGroup => i18n.search_empty_group(),
SearchErrorKind::UnopenedGroup => i18n.search_unopened_group(),
SearchErrorKind::UnclosedGroup => i18n.search_unclosed_group(),
SearchErrorKind::EmptyQuote => i18n.search_empty_quote(),
SearchErrorKind::UnclosedQuote => i18n.search_unclosed_quote(),
SearchErrorKind::MissingKey => i18n.search_missing_key(),
SearchErrorKind::MisplacedAnd => tr.search_misplaced_and(),
SearchErrorKind::MisplacedOr => tr.search_misplaced_or(),
SearchErrorKind::EmptyGroup => tr.search_empty_group(),
SearchErrorKind::UnopenedGroup => tr.search_unopened_group(),
SearchErrorKind::UnclosedGroup => tr.search_unclosed_group(),
SearchErrorKind::EmptyQuote => tr.search_empty_quote(),
SearchErrorKind::UnclosedQuote => tr.search_unclosed_quote(),
SearchErrorKind::MissingKey => tr.search_missing_key(),
SearchErrorKind::UnknownEscape(ctx) => {
i18n.search_unknown_escape(ctx.replace('`', "'"))
tr.search_unknown_escape(ctx.replace('`', "'"))
}
SearchErrorKind::InvalidState(state) => {
i18n.search_invalid_argument("is:", state.replace('`', "'"))
tr.search_invalid_argument("is:", state.replace('`', "'"))
}
SearchErrorKind::InvalidFlag => i18n.search_invalid_flag(),
SearchErrorKind::InvalidFlag => tr.search_invalid_flag(),
SearchErrorKind::InvalidPropProperty(prop) => {
i18n.search_invalid_argument("prop:", prop.replace('`', "'"))
tr.search_invalid_argument("prop:", prop.replace('`', "'"))
}
SearchErrorKind::InvalidPropOperator(ctx) => {
i18n.search_invalid_prop_operator(ctx.as_str())
tr.search_invalid_prop_operator(ctx.as_str())
}
SearchErrorKind::Regex(text) => {
format!("<pre>`{}`</pre>", text.replace('`', "'")).into()
}
SearchErrorKind::Other(Some(info)) => info.into(),
SearchErrorKind::Other(None) => i18n.search_invalid_other(),
SearchErrorKind::InvalidNumber { provided, context } => i18n
SearchErrorKind::Other(None) => tr.search_invalid_other(),
SearchErrorKind::InvalidNumber { provided, context } => tr
.search_invalid_number(
context.replace('`', "'"),
provided.replace('`', "'"),
),
SearchErrorKind::InvalidWholeNumber { provided, context } => i18n
SearchErrorKind::InvalidWholeNumber { provided, context } => tr
.search_invalid_whole_number(
context.replace('`', "'"),
provided.replace('`', "'"),
),
SearchErrorKind::InvalidPositiveWholeNumber { provided, context } => i18n
SearchErrorKind::InvalidPositiveWholeNumber { provided, context } => tr
.search_invalid_positive_whole_number(
context.replace('`', "'"),
provided.replace('`', "'"),
),
SearchErrorKind::InvalidNegativeWholeNumber { provided, context } => i18n
SearchErrorKind::InvalidNegativeWholeNumber { provided, context } => tr
.search_invalid_negative_whole_number(
context.replace('`', "'"),
provided.replace('`', "'"),
),
SearchErrorKind::InvalidAnswerButton { provided, context } => i18n
SearchErrorKind::InvalidAnswerButton { provided, context } => tr
.search_invalid_answer_button(
context.replace('`', "'"),
provided.replace('`', "'"),
),
};
i18n.search_invalid_search(reason).into()
tr.search_invalid_search(reason).into()
}
AnkiError::InvalidInput { info } => {
if info.is_empty() {
i18n.errors_invalid_input_empty().into()
tr.errors_invalid_input_empty().into()
} else {
i18n.errors_invalid_input_details(info.as_str()).into()
tr.errors_invalid_input_details(info.as_str()).into()
}
}
AnkiError::ParseNumError => i18n.errors_parse_number_fail().into(),
AnkiError::DeckIsFiltered => i18n.errors_filtered_parent_deck().into(),
AnkiError::FilteredDeckEmpty => i18n.decks_filtered_deck_search_empty().into(),
AnkiError::ParseNumError => tr.errors_parse_number_fail().into(),
AnkiError::DeckIsFiltered => tr.errors_filtered_parent_deck().into(),
AnkiError::FilteredDeckEmpty => tr.decks_filtered_deck_search_empty().into(),
_ => format!("{:?}", self),
}
}

View File

@ -89,7 +89,7 @@ where
pub fn summarize_output(&self, output: &mut MediaCheckOutput) -> String {
let mut buf = String::new();
let i = &self.ctx.i18n;
let i = &self.ctx.tr;
// top summary area
if output.trash_count > 0 {
@ -532,9 +532,9 @@ pub(crate) mod test {
let mgr = MediaManager::new(&media_dir, media_db.clone())?;
let log = log::terminal();
let i18n = I18n::template_only();
let tr = I18n::template_only();
let col = open_collection(col_path, media_dir, media_db, false, i18n, log)?;
let col = open_collection(col_path, media_dir, media_db, false, tr, log)?;
Ok((dir, mgr, col))
}

View File

@ -73,7 +73,7 @@ impl Collection {
write!(
buf,
"<div><b>{}</b></div><ol>",
self.i18n.empty_cards_for_note_type(nt.name.clone())
self.tr.empty_cards_for_note_type(nt.name.clone())
)
.unwrap();
@ -95,7 +95,7 @@ impl Collection {
// "Cloze 1, 3"
NoteTypeKind::Cloze => format!(
"{} {}",
self.i18n.notetypes_cloze_name(),
self.tr.notetypes_cloze_name(),
note.empty
.iter()
.map(|(ord, _)| (ord + 1).to_string())
@ -113,7 +113,7 @@ impl Collection {
"<li class={}>[anki:nid:{}] {}</li>",
class,
note.nid,
self.i18n.empty_cards_count_line(
self.tr.empty_cards_count_line(
note.empty.len(),
note.current_count,
templates

View File

@ -491,7 +491,7 @@ impl Collection {
col.storage.remove_notetype(ntid)?;
let all = col.storage.get_all_notetype_names()?;
if all.is_empty() {
let mut nt = all_stock_notetypes(&col.i18n).remove(0);
let mut nt = all_stock_notetypes(&col.tr).remove(0);
col.add_notetype_inner(&mut nt, col.usn()?)?;
col.set_current_notetype_id(nt.id)
} else {

View File

@ -56,7 +56,7 @@ impl Collection {
.ok_or_else(|| AnkiError::invalid_input("no such notetype"))?;
if fill_empty {
fill_empty_fields(note, &template.config.q_format, &nt, &self.i18n);
fill_empty_fields(note, &template.config.q_format, &nt, &self.tr);
}
self.render_card(note, &card, &nt, template, false)
@ -116,7 +116,7 @@ impl Collection {
&field_map,
card.template_idx,
nt.is_cloze(),
&self.i18n,
&self.tr,
)?;
Ok(RenderCardOutput { qnodes, anodes })
}
@ -165,14 +165,14 @@ fn flag_name(n: u8) -> &'static str {
}
}
fn fill_empty_fields(note: &mut Note, qfmt: &str, nt: &NoteType, i18n: &I18n) {
fn fill_empty_fields(note: &mut Note, qfmt: &str, nt: &NoteType, tr: &I18n) {
if let Ok(tmpl) = ParsedTemplate::from_text(qfmt) {
let cloze_fields = tmpl.cloze_fields();
for (val, field) in note.fields_mut().iter_mut().zip(nt.fields.iter()) {
if field_is_empty(val) {
if cloze_fields.contains(&field.name.as_str()) {
*val = i18n.card_templates_sample_cloze().into();
*val = tr.card_templates_sample_cloze().into();
} else {
*val = format!("({})", field.name);
}

View File

@ -14,8 +14,8 @@ use crate::{
use crate::backend_proto::stock_note_type::Kind;
impl SqliteStorage {
pub(crate) fn add_stock_notetypes(&self, i18n: &I18n) -> Result<()> {
for (idx, mut nt) in all_stock_notetypes(i18n).into_iter().enumerate() {
pub(crate) fn add_stock_notetypes(&self, tr: &I18n) -> Result<()> {
for (idx, mut nt) in all_stock_notetypes(tr).into_iter().enumerate() {
self.add_new_notetype(&mut nt)?;
if idx == Kind::Basic as usize {
self.set_config_entry(&ConfigEntry::boxed(
@ -31,13 +31,13 @@ impl SqliteStorage {
}
// if changing this, make sure to update StockNoteType enum
pub fn all_stock_notetypes(i18n: &I18n) -> Vec<NoteType> {
pub fn all_stock_notetypes(tr: &I18n) -> Vec<NoteType> {
vec![
basic(i18n),
basic_forward_reverse(i18n),
basic_optional_reverse(i18n),
basic_typing(i18n),
cloze(i18n),
basic(tr),
basic_forward_reverse(tr),
basic_optional_reverse(tr),
basic_typing(tr),
cloze(tr),
]
}
@ -46,17 +46,17 @@ fn fieldref<S: AsRef<str>>(name: S) -> String {
format!("{{{{{}}}}}", name.as_ref())
}
pub(crate) fn basic(i18n: &I18n) -> NoteType {
pub(crate) fn basic(tr: &I18n) -> NoteType {
let mut nt = NoteType {
name: i18n.notetypes_basic_name().into(),
name: tr.notetypes_basic_name().into(),
..Default::default()
};
let front = i18n.notetypes_front_field();
let back = i18n.notetypes_back_field();
let front = tr.notetypes_front_field();
let back = tr.notetypes_back_field();
nt.add_field(front.as_ref());
nt.add_field(back.as_ref());
nt.add_template(
i18n.notetypes_card_1_name(),
tr.notetypes_card_1_name(),
fieldref(front),
format!(
"{}\n\n<hr id=answer>\n\n{}",
@ -68,11 +68,11 @@ pub(crate) fn basic(i18n: &I18n) -> NoteType {
nt
}
pub(crate) fn basic_typing(i18n: &I18n) -> NoteType {
let mut nt = basic(i18n);
nt.name = i18n.notetypes_basic_type_answer_name().into();
let front = i18n.notetypes_front_field();
let back = i18n.notetypes_back_field();
pub(crate) fn basic_typing(tr: &I18n) -> NoteType {
let mut nt = basic(tr);
nt.name = tr.notetypes_basic_type_answer_name().into();
let front = tr.notetypes_front_field();
let back = tr.notetypes_back_field();
let tmpl = &mut nt.templates[0].config;
tmpl.q_format = format!("{}\n\n{{{{type:{}}}}}", fieldref(front.as_ref()), back);
tmpl.a_format = format!(
@ -84,13 +84,13 @@ pub(crate) fn basic_typing(i18n: &I18n) -> NoteType {
nt
}
pub(crate) fn basic_forward_reverse(i18n: &I18n) -> NoteType {
let mut nt = basic(i18n);
nt.name = i18n.notetypes_basic_reversed_name().into();
let front = i18n.notetypes_front_field();
let back = i18n.notetypes_back_field();
pub(crate) fn basic_forward_reverse(tr: &I18n) -> NoteType {
let mut nt = basic(tr);
nt.name = tr.notetypes_basic_reversed_name().into();
let front = tr.notetypes_front_field();
let back = tr.notetypes_back_field();
nt.add_template(
i18n.notetypes_card_2_name(),
tr.notetypes_card_2_name(),
fieldref(back),
format!(
"{}\n\n<hr id=answer>\n\n{}",
@ -102,10 +102,10 @@ pub(crate) fn basic_forward_reverse(i18n: &I18n) -> NoteType {
nt
}
pub(crate) fn basic_optional_reverse(i18n: &I18n) -> NoteType {
let mut nt = basic_forward_reverse(i18n);
nt.name = i18n.notetypes_basic_optional_reversed_name().into();
let addrev = i18n.notetypes_add_reverse_field();
pub(crate) fn basic_optional_reverse(tr: &I18n) -> NoteType {
let mut nt = basic_forward_reverse(tr);
nt.name = tr.notetypes_basic_optional_reversed_name().into();
let addrev = tr.notetypes_add_reverse_field();
nt.add_field(addrev.as_ref());
let tmpl = &mut nt.templates[1].config;
tmpl.q_format = format!("{{{{#{}}}}}{}{{{{/{}}}}}", addrev, tmpl.q_format, addrev);
@ -113,14 +113,14 @@ pub(crate) fn basic_optional_reverse(i18n: &I18n) -> NoteType {
nt
}
pub(crate) fn cloze(i18n: &I18n) -> NoteType {
pub(crate) fn cloze(tr: &I18n) -> NoteType {
let mut nt = NoteType {
name: i18n.notetypes_cloze_name().into(),
name: tr.notetypes_cloze_name().into(),
..Default::default()
};
let text = i18n.notetypes_text_field();
let text = tr.notetypes_text_field();
nt.add_field(text.as_ref());
let back_extra = i18n.notetypes_back_extra_field();
let back_extra = tr.notetypes_back_extra_field();
nt.add_field(back_extra.as_ref());
let qfmt = format!("{{{{cloze:{}}}}}", text);
let afmt = format!("{}<br>\n{{{{{}}}}}", qfmt, back_extra);

View File

@ -36,36 +36,36 @@ pub enum Op {
}
impl Op {
pub fn describe(self, i18n: &I18n) -> String {
pub fn describe(self, tr: &I18n) -> String {
match self {
Op::AddDeck => i18n.undo_add_deck(),
Op::AddNote => i18n.undo_add_note(),
Op::AnswerCard => i18n.undo_answer_card(),
Op::Bury => i18n.studying_bury(),
Op::RemoveDeck => i18n.decks_delete_deck(),
Op::RemoveNote => i18n.studying_delete_note(),
Op::RenameDeck => i18n.actions_rename_deck(),
Op::ScheduleAsNew => i18n.undo_forget_card(),
Op::SetDueDate => i18n.actions_set_due_date(),
Op::Suspend => i18n.studying_suspend(),
Op::UnburyUnsuspend => i18n.undo_unbury_unsuspend(),
Op::UpdateCard => i18n.undo_update_card(),
Op::UpdateDeck => i18n.undo_update_deck(),
Op::UpdateNote => i18n.undo_update_note(),
Op::UpdatePreferences => i18n.preferences_preferences(),
Op::UpdateTag => i18n.undo_update_tag(),
Op::SetDeck => i18n.browsing_change_deck(),
Op::SetFlag => i18n.undo_set_flag(),
Op::FindAndReplace => i18n.browsing_find_and_replace(),
Op::ClearUnusedTags => i18n.browsing_clear_unused_tags(),
Op::SortCards => i18n.browsing_reschedule(),
Op::RenameTag => i18n.actions_rename_tag(),
Op::RemoveTag => i18n.actions_remove_tag(),
Op::ReparentTag => i18n.actions_rename_tag(),
Op::ReparentDeck => i18n.actions_rename_deck(),
Op::BuildFilteredDeck => i18n.undo_build_filtered_deck(),
Op::RebuildFilteredDeck => i18n.undo_build_filtered_deck(),
Op::EmptyFilteredDeck => i18n.studying_empty(),
Op::AddDeck => tr.undo_add_deck(),
Op::AddNote => tr.undo_add_note(),
Op::AnswerCard => tr.undo_answer_card(),
Op::Bury => tr.studying_bury(),
Op::RemoveDeck => tr.decks_delete_deck(),
Op::RemoveNote => tr.studying_delete_note(),
Op::RenameDeck => tr.actions_rename_deck(),
Op::ScheduleAsNew => tr.undo_forget_card(),
Op::SetDueDate => tr.actions_set_due_date(),
Op::Suspend => tr.studying_suspend(),
Op::UnburyUnsuspend => tr.undo_unbury_unsuspend(),
Op::UpdateCard => tr.undo_update_card(),
Op::UpdateDeck => tr.undo_update_deck(),
Op::UpdateNote => tr.undo_update_note(),
Op::UpdatePreferences => tr.preferences_preferences(),
Op::UpdateTag => tr.undo_update_tag(),
Op::SetDeck => tr.browsing_change_deck(),
Op::SetFlag => tr.undo_set_flag(),
Op::FindAndReplace => tr.browsing_find_and_replace(),
Op::ClearUnusedTags => tr.browsing_clear_unused_tags(),
Op::SortCards => tr.browsing_reschedule(),
Op::RenameTag => tr.actions_rename_tag(),
Op::RemoveTag => tr.actions_remove_tag(),
Op::ReparentTag => tr.actions_rename_tag(),
Op::ReparentDeck => tr.actions_rename_deck(),
Op::BuildFilteredDeck => tr.undo_build_filtered_deck(),
Op::RebuildFilteredDeck => tr.undo_build_filtered_deck(),
Op::EmptyFilteredDeck => tr.studying_empty(),
}
.into()
}

View File

@ -207,7 +207,7 @@ impl Collection {
.maybe_as_days(secs_until_rollover)
.as_seconds(),
collapse_time,
&self.i18n,
&self.tr,
),
answer_button_time_collapsible(
choices
@ -216,7 +216,7 @@ impl Collection {
.maybe_as_days(secs_until_rollover)
.as_seconds(),
collapse_time,
&self.i18n,
&self.tr,
),
answer_button_time_collapsible(
choices
@ -225,7 +225,7 @@ impl Collection {
.maybe_as_days(secs_until_rollover)
.as_seconds(),
collapse_time,
&self.i18n,
&self.tr,
),
answer_button_time_collapsible(
choices
@ -234,7 +234,7 @@ impl Collection {
.maybe_as_days(secs_until_rollover)
.as_seconds(),
collapse_time,
&self.i18n,
&self.tr,
),
])
}

View File

@ -4,26 +4,26 @@
use crate::i18n::I18n;
/// Short string like '4d' to place above answer buttons.
pub fn answer_button_time(seconds: f32, i18n: &I18n) -> String {
pub fn answer_button_time(seconds: f32, tr: &I18n) -> String {
let span = Timespan::from_secs(seconds).natural_span();
let amount = span.as_rounded_unit_for_answer_buttons();
match span.unit() {
TimespanUnit::Seconds => i18n.scheduling_answer_button_time_seconds(amount),
TimespanUnit::Minutes => i18n.scheduling_answer_button_time_minutes(amount),
TimespanUnit::Hours => i18n.scheduling_answer_button_time_hours(amount),
TimespanUnit::Days => i18n.scheduling_answer_button_time_days(amount),
TimespanUnit::Months => i18n.scheduling_answer_button_time_months(amount),
TimespanUnit::Years => i18n.scheduling_answer_button_time_years(amount),
TimespanUnit::Seconds => tr.scheduling_answer_button_time_seconds(amount),
TimespanUnit::Minutes => tr.scheduling_answer_button_time_minutes(amount),
TimespanUnit::Hours => tr.scheduling_answer_button_time_hours(amount),
TimespanUnit::Days => tr.scheduling_answer_button_time_days(amount),
TimespanUnit::Months => tr.scheduling_answer_button_time_months(amount),
TimespanUnit::Years => tr.scheduling_answer_button_time_years(amount),
}
.into()
}
/// Short string like '4d' to place above answer buttons.
/// Times within the collapse time are represented like '<10m'
pub fn answer_button_time_collapsible(seconds: u32, collapse_secs: u32, i18n: &I18n) -> String {
let string = answer_button_time(seconds as f32, i18n);
pub fn answer_button_time_collapsible(seconds: u32, collapse_secs: u32, tr: &I18n) -> String {
let string = answer_button_time(seconds as f32, tr);
if seconds == 0 {
i18n.scheduling_end().into()
tr.scheduling_end().into()
} else if seconds < collapse_secs {
format!("<{}", string)
} else {
@ -35,7 +35,7 @@ pub fn answer_button_time_collapsible(seconds: u32, collapse_secs: u32, i18n: &I
/// If precise is true, show to two decimal places, eg
/// eg 70 seconds -> "1.17 minutes"
/// If false, seconds and days are shown without decimals.
pub fn time_span(seconds: f32, i18n: &I18n, precise: bool) -> String {
pub fn time_span(seconds: f32, tr: &I18n, precise: bool) -> String {
let span = Timespan::from_secs(seconds).natural_span();
let amount = if precise {
span.as_unit()
@ -43,12 +43,12 @@ pub fn time_span(seconds: f32, i18n: &I18n, precise: bool) -> String {
span.as_rounded_unit()
};
match span.unit() {
TimespanUnit::Seconds => i18n.scheduling_time_span_seconds(amount),
TimespanUnit::Minutes => i18n.scheduling_time_span_minutes(amount),
TimespanUnit::Hours => i18n.scheduling_time_span_hours(amount),
TimespanUnit::Days => i18n.scheduling_time_span_days(amount),
TimespanUnit::Months => i18n.scheduling_time_span_months(amount),
TimespanUnit::Years => i18n.scheduling_time_span_years(amount),
TimespanUnit::Seconds => tr.scheduling_time_span_seconds(amount),
TimespanUnit::Minutes => tr.scheduling_time_span_minutes(amount),
TimespanUnit::Hours => tr.scheduling_time_span_hours(amount),
TimespanUnit::Days => tr.scheduling_time_span_days(amount),
TimespanUnit::Months => tr.scheduling_time_span_months(amount),
TimespanUnit::Years => tr.scheduling_time_span_years(amount),
}
.into()
}
@ -171,20 +171,20 @@ mod test {
#[test]
fn answer_buttons() {
let i18n = I18n::template_only();
assert_eq!(answer_button_time(30.0, &i18n), "30s");
assert_eq!(answer_button_time(70.0, &i18n), "1m");
assert_eq!(answer_button_time(1.1 * MONTH, &i18n), "1.1mo");
let tr = I18n::template_only();
assert_eq!(answer_button_time(30.0, &tr), "30s");
assert_eq!(answer_button_time(70.0, &tr), "1m");
assert_eq!(answer_button_time(1.1 * MONTH, &tr), "1.1mo");
}
#[test]
fn time_spans() {
let i18n = I18n::template_only();
assert_eq!(time_span(1.0, &i18n, false), "1 second");
assert_eq!(time_span(30.3, &i18n, false), "30 seconds");
assert_eq!(time_span(30.3, &i18n, true), "30.3 seconds");
assert_eq!(time_span(90.0, &i18n, false), "1.5 minutes");
assert_eq!(time_span(45.0 * 86_400.0, &i18n, false), "1.5 months");
assert_eq!(time_span(365.0 * 86_400.0 * 1.5, &i18n, false), "1.5 years");
let tr = I18n::template_only();
assert_eq!(time_span(1.0, &tr, false), "1 second");
assert_eq!(time_span(30.3, &tr, false), "30 seconds");
assert_eq!(time_span(30.3, &tr, true), "30.3 seconds");
assert_eq!(time_span(90.0, &tr, false), "1.5 minutes");
assert_eq!(time_span(45.0 * 86_400.0, &tr, false), "1.5 months");
assert_eq!(time_span(365.0 * 86_400.0 * 1.5, &tr, false), "1.5 years");
}
}

View File

@ -610,13 +610,13 @@ mod test {
let col_path = dir.path().join("col.anki2");
fs::write(&col_path, MEDIACHECK_ANKI2).unwrap();
let i18n = I18n::template_only();
let tr = I18n::template_only();
let mut col = open_collection(
&col_path,
&PathBuf::new(),
&PathBuf::new(),
false,
i18n,
tr,
log::terminal(),
)
.unwrap();

View File

@ -127,74 +127,71 @@ impl Collection {
}
fn card_stats_to_string(&mut self, cs: CardStats) -> Result<String> {
let i18n = &self.i18n;
let tr = &self.tr;
let mut stats = vec![(i18n.card_stats_added().into(), cs.added.date_string())];
let mut stats = vec![(tr.card_stats_added().into(), cs.added.date_string())];
if let Some(first) = cs.first_review {
stats.push((i18n.card_stats_first_review().into(), first.date_string()))
stats.push((tr.card_stats_first_review().into(), first.date_string()))
}
if let Some(last) = cs.latest_review {
stats.push((i18n.card_stats_latest_review().into(), last.date_string()))
stats.push((tr.card_stats_latest_review().into(), last.date_string()))
}
match cs.due {
Due::Time(secs) => {
stats.push((i18n.statistics_due_date().into(), secs.date_string()));
stats.push((tr.statistics_due_date().into(), secs.date_string()));
}
Due::Position(pos) => {
stats.push((i18n.card_stats_new_card_position().into(), pos.to_string()));
stats.push((tr.card_stats_new_card_position().into(), pos.to_string()));
}
Due::Unknown => {}
};
if cs.interval_secs > 0 {
stats.push((
i18n.card_stats_interval().into(),
time_span(cs.interval_secs as f32, i18n, true),
tr.card_stats_interval().into(),
time_span(cs.interval_secs as f32, tr, true),
));
}
if cs.ease > 0 {
stats.push((i18n.card_stats_ease().into(), format!("{}%", cs.ease)));
stats.push((tr.card_stats_ease().into(), format!("{}%", cs.ease)));
}
stats.push((
i18n.card_stats_review_count().into(),
cs.reviews.to_string(),
));
stats.push((i18n.card_stats_lapse_count().into(), cs.lapses.to_string()));
stats.push((tr.card_stats_review_count().into(), cs.reviews.to_string()));
stats.push((tr.card_stats_lapse_count().into(), cs.lapses.to_string()));
if cs.total_secs > 0.0 {
stats.push((
i18n.card_stats_average_time().into(),
time_span(cs.average_secs, i18n, true),
tr.card_stats_average_time().into(),
time_span(cs.average_secs, tr, true),
));
stats.push((
i18n.card_stats_total_time().into(),
time_span(cs.total_secs, i18n, true),
tr.card_stats_total_time().into(),
time_span(cs.total_secs, tr, true),
));
}
stats.push((i18n.card_stats_card_template().into(), cs.card_type));
stats.push((i18n.card_stats_note_type().into(), cs.note_type));
stats.push((i18n.card_stats_deck_name().into(), cs.deck));
stats.push((i18n.card_stats_card_id().into(), cs.cid.0.to_string()));
stats.push((i18n.card_stats_note_id().into(), cs.nid.0.to_string()));
stats.push((tr.card_stats_card_template().into(), cs.card_type));
stats.push((tr.card_stats_note_type().into(), cs.note_type));
stats.push((tr.card_stats_deck_name().into(), cs.deck));
stats.push((tr.card_stats_card_id().into(), cs.cid.0.to_string()));
stats.push((tr.card_stats_note_id().into(), cs.nid.0.to_string()));
let revlog = cs
.revlog
.into_iter()
.rev()
.map(|e| revlog_to_text(e, i18n))
.map(|e| revlog_to_text(e, tr))
.collect();
let revlog_titles = RevlogText {
time: i18n.card_stats_review_log_date().into(),
kind: i18n.card_stats_review_log_type().into(),
time: tr.card_stats_review_log_date().into(),
kind: tr.card_stats_review_log_type().into(),
kind_class: "".to_string(),
rating: i18n.card_stats_review_log_rating().into(),
interval: i18n.card_stats_interval().into(),
ease: i18n.card_stats_ease().into(),
rating: tr.card_stats_review_log_rating().into(),
interval: tr.card_stats_interval().into(),
ease: tr.card_stats_ease().into(),
rating_class: "".to_string(),
taken_secs: i18n.card_stats_review_log_time_taken().into(),
taken_secs: tr.card_stats_review_log_time_taken().into(),
};
Ok(CardStatsTemplate {
@ -207,15 +204,15 @@ impl Collection {
}
}
fn revlog_to_text(e: RevlogEntry, i18n: &I18n) -> RevlogText {
fn revlog_to_text(e: RevlogEntry, tr: &I18n) -> RevlogText {
let dt = Local.timestamp(e.id.as_secs().0, 0);
let time = dt.format("<b>%Y-%m-%d</b> @ %H:%M").to_string();
let kind = match e.review_kind {
RevlogReviewKind::Learning => i18n.card_stats_review_log_type_learn().into(),
RevlogReviewKind::Review => i18n.card_stats_review_log_type_review().into(),
RevlogReviewKind::Relearning => i18n.card_stats_review_log_type_relearn().into(),
RevlogReviewKind::EarlyReview => i18n.card_stats_review_log_type_filtered().into(),
RevlogReviewKind::Manual => i18n.card_stats_review_log_type_manual().into(),
RevlogReviewKind::Learning => tr.card_stats_review_log_type_learn().into(),
RevlogReviewKind::Review => tr.card_stats_review_log_type_review().into(),
RevlogReviewKind::Relearning => tr.card_stats_review_log_type_relearn().into(),
RevlogReviewKind::EarlyReview => tr.card_stats_review_log_type_filtered().into(),
RevlogReviewKind::Manual => tr.card_stats_review_log_type_manual().into(),
};
let kind_class = match e.review_kind {
RevlogReviewKind::Learning => String::from("revlog-learn"),
@ -229,7 +226,7 @@ fn revlog_to_text(e: RevlogEntry, i18n: &I18n) -> RevlogText {
String::from("")
} else {
let interval_secs = e.interval_secs();
time_span(interval_secs as f32, i18n, true)
time_span(interval_secs as f32, tr, true)
};
let ease = if e.ease_factor > 0 {
format!("{}%", e.ease_factor / 10)
@ -241,7 +238,7 @@ fn revlog_to_text(e: RevlogEntry, i18n: &I18n) -> RevlogText {
} else {
"".to_string()
};
let taken_secs = i18n
let taken_secs = tr
.statistics_seconds_taken((e.taken_millis / 1000) as i32)
.into();

View File

@ -3,7 +3,7 @@
use crate::{i18n::I18n, prelude::*, scheduler::timespan::Timespan};
pub fn studied_today(cards: u32, secs: f32, i18n: &I18n) -> String {
pub fn studied_today(cards: u32, secs: f32, tr: &I18n) -> String {
let span = Timespan::from_secs(secs).natural_span();
let amount = span.as_unit();
let unit = span.unit().as_str();
@ -12,7 +12,7 @@ pub fn studied_today(cards: u32, secs: f32, i18n: &I18n) -> String {
} else {
0.0
};
i18n.statistics_studied_today(unit, secs_per_card, amount, cards)
tr.statistics_studied_today(unit, secs_per_card, amount, cards)
.into()
}
@ -20,7 +20,7 @@ impl Collection {
pub fn studied_today(&mut self) -> Result<String> {
let timing = self.timing_today()?;
let today = self.storage.studied_today(timing.next_day_at)?;
Ok(studied_today(today.cards, today.seconds as f32, &self.i18n))
Ok(studied_today(today.cards, today.seconds as f32, &self.tr))
}
}
@ -32,9 +32,9 @@ mod test {
#[test]
fn today() {
// temporary test of fluent term handling
let i18n = I18n::template_only();
let tr = I18n::template_only();
assert_eq!(
&studied_today(3, 13.0, &i18n).replace("\n", " "),
&studied_today(3, 13.0, &tr).replace("\n", " "),
"Studied 3 cards in 13 seconds today (4.33s/card)"
);
}

View File

@ -507,8 +507,8 @@ mod test {
#[test]
fn add_card() {
let i18n = I18n::template_only();
let storage = SqliteStorage::open_or_create(Path::new(":memory:"), &i18n, false).unwrap();
let tr = I18n::template_only();
let storage = SqliteStorage::open_or_create(Path::new(":memory:"), &tr, false).unwrap();
let mut card = Card::default();
storage.add_card(&mut card).unwrap();
let id1 = card.id;

View File

@ -335,11 +335,11 @@ impl SqliteStorage {
// Upgrading/downgrading/legacy
pub(super) fn add_default_deck(&self, i18n: &I18n) -> Result<()> {
pub(super) fn add_default_deck(&self, tr: &I18n) -> Result<()> {
let mut deck = Deck::new_normal();
deck.id.0 = 1;
// fixme: separate key
deck.name = i18n.deck_config_default_name().into();
deck.name = tr.deck_config_default_name().into();
self.add_or_update_deck_with_existing_id(&deck)
}

View File

@ -113,10 +113,10 @@ impl SqliteStorage {
// Creating/upgrading/downgrading
pub(super) fn add_default_deck_config(&self, i18n: &I18n) -> Result<()> {
pub(super) fn add_default_deck_config(&self, tr: &I18n) -> Result<()> {
let mut conf = DeckConf::default();
conf.id.0 = 1;
conf.name = i18n.deck_config_default_name().into();
conf.name = tr.deck_config_default_name().into();
self.add_deck_conf(&mut conf)
}

View File

@ -136,7 +136,7 @@ fn trace(s: &str) {
}
impl SqliteStorage {
pub(crate) fn open_or_create(path: &Path, i18n: &I18n, server: bool) -> Result<Self> {
pub(crate) fn open_or_create(path: &Path, tr: &I18n, server: bool) -> Result<Self> {
let db = open_or_create_collection_db(path)?;
let (create, ver) = schema_version(&db)?;
@ -184,9 +184,9 @@ impl SqliteStorage {
}
if create {
storage.add_default_deck_config(i18n)?;
storage.add_default_deck(i18n)?;
storage.add_stock_notetypes(i18n)?;
storage.add_default_deck_config(tr)?;
storage.add_default_deck(tr)?;
storage.add_stock_notetypes(tr)?;
}
if create || upgrade {

View File

@ -1235,8 +1235,8 @@ mod test {
fn open_col(dir: &Path, server: bool, fname: &str) -> Result<Collection> {
let path = dir.join(fname);
let i18n = I18n::template_only();
open_collection(path, "".into(), "".into(), server, i18n, log::terminal())
let tr = I18n::template_only();
open_collection(path, "".into(), "".into(), server, tr, log::terminal())
}
#[async_trait(?Send)]
@ -1390,7 +1390,7 @@ mod test {
col1.add_or_update_deck(&mut deck)?;
// and a new notetype
let mut nt = all_stock_notetypes(&col1.i18n).remove(0);
let mut nt = all_stock_notetypes(&col1.tr).remove(0);
nt.name = "new".into();
col1.add_notetype(&mut nt)?;
@ -1550,7 +1550,10 @@ mod test {
// removing things like a notetype forces a full sync
col2.remove_notetype(ntid)?;
let out = ctx.normal_sync(&mut col2).await;
assert!(matches!(out.required, SyncActionRequired::FullSyncRequired { .. }));
assert!(matches!(
out.required,
SyncActionRequired::FullSyncRequired { .. }
));
Ok(())
}

View File

@ -246,14 +246,14 @@ fn parse_inner<'a, I: Iterator<Item = TemplateResult<Token<'a>>>>(
}
}
fn template_error_to_anki_error(err: TemplateError, q_side: bool, i18n: &I18n) -> AnkiError {
fn template_error_to_anki_error(err: TemplateError, q_side: bool, tr: &I18n) -> AnkiError {
let header = if q_side {
i18n.card_template_rendering_front_side_problem()
tr.card_template_rendering_front_side_problem()
} else {
i18n.card_template_rendering_back_side_problem()
tr.card_template_rendering_back_side_problem()
};
let details = localized_template_error(i18n, err);
let more_info = i18n.card_template_rendering_more_info();
let details = localized_template_error(tr, err);
let more_info = tr.card_template_rendering_more_info();
let info = format!(
"{}<br>{}<br><a href='{}'>{}</a>",
header, details, TEMPLATE_ERROR_LINK, more_info
@ -262,31 +262,31 @@ fn template_error_to_anki_error(err: TemplateError, q_side: bool, i18n: &I18n) -
AnkiError::TemplateError { info }
}
fn localized_template_error(i18n: &I18n, err: TemplateError) -> String {
fn localized_template_error(tr: &I18n, err: TemplateError) -> String {
match err {
TemplateError::NoClosingBrackets(tag) => i18n
TemplateError::NoClosingBrackets(tag) => tr
.card_template_rendering_no_closing_brackets("}}", tag)
.into(),
TemplateError::ConditionalNotClosed(tag) => i18n
TemplateError::ConditionalNotClosed(tag) => tr
.card_template_rendering_conditional_not_closed(format!("{{{{/{}}}}}", tag))
.into(),
TemplateError::ConditionalNotOpen {
closed,
currently_open,
} => if let Some(open) = currently_open {
i18n.card_template_rendering_wrong_conditional_closed(
tr.card_template_rendering_wrong_conditional_closed(
format!("{{{{/{}}}}}", closed),
format!("{{{{/{}}}}}", open),
)
} else {
i18n.card_template_rendering_conditional_not_open(
tr.card_template_rendering_conditional_not_open(
format!("{{{{/{}}}}}", closed),
format!("{{{{#{}}}}}", closed),
format!("{{{{^{}}}}}", closed),
)
}
.into(),
TemplateError::FieldNotFound { field, filters } => i18n
TemplateError::FieldNotFound { field, filters } => tr
.card_template_rendering_no_such_field(format!("{{{{{}{}}}}}", filters, field), field)
.into(),
}
@ -531,7 +531,7 @@ pub fn render_card(
field_map: &HashMap<&str, Cow<str>>,
card_ord: u16,
is_cloze: bool,
i18n: &I18n,
tr: &I18n,
) -> Result<(Vec<RenderedNode>, Vec<RenderedNode>)> {
// prepare context
let mut context = RenderContext {
@ -544,22 +544,22 @@ pub fn render_card(
// question side
let (mut qnodes, qtmpl) = ParsedTemplate::from_text(qfmt)
.and_then(|tmpl| Ok((tmpl.render(&context)?, tmpl)))
.map_err(|e| template_error_to_anki_error(e, true, i18n))?;
.map_err(|e| template_error_to_anki_error(e, true, tr))?;
// check if the front side was empty
let empty_message = if is_cloze && cloze_is_empty(field_map, card_ord) {
Some(format!(
"<div>{}<br><a href='{}'>{}</a></div>",
i18n.card_template_rendering_missing_cloze(card_ord + 1),
tr.card_template_rendering_missing_cloze(card_ord + 1),
TEMPLATE_BLANK_CLOZE_LINK,
i18n.card_template_rendering_more_info()
tr.card_template_rendering_more_info()
))
} else if !is_cloze && !qtmpl.renders_with_fields(context.nonempty_fields) {
Some(format!(
"<div>{}<br><a href='{}'>{}</a></div>",
i18n.card_template_rendering_empty_front(),
tr.card_template_rendering_empty_front(),
TEMPLATE_BLANK_LINK,
i18n.card_template_rendering_more_info()
tr.card_template_rendering_more_info()
))
} else {
None
@ -573,7 +573,7 @@ pub fn render_card(
context.question_side = false;
let anodes = ParsedTemplate::from_text(afmt)
.and_then(|tmpl| tmpl.render(&context))
.map_err(|e| template_error_to_anki_error(e, false, i18n))?;
.map_err(|e| template_error_to_anki_error(e, false, tr))?;
Ok((qnodes, anodes))
}
@ -1113,10 +1113,10 @@ mod test {
.map(|r| (r.0, r.1.into()))
.collect();
let i18n = I18n::template_only();
let tr = I18n::template_only();
use crate::template::RenderedNode as FN;
let qnodes = super::render_card("test{{E}}", "", &map, 1, false, &i18n)
let qnodes = super::render_card("test{{E}}", "", &map, 1, false, &tr)
.unwrap()
.0;
assert_eq!(