@zhin.js/plugin-ledger 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/commands/ledger-add/index.js +6 -4
- package/commands/ledger-add/index.ts +6 -4
- package/commands/ledger-delete/index.js +6 -4
- package/commands/ledger-delete/index.ts +6 -4
- package/commands/ledger-list/index.js +8 -6
- package/commands/ledger-list/index.ts +8 -6
- package/commands/ledger-month/index.js +5 -3
- package/commands/ledger-month/index.ts +5 -3
- package/commands/ledger-report/index.js +11 -11
- package/commands/ledger-report/index.ts +10 -12
- package/commands/ledger-stats/index.js +8 -6
- package/commands/ledger-stats/index.ts +8 -6
- package/commands/ledger-today/index.js +5 -3
- package/commands/ledger-today/index.ts +5 -3
- package/commands/ledger-week/index.js +5 -3
- package/commands/ledger-week/index.ts +5 -3
- package/lib/config.js +3 -3
- package/lib/config.js.map +1 -1
- package/lib/entry-input.d.ts +3 -2
- package/lib/entry-input.d.ts.map +1 -1
- package/lib/entry-input.js +11 -9
- package/lib/entry-input.js.map +1 -1
- package/lib/i18n.d.ts +2 -0
- package/lib/i18n.d.ts.map +1 -0
- package/lib/i18n.js +200 -0
- package/lib/i18n.js.map +1 -0
- package/lib/identity.d.ts +3 -1
- package/lib/identity.d.ts.map +1 -1
- package/lib/identity.js +5 -2
- package/lib/identity.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/money.d.ts +2 -1
- package/lib/money.d.ts.map +1 -1
- package/lib/money.js +6 -5
- package/lib/money.js.map +1 -1
- package/lib/period.d.ts +4 -3
- package/lib/period.d.ts.map +1 -1
- package/lib/period.js +8 -16
- package/lib/period.js.map +1 -1
- package/lib/report-format.d.ts +5 -4
- package/lib/report-format.d.ts.map +1 -1
- package/lib/report-format.js +25 -24
- package/lib/report-format.js.map +1 -1
- package/lib/service.d.ts +1 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +59 -25
- package/lib/service.js.map +1 -1
- package/lib/store.d.ts +9 -1
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +32 -3
- package/lib/store.js.map +1 -1
- package/package.json +7 -7
- package/schema.json +15 -0
- package/src/config.ts +3 -3
- package/src/entry-input.ts +12 -9
- package/src/i18n.ts +200 -0
- package/src/identity.ts +6 -2
- package/src/index.ts +1 -1
- package/src/money.ts +7 -5
- package/src/period.ts +7 -13
- package/src/report-format.ts +26 -24
- package/src/service.ts +59 -26
- package/src/store.ts +33 -3
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
2
|
+
export const messages = createMessages({
|
|
3
|
+
"actor": {
|
|
4
|
+
"zh-CN": "记账需要在真实会话中由具体用户发送",
|
|
5
|
+
"en": "Ledger commands require a real user in a conversation."
|
|
6
|
+
},
|
|
7
|
+
"usage": {
|
|
8
|
+
"zh-CN": "格式:<类别> <金额> [备注],例如:午饭 18.5 和同事",
|
|
9
|
+
"en": "Format: <category> <amount> [note], for example: Lunch 18.5 With colleagues"
|
|
10
|
+
},
|
|
11
|
+
"order": {
|
|
12
|
+
"zh-CN": "类别要写在金额前面。{usage}",
|
|
13
|
+
"en": "Put the category before the amount. {usage}"
|
|
14
|
+
},
|
|
15
|
+
"amountError": {
|
|
16
|
+
"zh-CN": "{reason}。{usage}",
|
|
17
|
+
"en": "{reason} {usage}"
|
|
18
|
+
},
|
|
19
|
+
"categoryNumber": {
|
|
20
|
+
"zh-CN": "类别不能是数字。{usage}",
|
|
21
|
+
"en": "The category cannot be a number. {usage}"
|
|
22
|
+
},
|
|
23
|
+
"categoryLimit": {
|
|
24
|
+
"zh-CN": "类别最多 {max} 个字",
|
|
25
|
+
"en": "Category limit: {max} characters."
|
|
26
|
+
},
|
|
27
|
+
"noteLimit": {
|
|
28
|
+
"zh-CN": "备注最多 {max} 个字",
|
|
29
|
+
"en": "Note limit: {max} characters."
|
|
30
|
+
},
|
|
31
|
+
"negative": {
|
|
32
|
+
"zh-CN": "金额不能为负数",
|
|
33
|
+
"en": "Amount cannot be negative."
|
|
34
|
+
},
|
|
35
|
+
"amountFormat": {
|
|
36
|
+
"zh-CN": "金额格式不正确,请写成 18 或 18.50(最多两位小数)",
|
|
37
|
+
"en": "Use an amount such as 18 or 18.50, with at most two decimal places."
|
|
38
|
+
},
|
|
39
|
+
"zero": {
|
|
40
|
+
"zh-CN": "金额必须大于 0",
|
|
41
|
+
"en": "Amount must be greater than zero."
|
|
42
|
+
},
|
|
43
|
+
"amountMax": {
|
|
44
|
+
"zh-CN": "单笔金额不能超过 ¥1000000.00",
|
|
45
|
+
"en": "A single entry cannot exceed ¥1000000.00."
|
|
46
|
+
},
|
|
47
|
+
"period.today": {
|
|
48
|
+
"zh-CN": "今日",
|
|
49
|
+
"en": "Today"
|
|
50
|
+
},
|
|
51
|
+
"period.week": {
|
|
52
|
+
"zh-CN": "本周",
|
|
53
|
+
"en": "This week"
|
|
54
|
+
},
|
|
55
|
+
"period.month": {
|
|
56
|
+
"zh-CN": "本月",
|
|
57
|
+
"en": "This month"
|
|
58
|
+
},
|
|
59
|
+
"period.all": {
|
|
60
|
+
"zh-CN": "全部",
|
|
61
|
+
"en": "All time"
|
|
62
|
+
},
|
|
63
|
+
"allRecords": {
|
|
64
|
+
"zh-CN": "全部记录",
|
|
65
|
+
"en": "All records"
|
|
66
|
+
},
|
|
67
|
+
"periodInput": {
|
|
68
|
+
"zh-CN": "时间范围只能是:今日、本周、本月、全部",
|
|
69
|
+
"en": "Choose a period: today, week, month, all."
|
|
70
|
+
},
|
|
71
|
+
"actionInput": {
|
|
72
|
+
"zh-CN": "操作只能是:开启、关闭、状态",
|
|
73
|
+
"en": "Choose an action: on, off, status."
|
|
74
|
+
},
|
|
75
|
+
"added": {
|
|
76
|
+
"zh-CN": "✅ 已记账 #{seq} · {category} {amount}{note}",
|
|
77
|
+
"en": "✅ Recorded #{seq} · {category} {amount}{note}"
|
|
78
|
+
},
|
|
79
|
+
"notFound": {
|
|
80
|
+
"zh-CN": "没有找到你的账目 #{seq}(只能删除自己的记账,请查看账单编号)",
|
|
81
|
+
"en": "Your entry #{seq} was not found. You can only delete your own entries; check the entry list for its ID."
|
|
82
|
+
},
|
|
83
|
+
"deleted": {
|
|
84
|
+
"zh-CN": "🗑️ 已删除 #{seq} · {category} {amount}",
|
|
85
|
+
"en": "🗑️ Deleted #{seq} · {category} {amount}"
|
|
86
|
+
},
|
|
87
|
+
"spending": {
|
|
88
|
+
"zh-CN": "{period}消费",
|
|
89
|
+
"en": "{period} spending"
|
|
90
|
+
},
|
|
91
|
+
"statistics": {
|
|
92
|
+
"zh-CN": "分类统计",
|
|
93
|
+
"en": "Category statistics"
|
|
94
|
+
},
|
|
95
|
+
"entryList": {
|
|
96
|
+
"zh-CN": "{period}账单",
|
|
97
|
+
"en": "{period} entries"
|
|
98
|
+
},
|
|
99
|
+
"privateSent": {
|
|
100
|
+
"zh-CN": "🔒 {title}已私信发送给你",
|
|
101
|
+
"en": "🔒 {title} was sent to you privately."
|
|
102
|
+
},
|
|
103
|
+
"privateFallback": {
|
|
104
|
+
"zh-CN": "{text}\n(群聊中金额已隐藏,私聊我可查看完整金额)",
|
|
105
|
+
"en": "{text}\n(Amounts are hidden in public chats. Message me privately to view them.)"
|
|
106
|
+
},
|
|
107
|
+
"reportEnabled": {
|
|
108
|
+
"zh-CN": "记账日报已开启:每天 {time}({timezone})私信发送当天消费",
|
|
109
|
+
"en": "Daily reports are enabled: today’s spending is sent privately at {time} ({timezone})."
|
|
110
|
+
},
|
|
111
|
+
"reportDisabled": {
|
|
112
|
+
"zh-CN": "记账日报未开启。",
|
|
113
|
+
"en": "Daily reports are disabled."
|
|
114
|
+
},
|
|
115
|
+
"reportOff": {
|
|
116
|
+
"zh-CN": "已关闭记账日报",
|
|
117
|
+
"en": "Daily reports disabled."
|
|
118
|
+
},
|
|
119
|
+
"noPrivate": {
|
|
120
|
+
"zh-CN": "当前环境不支持主动私信,无法开启记账日报",
|
|
121
|
+
"en": "This environment cannot send proactive private messages; daily reports cannot be enabled."
|
|
122
|
+
},
|
|
123
|
+
"reportOn": {
|
|
124
|
+
"zh-CN": "已开启记账日报:每天 {time}({timezone})私信发送当天消费。",
|
|
125
|
+
"en": "Daily reports enabled: today’s spending is sent privately at {time} ({timezone})."
|
|
126
|
+
},
|
|
127
|
+
"cannotPrivate": {
|
|
128
|
+
"zh-CN": "无法私信你,请先私聊我后再开启记账日报。",
|
|
129
|
+
"en": "I could not message you privately. Message me privately before enabling daily reports."
|
|
130
|
+
},
|
|
131
|
+
"reportOnPrivate": {
|
|
132
|
+
"zh-CN": "🔒 已开启记账日报,确认消息已私信发送给你",
|
|
133
|
+
"en": "🔒 Daily reports enabled; confirmation was sent to you privately."
|
|
134
|
+
},
|
|
135
|
+
"report": {
|
|
136
|
+
"zh-CN": "🌙 记账日报\n{text}",
|
|
137
|
+
"en": "🌙 Daily expense report\n{text}"
|
|
138
|
+
},
|
|
139
|
+
"commandHint": {
|
|
140
|
+
"zh-CN": "发送「{command}」。",
|
|
141
|
+
"en": "Send \"{command}\"."
|
|
142
|
+
},
|
|
143
|
+
"emptySummary": {
|
|
144
|
+
"zh-CN": "📒 {period}还没有记账({range})",
|
|
145
|
+
"en": "📒 {period}: no expenses recorded ({range})."
|
|
146
|
+
},
|
|
147
|
+
"summaryHeader": {
|
|
148
|
+
"zh-CN": "📒 {period}消费({range})",
|
|
149
|
+
"en": "📒 {period} spending ({range})"
|
|
150
|
+
},
|
|
151
|
+
"total": {
|
|
152
|
+
"zh-CN": "合计 {amount} · {count} 笔",
|
|
153
|
+
"en": "Total {amount} · {count} entries"
|
|
154
|
+
},
|
|
155
|
+
"categoryFull": {
|
|
156
|
+
"zh-CN": "· {category} {amount}({count} 笔,{share}%)",
|
|
157
|
+
"en": "· {category} {amount} ({count} entries, {share}%)"
|
|
158
|
+
},
|
|
159
|
+
"categoryMasked": {
|
|
160
|
+
"zh-CN": "· {category} {amount}({count} 笔)",
|
|
161
|
+
"en": "· {category} {amount} ({count} entries)"
|
|
162
|
+
},
|
|
163
|
+
"emptyStats": {
|
|
164
|
+
"zh-CN": "📊 {period}没有可统计的记账({range})",
|
|
165
|
+
"en": "📊 {period}: no entries to analyze ({range})."
|
|
166
|
+
},
|
|
167
|
+
"statsHeader": {
|
|
168
|
+
"zh-CN": "📊 分类统计 · {period}({range})",
|
|
169
|
+
"en": "📊 Category statistics · {period} ({range})"
|
|
170
|
+
},
|
|
171
|
+
"statsFull": {
|
|
172
|
+
"zh-CN": "· {category}:合计 {amount},{count} 笔,平均 {average},最大 {max},占比 {share}%",
|
|
173
|
+
"en": "· {category}: total {amount}, {count} entries, average {average}, largest {max}, share {share}%"
|
|
174
|
+
},
|
|
175
|
+
"statsMasked": {
|
|
176
|
+
"zh-CN": "· {category}:{count} 笔,金额已隐藏",
|
|
177
|
+
"en": "· {category}: {count} entries, amounts hidden"
|
|
178
|
+
},
|
|
179
|
+
"categoryCount": {
|
|
180
|
+
"zh-CN": "共 {count} 个类别",
|
|
181
|
+
"en": "{count} categories"
|
|
182
|
+
},
|
|
183
|
+
"categoryCountTotal": {
|
|
184
|
+
"zh-CN": "共 {count} 个类别 · 合计 {amount}",
|
|
185
|
+
"en": "{count} categories · total {amount}"
|
|
186
|
+
},
|
|
187
|
+
"emptyList": {
|
|
188
|
+
"zh-CN": "🧾 {period}没有账目({range})",
|
|
189
|
+
"en": "🧾 {period}: no entries ({range})."
|
|
190
|
+
},
|
|
191
|
+
"listHeader": {
|
|
192
|
+
"zh-CN": "🧾 {period}账单({range})",
|
|
193
|
+
"en": "🧾 {period} entries ({range})"
|
|
194
|
+
},
|
|
195
|
+
"more": {
|
|
196
|
+
"zh-CN": "……另有 {count} 笔未显示",
|
|
197
|
+
"en": "… {count} more entries not shown"
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=i18n.js.map
|
package/lib/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"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"}
|
package/lib/identity.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import { type DeliveryTarget } from 'zhin.js';
|
|
2
3
|
import type { CommandMessage } from 'zhin.js/command';
|
|
3
4
|
/** The person using a ledger command and where they are talking from. */
|
|
4
5
|
export interface LedgerCaller {
|
|
5
6
|
/** Stable per-user key: adapter + endpoint (bot account) + sender id. */
|
|
6
7
|
readonly ownerKey: string;
|
|
8
|
+
readonly locale?: Locale;
|
|
7
9
|
readonly senderId: string;
|
|
8
10
|
/** The conversation the command came from. */
|
|
9
11
|
readonly conversation: DeliveryTarget;
|
|
@@ -16,7 +18,7 @@ export declare class LedgerInputError extends Error {
|
|
|
16
18
|
}
|
|
17
19
|
export declare function ledgerOwnerKey(adapter: string, endpointId: string, senderId: string): string;
|
|
18
20
|
/** Builds the caller from a command message; commands without a real sender are refused. */
|
|
19
|
-
export declare function ledgerCaller(input: CommandMessage | undefined): LedgerCaller;
|
|
21
|
+
export declare function ledgerCaller(input: CommandMessage | undefined, locale?: Locale): LedgerCaller;
|
|
20
22
|
/** Turns expected input problems into a reply and lets real failures surface. */
|
|
21
23
|
export declare function replyLedger(operation: () => Promise<string>): Promise<string>;
|
|
22
24
|
//# sourceMappingURL=identity.d.ts.map
|
package/lib/identity.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC;IACtC,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5F;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,EAAE,cAAc,CAAC;IACtC,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5F;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,EAAE,MAAM,GAAE,MAAqC,GAAG,YAAY,CAiB3H;AAED,iFAAiF;AACjF,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnF"}
|
package/lib/identity.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
2
|
+
import { messages as t } from './i18n.js';
|
|
1
3
|
import { createDeliveryTarget } from 'zhin.js';
|
|
2
4
|
export class LedgerInputError extends Error {
|
|
3
5
|
}
|
|
@@ -5,10 +7,10 @@ export function ledgerOwnerKey(adapter, endpointId, senderId) {
|
|
|
5
7
|
return JSON.stringify([adapter, endpointId, senderId]);
|
|
6
8
|
}
|
|
7
9
|
/** Builds the caller from a command message; commands without a real sender are refused. */
|
|
8
|
-
export function ledgerCaller(input) {
|
|
10
|
+
export function ledgerCaller(input, locale = resolveLocale(input?.locale)) {
|
|
9
11
|
const senderId = input?.sender?.id;
|
|
10
12
|
if (!input || !senderId)
|
|
11
|
-
throw new LedgerInputError('
|
|
13
|
+
throw new LedgerInputError(t(locale, 'actor'));
|
|
12
14
|
const conversation = createDeliveryTarget(input);
|
|
13
15
|
const endpoint = conversation.endpoint;
|
|
14
16
|
const isPrivate = conversation.kind === 'private';
|
|
@@ -18,6 +20,7 @@ export function ledgerCaller(input) {
|
|
|
18
20
|
return {
|
|
19
21
|
ownerKey: ledgerOwnerKey(endpoint.adapter, endpoint.id, senderId),
|
|
20
22
|
senderId,
|
|
23
|
+
locale,
|
|
21
24
|
conversation,
|
|
22
25
|
privateTarget,
|
|
23
26
|
isPrivate,
|
package/lib/identity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAuB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAe,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,QAAQ,IAAI,CAAC,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAuB,MAAM,SAAS,CAAC;AAiBpE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAE9C,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,UAAkB,EAAE,QAAgB;IAClF,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,KAAiC,EAAE,SAAiB,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC;IAC3G,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IACnC,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE,MAAM,YAAY,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;IACvC,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,KAAK,SAAS,CAAC;IAClD,MAAM,aAAa,GAAmB,SAAS;QAC7C,CAAC,CAAC,YAAY;QACd,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/D,OAAO;QACL,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;QACjE,QAAQ;QACR,MAAM;QACN,YAAY;QACZ,aAAa;QACb,SAAS;KACV,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAgC;IAChE,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,gBAAgB;YAAE,OAAO,KAAK,CAAC,OAAO,CAAC;QAC5D,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export { describePeriodRange, isInPeriod, localDateKey, localTimeKey, parsePerio
|
|
|
6
6
|
export { formatCategoryStatistics, formatEntryList, formatPeriodSummary, summarize, totalsByCategory, MAX_LISTED_ENTRIES, type CategoryTotal, type LedgerReply, type PeriodSummary, } from './report-format.js';
|
|
7
7
|
export { ledgerServiceToken } from './runtime.js';
|
|
8
8
|
export { LedgerService, type DailyReportAction, type LedgerServiceOptions } from './service.js';
|
|
9
|
-
export { defineLedgerTables, LedgerStore, ENTRY_TABLE, USER_TABLE, type LedgerEntry, type LedgerUserRecord } from './store.js';
|
|
9
|
+
export { defineLedgerTables, LedgerStore, ENTRY_TABLE, USER_TABLE, PRIVATE_TARGET_TABLE, type LedgerEntry, type LedgerUserRecord } from './store.js';
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACrJ,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,mBAAmB,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAClJ,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/G,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrH,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,EACZ,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACrJ,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,mBAAmB,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAClJ,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/G,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrH,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,EACZ,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -6,5 +6,5 @@ export { describePeriodRange, isInPeriod, localDateKey, localTimeKey, parsePerio
|
|
|
6
6
|
export { formatCategoryStatistics, formatEntryList, formatPeriodSummary, summarize, totalsByCategory, MAX_LISTED_ENTRIES, } from './report-format.js';
|
|
7
7
|
export { ledgerServiceToken } from './runtime.js';
|
|
8
8
|
export { LedgerService } from './service.js';
|
|
9
|
-
export { defineLedgerTables, LedgerStore, ENTRY_TABLE, USER_TABLE } from './store.js';
|
|
9
|
+
export { defineLedgerTables, LedgerStore, ENTRY_TABLE, USER_TABLE, PRIVATE_TARGET_TABLE } from './store.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB,EAAgD,MAAM,aAAa,CAAC;AACrJ,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,mBAAmB,EAAE,eAAe,EAAgD,MAAM,kBAAkB,CAAC;AAClJ,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAqB,MAAM,eAAe,CAAC;AAC/G,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAA0B,MAAM,YAAY,CAAC;AACrH,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,GAEb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,gBAAgB,EAChB,kBAAkB,GAInB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,aAAa,EAAqD,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAA2C,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,gBAAgB,EAAgD,MAAM,aAAa,CAAC;AACrJ,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,mBAAmB,EAAE,eAAe,EAAgD,MAAM,kBAAkB,CAAC;AAClJ,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAqB,MAAM,eAAe,CAAC;AAC/G,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAA0B,MAAM,YAAY,CAAC;AACrH,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,WAAW,EACX,UAAU,EACV,YAAY,GAEb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,mBAAmB,EACnB,SAAS,EACT,gBAAgB,EAChB,kBAAkB,GAInB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,aAAa,EAAqD,MAAM,cAAc,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,oBAAoB,EAA2C,MAAM,YAAY,CAAC"}
|
package/lib/money.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
/**
|
|
2
3
|
* Amounts are stored as integer cents (分) and parsed from text without floating point,
|
|
3
4
|
* so 0.1 + 0.2 style rounding errors cannot enter the ledger.
|
|
@@ -12,7 +13,7 @@ export type AmountParseResult = {
|
|
|
12
13
|
readonly reason: string;
|
|
13
14
|
};
|
|
14
15
|
/** Parses `18`, `18.5`, `18.50`, `¥18.5` or `18.5元` into cents; rejects negatives, zero, exponents and more than two decimals. */
|
|
15
|
-
export declare function parseAmountToCents(raw: string): AmountParseResult;
|
|
16
|
+
export declare function parseAmountToCents(raw: string, locale?: Locale): AmountParseResult;
|
|
16
17
|
/** Formats cents as `¥12.50`. */
|
|
17
18
|
export declare function formatCents(cents: number): string;
|
|
18
19
|
/** The text shown instead of an amount where other people can read the reply. */
|
package/lib/money.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,oGAAoG;AACpG,eAAO,MAAM,eAAe,YAAc,CAAC;AAE3C,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAmBpD,kIAAkI;AAClI,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"money.d.ts","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE1D;;;GAGG;AAEH,oGAAoG;AACpG,eAAO,MAAM,eAAe,YAAc,CAAC;AAE3C,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAmBpD,kIAAkI;AAClI,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAYlF;AAED,iCAAiC;AACjC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,iFAAiF;AACjF,eAAO,MAAM,aAAa,cAAS,CAAC"}
|
package/lib/money.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messages as t } from './i18n.js';
|
|
1
2
|
/**
|
|
2
3
|
* Amounts are stored as integer cents (分) and parsed from text without floating point,
|
|
3
4
|
* so 0.1 + 0.2 style rounding errors cannot enter the ledger.
|
|
@@ -20,21 +21,21 @@ function invalid(reason) {
|
|
|
20
21
|
return { ok: false, reason };
|
|
21
22
|
}
|
|
22
23
|
/** Parses `18`, `18.5`, `18.50`, `¥18.5` or `18.5元` into cents; rejects negatives, zero, exponents and more than two decimals. */
|
|
23
|
-
export function parseAmountToCents(raw) {
|
|
24
|
+
export function parseAmountToCents(raw, locale) {
|
|
24
25
|
const normalized = toHalfWidth(raw.trim());
|
|
25
26
|
const withoutCurrency = normalized.replace(CURRENCY_PREFIX, '').replace(CURRENCY_SUFFIX, '').trim();
|
|
26
27
|
if (withoutCurrency.startsWith('-'))
|
|
27
|
-
return invalid('
|
|
28
|
+
return invalid(t(locale, 'negative'));
|
|
28
29
|
const match = AMOUNT_PATTERN.exec(withoutCurrency);
|
|
29
30
|
if (!match)
|
|
30
|
-
return invalid(
|
|
31
|
+
return invalid(t(locale, 'amountFormat'));
|
|
31
32
|
const wholeYuan = Number(match[1]);
|
|
32
33
|
const fractionDigits = (match[2] ?? '').padEnd(2, '0');
|
|
33
34
|
const cents = wholeYuan * 100 + Number(fractionDigits);
|
|
34
35
|
if (cents === 0)
|
|
35
|
-
return invalid(
|
|
36
|
+
return invalid(t(locale, 'zero'));
|
|
36
37
|
if (cents > MAX_ENTRY_CENTS)
|
|
37
|
-
return invalid(
|
|
38
|
+
return invalid(t(locale, 'amountMax'));
|
|
38
39
|
return { ok: true, cents };
|
|
39
40
|
}
|
|
40
41
|
/** Formats cents as `¥12.50`. */
|
package/lib/money.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"money.js","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"money.js","sourceRoot":"","sources":["../src/money.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,IAAI,CAAC,EAAE,MAAM,WAAW,CAAC;AAC1C;;;GAGG;AAEH,oGAAoG;AACpG,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAM3C,MAAM,cAAc,GAAG,8BAA8B,CAAC;AACtD,MAAM,eAAe,GAAG,QAAQ,CAAC;AACjC,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,8FAA8F;AAC9F,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,EAAE;QAC5C,IAAI,SAAS,KAAK,GAAG;YAAE,OAAO,GAAG,CAAC;QAClC,MAAM,aAAa,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC;QAC1C,OAAO,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAE,GAAG,aAAa,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,MAAc;IAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,kIAAkI;AAClI,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,MAAe;IAC7D,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpG,IAAI,eAAe,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,SAAS,GAAG,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IACvD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,IAAI,KAAK,GAAG,eAAe;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IACpE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;IAC1C,MAAM,cAAc,GAAG,KAAK,GAAG,GAAG,CAAC;IACnC,OAAO,IAAI,SAAS,IAAI,MAAM,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACpE,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC"}
|
package/lib/period.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
/**
|
|
2
3
|
* Calendar periods evaluated in the configured IANA timezone. Entries are compared by their
|
|
3
4
|
* local calendar date (YYYY-MM-DD), so daylight-saving shifts never move an entry into another day.
|
|
4
5
|
*/
|
|
5
6
|
export type LedgerPeriod = 'today' | 'week' | 'month' | 'all';
|
|
6
|
-
export declare function periodLabel(period: LedgerPeriod): string;
|
|
7
|
+
export declare function periodLabel(period: LedgerPeriod, locale?: Locale): string;
|
|
7
8
|
/** Maps a user-typed period word to a period; undefined when the word is not recognised. */
|
|
8
|
-
export declare function parsePeriodWord(word: string | undefined, fallback: LedgerPeriod): LedgerPeriod | undefined;
|
|
9
|
+
export declare function parsePeriodWord(word: string | undefined, fallback: LedgerPeriod, locale?: Locale): LedgerPeriod | undefined;
|
|
9
10
|
/** Local calendar date `YYYY-MM-DD` of an instant in a timezone. */
|
|
10
11
|
export declare function localDateKey(instantMs: number, timeZone: string): string;
|
|
11
12
|
/** Local wall-clock time `HH:MM` of an instant in a timezone. */
|
|
@@ -17,5 +18,5 @@ export declare function weekEndKey(weekStart: string): string;
|
|
|
17
18
|
/** Whether an entry made at `entryMs` falls in `period` as seen at `nowMs`. */
|
|
18
19
|
export declare function isInPeriod(entryMs: number, nowMs: number, period: LedgerPeriod, timeZone: string): boolean;
|
|
19
20
|
/** Human-readable range for a period header, e.g. `2026-10-19 ~ 2026-10-25`. */
|
|
20
|
-
export declare function describePeriodRange(nowMs: number, period: LedgerPeriod, timeZone: string): string;
|
|
21
|
+
export declare function describePeriodRange(nowMs: number, period: LedgerPeriod, timeZone: string, locale?: Locale): string;
|
|
21
22
|
//# sourceMappingURL=period.d.ts.map
|
package/lib/period.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"period.d.ts","sourceRoot":"","sources":["../src/period.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"period.d.ts","sourceRoot":"","sources":["../src/period.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE1D;;;GAGG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAI9D,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAA0C;AAEpH,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAS3H;AAiBD,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxE;AAED,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxE;AAWD,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAO1G;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CASlH"}
|
package/lib/period.js
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* local calendar date (YYYY-MM-DD), so daylight-saving shifts never move an entry into another day.
|
|
4
|
-
*/
|
|
1
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
2
|
+
import { messages as t } from './i18n.js';
|
|
5
3
|
const DAY_MS = 86_400_000;
|
|
6
|
-
|
|
7
|
-
today: '今日',
|
|
8
|
-
week: '本周',
|
|
9
|
-
month: '本月',
|
|
10
|
-
all: '全部',
|
|
11
|
-
};
|
|
12
|
-
export function periodLabel(period) {
|
|
13
|
-
return PERIOD_LABELS[period];
|
|
14
|
-
}
|
|
4
|
+
export function periodLabel(period, locale) { return t(locale, `period.${period}`); }
|
|
15
5
|
/** Maps a user-typed period word to a period; undefined when the word is not recognised. */
|
|
16
|
-
export function parsePeriodWord(word, fallback) {
|
|
6
|
+
export function parsePeriodWord(word, fallback, locale) {
|
|
17
7
|
if (word === undefined || word.trim() === '')
|
|
18
8
|
return fallback;
|
|
19
9
|
const normalized = word.trim();
|
|
10
|
+
if (resolveLocale(locale) === 'en')
|
|
11
|
+
return ['today', 'week', 'month', 'all'].includes(normalized.toLowerCase()) ? normalized.toLowerCase() : undefined;
|
|
20
12
|
if (normalized === '今日' || normalized === '今天')
|
|
21
13
|
return 'today';
|
|
22
14
|
if (normalized === '本周' || normalized === '这周')
|
|
@@ -83,7 +75,7 @@ export function isInPeriod(entryMs, nowMs, period, timeZone) {
|
|
|
83
75
|
return weekStartKey(entryDate) === weekStartKey(today);
|
|
84
76
|
}
|
|
85
77
|
/** Human-readable range for a period header, e.g. `2026-10-19 ~ 2026-10-25`. */
|
|
86
|
-
export function describePeriodRange(nowMs, period, timeZone) {
|
|
78
|
+
export function describePeriodRange(nowMs, period, timeZone, locale) {
|
|
87
79
|
const today = localDateKey(nowMs, timeZone);
|
|
88
80
|
if (period === 'today')
|
|
89
81
|
return today;
|
|
@@ -93,6 +85,6 @@ export function describePeriodRange(nowMs, period, timeZone) {
|
|
|
93
85
|
const start = weekStartKey(today);
|
|
94
86
|
return `${start} ~ ${weekEndKey(start)}`;
|
|
95
87
|
}
|
|
96
|
-
return '
|
|
88
|
+
return t(locale, 'allRecords');
|
|
97
89
|
}
|
|
98
90
|
//# sourceMappingURL=period.js.map
|
package/lib/period.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"period.js","sourceRoot":"","sources":["../src/period.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"period.js","sourceRoot":"","sources":["../src/period.ts"],"names":[],"mappings":"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"}
|
package/lib/report-format.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import { type LedgerPeriod } from './period.js';
|
|
2
3
|
import type { LedgerEntry } from './store.js';
|
|
3
4
|
/** Total, entry count and share for one category. */
|
|
@@ -21,10 +22,10 @@ export interface LedgerReply {
|
|
|
21
22
|
}
|
|
22
23
|
/** Groups entries by category, largest total first (ties by name for a stable order). */
|
|
23
24
|
export declare function totalsByCategory(entries: readonly LedgerEntry[]): CategoryTotal[];
|
|
24
|
-
export declare function summarize(entries: readonly LedgerEntry[], period: LedgerPeriod, nowMs: number, timeZone: string): PeriodSummary;
|
|
25
|
-
export declare function formatPeriodSummary(summary: PeriodSummary): LedgerReply;
|
|
26
|
-
export declare function formatCategoryStatistics(summary: PeriodSummary): LedgerReply;
|
|
25
|
+
export declare function summarize(entries: readonly LedgerEntry[], period: LedgerPeriod, nowMs: number, timeZone: string, locale?: Locale): PeriodSummary;
|
|
26
|
+
export declare function formatPeriodSummary(summary: PeriodSummary, locale?: Locale): LedgerReply;
|
|
27
|
+
export declare function formatCategoryStatistics(summary: PeriodSummary, locale?: Locale): LedgerReply;
|
|
27
28
|
export declare const MAX_LISTED_ENTRIES = 20;
|
|
28
29
|
/** Newest first, with `#id` so users can find what to delete. */
|
|
29
|
-
export declare function formatEntryList(entries: readonly LedgerEntry[], period: LedgerPeriod, nowMs: number, timeZone: string): LedgerReply;
|
|
30
|
+
export declare function formatEntryList(entries: readonly LedgerEntry[], period: LedgerPeriod, nowMs: number, timeZone: string, locale?: Locale): LedgerReply;
|
|
30
31
|
//# sourceMappingURL=report-format.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report-format.d.ts","sourceRoot":"","sources":["../src/report-format.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"report-format.d.ts","sourceRoot":"","sources":["../src/report-format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAG1D,OAAO,EAAkD,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC/C;AAED,oGAAoG;AACpG,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,aAAa,EAAE,CAYjF;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAUhJ;AASD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,CAexF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,CAkB7F;AAED,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,CAwBpJ"}
|
package/lib/report-format.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messages as t } from './i18n.js';
|
|
1
2
|
import { formatCents, MASKED_AMOUNT } from './money.js';
|
|
2
3
|
import { describePeriodRange, localDateKey, periodLabel } from './period.js';
|
|
3
4
|
/** Groups entries by category, largest total first (ties by name for a stable order). */
|
|
@@ -14,13 +15,13 @@ export function totalsByCategory(entries) {
|
|
|
14
15
|
totals.sort((left, right) => right.totalCents - left.totalCents || left.category.localeCompare(right.category));
|
|
15
16
|
return totals;
|
|
16
17
|
}
|
|
17
|
-
export function summarize(entries, period, nowMs, timeZone) {
|
|
18
|
+
export function summarize(entries, period, nowMs, timeZone, locale) {
|
|
18
19
|
let totalCents = 0;
|
|
19
20
|
for (const entry of entries)
|
|
20
21
|
totalCents += entry.amountCents;
|
|
21
22
|
return {
|
|
22
23
|
period,
|
|
23
|
-
range: describePeriodRange(nowMs, period, timeZone),
|
|
24
|
+
range: describePeriodRange(nowMs, period, timeZone, locale),
|
|
24
25
|
totalCents,
|
|
25
26
|
count: entries.length,
|
|
26
27
|
categories: totalsByCategory(entries),
|
|
@@ -33,53 +34,53 @@ function sharePercent(partCents, totalCents) {
|
|
|
33
34
|
const tenths = Math.round((partCents * 1000) / totalCents);
|
|
34
35
|
return `${Math.floor(tenths / 10)}.${tenths % 10}`;
|
|
35
36
|
}
|
|
36
|
-
export function formatPeriodSummary(summary) {
|
|
37
|
-
const label = periodLabel(summary.period);
|
|
37
|
+
export function formatPeriodSummary(summary, locale) {
|
|
38
|
+
const label = periodLabel(summary.period, locale);
|
|
38
39
|
if (summary.count === 0) {
|
|
39
|
-
const empty =
|
|
40
|
+
const empty = t(locale, 'emptySummary', { period: label, range: summary.range });
|
|
40
41
|
return { full: empty, masked: empty };
|
|
41
42
|
}
|
|
42
|
-
const header =
|
|
43
|
-
const fullLines = [header,
|
|
44
|
-
const maskedLines = [header,
|
|
43
|
+
const header = t(locale, 'summaryHeader', { period: label, range: summary.range });
|
|
44
|
+
const fullLines = [header, t(locale, 'total', { amount: formatCents(summary.totalCents), count: summary.count })];
|
|
45
|
+
const maskedLines = [header, t(locale, 'total', { amount: MASKED_AMOUNT, count: summary.count })];
|
|
45
46
|
for (const category of summary.categories) {
|
|
46
47
|
const share = sharePercent(category.totalCents, summary.totalCents);
|
|
47
|
-
fullLines.push(
|
|
48
|
-
maskedLines.push(
|
|
48
|
+
fullLines.push(t(locale, 'categoryFull', { category: category.category, amount: formatCents(category.totalCents), count: category.count, share }));
|
|
49
|
+
maskedLines.push(t(locale, 'categoryMasked', { category: category.category, amount: MASKED_AMOUNT, count: category.count }));
|
|
49
50
|
}
|
|
50
51
|
return { full: fullLines.join('\n'), masked: maskedLines.join('\n') };
|
|
51
52
|
}
|
|
52
|
-
export function formatCategoryStatistics(summary) {
|
|
53
|
-
const label = periodLabel(summary.period);
|
|
53
|
+
export function formatCategoryStatistics(summary, locale) {
|
|
54
|
+
const label = periodLabel(summary.period, locale);
|
|
54
55
|
if (summary.count === 0) {
|
|
55
|
-
const empty =
|
|
56
|
+
const empty = t(locale, 'emptyStats', { period: label, range: summary.range });
|
|
56
57
|
return { full: empty, masked: empty };
|
|
57
58
|
}
|
|
58
|
-
const header =
|
|
59
|
+
const header = t(locale, 'statsHeader', { period: label, range: summary.range });
|
|
59
60
|
const fullLines = [header];
|
|
60
61
|
const maskedLines = [header];
|
|
61
62
|
for (const category of summary.categories) {
|
|
62
63
|
const averageCents = Math.round(category.totalCents / category.count);
|
|
63
64
|
const share = sharePercent(category.totalCents, summary.totalCents);
|
|
64
|
-
fullLines.push(
|
|
65
|
-
maskedLines.push(
|
|
65
|
+
fullLines.push(t(locale, 'statsFull', { category: category.category, amount: formatCents(category.totalCents), count: category.count, average: formatCents(averageCents), max: formatCents(category.largestCents), share }));
|
|
66
|
+
maskedLines.push(t(locale, 'statsMasked', { category: category.category, count: category.count }));
|
|
66
67
|
}
|
|
67
|
-
fullLines.push(
|
|
68
|
-
maskedLines.push(
|
|
68
|
+
fullLines.push(t(locale, 'categoryCountTotal', { count: summary.categories.length, amount: formatCents(summary.totalCents) }));
|
|
69
|
+
maskedLines.push(t(locale, 'categoryCount', { count: summary.categories.length }));
|
|
69
70
|
return { full: fullLines.join('\n'), masked: maskedLines.join('\n') };
|
|
70
71
|
}
|
|
71
72
|
export const MAX_LISTED_ENTRIES = 20;
|
|
72
73
|
/** Newest first, with `#id` so users can find what to delete. */
|
|
73
|
-
export function formatEntryList(entries, period, nowMs, timeZone) {
|
|
74
|
-
const label = periodLabel(period);
|
|
75
|
-
const range = describePeriodRange(nowMs, period, timeZone);
|
|
74
|
+
export function formatEntryList(entries, period, nowMs, timeZone, locale) {
|
|
75
|
+
const label = periodLabel(period, locale);
|
|
76
|
+
const range = describePeriodRange(nowMs, period, timeZone, locale);
|
|
76
77
|
if (entries.length === 0) {
|
|
77
|
-
const empty =
|
|
78
|
+
const empty = t(locale, 'emptyList', { period: label, range });
|
|
78
79
|
return { full: empty, masked: empty };
|
|
79
80
|
}
|
|
80
81
|
const newestFirst = [...entries].sort((left, right) => right.createdAt - left.createdAt || right.seq - left.seq);
|
|
81
82
|
const shown = newestFirst.slice(0, MAX_LISTED_ENTRIES);
|
|
82
|
-
const header =
|
|
83
|
+
const header = t(locale, 'listHeader', { period: label, range });
|
|
83
84
|
const fullLines = [header];
|
|
84
85
|
const maskedLines = [header];
|
|
85
86
|
for (const entry of shown) {
|
|
@@ -89,7 +90,7 @@ export function formatEntryList(entries, period, nowMs, timeZone) {
|
|
|
89
90
|
maskedLines.push(`#${entry.seq} ${date} ${entry.category} ${MASKED_AMOUNT}`);
|
|
90
91
|
}
|
|
91
92
|
if (newestFirst.length > shown.length) {
|
|
92
|
-
const more =
|
|
93
|
+
const more = t(locale, 'more', { count: newestFirst.length - shown.length });
|
|
93
94
|
fullLines.push(more);
|
|
94
95
|
maskedLines.push(more);
|
|
95
96
|
}
|