move answer button labels into fluent

This commit is contained in:
Damien Elmes 2020-02-20 12:48:46 +10:00
parent 370bb38b8b
commit 232a8625bf
10 changed files with 67 additions and 25 deletions

View file

@ -21,6 +21,7 @@ enum StringsGroup {
NETWORK = 5; NETWORK = 5;
STATISTICS = 6; STATISTICS = 6;
FILTERING = 7; FILTERING = 7;
SCHEDULING = 8;
} }
// 1-15 reserved for future use; 2047 for errors // 1-15 reserved for future use; 2047 for errors

View file

@ -169,3 +169,9 @@ def set_lang(lang: str, locale_dir: str) -> None:
"anki", gettext_dir, languages=[lang], fallback=True "anki", gettext_dir, languages=[lang], fallback=True
) )
locale_folder = locale_dir locale_folder = locale_dir
# strip off unicode isolation markers from a translated string
# for testing purposes
def without_unicode_isolation(s: str) -> str:
return s.replace("\u2068", "").replace("\u2069", "")

View file

@ -12,7 +12,7 @@ from anki import hooks
from anki.cards import Card from anki.cards import Card
from anki.consts import * from anki.consts import *
from anki.lang import _ from anki.lang import _
from anki.utils import fmtTimeSpan, ids2str, intTime from anki.utils import answer_button_time, ids2str, intTime
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried # queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram # revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
@ -1351,11 +1351,11 @@ To study outside of the normal schedule, click the Custom Study button below."""
def nextIvlStr(self, card, ease, short=False): def nextIvlStr(self, card, ease, short=False):
"Return the next interval for CARD as a string." "Return the next interval for CARD as a string."
ivl = self.nextIvl(card, ease) ivl_secs = self.nextIvl(card, ease)
if not ivl: if not ivl_secs:
return _("(end)") return _("(end)")
s = fmtTimeSpan(ivl, short=short) s = answer_button_time(self.col, ivl_secs)
if ivl < self.col.conf["collapseTime"]: if ivl_secs < self.col.conf["collapseTime"]:
s = "<" + s s = "<" + s
return s return s

View file

@ -19,7 +19,7 @@ from anki.cards import Card
from anki.consts import * from anki.consts import *
from anki.lang import _ from anki.lang import _
from anki.rsbackend import SchedTimingToday from anki.rsbackend import SchedTimingToday
from anki.utils import fmtTimeSpan, ids2str, intTime from anki.utils import answer_button_time, ids2str, intTime
# card types: 0=new, 1=lrn, 2=rev, 3=relrn # card types: 0=new, 1=lrn, 2=rev, 3=relrn
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn, # queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
@ -1545,11 +1545,11 @@ To study outside of the normal schedule, click the Custom Study button below."""
def nextIvlStr(self, card: Card, ease: int, short: bool = False) -> str: def nextIvlStr(self, card: Card, ease: int, short: bool = False) -> str:
"Return the next interval for CARD as a string." "Return the next interval for CARD as a string."
ivl = self.nextIvl(card, ease) ivl_secs = self.nextIvl(card, ease)
if not ivl: if not ivl_secs:
return _("(end)") return _("(end)")
s = fmtTimeSpan(ivl, short=short) s = answer_button_time(self.col, ivl_secs)
if ivl < self.col.conf["collapseTime"]: if ivl_secs < self.col.conf["collapseTime"]:
s = "<" + s s = "<" + s
return s return s

View file

@ -1,6 +1,8 @@
# Copyright: Ankitects Pty Ltd and contributors # Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
# some add-ons expect json to be in the utils module # some add-ons expect json to be in the utils module
import json # pylint: disable=unused-import import json # pylint: disable=unused-import
import locale import locale
@ -19,8 +21,10 @@ import traceback
from contextlib import contextmanager from contextlib import contextmanager
from hashlib import sha1 from hashlib import sha1
from html.entities import name2codepoint from html.entities import name2codepoint
from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union from typing import Iterable, Iterator, List, Optional, Tuple, Union
import anki
from anki.backend_pb2 import StringsGroup
from anki.db import DB from anki.db import DB
from anki.lang import _, ngettext from anki.lang import _, ngettext
@ -35,6 +39,22 @@ def intTime(scale: int = 1) -> int:
return int(time.time() * scale) return int(time.time() * scale)
# eg 70 seconds -> (1.16, "minutes")
def seconds_to_appropriate_unit(seconds: int) -> Tuple[float, str]:
unit, _ = optimalPeriod(seconds, 0, 99)
amount = convertSecondsTo(seconds, unit)
return (amount, unit)
def answer_button_time(col: anki.storage._Collection, seconds: int) -> str:
(amount, unit) = seconds_to_appropriate_unit(seconds)
if unit not in ("months", "years"):
amount = int(amount)
return col.backend.translate(
StringsGroup.SCHEDULING, f"answer-button-time-{unit}", amount=amount
)
timeTable = { timeTable = {
"years": lambda n: ngettext("%s year", "%s years", n), "years": lambda n: ngettext("%s year", "%s years", n),
"months": lambda n: ngettext("%s month", "%s months", n), "months": lambda n: ngettext("%s month", "%s months", n),
@ -114,7 +134,7 @@ def optimalPeriod(time: Union[int, float], point: int, unit: int) -> Tuple[str,
return (type, max(point, 0)) return (type, max(point, 0))
def convertSecondsTo(seconds: Union[int, float], type: str) -> Any: def convertSecondsTo(seconds: Union[int, float], type: str) -> float:
if type == "seconds": if type == "seconds":
return seconds return seconds
elif type == "minutes": elif type == "minutes":

View file

@ -4,6 +4,7 @@ import os
import tempfile import tempfile
from anki import Collection as aopen from anki import Collection as aopen
from anki.lang import without_unicode_isolation
from anki.rsbackend import StringsGroup from anki.rsbackend import StringsGroup
from anki.stdmodels import addBasicModel, models from anki.stdmodels import addBasicModel, models
from anki.utils import isWin from anki.utils import isWin
@ -153,10 +154,7 @@ def test_furigana():
def test_translate(): def test_translate():
d = getEmptyCol() d = getEmptyCol()
tr = d.backend.translate tr = d.backend.translate
no_uni = without_unicode_isolation
# strip off unicode separators
def no_uni(s: str) -> str:
return s.replace("\u2068", "").replace("\u2069", "")
assert tr(StringsGroup.TEST, "valid-key") == "a valid key" assert tr(StringsGroup.TEST, "valid-key") == "a valid key"
assert "invalid-key" in tr(StringsGroup.TEST, "invalid-key") assert "invalid-key" in tr(StringsGroup.TEST, "invalid-key")

View file

@ -5,6 +5,7 @@ import time
from anki import hooks from anki import hooks
from anki.consts import * from anki.consts import *
from anki.lang import without_unicode_isolation
from anki.utils import intTime from anki.utils import intTime
from tests.shared import getEmptyCol as getEmptyColOrig from tests.shared import getEmptyCol as getEmptyColOrig
@ -397,9 +398,10 @@ def test_button_spacing():
c.flush() c.flush()
d.reset() d.reset()
ni = d.sched.nextIvlStr ni = d.sched.nextIvlStr
assert ni(c, 2) == "2 days" wo = without_unicode_isolation
assert ni(c, 3) == "3 days" assert wo(ni(c, 2)) == "2d"
assert ni(c, 4) == "4 days" assert wo(ni(c, 3)) == "3d"
assert wo(ni(c, 4)) == "4d"
def test_overdue_lapse(): def test_overdue_lapse():
@ -514,7 +516,7 @@ def test_nextIvl():
assert ni(c, 3) == 21600000 assert ni(c, 3) == 21600000
# (* 100 2.5 1.3 86400)28080000.0 # (* 100 2.5 1.3 86400)28080000.0
assert ni(c, 4) == 28080000 assert ni(c, 4) == 28080000
assert d.sched.nextIvlStr(c, 4) == "10.8 months" assert without_unicode_isolation(d.sched.nextIvlStr(c, 4)) == "10.83mo"
def test_misc(): def test_misc():

View file

@ -5,6 +5,7 @@ import time
from anki import hooks from anki import hooks
from anki.consts import * from anki.consts import *
from anki.lang import without_unicode_isolation
from anki.utils import intTime from anki.utils import intTime
from tests.shared import getEmptyCol as getEmptyColOrig from tests.shared import getEmptyCol as getEmptyColOrig
@ -486,14 +487,15 @@ def test_button_spacing():
c.flush() c.flush()
d.reset() d.reset()
ni = d.sched.nextIvlStr ni = d.sched.nextIvlStr
assert ni(c, 2) == "2 days" wo = without_unicode_isolation
assert ni(c, 3) == "3 days" assert wo(ni(c, 2)) == "2d"
assert ni(c, 4) == "4 days" assert wo(ni(c, 3)) == "3d"
assert wo(ni(c, 4)) == "4d"
# if hard factor is <= 1, then hard may not increase # if hard factor is <= 1, then hard may not increase
conf = d.decks.confForDid(1) conf = d.decks.confForDid(1)
conf["rev"]["hardFactor"] = 1 conf["rev"]["hardFactor"] = 1
assert ni(c, 2) == "1 day" assert wo(ni(c, 2)) == "1d"
def test_overdue_lapse(): def test_overdue_lapse():
@ -611,7 +613,7 @@ def test_nextIvl():
assert ni(c, 3) == 21600000 assert ni(c, 3) == 21600000
# (* 100 2.5 1.3 86400)28080000.0 # (* 100 2.5 1.3 86400)28080000.0
assert ni(c, 4) == 28080000 assert ni(c, 4) == 28080000
assert d.sched.nextIvlStr(c, 4) == "10.8 months" assert without_unicode_isolation(d.sched.nextIvlStr(c, 4)) == "10.83mo"
def test_bury(): def test_bury():

View file

@ -61,6 +61,7 @@ fn ftl_fallback_for_group(group: StringsGroup) -> String {
StringsGroup::Network => include_str!("network.ftl"), StringsGroup::Network => include_str!("network.ftl"),
StringsGroup::Statistics => include_str!("statistics.ftl"), StringsGroup::Statistics => include_str!("statistics.ftl"),
StringsGroup::Filtering => include_str!("filtering.ftl"), StringsGroup::Filtering => include_str!("filtering.ftl"),
StringsGroup::Scheduling => include_str!("scheduling.ftl"),
} }
.to_string() .to_string()
} }
@ -77,6 +78,7 @@ fn localized_ftl_for_group(group: StringsGroup, lang_ftl_folder: &Path) -> Optio
StringsGroup::Network => "network.ftl", StringsGroup::Network => "network.ftl",
StringsGroup::Statistics => "statistics.ftl", StringsGroup::Statistics => "statistics.ftl",
StringsGroup::Filtering => "filtering.ftl", StringsGroup::Filtering => "filtering.ftl",
StringsGroup::Scheduling => "scheduling.ftl",
}); });
fs::read_to_string(&path) fs::read_to_string(&path)
.map_err(|e| { .map_err(|e| {

View file

@ -0,0 +1,11 @@
## The next time a card will be shown, in a short form that will fit
## on the answer buttons. For example, English shows "4d" to
## represent the card will be due in 4 days, "3m" for 3 minutes, and
## "5mo" for 5 months.
answer-button-time-seconds = {$amount}s
answer-button-time-minutes = {$amount}m
answer-button-time-hours = {$amount}h
answer-button-time-days = {$amount}d
answer-button-time-months = {$amount}mo
answer-button-time-years = {$amount}y