mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00
move answer button labels into fluent
This commit is contained in:
parent
370bb38b8b
commit
232a8625bf
10 changed files with 67 additions and 25 deletions
|
@ -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
|
||||||
|
|
|
@ -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", "")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
11
rslib/src/i18n/scheduling.ftl
Normal file
11
rslib/src/i18n/scheduling.ftl
Normal 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
|
Loading…
Reference in a new issue