mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
i18n some tooltips
This commit is contained in:
parent
93b8cebf1e
commit
41b296e96c
7 changed files with 131 additions and 34 deletions
|
@ -83,6 +83,9 @@ statistics-counts-young-cards = Young
|
||||||
statistics-counts-mature-cards = Mature
|
statistics-counts-mature-cards = Mature
|
||||||
statistics-counts-suspended-cards = Suspended
|
statistics-counts-suspended-cards = Suspended
|
||||||
statistics-counts-buried-cards = Buried
|
statistics-counts-buried-cards = Buried
|
||||||
|
statistics-counts-early-cards = Early
|
||||||
|
statistics-counts-learning-cards = Learning
|
||||||
|
statistics-counts-relearning-cards = Relearning
|
||||||
statistics-counts-title = Card Counts
|
statistics-counts-title = Card Counts
|
||||||
statistics-range-all-time = deck life
|
statistics-range-all-time = deck life
|
||||||
statistics-range-deck = deck
|
statistics-range-deck = deck
|
||||||
|
@ -102,3 +105,21 @@ statistics-reviews-time-subtitle = The time taken to answer the questions.
|
||||||
statistics-intervals-subtitle = Delays until reviews are shown again.
|
statistics-intervals-subtitle = Delays until reviews are shown again.
|
||||||
statistics-answer-buttons-subtitle = The number of times you have pressed each button.
|
statistics-answer-buttons-subtitle = The number of times you have pressed each button.
|
||||||
statistics-hours-subtitle = Review success rate for each hour of the day.
|
statistics-hours-subtitle = Review success rate for each hour of the day.
|
||||||
|
statistics-reviews-time-checkbox = Time
|
||||||
|
statistics-in-days-single = { $days ->
|
||||||
|
[0] Today
|
||||||
|
[1] Tomorrow
|
||||||
|
*[other] In { $days } days
|
||||||
|
}
|
||||||
|
statistics-in-days-range = In { $daysStart }~{ $daysEnd } days
|
||||||
|
statistics-days-ago-single = { $days ->
|
||||||
|
[1] Yesterday
|
||||||
|
*[other] { $days } days ago
|
||||||
|
}
|
||||||
|
statistics-days-ago-range = { $daysStart }~{ $daysEnd } days ago
|
||||||
|
statistics-running-total = Running total
|
||||||
|
statistics-cards-due = { $cards ->
|
||||||
|
[one] 1 card due
|
||||||
|
*[other] { $cards } cards due
|
||||||
|
}
|
||||||
|
statistics-backlog-checkbox = Backlog
|
||||||
|
|
|
@ -10,11 +10,10 @@ function formatNumbers(args?: Record<string, RecordVal>): void {
|
||||||
if (!args) {
|
if (!args) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(args)) {
|
for (const key of Object.keys(args)) {
|
||||||
if (typeof args[key] === "number") {
|
if (typeof args[key] === "number") {
|
||||||
args[key] = new FluentNumber(args[key] as number, {
|
args[key] = new FluentNumber(args[key] as number, {
|
||||||
maximumSignificantDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
let graphData = null as GraphData | null;
|
let graphData = null as GraphData | null;
|
||||||
let histogramData = null as HistogramData | null;
|
let histogramData = null as HistogramData | null;
|
||||||
|
let backlog: boolean = true;
|
||||||
let svg = null as HTMLElement | SVGElement | null;
|
let svg = null as HTMLElement | SVGElement | null;
|
||||||
let range = FutureDueRange.Month;
|
let range = FutureDueRange.Month;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (graphData) {
|
$: if (graphData) {
|
||||||
histogramData = buildHistogram(graphData, range);
|
histogramData = buildHistogram(graphData, range, backlog, i18n);
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_TITLE);
|
const title = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_TITLE);
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
const year = timeSpan(i18n, 1 * YEAR);
|
const year = timeSpan(i18n, 1 * YEAR);
|
||||||
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
||||||
const subtitle = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_SUBTITLE);
|
const subtitle = i18n.tr(i18n.TR.STATISTICS_FUTURE_DUE_SUBTITLE);
|
||||||
|
const backlogLabel = i18n.tr(i18n.TR.STATISTICS_BACKLOG_CHECKBOX);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if histogramData}
|
{#if histogramData}
|
||||||
|
@ -44,6 +45,11 @@
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
|
|
||||||
<div class="range-box-inner">
|
<div class="range-box-inner">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={backlog} />
|
||||||
|
{backlogLabel}
|
||||||
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" bind:group={range} value={FutureDueRange.Month} />
|
<input type="radio" bind:group={range} value={FutureDueRange.Month} />
|
||||||
{month}
|
{month}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
const month3 = timeSpan(i18n, 3 * MONTH);
|
const month3 = timeSpan(i18n, 3 * MONTH);
|
||||||
const year = timeSpan(i18n, 1 * YEAR);
|
const year = timeSpan(i18n, 1 * YEAR);
|
||||||
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
const all = i18n.tr(i18n.TR.STATISTICS_RANGE_ALL_TIME);
|
||||||
|
const time = i18n.tr(i18n.TR.STATISTICS_REVIEWS_TIME_CHECKBOX);
|
||||||
|
|
||||||
let subtitle: string;
|
let subtitle: string;
|
||||||
$: if (showTime) {
|
$: if (showTime) {
|
||||||
|
@ -59,7 +60,7 @@
|
||||||
<div class="range-box-inner">
|
<div class="range-box-inner">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" bind:checked={showTime} />
|
<input type="checkbox" bind:checked={showTime} />
|
||||||
Time
|
{time}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{#if revlogRange >= RevlogRange.Year}
|
{#if revlogRange >= RevlogRange.Year}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { scaleLinear, scaleSequential } from "d3-scale";
|
||||||
import { CardQueue } from "../cards";
|
import { CardQueue } from "../cards";
|
||||||
import { HistogramData } from "./histogram-graph";
|
import { HistogramData } from "./histogram-graph";
|
||||||
import { interpolateGreens } from "d3-scale-chromatic";
|
import { interpolateGreens } from "d3-scale-chromatic";
|
||||||
|
import { dayLabel } from "../time";
|
||||||
|
import { I18n } from "../i18n";
|
||||||
|
|
||||||
export interface GraphData {
|
export interface GraphData {
|
||||||
dueCounts: Map<number, number>;
|
dueCounts: Map<number, number>;
|
||||||
|
@ -26,7 +28,7 @@ export enum FutureDueRange {
|
||||||
|
|
||||||
export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
||||||
const due = (data.cards as pb.BackendProto.Card[])
|
const due = (data.cards as pb.BackendProto.Card[])
|
||||||
.filter((c) => c.queue == CardQueue.Review && c.due >= data.daysElapsed)
|
.filter((c) => c.queue == CardQueue.Review) // && c.due >= data.daysElapsed)
|
||||||
.map((c) => c.due - data.daysElapsed);
|
.map((c) => c.due - data.daysElapsed);
|
||||||
const dueCounts = rollup(
|
const dueCounts = rollup(
|
||||||
due,
|
due,
|
||||||
|
@ -40,24 +42,11 @@ function binValue(d: Bin<Map<number, number>, number>): number {
|
||||||
return sum(d, (d) => d[1]);
|
return sum(d, (d) => d[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoverText(
|
|
||||||
data: HistogramData,
|
|
||||||
binIdx: number,
|
|
||||||
cumulative: number,
|
|
||||||
_percent: number
|
|
||||||
): string {
|
|
||||||
const bin = data.bins[binIdx];
|
|
||||||
const interval =
|
|
||||||
bin.x1! - bin.x0! === 1 ? `${bin.x0} days` : `${bin.x0}~${bin.x1} days`;
|
|
||||||
return (
|
|
||||||
`${binValue(data.bins[binIdx] as any)} cards due in ${interval}. ` +
|
|
||||||
`<br>${cumulative} cards at or before this point.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildHistogram(
|
export function buildHistogram(
|
||||||
sourceData: GraphData,
|
sourceData: GraphData,
|
||||||
range: FutureDueRange
|
range: FutureDueRange,
|
||||||
|
backlog: boolean,
|
||||||
|
i18n: I18n
|
||||||
): HistogramData | null {
|
): HistogramData | null {
|
||||||
// get min/max
|
// get min/max
|
||||||
const data = sourceData.dueCounts;
|
const data = sourceData.dueCounts;
|
||||||
|
@ -65,8 +54,11 @@ export function buildHistogram(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [_xMinOrig, origXMax] = extent<number>(data.keys());
|
const [xMinOrig, origXMax] = extent<number>(data.keys());
|
||||||
const xMin = 0;
|
let xMin = xMinOrig;
|
||||||
|
if (!backlog) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
let xMax = origXMax;
|
let xMax = origXMax;
|
||||||
|
|
||||||
// cap max to selected range
|
// cap max to selected range
|
||||||
|
@ -103,6 +95,22 @@ export function buildHistogram(
|
||||||
|
|
||||||
const total = sum(bins as any, binValue);
|
const total = sum(bins as any, binValue);
|
||||||
|
|
||||||
|
function hoverText(
|
||||||
|
data: HistogramData,
|
||||||
|
binIdx: number,
|
||||||
|
cumulative: number,
|
||||||
|
_percent: number
|
||||||
|
): string {
|
||||||
|
const bin = data.bins[binIdx];
|
||||||
|
const days = dayLabel(i18n, bin.x0!, bin.x1!);
|
||||||
|
const cards = i18n.tr(i18n.TR.STATISTICS_CARDS_DUE, {
|
||||||
|
cards: binValue(data.bins[binIdx] as any),
|
||||||
|
});
|
||||||
|
const totalLabel = i18n.tr(i18n.TR.STATISTICS_RUNNING_TOTAL);
|
||||||
|
|
||||||
|
return `${days}:<br>${cards}<br>${totalLabel}: ${cumulative}`;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scale: x,
|
scale: x,
|
||||||
bins,
|
bins,
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { showTooltip, hideTooltip } from "./tooltip";
|
||||||
import { GraphBounds } from "./graphs";
|
import { GraphBounds } from "./graphs";
|
||||||
import { area, curveBasis } from "d3-shape";
|
import { area, curveBasis } from "d3-shape";
|
||||||
import { min, histogram, sum, max, Bin, cumsum } from "d3-array";
|
import { min, histogram, sum, max, Bin, cumsum } from "d3-array";
|
||||||
import { timeSpan } from "../time";
|
import { timeSpan, dayLabel } from "../time";
|
||||||
import { I18n } from "../i18n";
|
import { I18n } from "../i18n";
|
||||||
|
|
||||||
interface Reviews {
|
interface Reviews {
|
||||||
|
@ -116,7 +116,7 @@ export function renderReviews(
|
||||||
showTime: boolean,
|
showTime: boolean,
|
||||||
i18n: I18n
|
i18n: I18n
|
||||||
): void {
|
): void {
|
||||||
const xMax = 0;
|
const xMax = 1;
|
||||||
let xMin = 0;
|
let xMin = 0;
|
||||||
// cap max to selected range
|
// cap max to selected range
|
||||||
switch (range) {
|
switch (range) {
|
||||||
|
@ -202,19 +202,58 @@ export function renderReviews(
|
||||||
x.domain() as any
|
x.domain() as any
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function valueLabel(n: number): string {
|
||||||
|
if (showTime) {
|
||||||
|
return timeSpan(i18n, n / 1000);
|
||||||
|
} else {
|
||||||
|
return i18n.tr(i18n.TR.STATISTICS_CARDS, { cards: n });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function tooltipText(d: BinType, cumulative: number): string {
|
function tooltipText(d: BinType, cumulative: number): string {
|
||||||
let buf = `<div>day ${d.x0}-${d.x1}</div>`;
|
const day = dayLabel(i18n, d.x0!, d.x1!);
|
||||||
|
let buf = `<div>${day}</div>`;
|
||||||
const totals = totalsForBin(d);
|
const totals = totalsForBin(d);
|
||||||
const lines = [
|
const lines = [
|
||||||
[darkerGreens(1), `Mature: ${totals[0]}`],
|
[
|
||||||
[lighterGreens(1), `Young: ${totals[1]}`],
|
darkerGreens(1),
|
||||||
[blues(1), `New/learn: ${totals[2]}`],
|
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_MATURE_CARDS)}: ${valueLabel(
|
||||||
[reds(1), `Relearn: ${totals[3]}`],
|
totals[0]
|
||||||
[oranges(1), `Early: ${totals[4]}`],
|
)}`,
|
||||||
["grey", `Total: ${cumulative}`],
|
],
|
||||||
|
[
|
||||||
|
lighterGreens(1),
|
||||||
|
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_YOUNG_CARDS)}: ${valueLabel(
|
||||||
|
totals[1]
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
blues(1),
|
||||||
|
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_LEARNING_CARDS)}: ${valueLabel(
|
||||||
|
totals[2]
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
reds(1),
|
||||||
|
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_RELEARNING_CARDS)}: ${valueLabel(
|
||||||
|
totals[3]
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
oranges(1),
|
||||||
|
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_EARLY_CARDS)}: ${valueLabel(
|
||||||
|
totals[4]
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"grey",
|
||||||
|
`${i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS)}: ${valueLabel(
|
||||||
|
cumulative
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
for (const [colour, text] of lines) {
|
for (const [colour, text] of lines) {
|
||||||
buf += `<div><span style="color: ${colour}">■</span>${text}</div>`;
|
buf += `<div><span style="color: ${colour};">■</span>${text}</div>`;
|
||||||
}
|
}
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,3 +132,26 @@ export function timeSpan(i18n: I18n, seconds: number, precise = true): string {
|
||||||
|
|
||||||
return i18n.tr(key, { amount });
|
return i18n.tr(key, { amount });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dayLabel(i18n: I18n, daysStart: number, daysEnd: number): string {
|
||||||
|
const larger = Math.max(Math.abs(daysStart), Math.abs(daysEnd));
|
||||||
|
const smaller = Math.min(Math.abs(daysStart), Math.abs(daysEnd));
|
||||||
|
if (larger - smaller <= 1) {
|
||||||
|
// singular
|
||||||
|
if (daysStart >= 0) {
|
||||||
|
return i18n.tr(i18n.TR.STATISTICS_IN_DAYS_SINGLE, { days: daysStart });
|
||||||
|
} else {
|
||||||
|
return i18n.tr(i18n.TR.STATISTICS_DAYS_AGO_SINGLE, { days: -daysStart });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// range
|
||||||
|
if (daysStart >= 0) {
|
||||||
|
return i18n.tr(i18n.TR.STATISTICS_IN_DAYS_RANGE, { daysStart, daysEnd });
|
||||||
|
} else {
|
||||||
|
return i18n.tr(i18n.TR.STATISTICS_DAYS_AGO_RANGE, {
|
||||||
|
daysStart: Math.abs(daysEnd),
|
||||||
|
daysEnd: -daysStart,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue