add non-abbreviated timespan translation; update existing short=True calls

- drop the '5m3s' special casing done in the card stats screen, and
just use decimals
- change alignment of the review log so that the non-abbreviated
spans are easier to read
This commit is contained in:
Damien Elmes 2020-02-20 16:07:28 +10:00
parent 99c07cfdcb
commit 2fc15d0d3a
8 changed files with 69 additions and 51 deletions

View file

@ -308,7 +308,8 @@ message TranslateArgValue {
message FormatTimeSpanIn { message FormatTimeSpanIn {
enum Context { enum Context {
ANSWER_BUTTONS = 0; NORMAL = 0;
ANSWER_BUTTONS = 1;
} }
float seconds = 1; float seconds = 1;

View file

@ -345,7 +345,11 @@ class RustBackend:
) )
).translate_string ).translate_string
def format_time_span(self, seconds: float, context: FormatTimeSpanContext) -> str: def format_time_span(
self,
seconds: float,
context: FormatTimeSpanContext = FormatTimeSpanContext.NORMAL,
) -> str:
return self._run_command( return self._run_command(
pb.BackendInput( pb.BackendInput(
format_time_span=pb.FormatTimeSpanIn(seconds=seconds, context=context) format_time_span=pb.FormatTimeSpanIn(seconds=seconds, context=context)

View file

@ -28,8 +28,6 @@ class CardStats:
def report(self) -> str: def report(self) -> str:
c = self.card c = self.card
# pylint: disable=unnecessary-lambda
fmt = lambda x, **kwargs: fmtTimeSpan(x, short=True, **kwargs)
self.txt = "<table width=100%>" self.txt = "<table width=100%>"
self.addLine(_("Added"), self.date(c.id / 1000)) self.addLine(_("Added"), self.date(c.id / 1000))
first = self.col.db.scalar("select min(id) from revlog where cid = ?", c.id) first = self.col.db.scalar("select min(id) from revlog where cid = ?", c.id)
@ -52,7 +50,9 @@ class CardStats:
next, next,
) )
if c.queue == QUEUE_TYPE_REV: if c.queue == QUEUE_TYPE_REV:
self.addLine(_("Interval"), fmt(c.ivl * 86400)) self.addLine(
_("Interval"), self.col.backend.format_time_span(c.ivl * 86400)
)
self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0)) self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0))
self.addLine(_("Reviews"), "%d" % c.reps) self.addLine(_("Reviews"), "%d" % c.reps)
self.addLine(_("Lapses"), "%d" % c.lapses) self.addLine(_("Lapses"), "%d" % c.lapses)
@ -83,13 +83,8 @@ class CardStats:
def date(self, tm) -> str: def date(self, tm) -> str:
return time.strftime("%Y-%m-%d", time.localtime(tm)) return time.strftime("%Y-%m-%d", time.localtime(tm))
def time(self, tm) -> str: def time(self, tm: float) -> str:
s = "" return self.col.backend.format_time_span(tm)
if tm >= 60:
s = fmtTimeSpan((tm / 60) * 60, short=True, point=-1, unit=1)
if tm % 60 != 0 or not s:
s += fmtTimeSpan(tm % 60, point=2 if not s else -1, short=True)
return s
# Collection stats # Collection stats

View file

@ -24,7 +24,7 @@ from html.entities import name2codepoint
from typing import Iterable, Iterator, List, Optional, Tuple, Union from typing import Iterable, Iterator, List, Optional, Tuple, Union
from anki.db import DB from anki.db import DB
from anki.lang import _, ngettext from anki.lang import ngettext
_tmpdir: Optional[str] _tmpdir: Optional[str]
@ -56,28 +56,10 @@ inTimeTable = {
} }
def shortTimeFmt(type: str) -> str:
return {
# T: year is an abbreviation for year. %s is a number of years
"years": _("%sy"),
# T: m is an abbreviation for month. %s is a number of months
"months": _("%smo"),
# T: d is an abbreviation for day. %s is a number of days
"days": _("%sd"),
# T: h is an abbreviation for hour. %s is a number of hours
"hours": _("%sh"),
# T: m is an abbreviation for minute. %s is a number of minutes
"minutes": _("%sm"),
# T: s is an abbreviation for second. %s is a number of seconds
"seconds": _("%ss"),
}[type]
def fmtTimeSpan( def fmtTimeSpan(
time: Union[int, float], time: Union[int, float],
pad: int = 0, pad: int = 0,
point: int = 0, point: int = 0,
short: bool = False,
inTime: bool = False, inTime: bool = False,
unit: int = 99, unit: int = 99,
) -> str: ) -> str:
@ -86,13 +68,10 @@ def fmtTimeSpan(
time = convertSecondsTo(time, type) time = convertSecondsTo(time, type)
if not point: if not point:
time = int(round(time)) time = int(round(time))
if short: if inTime:
fmt = shortTimeFmt(type) fmt = inTimeTable[type](_pluralCount(time, point))
else: else:
if inTime: fmt = timeTable[type](_pluralCount(time, point))
fmt = inTimeTable[type](_pluralCount(time, point))
else:
fmt = timeTable[type](_pluralCount(time, point))
timestr = "%%%(a)d.%(b)df" % {"a": pad, "b": point} timestr = "%%%(a)d.%(b)df" % {"a": pad, "b": point}
return locale.format_string(fmt % timestr, time) return locale.format_string(fmt % timestr, time)

View file

@ -1451,13 +1451,10 @@ border: 1px solid #000; padding: 3px; '>%s</div>"""
if not entries: if not entries:
return "" return ""
s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date") s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date")
s += ("<th align=right>%s</th>" * 5) % ( s += "<th align=right>%s</th>" % _("Type")
_("Type"), s += "<th align=center>%s</th>" % _("Rating")
_("Rating"), s += "<th align=left>%s</th>" % _("Interval")
_("Interval"), s += ("<th align=right>%s</th>" * 2) % (_("Ease"), _("Time"),)
_("Ease"),
_("Time"),
)
cnt = 0 cnt = 0
for (date, ease, ivl, factor, taken, type) in reversed(entries): for (date, ease, ivl, factor, taken, type) in reversed(entries):
cnt += 1 cnt += 1
@ -1485,13 +1482,14 @@ border: 1px solid #000; padding: 3px; '>%s</div>"""
if ivl == 0: if ivl == 0:
ivl = _("0d") ivl = _("0d")
elif ivl > 0: elif ivl > 0:
ivl = fmtTimeSpan(ivl * 86400, short=True) ivl = fmtTimeSpan(ivl * 86400)
else: else:
ivl = cs.time(-ivl) ivl = cs.time(-ivl)
s += ("<td align=right>%s</td>" * 5) % ( s += "<td align=right>%s</td>" % tstr
tstr, s += "<td align=center>%s</td>" % ease
ease, s += "<td align=left>%s</td>" % ivl
ivl,
s += ("<td align=right>%s</td>" * 2) % (
"%d%%" % (factor / 10) if factor else "", "%d%%" % (factor / 10) if factor else "",
cs.time(taken), cs.time(taken),
) + "</tr>" ) + "</tr>"

View file

@ -11,7 +11,7 @@ use crate::media::check::MediaChecker;
use crate::media::sync::MediaSyncProgress; use crate::media::sync::MediaSyncProgress;
use crate::media::MediaManager; use crate::media::MediaManager;
use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today}; use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today};
use crate::sched::timespan::answer_button_time; use crate::sched::timespan::{answer_button_time, time_span};
use crate::template::{ use crate::template::{
render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate, render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate,
RenderedNode, RenderedNode,
@ -406,6 +406,7 @@ impl Backend {
None => return "".to_string(), None => return "".to_string(),
}; };
match context { match context {
pb::format_time_span_in::Context::Normal => time_span(input.seconds, &self.i18n),
pb::format_time_span_in::Context::AnswerButtons => { pb::format_time_span_in::Context::AnswerButtons => {
answer_button_time(input.seconds, &self.i18n) answer_button_time(input.seconds, &self.i18n)
} }

View file

@ -9,3 +9,31 @@ answer-button-time-hours = {$amount}h
answer-button-time-days = {$amount}d answer-button-time-days = {$amount}d
answer-button-time-months = {$amount}mo answer-button-time-months = {$amount}mo
answer-button-time-years = {$amount}y answer-button-time-years = {$amount}y
## A span of time, such as the delay until a card is shown again, the
## amount of time taken to answer a card, and so on.
time-span-seconds = { $amount ->
[one] {$amount} second
*[other] {$amount} seconds
}
time-span-minutes = { $amount ->
[one] {$amount} minute
*[other] {$amount} minutes
}
time-span-hours = { $amount ->
[one] {$amount} hour
*[other] {$amount} hours
}
time-span-days = { $amount ->
[one] {$amount} day
*[other] {$amount} days
}
time-span-months = { $amount ->
[one] {$amount} month
*[other] {$amount} months
}
time-span-years = { $amount ->
[one] {$amount} year
*[other] {$amount} years
}

View file

@ -3,11 +3,12 @@
use crate::i18n::{tr_args, I18n, StringsGroup}; use crate::i18n::{tr_args, I18n, StringsGroup};
/// 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, i18n: &I18n) -> String {
let span = Timespan::from_secs(seconds).natural_span(); let span = Timespan::from_secs(seconds).natural_span();
let amount = match span.unit() { let amount = match span.unit() {
TimespanUnit::Months | TimespanUnit::Years => span.as_unit(), TimespanUnit::Months | TimespanUnit::Years => span.as_unit(),
// we don't show fractional value except for months/years // we don't show fractional values except for months/years
_ => span.as_unit().round(), _ => span.as_unit().round(),
}; };
let unit = span.unit().as_str(); let unit = span.unit().as_str();
@ -16,6 +17,17 @@ pub fn answer_button_time(seconds: f32, i18n: &I18n) -> String {
.trn(&format!("answer-button-time-{}", unit), args) .trn(&format!("answer-button-time-{}", unit), args)
} }
/// Describe the given seconds using the largest appropriate unit
/// eg 70 seconds -> "1.17 minutes"
pub fn time_span(seconds: f32, i18n: &I18n) -> String {
let span = Timespan::from_secs(seconds).natural_span();
let amount = span.as_unit();
let unit = span.unit().as_str();
let args = tr_args!["amount" => amount];
i18n.get(StringsGroup::Scheduling)
.trn(&format!("time-span-{}", unit), args)
}
const SECOND: f32 = 1.0; const SECOND: f32 = 1.0;
const MINUTE: f32 = 60.0 * SECOND; const MINUTE: f32 = 60.0 * SECOND;
const HOUR: f32 = 60.0 * MINUTE; const HOUR: f32 = 60.0 * MINUTE;