use singleton + free functions for i18n in ts

This allows for tree shaking, and reduces the congrats page from 150k
with the old enum solution to about 80k.
This commit is contained in:
Damien Elmes 2021-03-26 20:23:43 +10:00
parent 0de7ab87a5
commit c039845c16
34 changed files with 231 additions and 267 deletions

View file

@ -1,23 +1,22 @@
<script lang="ts">
import "../sass/core.css";
import { I18n } from "anki/i18n";
import pb from "anki/backend_proto";
import type pb from "anki/backend_proto";
import { buildNextLearnMsg } from "./lib";
import { bridgeLink } from "anki/bridgecommand";
export let info: pb.BackendProto.CongratsInfoOut;
export let i18n: I18n;
import * as tr from "anki/i18n";
const congrats = i18n.schedulingCongratulationsFinished();
const nextLearnMsg = buildNextLearnMsg(info, i18n);
const today_reviews = i18n.schedulingTodayReviewLimitReached();
const today_new = i18n.schedulingTodayNewLimitReached();
const congrats = tr.schedulingCongratulationsFinished();
const nextLearnMsg = buildNextLearnMsg(info);
const today_reviews = tr.schedulingTodayReviewLimitReached();
const today_new = tr.schedulingTodayNewLimitReached();
const unburyThem = bridgeLink("unbury", i18n.schedulingUnburyThem());
const buriedMsg = i18n.schedulingBuriedCardsFound({ unburyThem });
const customStudy = bridgeLink("customStudy", i18n.schedulingCustomStudy());
const customStudyMsg = i18n.schedulingHowToCustomStudy({
const unburyThem = bridgeLink("unbury", tr.schedulingUnburyThem());
const buriedMsg = tr.schedulingBuriedCardsFound({ unburyThem });
const customStudy = bridgeLink("customStudy", tr.schedulingCustomStudy());
const customStudyMsg = tr.schedulingHowToCustomStudy({
customStudy,
});
</script>

View file

@ -9,10 +9,10 @@ import CongratsPage from "./CongratsPage.svelte";
export async function congrats(target: HTMLDivElement): Promise<void> {
checkNightMode();
const i18n = await setupI18n();
await setupI18n();
const info = await getCongratsInfo();
new CongratsPage({
target,
props: { info, i18n },
props: { info },
});
}

View file

@ -4,7 +4,8 @@
import pb from "anki/backend_proto";
import { postRequest } from "anki/postrequest";
import { naturalUnit, unitAmount, unitName } from "anki/time";
import type { I18n } from "anki/i18n";
import * as tr from "anki/i18n";
export async function getCongratsInfo(): Promise<pb.BackendProto.CongratsInfoOut> {
return pb.BackendProto.CongratsInfoOut.decode(
@ -12,10 +13,7 @@ export async function getCongratsInfo(): Promise<pb.BackendProto.CongratsInfoOut
);
}
export function buildNextLearnMsg(
info: pb.BackendProto.CongratsInfoOut,
i18n: I18n
): string {
export function buildNextLearnMsg(info: pb.BackendProto.CongratsInfoOut): string {
const secsUntil = info.secsUntilNextLearn;
// next learning card not due (/ until tomorrow)?
if (secsUntil == 0 || secsUntil > 86_400) {
@ -25,11 +23,11 @@ export function buildNextLearnMsg(
const unit = naturalUnit(secsUntil);
const amount = Math.round(unitAmount(unit, secsUntil));
const unitStr = unitName(unit);
const nextLearnDue = i18n.schedulingNextLearnDue({
const nextLearnDue = tr.schedulingNextLearnDue({
amount,
unit: unitStr,
});
const remaining = i18n.schedulingLearnRemaining({
const remaining = tr.schedulingLearnRemaining({
remaining: info.learnRemaining,
});
return `${nextLearnDue} ${remaining}`;

View file

@ -1,5 +1,4 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
import type pb from "anki/backend_proto";
import { createEventDispatcher } from "svelte";
@ -17,7 +16,7 @@
import type { PreferenceStore } from "./preferences";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let preferences: PreferenceStore;
let histogramData = null as HistogramData | null;
@ -36,22 +35,21 @@
[histogramData, tableData] = buildHistogram(
addedData,
graphRange,
i18n,
dispatch,
$browserLinksSupported
);
}
const title = i18n.statisticsAddedTitle();
const subtitle = i18n.statisticsAddedSubtitle();
const title = tr.statisticsAddedTitle();
const subtitle = tr.statisticsAddedSubtitle();
</script>
<Graph {title} {subtitle}>
<InputBox>
<GraphRangeRadios bind:graphRange {i18n} revlogRange={RevlogRange.All} />
<GraphRangeRadios bind:graphRange revlogRange={RevlogRange.All} />
</InputBox>
<HistogramGraph data={histogramData} {i18n} />
<HistogramGraph data={histogramData} />
<TableData {i18n} {tableData} />
<TableData {tableData} />
</Graph>

View file

@ -1,6 +1,5 @@
<script lang="typescript">
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte";
@ -12,7 +11,7 @@
import { defaultGraphBounds, GraphRange, RevlogRange } from "./graph-helpers";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let revlogRange: RevlogRange;
let graphRange: GraphRange = GraphRange.Year;
@ -22,22 +21,22 @@
let svg = null as HTMLElement | SVGElement | null;
$: if (sourceData) {
renderButtons(svg as SVGElement, bounds, sourceData, i18n, graphRange);
renderButtons(svg as SVGElement, bounds, sourceData, graphRange);
}
const title = i18n.statisticsAnswerButtonsTitle();
const subtitle = i18n.statisticsAnswerButtonsSubtitle();
const title = tr.statisticsAnswerButtonsTitle();
const subtitle = tr.statisticsAnswerButtonsSubtitle();
</script>
<Graph {title} {subtitle}>
<InputBox>
<GraphRangeRadios bind:graphRange {i18n} {revlogRange} followRevlog={true} />
<GraphRangeRadios bind:graphRange {revlogRange} followRevlog={true} />
</InputBox>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
<g class="bars" />
<HoverColumns />
<AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
<NoDataOverlay {bounds} />
</svg>
</Graph>

View file

@ -1,6 +1,6 @@
<script lang="typescript">
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import { createEventDispatcher } from "svelte";
import Graph from "./Graph.svelte";
@ -17,7 +17,7 @@
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let preferences: PreferenceStore | null = null;
export let revlogRange: RevlogRange;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let nightMode: boolean;
let { calendarFirstDayOfWeek } = preferences;
@ -59,14 +59,13 @@
graphData,
dispatch,
targetYear,
i18n,
nightMode,
revlogRange,
calendarFirstDayOfWeek.set
);
}
const title = i18n.statisticsCalendarTitle();
const title = tr.statisticsCalendarTitle();
</script>
<Graph {title}>
@ -88,6 +87,6 @@
<g class="weekdays" />
<g class="days" />
<AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
<NoDataOverlay {bounds} />
</svg>
</Graph>

View file

@ -1,7 +1,6 @@
<script lang="typescript">
import { createEventDispatcher } from "svelte";
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte";
@ -13,7 +12,7 @@
import type { PreferenceStore } from "./preferences";
export let sourceData: pb.BackendProto.GraphsOut;
export let i18n: I18n;
import * as tr2 from "anki/i18n";
export let preferences: PreferenceStore;
let { cardCountsSeparateInactive, browserLinksSupported } = preferences;
@ -29,12 +28,12 @@
let tableData = (null as unknown) as TableDatum[];
$: {
graphData = gatherData(sourceData, $cardCountsSeparateInactive, i18n);
graphData = gatherData(sourceData, $cardCountsSeparateInactive);
tableData = renderCards(svg as any, bounds, graphData);
}
const label = i18n.statisticsCountsSeparateSuspendedBuriedCards();
const total = i18n.statisticsCountsTotalCards();
const label = tr2.statisticsCountsSeparateSuspendedBuriedCards();
const total = tr2.statisticsCountsTotalCards();
</script>
<style lang="scss">

View file

@ -1,6 +1,6 @@
<script lang="typescript">
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import { createEventDispatcher } from "svelte";
import HistogramGraph from "./HistogramGraph.svelte";
@ -13,7 +13,7 @@
import type { PreferenceStore } from "./preferences";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let preferences: PreferenceStore;
const dispatch = createEventDispatcher<SearchEventMap>();
@ -25,18 +25,17 @@
$: if (sourceData) {
[histogramData, tableData] = prepareData(
gatherData(sourceData),
i18n,
dispatch,
$browserLinksSupported
);
}
const title = i18n.statisticsCardEaseTitle();
const subtitle = i18n.statisticsCardEaseSubtitle();
const title = tr.statisticsCardEaseTitle();
const subtitle = tr.statisticsCardEaseSubtitle();
</script>
<Graph {title} {subtitle}>
<HistogramGraph data={histogramData} {i18n} />
<HistogramGraph data={histogramData} />
<TableData {i18n} {tableData} />
<TableData {tableData} />
</Graph>

View file

@ -1,6 +1,6 @@
<script lang="typescript">
import { createEventDispatcher } from "svelte";
import type { I18n } from "anki/i18n";
import type pb from "anki/backend_proto";
import Graph from "./Graph.svelte";
@ -17,7 +17,7 @@
import type { PreferenceStore } from "./preferences";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let preferences: PreferenceStore;
const dispatch = createEventDispatcher<SearchEventMap>();
@ -37,15 +37,14 @@
graphData,
graphRange,
$futureDueShowBacklog,
i18n,
dispatch,
$browserLinksSupported
));
}
const title = i18n.statisticsFutureDueTitle();
const subtitle = i18n.statisticsFutureDueSubtitle();
const backlogLabel = i18n.statisticsBacklogCheckbox();
const title = tr.statisticsFutureDueTitle();
const subtitle = tr.statisticsFutureDueSubtitle();
const backlogLabel = tr.statisticsBacklogCheckbox();
</script>
<Graph {title} {subtitle}>
@ -57,10 +56,10 @@
</label>
{/if}
<GraphRangeRadios bind:graphRange {i18n} revlogRange={RevlogRange.All} />
<GraphRangeRadios bind:graphRange revlogRange={RevlogRange.All} />
</InputBox>
<HistogramGraph data={histogramData} {i18n} />
<HistogramGraph data={histogramData} />
<TableData {i18n} {tableData} />
<TableData {tableData} />
</Graph>

View file

@ -1,9 +1,8 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
import { RevlogRange, GraphRange } from "./graph-helpers";
import { timeSpan, MONTH, YEAR } from "anki/time";
export let i18n: I18n;
import * as tr from "anki/i18n";
export let revlogRange: RevlogRange;
export let graphRange: GraphRange;
export let followRevlog: boolean = false;
@ -22,10 +21,10 @@
onFollowRevlog(revlogRange);
}
const month = timeSpan(i18n, 1 * MONTH);
const month3 = timeSpan(i18n, 3 * MONTH);
const year = timeSpan(i18n, 1 * YEAR);
const all = i18n.statisticsRangeAllTime();
const month = timeSpan(1 * MONTH);
const month3 = timeSpan(3 * MONTH);
const year = timeSpan(1 * YEAR);
const all = tr.statisticsRangeAllTime();
</script>
<label>

View file

@ -3,12 +3,12 @@
import type { SvelteComponent } from "svelte/internal";
import { writable } from "svelte/store";
import type { I18n } from "anki/i18n";
import { bridgeCommand } from "anki/bridgecommand";
import WithGraphData from "./WithGraphData.svelte";
export let i18n: I18n;
import * as tr from "anki/i18n";
export let nightMode: boolean;
export let graphs: SvelteComponent[];
@ -41,7 +41,7 @@
let:preferences
let:revlogRange>
{#if controller}
<svelte:component this={controller} {i18n} {search} {days} {loading} />
<svelte:component this={controller} {search} {days} {loading} />
{/if}
{#if sourceData && preferences && revlogRange}
@ -51,7 +51,6 @@
{sourceData}
{preferences}
{revlogRange}
{i18n}
{nightMode}
on:search={browserSearch} />
{/each}

View file

@ -1,6 +1,4 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
import AxisTicks from "./AxisTicks.svelte";
import NoDataOverlay from "./NoDataOverlay.svelte";
import CumulativeOverlay from "./CumulativeOverlay.svelte";
@ -11,7 +9,7 @@
import { defaultGraphBounds } from "./graph-helpers";
export let data: HistogramData | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
let bounds = defaultGraphBounds();
let svg = null as HTMLElement | SVGElement | null;
@ -24,5 +22,5 @@
<HoverColumns />
<CumulativeOverlay />
<AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
<NoDataOverlay {bounds} />
</svg>

View file

@ -1,6 +1,5 @@
<script lang="typescript">
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte";
@ -13,7 +12,7 @@
import { renderHours } from "./hours";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let revlogRange: RevlogRange;
let graphRange: GraphRange = GraphRange.Year;
@ -22,16 +21,16 @@
let svg = null as HTMLElement | SVGElement | null;
$: if (sourceData) {
renderHours(svg as SVGElement, bounds, sourceData, i18n, graphRange);
renderHours(svg as SVGElement, bounds, sourceData, graphRange);
}
const title = i18n.statisticsHoursTitle();
const subtitle = i18n.statisticsHoursSubtitle();
const title = tr.statisticsHoursTitle();
const subtitle = tr.statisticsHoursSubtitle();
</script>
<Graph {title} {subtitle}>
<InputBox>
<GraphRangeRadios bind:graphRange {i18n} {revlogRange} followRevlog={true} />
<GraphRangeRadios bind:graphRange {revlogRange} followRevlog={true} />
</InputBox>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
@ -39,6 +38,6 @@
<CumulativeOverlay />
<HoverColumns />
<AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
<NoDataOverlay {bounds} />
</svg>
</Graph>

View file

@ -1,6 +1,6 @@
<script lang="typescript">
import { timeSpan, MONTH } from "anki/time";
import type { I18n } from "anki/i18n";
import type pb from "anki/backend_proto";
import { createEventDispatcher } from "svelte";
@ -20,7 +20,7 @@
import type { PreferenceStore } from "./preferences";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
import * as tr from "anki/i18n";
export let preferences: PreferenceStore;
const dispatch = createEventDispatcher<SearchEventMap>();
@ -39,16 +39,15 @@
[histogramData, tableData] = prepareIntervalData(
intervalData,
range,
i18n,
dispatch,
$browserLinksSupported
);
}
const title = i18n.statisticsIntervalsTitle();
const subtitle = i18n.statisticsIntervalsSubtitle();
const month = timeSpan(i18n, 1 * MONTH);
const all = i18n.statisticsRangeAllTime();
const title = tr.statisticsIntervalsTitle();
const subtitle = tr.statisticsIntervalsSubtitle();
const month = timeSpan(1 * MONTH);
const all = tr.statisticsRangeAllTime();
</script>
<Graph {title} {subtitle}>
@ -71,7 +70,7 @@
</label>
</InputBox>
<HistogramGraph data={histogramData} {i18n} />
<HistogramGraph data={histogramData} />
<TableData {i18n} {tableData} />
<TableData {tableData} />
</Graph>

View file

@ -1,9 +1,8 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
import type { GraphBounds } from "./graph-helpers";
export let bounds: GraphBounds;
export let i18n: I18n;
const noData = i18n.statisticsNoData();
import * as tr from "anki/i18n";
const noData = tr.statisticsNoData();
</script>
<style lang="scss">

View file

@ -3,7 +3,7 @@
import InputBox from "./InputBox.svelte";
import type { I18n } from "anki/i18n";
import * as tr from "anki/i18n";
import { RevlogRange, daysToRevlogRange } from "./graph-helpers";
enum SearchRange {
@ -12,7 +12,6 @@
Custom = 3,
}
export let i18n: I18n;
export let loading: boolean;
export let days: Writable<number>;
@ -57,11 +56,11 @@
}
}
const year = i18n.statisticsRange_1YearHistory();
const deck = i18n.statisticsRangeDeck();
const collection = i18n.statisticsRangeCollection();
const searchLabel = i18n.statisticsRangeSearch();
const all = i18n.statisticsRangeAllHistory();
const year = tr.statisticsRange_1YearHistory();
const deck = tr.statisticsRangeDeck();
const collection = tr.statisticsRangeCollection();
const searchLabel = tr.statisticsRangeSearch();
const all = tr.statisticsRangeAllHistory();
</script>
<style lang="scss">

View file

@ -1,6 +1,5 @@
<script lang="typescript">
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import Graph from "./Graph.svelte";
import InputBox from "./InputBox.svelte";
@ -18,7 +17,7 @@
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let revlogRange: RevlogRange;
export let i18n: I18n;
import * as tr from "anki/i18n";
let graphData: GraphData | null = null;
@ -38,19 +37,18 @@
bounds,
graphData,
graphRange,
showTime,
i18n
showTime
);
}
const title = i18n.statisticsReviewsTitle();
const time = i18n.statisticsReviewsTimeCheckbox();
const title = tr.statisticsReviewsTitle();
const time = tr.statisticsReviewsTimeCheckbox();
let subtitle = "";
$: if (showTime) {
subtitle = i18n.statisticsReviewsTimeSubtitle();
subtitle = tr.statisticsReviewsTimeSubtitle();
} else {
subtitle = i18n.statisticsReviewsCountSubtitle();
subtitle = tr.statisticsReviewsCountSubtitle();
}
</script>
@ -58,7 +56,7 @@
<InputBox>
<label> <input type="checkbox" bind:checked={showTime} /> {time} </label>
<GraphRangeRadios bind:graphRange {i18n} {revlogRange} followRevlog={true} />
<GraphRangeRadios bind:graphRange {revlogRange} followRevlog={true} />
</InputBox>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
@ -68,8 +66,8 @@
<CumulativeOverlay />
<HoverColumns />
<AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
<NoDataOverlay {bounds} />
</svg>
<TableData {i18n} {tableData} />
<TableData {tableData} />
</Graph>

View file

@ -1,8 +1,6 @@
<script lang="typescript">
import type { I18n } from "anki/i18n";
import type { TableDatum } from "./graph-helpers";
export let i18n: I18n;
import { i18n } from "anki/i18n";
export let tableData: TableDatum[];
</script>

View file

@ -1,6 +1,5 @@
<script lang="typescript">
import type pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import Graph from "./Graph.svelte";
@ -8,11 +7,10 @@
import { gatherData } from "./today";
export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n;
let todayData: TodayData | null = null;
$: if (sourceData) {
todayData = gatherData(sourceData, i18n);
todayData = gatherData(sourceData);
}
</script>

View file

@ -18,10 +18,11 @@ import {
} from "d3";
import type { Bin } from "d3";
import type { HistogramData } from "./histogram-graph";
import type { I18n } from "anki/i18n";
import { dayLabel } from "anki/time";
import { GraphRange } from "./graph-helpers";
import type { TableDatum, SearchDispatch } from "./graph-helpers";
import * as tr from "anki/i18n";
export interface GraphData {
daysAdded: number[];
@ -49,7 +50,6 @@ function makeQuery(start: number, end: number): string {
export function buildHistogram(
data: GraphData,
range: GraphRange,
i18n: I18n,
dispatch: SearchDispatch,
browserLinksSupported: boolean
): [HistogramData | null, TableDatum[]] {
@ -99,12 +99,12 @@ export function buildHistogram(
const cardsPerDay = Math.round(totalInPeriod / periodDays);
const tableData = [
{
label: i18n.statisticsTotal(),
value: i18n.statisticsCards({ cards: totalInPeriod }),
label: tr.statisticsTotal(),
value: tr.statisticsCards({ cards: totalInPeriod }),
},
{
label: i18n.statisticsAverage(),
value: i18n.statisticsCardsPerDay({ count: cardsPerDay }),
label: tr.statisticsAverage(),
value: tr.statisticsCardsPerDay({ count: cardsPerDay }),
},
];
@ -113,10 +113,10 @@ export function buildHistogram(
cumulative: number,
_percent: number
): string {
const day = dayLabel(i18n, bin.x0!, bin.x1!);
const cards = i18n.statisticsCards({ cards: bin.length });
const total = i18n.statisticsRunningTotal();
const totalCards = i18n.statisticsCards({ cards: cumulative });
const day = dayLabel(bin.x0!, bin.x1!);
const cards = tr.statisticsCards({ cards: bin.length });
const total = tr.statisticsRunningTotal();
const totalCards = tr.statisticsCards({ cards: cumulative });
return `${day}:<br>${cards}<br>${total}: ${totalCards}`;
}

View file

@ -7,7 +7,7 @@
*/
import pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import {
interpolateRdYlGn,
select,
@ -26,6 +26,7 @@ import {
GraphRange,
millisecondCutoffForRange,
} from "./graph-helpers";
import * as tr from "anki/i18n";
type ButtonCounts = [number, number, number, number];
@ -99,7 +100,6 @@ export function renderButtons(
svgElem: SVGElement,
bounds: GraphBounds,
origData: pb.BackendProto.GraphsOut,
i18n: I18n,
range: GraphRange
): void {
const sourceData = gatherData(origData, range);
@ -160,14 +160,14 @@ export function renderButtons(
let kind: string;
switch (d) {
case "learning":
kind = i18n.statisticsCountsLearningCards();
kind = tr.statisticsCountsLearningCards();
break;
case "young":
kind = i18n.statisticsCountsYoungCards();
kind = tr.statisticsCountsYoungCards();
break;
case "mature":
default:
kind = i18n.statisticsCountsMatureCards();
kind = tr.statisticsCountsMatureCards();
break;
}
return `${kind} \u200e(${totalCorrect(d).percent}%)`;
@ -239,9 +239,9 @@ export function renderButtons(
// hover/tooltip
function tooltipText(d: Datum): string {
const button = i18n.statisticsAnswerButtonsButtonNumber();
const timesPressed = i18n.statisticsAnswerButtonsButtonPressed();
const correctStr = i18n.statisticsHoursCorrect(totalCorrect(d.group));
const button = tr.statisticsAnswerButtonsButtonNumber();
const timesPressed = tr.statisticsAnswerButtonsButtonPressed();
const correctStr = tr.statisticsHoursCorrect(totalCorrect(d.group));
return `${button}: ${d.buttonNum}<br>${timesPressed}: ${d.count}<br>${correctStr}`;
}

View file

@ -5,7 +5,6 @@
@typescript-eslint/no-non-null-assertion: "off",
*/
import type { I18n } from "anki/i18n";
import pb from "anki/backend_proto";
import {
interpolateBlues,
@ -30,6 +29,8 @@ import {
SearchDispatch,
} from "./graph-helpers";
import { clickableClass } from "./graph-styles";
import { i18n } from "anki/i18n";
import * as tr from "anki/i18n";
export interface GraphData {
// indexed by day, where day is relative to today
@ -91,7 +92,6 @@ export function renderCalendar(
sourceData: GraphData,
dispatch: SearchDispatch,
targetYear: number,
i18n: I18n,
nightMode: boolean,
revlogRange: RevlogRange,
setFirstDayOfWeek: (d: number) => void
@ -169,7 +169,7 @@ export function renderCalendar(
month: "long",
day: "numeric",
});
const cards = i18n.statisticsReviews({ reviews: d.count });
const cards = tr.statisticsReviews({ reviews: d.count });
return `${date}<br>${cards}`;
}

View file

@ -21,7 +21,8 @@ import {
cumsum,
} from "d3";
import type { GraphBounds } from "./graph-helpers";
import type { I18n } from "anki/i18n";
import * as tr from "anki/i18n";
type Count = [string, number, boolean, string];
export interface GraphData {
@ -42,8 +43,7 @@ const barColours = [
function countCards(
cards: pb.BackendProto.ICard[],
separateInactive: boolean,
i18n: I18n
separateInactive: boolean
): Count[] {
let newCards = 0;
let learn = 0;
@ -89,38 +89,38 @@ function countCards(
const extraQuery = separateInactive ? 'AND -("is:buried" OR "is:suspended")' : "";
const counts: Count[] = [
[i18n.statisticsCountsNewCards(), newCards, true, `"is:new"${extraQuery}`],
[tr.statisticsCountsNewCards(), newCards, true, `"is:new"${extraQuery}`],
[
i18n.statisticsCountsLearningCards(),
tr.statisticsCountsLearningCards(),
learn,
true,
`(-"is:review" AND "is:learn")${extraQuery}`,
],
[
i18n.statisticsCountsRelearningCards(),
tr.statisticsCountsRelearningCards(),
relearn,
true,
`("is:review" AND "is:learn")${extraQuery}`,
],
[
i18n.statisticsCountsYoungCards(),
tr.statisticsCountsYoungCards(),
young,
true,
`("is:review" AND -"is:learn") AND "prop:ivl<21"${extraQuery}`,
],
[
i18n.statisticsCountsMatureCards(),
tr.statisticsCountsMatureCards(),
mature,
true,
`("is:review" -"is:learn") AND "prop:ivl>=21"${extraQuery}`,
],
[
i18n.statisticsCountsSuspendedCards(),
tr.statisticsCountsSuspendedCards(),
suspended,
separateInactive,
'"is:suspended"',
],
[i18n.statisticsCountsBuriedCards(), buried, separateInactive, '"is:buried"'],
[tr.statisticsCountsBuriedCards(), buried, separateInactive, '"is:buried"'],
];
return counts;
@ -128,14 +128,13 @@ function countCards(
export function gatherData(
data: pb.BackendProto.GraphsOut,
separateInactive: boolean,
i18n: I18n
separateInactive: boolean
): GraphData {
const totalCards = data.cards.length;
const counts = countCards(data.cards, separateInactive, i18n);
const counts = countCards(data.cards, separateInactive);
return {
title: i18n.statisticsCountsTitle(),
title: tr.statisticsCountsTitle(),
counts,
totalCards,
};

View file

@ -18,8 +18,9 @@ import {
import type { Bin, ScaleLinear } from "d3";
import { CardType } from "anki/cards";
import type { HistogramData } from "./histogram-graph";
import type { I18n } from "anki/i18n";
import type { TableDatum, SearchDispatch } from "./graph-helpers";
import * as tr from "anki/i18n";
export interface GraphData {
eases: number[];
@ -69,7 +70,6 @@ function getAdjustedScaleAndTicks(
export function prepareData(
data: GraphData,
i18n: I18n,
dispatch: SearchDispatch,
browserLinksSupported: boolean
): [HistogramData | null, TableDatum[]] {
@ -96,7 +96,7 @@ export function prepareData(
const minPct = Math.floor(bin.x0!);
const maxPct = Math.floor(bin.x1!);
const percent = maxPct - minPct <= 10 ? `${bin.x0}%` : `${bin.x0}%-${bin.x1}%`;
return i18n.statisticsCardEaseTooltip({
return tr.statisticsCardEaseTooltip({
cards: bin.length,
percent,
});
@ -112,7 +112,7 @@ export function prepareData(
const xTickFormat = (num: number): string => `${num.toFixed(0)}%`;
const tableData = [
{
label: i18n.statisticsAverageEase(),
label: tr.statisticsAverageEase(),
value: xTickFormat(sum(allEases) / total),
},
];

View file

@ -20,9 +20,10 @@ import type { Bin } from "d3";
import { CardQueue } from "anki/cards";
import type { HistogramData } from "./histogram-graph";
import { dayLabel } from "anki/time";
import type { I18n } from "anki/i18n";
import { GraphRange } from "./graph-helpers";
import type { TableDatum, SearchDispatch } from "./graph-helpers";
import * as tr from "anki/i18n";
export interface GraphData {
dueCounts: Map<number, number>;
@ -93,7 +94,6 @@ export function buildHistogram(
sourceData: GraphData,
range: GraphRange,
backlog: boolean,
i18n: I18n,
dispatch: SearchDispatch,
browserLinksSupported: boolean
): FutureDueOut {
@ -153,11 +153,11 @@ export function buildHistogram(
cumulative: number,
_percent: number
): string {
const days = dayLabel(i18n, bin.x0!, bin.x1!);
const cards = i18n.statisticsCardsDue({
const days = dayLabel(bin.x0!, bin.x1!);
const cards = tr.statisticsCardsDue({
cards: binValue(bin as any),
});
const totalLabel = i18n.statisticsRunningTotal();
const totalLabel = tr.statisticsRunningTotal();
return `${days}:<br>${cards}<br>${totalLabel}: ${cumulative}`;
}
@ -172,18 +172,18 @@ export function buildHistogram(
const periodDays = xMax! - xMin!;
const tableData = [
{
label: i18n.statisticsTotal(),
value: i18n.statisticsReviews({ reviews: total }),
label: tr.statisticsTotal(),
value: tr.statisticsReviews({ reviews: total }),
},
{
label: i18n.statisticsAverage(),
value: i18n.statisticsReviewsPerDay({
label: tr.statisticsAverage(),
value: tr.statisticsReviewsPerDay({
count: Math.round(total / periodDays),
}),
},
{
label: i18n.statisticsDueTomorrow(),
value: i18n.statisticsReviews({
label: tr.statisticsDueTomorrow(),
value: tr.statisticsReviews({
reviews: sourceData.dueCounts.get(1) ?? 0,
}),
},

View file

@ -6,7 +6,6 @@
@typescript-eslint/no-explicit-any: "off",
*/
import type { I18n } from "anki/i18n";
import pb from "anki/backend_proto";
import {
interpolateBlues,
@ -30,6 +29,7 @@ import {
millisecondCutoffForRange,
} from "./graph-helpers";
import { oddTickClass } from "./graph-styles";
import * as tr from "anki/i18n";
interface Hour {
hour: number;
@ -75,7 +75,6 @@ export function renderHours(
svgElem: SVGElement,
bounds: GraphBounds,
origData: pb.BackendProto.GraphsOut,
i18n: I18n,
range: GraphRange
): void {
const data = gatherData(origData, range);
@ -185,11 +184,11 @@ export function renderHours(
);
function tooltipText(d: Hour): string {
const hour = i18n.statisticsHoursRange({
const hour = tr.statisticsHoursRange({
hourStart: d.hour,
hourEnd: d.hour + 1,
});
const correct = i18n.statisticsHoursCorrect({
const correct = tr.statisticsHoursCorrect({
correct: d.correctCount,
total: d.totalCount,
percent: d.totalCount ? (d.correctCount / d.totalCount) * 100 : 0,

View file

@ -33,11 +33,10 @@ export function graphs(
): void {
const nightMode = checkNightMode();
setupI18n().then((i18n) => {
setupI18n().then(() => {
new GraphsPage({
target,
props: {
i18n,
graphs,
nightMode,
initialSearch: search,

View file

@ -20,9 +20,10 @@ import {
import type { Bin } from "d3";
import { CardType } from "anki/cards";
import type { HistogramData } from "./histogram-graph";
import type { I18n } from "anki/i18n";
import type { TableDatum, SearchDispatch } from "./graph-helpers";
import { timeSpan } from "anki/time";
import * as tr from "anki/i18n";
export interface IntervalGraphData {
intervals: number[];
@ -43,20 +44,19 @@ export function gatherIntervalData(data: pb.BackendProto.GraphsOut): IntervalGra
}
export function intervalLabel(
i18n: I18n,
daysStart: number,
daysEnd: number,
cards: number
): string {
if (daysEnd - daysStart <= 1) {
// singular
return i18n.statisticsIntervalsDaySingle({
return tr.statisticsIntervalsDaySingle({
day: daysStart,
cards,
});
} else {
// range
return i18n.statisticsIntervalsDayRange({
return tr.statisticsIntervalsDayRange({
daysStart,
daysEnd: daysEnd - 1,
cards,
@ -78,7 +78,6 @@ function makeQuery(start: number, end: number): string {
export function prepareIntervalData(
data: IntervalGraphData,
range: IntervalRange,
i18n: I18n,
dispatch: SearchDispatch,
browserLinksSupported: boolean
): [HistogramData | null, TableDatum[]] {
@ -146,9 +145,9 @@ export function prepareIntervalData(
_cumulative: number,
percent: number
): string {
// const day = dayLabel(i18n, bin.x0!, bin.x1!);
const interval = intervalLabel(i18n, bin.x0!, bin.x1!, bin.length);
const total = i18n.statisticsRunningTotal();
// const day = dayLabel(bin.x0!, bin.x1!);
const interval = intervalLabel(bin.x0!, bin.x1!, bin.length);
const total = tr.statisticsRunningTotal();
return `${interval}<br>${total}: \u200e${percent.toFixed(1)}%`;
}
@ -160,10 +159,10 @@ export function prepareIntervalData(
}
const meanInterval = Math.round(mean(allIntervals) ?? 0);
const meanIntervalString = timeSpan(i18n, meanInterval * 86400, false);
const meanIntervalString = timeSpan(meanInterval * 86400, false);
const tableData = [
{
label: i18n.statisticsAverageInterval(),
label: tr.statisticsAverageInterval(),
value: meanIntervalString,
},
];

View file

@ -7,7 +7,7 @@
*/
import pb from "anki/backend_proto";
import type { I18n } from "anki/i18n";
import { timeSpan, dayLabel } from "anki/time";
import {
interpolateGreens,
@ -34,6 +34,7 @@ import type { Bin } from "d3";
import type { TableDatum } from "./graph-helpers";
import { GraphBounds, setDataAvailable, GraphRange } from "./graph-helpers";
import { showTooltip, hideTooltip } from "./tooltip";
import * as tr from "anki/i18n";
interface Reviews {
learn: number;
@ -121,8 +122,7 @@ export function renderReviews(
bounds: GraphBounds,
sourceData: GraphData,
range: GraphRange,
showTime: boolean,
i18n: I18n
showTime: boolean
): TableDatum[] {
const svg = select(svgElem);
const trans = svg.transition().duration(600) as any;
@ -175,7 +175,7 @@ export function renderReviews(
const yTickFormat = (n: number): string => {
if (showTime) {
return timeSpan(i18n, n / 1000, true);
return timeSpan(n / 1000, true);
} else {
if (Math.round(n) != n) {
return "";
@ -226,32 +226,24 @@ export function renderReviews(
function valueLabel(n: number): string {
if (showTime) {
return timeSpan(i18n, n / 1000);
return timeSpan(n / 1000);
} else {
return i18n.statisticsReviews({ reviews: n });
return tr.statisticsReviews({ reviews: n });
}
}
function tooltipText(d: BinType, cumulative: number): string {
const day = dayLabel(i18n, d.x0!, d.x1!);
const day = dayLabel(d.x0!, d.x1!);
const totals = totalsForBin(d);
const dayTotal = valueLabel(sum(totals));
let buf = `<table><tr><td>${day}</td><td align=right>${dayTotal}</td></tr>`;
const lines = [
[oranges(1), i18n.statisticsCountsLearningCards(), valueLabel(totals[0])],
[reds(1), i18n.statisticsCountsRelearningCards(), valueLabel(totals[1])],
[
lighterGreens(1),
i18n.statisticsCountsYoungCards(),
valueLabel(totals[2]),
],
[
darkerGreens(1),
i18n.statisticsCountsMatureCards(),
valueLabel(totals[3]),
],
[purples(1), i18n.statisticsCountsEarlyCards(), valueLabel(totals[4])],
["transparent", i18n.statisticsRunningTotal(), valueLabel(cumulative)],
[oranges(1), tr.statisticsCountsLearningCards(), valueLabel(totals[0])],
[reds(1), tr.statisticsCountsRelearningCards(), valueLabel(totals[1])],
[lighterGreens(1), tr.statisticsCountsYoungCards(), valueLabel(totals[2])],
[darkerGreens(1), tr.statisticsCountsMatureCards(), valueLabel(totals[3])],
[purples(1), tr.statisticsCountsEarlyCards(), valueLabel(totals[4])],
["transparent", tr.statisticsRunningTotal(), valueLabel(cumulative)],
];
for (const [colour, label, detail] of lines) {
buf += `<tr>
@ -377,14 +369,14 @@ export function renderReviews(
averageAnswerTime: string,
averageAnswerTimeLabel: string;
if (showTime) {
totalString = timeSpan(i18n, total / 1000, false);
averageForDaysStudied = i18n.statisticsMinutesPerDay({
totalString = timeSpan(total / 1000, false);
averageForDaysStudied = tr.statisticsMinutesPerDay({
count: Math.round(studiedAvg / 1000 / 60),
});
averageForPeriod = i18n.statisticsMinutesPerDay({
averageForPeriod = tr.statisticsMinutesPerDay({
count: Math.round(periodAvg / 1000 / 60),
});
averageAnswerTimeLabel = i18n.statisticsAverageAnswerTimeLabel();
averageAnswerTimeLabel = tr.statisticsAverageAnswerTimeLabel();
// need to get total review count to calculate average time
const countBins = histogram()
@ -396,16 +388,16 @@ export function renderReviews(
const totalSecs = total / 1000;
const avgSecs = totalSecs / totalReviews;
const cardsPerMin = (totalReviews * 60) / totalSecs;
averageAnswerTime = i18n.statisticsAverageAnswerTime({
averageAnswerTime = tr.statisticsAverageAnswerTime({
averageSeconds: avgSecs,
cardsPerMinute: cardsPerMin,
});
} else {
totalString = i18n.statisticsReviews({ reviews: total });
averageForDaysStudied = i18n.statisticsReviewsPerDay({
totalString = tr.statisticsReviews({ reviews: total });
averageForDaysStudied = tr.statisticsReviewsPerDay({
count: Math.round(studiedAvg),
});
averageForPeriod = i18n.statisticsReviewsPerDay({
averageForPeriod = tr.statisticsReviewsPerDay({
count: Math.round(periodAvg),
});
averageAnswerTime = averageAnswerTimeLabel = "";
@ -413,23 +405,23 @@ export function renderReviews(
const tableData: TableDatum[] = [
{
label: i18n.statisticsDaysStudied(),
value: i18n.statisticsAmountOfTotalWithPercentage({
label: tr.statisticsDaysStudied(),
value: tr.statisticsAmountOfTotalWithPercentage({
amount: studiedDays,
total: periodDays,
percent: Math.round((studiedDays / periodDays) * 100),
}),
},
{ label: i18n.statisticsTotal(), value: totalString },
{ label: tr.statisticsTotal(), value: totalString },
{
label: i18n.statisticsAverageForDaysStudied(),
label: tr.statisticsAverageForDaysStudied(),
value: averageForDaysStudied,
},
{
label: i18n.statisticsAverageOverPeriod(),
label: tr.statisticsAverageOverPeriod(),
value: averageForPeriod,
},
];

View file

@ -3,7 +3,8 @@
import pb from "anki/backend_proto";
import { studiedToday } from "anki/time";
import type { I18n } from "anki/i18n";
import * as tr from "anki/i18n";
export interface TodayData {
title: string;
@ -12,7 +13,7 @@ export interface TodayData {
const ReviewKind = pb.BackendProto.RevlogEntry.ReviewKind;
export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): TodayData {
export function gatherData(data: pb.BackendProto.GraphsOut): TodayData {
let answerCount = 0;
let answerMillis = 0;
let correctCount = 0;
@ -70,13 +71,13 @@ export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): TodayDa
let lines: string[];
if (answerCount) {
const studiedTodayText = studiedToday(i18n, answerCount, answerMillis / 1000);
const studiedTodayText = studiedToday(answerCount, answerMillis / 1000);
const againCount = answerCount - correctCount;
let againCountText = i18n.statisticsTodayAgainCount();
let againCountText = tr.statisticsTodayAgainCount();
againCountText += ` ${againCount} (${((againCount / answerCount) * 100).toFixed(
2
)}%)`;
const typeCounts = i18n.statisticsTodayTypeCounts({
const typeCounts = tr.statisticsTodayTypeCounts({
learnCount,
reviewCount,
relearnCount,
@ -84,22 +85,22 @@ export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): TodayDa
});
let matureText: string;
if (matureCount) {
matureText = i18n.statisticsTodayCorrectMature({
matureText = tr.statisticsTodayCorrectMature({
correct: matureCorrect,
total: matureCount,
percent: (matureCorrect / matureCount) * 100,
});
} else {
matureText = i18n.statisticsTodayNoMatureCards();
matureText = tr.statisticsTodayNoMatureCards();
}
lines = [studiedTodayText, againCountText, typeCounts, matureText];
} else {
lines = [i18n.statisticsTodayNoCards()];
lines = [tr.statisticsTodayNoCards()];
}
return {
title: i18n.statisticsTodayTitle(),
title: tr.statisticsTodayTitle(),
lines,
};
}

View file

@ -31,7 +31,7 @@ py_binary(
genrule(
name = "fluent_gen",
outs = ["i18n_generated.ts"],
outs = ["i18n.ts"],
cmd = "$(location genfluent) $(location //rslib/i18n:strings.json) $@",
tools = [
"genfluent",
@ -44,7 +44,7 @@ genrule(
ts_library(
name = "lib",
srcs = glob(["**/*.ts"]) + [":i18n_generated.ts"],
srcs = glob(["**/*.ts"]) + [":i18n.ts"],
data = [
"backend_proto",
],

View file

@ -19,8 +19,8 @@ class Variable(TypedDict):
def methods() -> str:
out = [
"export class GeneratedTranslations {",
" translate(key: string, args?: Record<string, any>): string { return 'nyi' } ",
'import { i18n } from "./i18n_helpers";',
'export { i18n, setupI18n } from "./i18n_helpers";',
]
for module in modules:
for translation in module["translations"]:
@ -30,15 +30,13 @@ def methods() -> str:
doc = translation["text"]
out.append(
f"""
/** {doc} */
{key}({arg_types}): string {{
return this.translate("{translation["key"]}"{args})
}}
/** {doc} */
export function {key}({arg_types}): string {{
return i18n.translate("{translation["key"]}"{args})
}}
"""
)
out.append("}")
return "\n".join(out) + "\n"

View file

@ -1,9 +1,11 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
// An i18n singleton and setupI18n is re-exported via the generated i18n.ts file,
// so you should not need to access this file directly.
import "intl-pluralrules";
import { FluentBundle, FluentResource, FluentNumber } from "@fluent/bundle/compat";
import { GeneratedTranslations } from "anki/i18n_generated";
type RecordVal = number | string | FluentNumber;
@ -20,11 +22,11 @@ function formatNumbers(args?: Record<string, RecordVal>): void {
}
}
export class I18n extends GeneratedTranslations {
export class I18n {
bundles: FluentBundle[] = [];
langs: string[] = [];
translate(key: string, args: Record<string, RecordVal>): string {
translate(key: string, args?: Record<string, RecordVal>): string {
formatNumbers(args);
for (const bundle of this.bundles) {
const msg = bundle.getMessage(key);
@ -65,15 +67,17 @@ export class I18n extends GeneratedTranslations {
}
}
export async function setupI18n(): Promise<I18n> {
const i18n = new I18n();
// global singleton
export const i18n = new I18n();
export async function setupI18n(): Promise<void> {
const resp = await fetch("/_anki/i18nResources", { method: "POST" });
if (!resp.ok) {
throw Error(`unexpected reply: ${resp.statusText}`);
}
const json = await resp.json();
i18n.bundles = [];
for (const i in json.resources) {
const text = json.resources[i];
const lang = json.langs[i];
@ -83,6 +87,4 @@ export async function setupI18n(): Promise<I18n> {
i18n.bundles.push(bundle);
}
i18n.langs = json.langs;
return i18n;
}

View file

@ -1,7 +1,7 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { I18n } from "./i18n";
import * as tr from "./i18n";
export const SECOND = 1.0;
export const MINUTE = 60.0 * SECOND;
@ -70,7 +70,7 @@ export function unitAmount(unit: TimespanUnit, secs: number): number {
}
}
export function studiedToday(i18n: I18n, cards: number, secs: number): string {
export function studiedToday(cards: number, secs: number): string {
const unit = naturalUnit(secs);
const amount = unitAmount(unit, secs);
const name = unitName(unit);
@ -79,7 +79,7 @@ export function studiedToday(i18n: I18n, cards: number, secs: number): string {
if (cards > 0) {
secsPer = secs / cards;
}
return i18n.statisticsStudiedToday({
return tr.statisticsStudiedToday({
unit: name,
secsPerCard: secsPer,
// these two are required, but don't appear in the generated code
@ -91,39 +91,38 @@ export function studiedToday(i18n: I18n, cards: number, secs: number): string {
}
function i18nFuncForUnit(
i18n: I18n,
unit: TimespanUnit,
short: boolean
): ({ amount: number }) => string {
if (short) {
switch (unit) {
case TimespanUnit.Seconds:
return i18n.statisticsElapsedTimeSeconds;
return tr.statisticsElapsedTimeSeconds;
case TimespanUnit.Minutes:
return i18n.statisticsElapsedTimeMinutes;
return tr.statisticsElapsedTimeMinutes;
case TimespanUnit.Hours:
return i18n.statisticsElapsedTimeHours;
return tr.statisticsElapsedTimeHours;
case TimespanUnit.Days:
return i18n.statisticsElapsedTimeDays;
return tr.statisticsElapsedTimeDays;
case TimespanUnit.Months:
return i18n.statisticsElapsedTimeMonths;
return tr.statisticsElapsedTimeMonths;
case TimespanUnit.Years:
return i18n.statisticsElapsedTimeYears;
return tr.statisticsElapsedTimeYears;
}
} else {
switch (unit) {
case TimespanUnit.Seconds:
return i18n.schedulingTimeSpanSeconds;
return tr.schedulingTimeSpanSeconds;
case TimespanUnit.Minutes:
return i18n.schedulingTimeSpanMinutes;
return tr.schedulingTimeSpanMinutes;
case TimespanUnit.Hours:
return i18n.schedulingTimeSpanHours;
return tr.schedulingTimeSpanHours;
case TimespanUnit.Days:
return i18n.schedulingTimeSpanDays;
return tr.schedulingTimeSpanDays;
case TimespanUnit.Months:
return i18n.schedulingTimeSpanMonths;
return tr.schedulingTimeSpanMonths;
case TimespanUnit.Years:
return i18n.schedulingTimeSpanYears;
return tr.schedulingTimeSpanYears;
}
}
}
@ -132,31 +131,31 @@ function i18nFuncForUnit(
/// 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.
export function timeSpan(i18n: I18n, seconds: number, short = false): string {
export function timeSpan(seconds: number, short = false): string {
const unit = naturalUnit(seconds);
const amount = unitAmount(unit, seconds);
return i18nFuncForUnit(i18n, unit, short).call(i18n, { amount });
return i18nFuncForUnit(unit, short)({ amount });
}
export function dayLabel(i18n: I18n, daysStart: number, daysEnd: number): string {
export function dayLabel(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.statisticsInDaysSingle({ days: daysStart });
return tr.statisticsInDaysSingle({ days: daysStart });
} else {
return i18n.statisticsDaysAgoSingle({ days: -daysStart });
return tr.statisticsDaysAgoSingle({ days: -daysStart });
}
} else {
// range
if (daysStart >= 0) {
return i18n.statisticsInDaysRange({
return tr.statisticsInDaysRange({
daysStart,
daysEnd: daysEnd - 1,
});
} else {
return i18n.statisticsDaysAgoRange({
return tr.statisticsDaysAgoRange({
daysStart: Math.abs(daysEnd - 1),
daysEnd: -daysStart,
});