perf: optimize portfolio queries and workflows
This commit is contained in:
1 parent
da98d02661
commit
a7e0a0fe58
45 files changed
+4446
-637
No files matched your search
+788
-335
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,160 @@
|
||||
import { useState, type FormEvent } from 'react';
|
||||
import { type Position, nowMinute, money, requestUuid } from './api';
|
||||
import { t as tr } from './i18n';
|
||||
export type DebtOperation = 'borrow' | 'lend' | 'collect' | 'repay';
|
||||
export const operationLabel = (operation: string) =>
|
||||
tr(
|
||||
(
|
||||
{
|
||||
borrow: '借入到账',
|
||||
lend: '借出付款',
|
||||
collect: '收回借款',
|
||||
repay: '偿还债务',
|
||||
transfer: '账户转账',
|
||||
} as Record<string, string>
|
||||
)[operation] || operation,
|
||||
);
|
||||
export function DebtPaymentForm({
|
||||
accounts,
|
||||
debts,
|
||||
initialAccount,
|
||||
initialDebt,
|
||||
initialOperation,
|
||||
busy,
|
||||
submit,
|
||||
}: {
|
||||
accounts: Position[];
|
||||
debts: Position[];
|
||||
initialAccount?: string;
|
||||
initialDebt?: string;
|
||||
initialOperation?: DebtOperation;
|
||||
busy: boolean;
|
||||
submit: (value: unknown) => void;
|
||||
}) {
|
||||
const [requestId] = useState(requestUuid);
|
||||
const [accountId, setAccount] = useState(initialAccount || accounts[0]?.id || '');
|
||||
const [debtId, setDebt] = useState(initialDebt || debts[0]?.id || '');
|
||||
const debt = debts.find((p) => p.id === debtId),
|
||||
account = accounts.find((p) => p.id === accountId);
|
||||
const operations: DebtOperation[] =
|
||||
debt?.side === 'asset' ? ['collect', 'lend'] : ['repay', 'borrow'];
|
||||
const [chosen, setChosen] = useState<DebtOperation | undefined>(initialOperation);
|
||||
const operation = chosen && operations.includes(chosen) ? chosen : operations[0];
|
||||
const [amount, setAmount] = useState('');
|
||||
const same = debt?.currency === account?.currency;
|
||||
function save(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
submit(Object.fromEntries(new FormData(event.currentTarget)));
|
||||
}
|
||||
if (!accounts.length || !debts.length)
|
||||
return <p>{tr('请先创建启用的资产账户和借入或借出债务。')}</p>;
|
||||
return (
|
||||
<form onSubmit={save} className="transfer-form">
|
||||
<input type="hidden" name="requestId" value={requestId} />
|
||||
<label>
|
||||
{tr('债务')}
|
||||
<select
|
||||
name="targetId"
|
||||
value={debtId}
|
||||
onChange={(event) => {
|
||||
setDebt(event.target.value);
|
||||
setChosen(undefined);
|
||||
}}
|
||||
>
|
||||
{debts.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name} · {tr(p.side === 'asset' ? '借出(应收)' : '借入(应付)')} ·{' '}
|
||||
{money(p.amount, p.currency)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
{tr('操作')}
|
||||
<select
|
||||
name="operation"
|
||||
value={operation}
|
||||
onChange={(event) => setChosen(event.target.value as DebtOperation)}
|
||||
>
|
||||
{operations.map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{operationLabel(value)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
{tr(operation === 'borrow' || operation === 'collect' ? '收款账户' : '付款账户')}
|
||||
<select
|
||||
name="sourceId"
|
||||
value={accountId}
|
||||
onChange={(event) => setAccount(event.target.value)}
|
||||
>
|
||||
{accounts.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name} · {money(p.amount, p.currency)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<p className="muted">
|
||||
{tr('账户与债务同时记账并自动建立关联;这里填写本次金额,不是操作后的余额。')}
|
||||
</p>
|
||||
<label>
|
||||
{tr('本次账户金额')} ({account?.currency})
|
||||
<input
|
||||
name="amount"
|
||||
required
|
||||
inputMode="decimal"
|
||||
pattern="(0|[1-9][0-9]{0,15})(\.[0-9]{1,8})?"
|
||||
value={amount}
|
||||
onChange={(event) => setAmount(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{tr('本次债务本金')} ({debt?.currency})
|
||||
{same ? (
|
||||
<input name="received" required readOnly value={amount} />
|
||||
) : (
|
||||
<input
|
||||
name="received"
|
||||
key={debtId}
|
||||
required
|
||||
inputMode="decimal"
|
||||
pattern="(0|[1-9][0-9]{0,15})(\.[0-9]{1,8})?"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<label>
|
||||
{tr('手续费 / 优惠')} ({account?.currency})
|
||||
<input
|
||||
name="fee"
|
||||
required
|
||||
defaultValue="0"
|
||||
inputMode="decimal"
|
||||
pattern="-?(0|[1-9][0-9]{0,15})(\.[0-9]{1,8})?"
|
||||
/>
|
||||
</label>
|
||||
<p className="muted">
|
||||
{tr('手续费只影响账户余额:付款时额外扣除,收款时从到账金额扣除;负数表示优惠。')}
|
||||
</p>
|
||||
<label>
|
||||
{tr('业务时间(北京时间,精确到分钟)')}
|
||||
<input
|
||||
name="date"
|
||||
type="datetime-local"
|
||||
required
|
||||
defaultValue={nowMinute()}
|
||||
max={nowMinute()}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{tr('备注')}
|
||||
<textarea className="note-input" name="notes" maxLength={2000} rows={2} />
|
||||
</label>
|
||||
<button className="primary" disabled={busy}>
|
||||
{busy ? tr('正在保存…') : operationLabel(operation)}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t as tr } from './i18n';
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { api } from './api';
|
||||
type Icon = { id: string; name: string; shared: boolean; source: string | null };
|
||||
@@ -55,15 +56,15 @@ export function IconLibrary({
|
||||
setError('');
|
||||
setNotice('');
|
||||
if (!image) {
|
||||
setError('请选择图片');
|
||||
setError(tr('请选择图片'));
|
||||
return;
|
||||
}
|
||||
if (!name.trim() || (shared && (!/\p{Script=Han}/u.test(name) || !confirmed))) {
|
||||
setError('请填写名称;共享图标须含中文并确认公开');
|
||||
setError(tr('请填写名称;共享图标须含中文并确认公开'));
|
||||
return;
|
||||
}
|
||||
if (image.size > 2 * 1024 * 1024) {
|
||||
setError('图片不能超过 2 MB');
|
||||
setError(tr('图片不能超过 2 MB'));
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
@@ -79,10 +80,12 @@ export function IconLibrary({
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
const v = await res.json();
|
||||
if (!res.ok) throw Error(v.message || '上传失败');
|
||||
if (!res.ok) throw Error(v.message || tr('上传失败'));
|
||||
if (picker) select(v.id);
|
||||
setNotice(
|
||||
shared ? '图标已发布,所有登录用户均可搜索和使用' : '图标已存入我的图标,仅自己可见',
|
||||
shared
|
||||
? tr('图标已发布,所有登录用户均可搜索和使用')
|
||||
: tr('图标已存入我的图标,仅自己可见'),
|
||||
);
|
||||
setName('');
|
||||
setConfirmed(false);
|
||||
@@ -99,7 +102,7 @@ export function IconLibrary({
|
||||
return (
|
||||
<section
|
||||
className="icon-library"
|
||||
aria-label="账户图标库"
|
||||
aria-label={tr('账户图标库')}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === 'Enter' &&
|
||||
@@ -109,31 +112,31 @@ export function IconLibrary({
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<h2>{picker ? '选择账户图标' : '图标库'}</h2>
|
||||
<h2>{picker ? tr('选择账户图标') : tr('图标库')}</h2>
|
||||
<p className="muted">
|
||||
搜索共享图标和我的图标,选择后可在多个账户复用。直接上传默认仅自己可见。
|
||||
{tr('搜索共享图标和我的图标,选择后可在多个账户复用。直接上传默认仅自己可见。')}
|
||||
</p>
|
||||
{picker && (
|
||||
<div className="icon-selection">
|
||||
{selected ? (
|
||||
<>
|
||||
<img src={iconUrl(selected)} alt="当前账户图标" />
|
||||
<span>已选择图标</span>
|
||||
<img src={iconUrl(selected)} alt={tr('当前账户图标')} />
|
||||
<span>{tr('已选择图标')}</span>
|
||||
<button type="button" className="text" onClick={() => select('')}>
|
||||
移除图标
|
||||
{tr('移除图标')}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<span>使用默认账户图标</span>
|
||||
<span>{tr('使用默认账户图标')}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<label>
|
||||
搜索图标名称
|
||||
{tr('搜索图标名称')}
|
||||
<input
|
||||
type="search"
|
||||
value={q}
|
||||
placeholder="例如:工商银行、支付宝"
|
||||
placeholder={tr('例如:工商银行、支付宝')}
|
||||
onChange={(e) => {
|
||||
search(e.target.value);
|
||||
setPage(1);
|
||||
@@ -142,7 +145,7 @@ export function IconLibrary({
|
||||
/>
|
||||
</label>
|
||||
{loading ? (
|
||||
<p role="status">正在加载图标…</p>
|
||||
<p role="status">{tr('正在加载图标…')}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="icon-grid">
|
||||
@@ -157,27 +160,30 @@ export function IconLibrary({
|
||||
>
|
||||
<img src={iconUrl(i.id)} alt="" loading="lazy" />
|
||||
<span>{i.name}</span>
|
||||
<small>{i.shared ? '共享' : '仅自己'}</small>
|
||||
<small>{i.shared ? tr('共享') : tr('仅自己')}</small>
|
||||
</button>
|
||||
) : (
|
||||
<div className="icon-option" key={i.id}>
|
||||
<img src={iconUrl(i.id)} alt="" loading="lazy" />
|
||||
<span>{i.name}</span>
|
||||
<small>{i.shared ? '共享' : '仅自己'}</small>
|
||||
<small>{i.shared ? tr('共享') : tr('仅自己')}</small>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
{!items.length && <p>没有匹配图标,可以在下方上传。</p>}
|
||||
{!items.length && <p>{tr('没有匹配图标,可以在下方上传。')}</p>}
|
||||
<div className="icon-pagination">
|
||||
<span>共 {total} 个图标</span>
|
||||
<span>
|
||||
{tr('共')}
|
||||
{total} {tr('个图标')}
|
||||
</span>
|
||||
<button
|
||||
className="secondary"
|
||||
type="button"
|
||||
disabled={page === 1}
|
||||
onClick={() => setPage((n) => n - 1)}
|
||||
>
|
||||
上一页
|
||||
{tr('上一页')}
|
||||
</button>
|
||||
<button
|
||||
className="secondary"
|
||||
@@ -185,24 +191,24 @@ export function IconLibrary({
|
||||
disabled={page * 60 >= total}
|
||||
onClick={() => setPage((n) => n + 1)}
|
||||
>
|
||||
下一页
|
||||
{tr('下一页')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{onSelect && (
|
||||
<button type="button" className="primary" onClick={() => onSelect(selected)}>
|
||||
应用所选图标
|
||||
{tr('应用所选图标')}
|
||||
</button>
|
||||
)}
|
||||
<details className="icon-upload">
|
||||
<summary>上传我的图标 / 导入共享图标</summary>
|
||||
<summary>{tr('上传我的图标 / 导入共享图标')}</summary>
|
||||
<label>
|
||||
图标名称(共享时必须含中文)
|
||||
{tr('图标名称(共享时必须含中文)')}
|
||||
<input value={name} onChange={(e) => setName(e.target.value)} maxLength={100} />
|
||||
</label>
|
||||
<label>
|
||||
图片文件(PNG、JPG、WebP,最大 2 MB)
|
||||
{tr('图片文件(PNG、JPG、WebP,最大 2 MB)')}
|
||||
<input ref={file} type="file" accept="image/png,image/jpeg,image/webp" />
|
||||
</label>
|
||||
<label className="check-line">
|
||||
@@ -214,7 +220,7 @@ export function IconLibrary({
|
||||
setConfirmed(false);
|
||||
}}
|
||||
/>
|
||||
发布到共享图标库
|
||||
{tr('发布到共享图标库')}
|
||||
</label>
|
||||
{shared && (
|
||||
<label className="check-line">
|
||||
@@ -223,19 +229,19 @@ export function IconLibrary({
|
||||
checked={confirmed}
|
||||
onChange={(e) => setConfirmed(e.target.checked)}
|
||||
/>
|
||||
确认此图片可公开,所有用户均可搜索并复用;已发布图标会保留供他人使用
|
||||
{tr('确认此图片可公开,所有用户均可搜索并复用;已发布图标会保留供他人使用')}
|
||||
</label>
|
||||
)}
|
||||
<button className="primary" type="button" disabled={busy} onClick={() => void upload()}>
|
||||
{busy ? '正在上传…' : shared ? '导入共享图标' : '上传私有图标'}
|
||||
{busy ? tr('正在上传…') : shared ? tr('导入共享图标') : tr('上传私有图标')}
|
||||
</button>
|
||||
</details>
|
||||
{error && (
|
||||
<p role="alert" className="icon-error">
|
||||
{error}
|
||||
{tr(error)}
|
||||
</p>
|
||||
)}
|
||||
{notice && <p role="status">{notice}</p>}
|
||||
{notice && <p role="status">{tr(notice)}</p>}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { t as tr } from './i18n';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { IconLibrary, iconUrl } from './IconLibrary';
|
||||
@@ -39,13 +40,13 @@ export function IconPicker({ initialId }: { initialId?: string | null }) {
|
||||
return (
|
||||
<div className="account-icon-field">
|
||||
<input type="hidden" name="iconId" value={id} />
|
||||
{id && <img src={iconUrl(id)} alt="已选账户图标" />}
|
||||
{id && <img src={iconUrl(id)} alt={tr('已选账户图标')} />}
|
||||
<button type="button" className="secondary" onClick={() => setOpen(true)}>
|
||||
{id ? '更换图标' : '选择图标'}
|
||||
{id ? tr('更换图标') : tr('选择图标')}
|
||||
</button>
|
||||
{id && (
|
||||
<button type="button" className="text" onClick={() => setId('')}>
|
||||
移除
|
||||
{tr('移除')}
|
||||
</button>
|
||||
)}
|
||||
{open &&
|
||||
@@ -61,12 +62,12 @@ export function IconPicker({ initialId }: { initialId?: string | null }) {
|
||||
className="icon-picker-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="选择账户图标"
|
||||
aria-label={tr('选择账户图标')}
|
||||
>
|
||||
<div className="panel-title">
|
||||
<h2>账户图标</h2>
|
||||
<h2>{tr('账户图标')}</h2>
|
||||
<button type="button" className="secondary" onClick={() => setOpen(false)}>
|
||||
关闭图标选择
|
||||
{tr('关闭图标选择')}
|
||||
</button>
|
||||
</div>
|
||||
<IconLibrary
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { t as tr } from './i18n';
|
||||
import { useState, type FormEvent } from 'react';
|
||||
import { nowMinute, money, requestUuid, type Position } from './api';
|
||||
export function TransferForm({
|
||||
accounts,
|
||||
busy,
|
||||
submit,
|
||||
initialSourceId,
|
||||
}: {
|
||||
accounts: Position[];
|
||||
busy: boolean;
|
||||
submit: (data: unknown) => void;
|
||||
initialSourceId?: string;
|
||||
}) {
|
||||
const [requestId] = useState(() => requestUuid());
|
||||
const [sourceId, setSource] = useState(accounts[0]?.id || ''),
|
||||
[targetId, setTarget] = useState(accounts[1]?.id || ''),
|
||||
const [sourceId, setSource] = useState(initialSourceId || accounts[0]?.id || ''),
|
||||
[targetId, setTarget] = useState(
|
||||
accounts.find((p) => p.id !== (initialSourceId || accounts[0]?.id))?.id || '',
|
||||
),
|
||||
[amount, setAmount] = useState('');
|
||||
const source = accounts.find((p) => p.id === sourceId),
|
||||
target = accounts.find((p) => p.id === targetId);
|
||||
@@ -21,15 +26,17 @@ export function TransferForm({
|
||||
submit(Object.fromEntries(new FormData(e.currentTarget)));
|
||||
}
|
||||
if (accounts.length < 2)
|
||||
return <p>请先创建至少两个启用中的资产账户。隐藏账户需验证密码后才能选择。</p>;
|
||||
return <p>{tr('请先创建至少两个启用中的资产账户。隐藏账户需验证密码后才能选择。')}</p>;
|
||||
return (
|
||||
<form onSubmit={save} className="transfer-form">
|
||||
<input type="hidden" name="requestId" value={requestId} />
|
||||
<p className="muted">
|
||||
双方余额同时更新。手续费从转出账户额外扣除;转账时间不能早于双方最新余额记录。
|
||||
{tr(
|
||||
'双方余额同时更新。正数手续费额外扣除,负数表示优惠抵扣(不超过转出金额);转账时间不能早于双方最新余额记录。',
|
||||
)}
|
||||
</p>
|
||||
<label>
|
||||
转出账户
|
||||
{tr('转出账户')}
|
||||
<select name="sourceId" value={sourceId} onChange={(e) => setSource(e.target.value)}>
|
||||
{accounts.map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
@@ -39,7 +46,7 @@ export function TransferForm({
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
转入账户
|
||||
{tr('转入账户')}
|
||||
<select name="targetId" value={targetId} onChange={(e) => setTarget(e.target.value)}>
|
||||
{accounts.map((p) => (
|
||||
<option key={p.id} value={p.id} disabled={p.id === sourceId}>
|
||||
@@ -50,7 +57,8 @@ export function TransferForm({
|
||||
</label>
|
||||
<div className="form-columns">
|
||||
<label>
|
||||
转出金额({source?.currency})
|
||||
{tr('转出金额(')}
|
||||
{source?.currency})
|
||||
<input
|
||||
name="amount"
|
||||
required
|
||||
@@ -61,7 +69,8 @@ export function TransferForm({
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
实际到账({target?.currency})
|
||||
{tr('实际到账(')}
|
||||
{target?.currency})
|
||||
{same ? (
|
||||
<input name="received" required readOnly value={amount} />
|
||||
) : (
|
||||
@@ -76,20 +85,24 @@ export function TransferForm({
|
||||
</label>
|
||||
</div>
|
||||
{!same && (
|
||||
<p className="notice warning">跨币种请填写银行实际到账金额;本次转账不修改公共参考汇率。</p>
|
||||
<p className="notice warning">
|
||||
{tr('跨币种请填写银行实际到账金额;本次转账不修改公共参考汇率。')}
|
||||
</p>
|
||||
)}
|
||||
<label>
|
||||
额外手续费({source?.currency})
|
||||
{tr('手续费 / 优惠(')}
|
||||
{source?.currency}
|
||||
{tr(',优惠填负数)')}
|
||||
<input
|
||||
name="fee"
|
||||
required
|
||||
defaultValue="0"
|
||||
inputMode="decimal"
|
||||
pattern="(0|[1-9][0-9]{0,15})(\.[0-9]{1,8})?"
|
||||
pattern="-?(0|[1-9][0-9]{0,15})(\.[0-9]{1,8})?"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
转账时间(北京时间)
|
||||
{tr('转账时间(北京时间)')}
|
||||
<input
|
||||
name="date"
|
||||
type="datetime-local"
|
||||
@@ -99,11 +112,11 @@ export function TransferForm({
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
备注
|
||||
{tr('备注')}
|
||||
<textarea className="note-input" name="notes" maxLength={2000} rows={2} />
|
||||
</label>
|
||||
<button className="primary" disabled={busy || sourceId === targetId}>
|
||||
{busy ? '正在转账…' : '确认转账'}
|
||||
{busy ? tr('正在转账…') : tr('确认转账')}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
|
||||
+11
-4
@@ -55,8 +55,9 @@ export type Position = {
|
||||
notes: string;
|
||||
archived: boolean;
|
||||
hidden: boolean;
|
||||
history: History[];
|
||||
outgoing: { targetId: string }[];
|
||||
converted: string | null;
|
||||
rateDate: string | null;
|
||||
outgoing: { targetId: string; target: { name: string; kind: string } }[];
|
||||
};
|
||||
export type Total = {
|
||||
date: string;
|
||||
@@ -75,6 +76,7 @@ export type Overview = Total & {
|
||||
recent: History[];
|
||||
items: {
|
||||
id: string;
|
||||
kind: string;
|
||||
converted: string | null;
|
||||
rateDate: string | null;
|
||||
currency: string;
|
||||
@@ -107,8 +109,11 @@ export function money(v: string, currency: string) {
|
||||
|
||||
export type Transfer = {
|
||||
id: string;
|
||||
source: { name: string };
|
||||
target: { name: string };
|
||||
operation: string;
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
source: { name: string; kind: string };
|
||||
target: { name: string; kind: string };
|
||||
sourceCurrency: string;
|
||||
targetCurrency: string;
|
||||
amount: string;
|
||||
@@ -126,3 +131,5 @@ export function requestUuid() {
|
||||
const hex = Array.from(bytes, (v) => v.toString(16).padStart(2, '0')).join('');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
export type PageResult<T> = { items: T[]; nextCursor: string | null; revealed: boolean };
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useSyncExternalStore, useEffect } from 'react';
|
||||
import english from './locales/en.json';
|
||||
import traditional from './locales/zh-Hant.json';
|
||||
export type Locale = 'zh-Hans' | 'en' | 'zh-Hant';
|
||||
const storageKey = 'worthpath.locale';
|
||||
export const localeOptions: { value: Locale; label: string }[] = [
|
||||
{ value: 'zh-Hans', label: '简体中文' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'zh-Hant', label: '繁體中文' },
|
||||
];
|
||||
export const validLocale = (value: unknown): value is Locale =>
|
||||
localeOptions.some((option) => option.value === value);
|
||||
function initialLocale(): Locale {
|
||||
try {
|
||||
const value = localStorage.getItem(storageKey);
|
||||
return validLocale(value) ? value : 'zh-Hans';
|
||||
} catch {
|
||||
return 'zh-Hans';
|
||||
}
|
||||
}
|
||||
let locale = initialLocale();
|
||||
const listeners = new Set<() => void>();
|
||||
const originalPhrases = new Map<string, string>();
|
||||
for (const dictionary of [english, traditional])
|
||||
for (const [key, value] of Object.entries(dictionary))
|
||||
if (!originalPhrases.has(value)) originalPhrases.set(value, key);
|
||||
const compoundPhrases = Object.keys(english)
|
||||
.filter((key) => key.length >= 6)
|
||||
.sort((a, b) => b.length - a.length);
|
||||
export function setLocale(value: Locale) {
|
||||
if (!validLocale(value)) return;
|
||||
locale = value;
|
||||
try {
|
||||
localStorage.setItem(storageKey, value);
|
||||
} catch {
|
||||
/* Session-only preference if storage is unavailable. */
|
||||
}
|
||||
listeners.forEach((listener) => listener());
|
||||
}
|
||||
export function translate(text: string, language: Locale): string {
|
||||
const original = originalPhrases.get(text) || text;
|
||||
if (language === 'zh-Hans') return original;
|
||||
const dictionary: Record<string, string> = language === 'en' ? english : traditional;
|
||||
if (dictionary[original] !== undefined) return dictionary[original];
|
||||
// Server validation messages can include a field prefix or a date. Translate
|
||||
// only known application phrases; user names and notes never pass through t().
|
||||
let result = original;
|
||||
for (const key of compoundPhrases) {
|
||||
if (result.includes(key)) result = result.split(key).join(dictionary[key]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export const t = (text: string) => translate(text, locale);
|
||||
export function useLocale() {
|
||||
const value = useSyncExternalStore(
|
||||
(listener) => {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
},
|
||||
() => locale,
|
||||
() => 'zh-Hans' as Locale,
|
||||
);
|
||||
useEffect(() => {
|
||||
document.documentElement.lang = value;
|
||||
document.title = 'WorthPath · ' + translate('我的资产空间', value);
|
||||
}, [value]);
|
||||
return value;
|
||||
}
|
||||
export function LanguagePicker() {
|
||||
const value = useLocale();
|
||||
return (
|
||||
<label className="language-picker">
|
||||
<span>{t('语言')}</span>
|
||||
<select
|
||||
aria-label={t('语言')}
|
||||
value={value}
|
||||
onChange={(event) => setLocale(event.target.value as Locale)}
|
||||
>
|
||||
{localeOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
{
|
||||
"资产总览": "Overview",
|
||||
"账户": "Accounts",
|
||||
"独立资产": "Independent assets",
|
||||
"债务": "Debts",
|
||||
"变化记录": "Activity",
|
||||
"设置与备份": "Settings & backups",
|
||||
"银行账户": "Bank account",
|
||||
"现金": "Cash",
|
||||
"投资账户": "Investment account",
|
||||
"信用卡": "Credit card",
|
||||
"贷款账户": "Loan account",
|
||||
"其他": "Other",
|
||||
"房产": "Property",
|
||||
"车辆": "Vehicle",
|
||||
"贵金属": "Precious metals",
|
||||
"其他资产": "Other assets",
|
||||
"房贷": "Mortgage",
|
||||
"车贷": "Car loan",
|
||||
"个人借款": "Personal borrowing",
|
||||
"其他债务": "Other debts",
|
||||
"备注": "Notes",
|
||||
"清空本账号数据": "Clear this account's data",
|
||||
"关闭": "Close",
|
||||
"等待更多历史记录": "Waiting for more history",
|
||||
"至少两个日期的完整余额和汇率记录,才能绘制趋势。": "At least two dates with complete balances and exchange rates are needed to draw a trend.",
|
||||
"总资产": "Total assets",
|
||||
"总负债": "Total liabilities",
|
||||
"净资产": "Net worth",
|
||||
"资产、负债和净资产历史趋势": "Historical assets, liabilities and net worth",
|
||||
"缺失汇率的日期未绘制,曲线在该处断开。": "Dates with missing exchange rates are omitted, leaving gaps in the chart.",
|
||||
"操作失败": "Operation failed",
|
||||
"长时间无操作,已自动退出登录": "Signed out automatically due to inactivity",
|
||||
"新增": "Add ",
|
||||
"WorthPath · 正在连接你的资产空间…": "WorthPath · Connecting to your asset workspace…",
|
||||
"让每一步积累,": "Give every step forward",
|
||||
"都有清晰的方向。": "a clear direction.",
|
||||
"在一个空间里,连接你的账户、资产与债务。": "Connect your accounts, assets and debts in one place.",
|
||||
"看见当下,也看见净资产的每一次变化。": "See your finances today and every change in net worth.",
|
||||
"关注长期积累": "Focus on long-term progress",
|
||||
"从第一份真实资产开始": "Start with your first real asset",
|
||||
"个人资产管理 · 多币种 · 历史追踪": "Personal asset management · Multiple currencies · Full history",
|
||||
"开始你的资产之路": "Start your asset journey",
|
||||
"创建账号": "Create account",
|
||||
"欢迎回来": "Welcome back",
|
||||
"建立属于你的私人资产空间": "Create your private asset workspace",
|
||||
"登录以查看你的资产与负债": "Sign in to view your assets and liabilities",
|
||||
"账号": "Username",
|
||||
"输入账号(至少 3 个字符)": "Enter a username (at least 3 characters)",
|
||||
"密码": "Password",
|
||||
"至少 10 个字符,最多 72 字节": "At least 10 characters, up to 72 bytes",
|
||||
"正在处理…": "Processing…",
|
||||
"注册并进入": "Register & continue",
|
||||
"登录": "Sign in",
|
||||
"已有账号?": "Already have an account?",
|
||||
"还没有账号?": "Don't have an account?",
|
||||
"个人数据按登录身份隔离": "Your data is isolated by account",
|
||||
"我的资产空间": "My asset workspace",
|
||||
"个人账户 ·": "Personal account ·",
|
||||
"退出登录": "Sign out",
|
||||
"个人财务 /": "Personal finances /",
|
||||
"本位币": "Base currency",
|
||||
"刷新数据": "Refresh data",
|
||||
"当前包含隐藏项目 · 验证 5 分钟后自动锁定": "Hidden items included · Access locks automatically after 5 minutes",
|
||||
"当前不包含隐藏账户、资产和债务,净资产按此范围计算": "Hidden accounts, assets and debts are excluded from the displayed net worth",
|
||||
"隐藏项目已锁定": "Hidden items locked",
|
||||
"锁定隐藏项目": "Lock hidden items",
|
||||
"显示隐藏资产(验证密码)": "Show hidden assets (verify password)",
|
||||
"账户图标": "Account icon",
|
||||
"你的财富全貌,从这里开始。": "Your financial picture starts here.",
|
||||
"让数据始终可控,让换算清晰可靠。": "Keep your data under control and conversions clear.",
|
||||
"保留原币金额,追踪长期变化。": "Keep original currency values and track long-term changes.",
|
||||
"账户间转账": "Transfer between accounts",
|
||||
"正在刷新数据…": "Refreshing data…",
|
||||
"开始": "From",
|
||||
"结束": "To",
|
||||
"日(最多366天)": "Daily (up to 366 days)",
|
||||
"周(最多3年)": "Weekly (up to 3 years)",
|
||||
"月(最多10年)": "Monthly (up to 10 years)",
|
||||
"默认最近90天;周一至周日及自然月取期末余额,变化归因累计区间内每日变化,首尾不足周期按实际范围。": "Defaults to 90 days. Weeks run Monday–Sunday; months use calendar boundaries. Balances are sampled at period end and daily changes are added within each period. Partial periods use the selected range.",
|
||||
"缺少": "Missing ",
|
||||
"到": " to ",
|
||||
"的汇率。以下仅展示已能换算的部分,不能作为完整总额。": " exchange rates. These are partial totals of convertible balances.",
|
||||
"设置汇率": "Exchange rate settings",
|
||||
"(部分)": " (partial)",
|
||||
"总资产 − 总负债": "Total assets − Total liabilities",
|
||||
"账户资产 + 独立资产": "Account assets + Independent assets",
|
||||
"负债账户 + 独立债务": "Liability accounts + Independent debts",
|
||||
"净资产轨迹": "Net worth trend",
|
||||
"按业务日期": "By business date",
|
||||
"按当时可用汇率重放;更正历史后重新计算。归档项目仍计入。": "Uses available exchange rates at each date. Historical corrections recalculate the trend. Archived items remain included.",
|
||||
"资产与负债组成": "Assets and liabilities",
|
||||
"个项目": " items",
|
||||
"从一个账户开始": "Start with an account",
|
||||
"添加银行、现金或投资账户,建立你的资产全貌。": "Add a bank, cash or investment account to see your finances.",
|
||||
"关联关系不会重复计入总额": "Links do not count balances twice",
|
||||
"最近变化": "Recent activity",
|
||||
"查看全部 →": "View all →",
|
||||
"· 汇率日期:": " · Rate dates:",
|
||||
"尚无外币汇率": "No foreign exchange rates yet",
|
||||
"· 每日尝试更新,失败保留已有数据;旧日期汇率可能产生换算偏差。": " · Updates are attempted daily; failures preserve existing data. Older rates may affect conversions.",
|
||||
"变化归因": "Change attribution",
|
||||
"日期": "Date",
|
||||
"余额 / 估值变化": "Balance / valuation changes",
|
||||
"汇率变化": "Exchange rate changes",
|
||||
"汇率不完整": "Incomplete exchange rates",
|
||||
"返回列表": "Back to list",
|
||||
"资产": "Asset",
|
||||
"负债": "Liability",
|
||||
"· 已归档": " · Archived",
|
||||
"隐藏项目": "Hidden item",
|
||||
"本位币:": "Base currency:",
|
||||
"缺少汇率": "Missing exchange rate",
|
||||
"暂无备注": "No notes",
|
||||
"币种和资产负债属性创建后固定。归档项目仍计入总览。": "Currency and asset/liability type are fixed at creation. Archived items remain included in totals.",
|
||||
"编辑资料": "Edit details",
|
||||
"项目已恢复": "Item restored",
|
||||
"项目已归档": "Item archived",
|
||||
"恢复项目": "Restore item",
|
||||
"归档项目": "Archive item",
|
||||
"更新": "Update ",
|
||||
"估值": "valuation",
|
||||
"余额": "balance",
|
||||
"关联资产与账户": "Linked assets and accounts",
|
||||
"管理关联": "Manage links",
|
||||
"只展示关系,不改变资产或债务的统计。": "Links display relationships without changing asset or debt totals.",
|
||||
"尚未关联": "No links yet",
|
||||
"余额与估值历史": "Balance and valuation history",
|
||||
"每次更新保留独立历史,同日按记录顺序展示。错误记录可单独更正,后续变化自动重算。": "Each update preserves a separate record. Records on the same date are ordered by sequence. Correcting a record recalculates later changes.",
|
||||
"加载更早记录": "Load earlier records",
|
||||
"折合": "Equivalent to",
|
||||
"还没有": "No ",
|
||||
"添加真实数据后,这里将展示原币余额和历史变化。": "After adding your data, this page shows original balances and changes.",
|
||||
"账户转账": "Account transfers",
|
||||
"· 手续费": " · Fee ",
|
||||
"暂无账户间转账记录。": "No account transfers yet.",
|
||||
"加载更早转账": "Load earlier transfers",
|
||||
"全部金额变化": "All balance changes",
|
||||
"原币绝对余额记录;负债减少即还款。选择项目可查看详情并更正。": "Records store absolute balances. A decrease in liabilities indicates repayment. Select an item to view details and correct records.",
|
||||
"设置分类": "Settings category",
|
||||
"个人与菜单设置": "Personal & menu settings",
|
||||
"图标库": "Icon library",
|
||||
"每日汇率": "Daily exchange rates",
|
||||
"备份与恢复": "Backup & restore",
|
||||
"账号:": "Username:",
|
||||
"。切换本位币会重新换算整个历史,原币数据保留。": ". Changing the base currency recalculates history while preserving original values.",
|
||||
"个人设置已保存": "Personal settings saved",
|
||||
"选择需要显示的菜单项。设置入口始终保留,隐藏仅影响导航,不删除数据。": "Choose which menus to display. Settings remain accessible. Hiding a menu does not delete data.",
|
||||
"显示": "Show ",
|
||||
"显示备注": "Show notes",
|
||||
"关闭后隐藏列表、详情及编辑表单中的备注,已有备注仍会保留在备份中。": "Hide notes in lists, details and edit forms. Existing notes remain in backups.",
|
||||
"无操作自动退出(分钟)": "Sign out after inactivity (minutes)",
|
||||
"关闭自动退出": "Disable automatic sign-out",
|
||||
"分钟": " minutes",
|
||||
"0 为关闭;最长 1440 分钟。到时退出登录并清除页面数据,浏览器标签页保留。": "0 disables it; maximum 1440 minutes. Signing out clears the displayed data and keeps the browser tab open.",
|
||||
"保存设置": "Save settings",
|
||||
"服务器每小时检查当日是否已尝试更新。外部请求固定下载公共汇率表,不发送个人数据。失败保留已有汇率,可稍后重试。": "The server checks hourly whether today's update has been attempted. The public rate request sends no personal data. Failed updates preserve saved rates; you can retry later.",
|
||||
"汇率已更新至数据源最新可用日期": "Exchange rates updated to the latest available source date",
|
||||
"立即更新": "Update now",
|
||||
"公共参考汇率可能与银行成交价不同;休市日日期可能滞后。": "Reference rates may differ from bank rates. Non-trading days may use earlier dates.",
|
||||
"已保存汇率": "Saved exchange rates",
|
||||
"原币 → 本位币": "Original → Base currency",
|
||||
"汇率": "Rate",
|
||||
"来源": "Source",
|
||||
"历史导入": "Imported historical rate",
|
||||
"没有保存的外币汇率": "No saved foreign exchange rates",
|
||||
"添加外币项目后更新并保存自动汇率。": "Add an item in a foreign currency, then update and save exchange rates.",
|
||||
"清除本账号全部账户、资产、债务、历史、私有图标和汇率(包括隐藏项目);保留登录账号、个人设置和已发布的共享图标。请先下载备份并确认文件已保存。": "Clear all accounts, assets, debts, history, private icons and exchange rates, including hidden items. Your login, settings and published shared icons remain. Download and save a backup first.",
|
||||
"第一步:下载备份": "Step 1: Download backup",
|
||||
"备份尚未完成,请重新下载": "Backup is not ready; please download again",
|
||||
"备份已保存,进入下一步": "Backup saved; continue",
|
||||
"确定清空": "确定清空",
|
||||
"输入“确定清空”以确认": "Type “确定清空” to confirm",
|
||||
"取消清空 / 重新下载": "Cancel / download again",
|
||||
"数据备份与恢复": "Data backup & restore",
|
||||
"ZIP 内分文件保存可读 JSON,包括全部账户、资产、债务、历史、关联、币种、设置、汇率、图标及配对转账,不限制记录条数;不包含任何认证凭据。备份含个人财务信息,请妥善保管。": "The ZIP contains separate readable JSON files for all accounts, assets, debts, history, links, currencies, settings, rates, icons and paired transfers, without record limits or login credentials. It contains personal financial information; store it securely.",
|
||||
"备份下载请求已发起,请检查浏览器下载列表": "Backup download requested; check your browser downloads",
|
||||
"下载我的数据备份": "Download my backup",
|
||||
"选择备份并验证": "Select and validate backup",
|
||||
"ZIP 文件不能超过 512 MB(不限制记录条数)": "ZIP files must not exceed 512 MB (no record count limit)",
|
||||
"ZIP 验证失败": "ZIP validation failed",
|
||||
"导入预览": "Import preview",
|
||||
"个项目 ·": " items ·",
|
||||
"条历史 ·": " history records ·",
|
||||
"条汇率": " exchange rates",
|
||||
"备份本位币": "Backup base currency",
|
||||
"/ 当前本位币": "/ Current base currency",
|
||||
"备份已完整导入": "Backup imported successfully",
|
||||
"确认追加导入": "Confirm append import",
|
||||
"取消": "Cancel",
|
||||
"WorthPath · 记录你的积累,理解你的财富": "WorthPath · Track your progress, understand your wealth",
|
||||
"第一次确认:清空本账号数据": "First confirmation: Clear this account's data",
|
||||
"第二次确认:清空本账号数据": "Second confirmation: Clear this account's data",
|
||||
"将清空本账号全部财务数据及私有图标,请确认备份已保存。": "All financial data and private icons in this account will be cleared. Confirm you have saved a backup.",
|
||||
"这是最后一次确认。清空后只能通过备份恢复,其他用户的数据不受影响。": "This is the final confirmation. You can only restore from a backup afterward. Other users' data is unaffected.",
|
||||
"登录账号、个人设置和已发布的共享图标会保留。": "Your login account, personal settings and published shared icons remain.",
|
||||
"取消清空": "Cancel clearing",
|
||||
"继续清空": "Continue clearing",
|
||||
"本账号数据已清空": "Account data cleared",
|
||||
"正在清空…": "Clearing…",
|
||||
"最终确认清空": "Final confirmation: Clear data",
|
||||
"验证密码以显示隐藏资产": "Verify password to reveal hidden assets",
|
||||
"编辑项目": "Edit item",
|
||||
"更新余额 / 估值": "Update balance / valuation",
|
||||
"更正历史记录": "Correct history",
|
||||
"管理债务关联": "Manage debt links",
|
||||
"转账已保存,双方余额已更新": "Transfer saved; both balances updated",
|
||||
"隐藏项目已解锁 5 分钟": "Hidden items unlocked for 5 minutes",
|
||||
"关联已更新": "Links updated",
|
||||
"金额历史已保存,总览已重新计算": "Balance history saved; totals recalculated",
|
||||
"项目资料已保存": "Item details saved",
|
||||
"项目已添加": "Item added",
|
||||
"登录密码": "Login password",
|
||||
"隐藏此项目(密码验证后可查看和编辑)": "Hide this item (verify your password to view or edit)",
|
||||
"名称": "Name",
|
||||
"类别": "Category",
|
||||
"信用卡、贷款账户自动计入负债,其余账户计入资产。": "Credit cards and loan accounts count as liabilities; other accounts count as assets.",
|
||||
"原币币种": "Original currency",
|
||||
"变更后的绝对金额": "Absolute balance after this change",
|
||||
"业务时间(北京时间,精确到分钟)": "Business time (Beijing time, minute precision)",
|
||||
"变更原因": "Reason for change",
|
||||
"余额更新": "Balance update",
|
||||
"估值更新": "Valuation update",
|
||||
"还款(填写还款后欠款)": "Repayment (enter remaining debt)",
|
||||
"填写更新后的余额或剩余欠款,每次保存会新增历史。已有错误记录请在历史中更正。": "Enter the updated balance or remaining debt. Each save adds a record. Correct existing mistakes in the history table.",
|
||||
"这将修改选中的历史金额并重算后续变化,请确认日期和余额。": "This changes the selected historical balance and recalculates later changes. Check the date and balance.",
|
||||
"请先创建账户或独立资产。": "Create an account or independent asset first.",
|
||||
"正在保存…": "Saving…",
|
||||
"确认更正": "Confirm correction",
|
||||
"保存": "Save",
|
||||
"项目 / 日期": "Item / Date",
|
||||
"变更前": "Before",
|
||||
"变更后": "After",
|
||||
"变化额": "Change",
|
||||
"操作": "Actions",
|
||||
"初始记录": "Initial record",
|
||||
"还款": "Repayment",
|
||||
"更正": "Correction",
|
||||
"转账转出": "Transfer out",
|
||||
"转账到账": "Transfer in",
|
||||
"暂无变化记录": "No activity yet",
|
||||
"添加项目或更新金额后,历史会自动保存在这里。": "History is saved here when you add an item or update a balance.",
|
||||
"请先创建至少两个启用中的资产账户。隐藏账户需验证密码后才能选择。": "Create at least two active asset accounts first. Hidden accounts must be unlocked before selection.",
|
||||
"双方余额同时更新。正数手续费额外扣除,负数表示优惠抵扣(不超过转出金额);转账时间不能早于双方最新余额记录。": "Both balances update together. Positive fees add to the debit; negative fees are discounts up to the transfer amount. The time cannot precede either account's latest balance record.",
|
||||
"转出账户": "Source account",
|
||||
"转入账户": "Destination account",
|
||||
"转出金额(": "Amount sent (",
|
||||
"实际到账(": "Amount received (",
|
||||
"跨币种请填写银行实际到账金额;本次转账不修改公共参考汇率。": "For different currencies, enter the actual bank credit. This transfer does not change reference rates.",
|
||||
"手续费 / 优惠(": "Fee / discount (",
|
||||
",优惠填负数)": ", enter a negative discount)",
|
||||
"转账时间(北京时间)": "Transfer time (Beijing time)",
|
||||
"正在转账…": "Transferring…",
|
||||
"确认转账": "Confirm transfer",
|
||||
"已选账户图标": "Selected account icon",
|
||||
"更换图标": "Change icon",
|
||||
"选择图标": "Choose icon",
|
||||
"移除": "Remove",
|
||||
"选择账户图标": "Choose account icon",
|
||||
"关闭图标选择": "Close icon picker",
|
||||
"请选择图片": "Select an image",
|
||||
"请填写名称;共享图标须含中文并确认公开": "Enter a name; shared icons need a Chinese name and explicit public confirmation",
|
||||
"图片不能超过 2 MB": "Images must not exceed 2 MB",
|
||||
"上传失败": "Upload failed",
|
||||
"图标已发布,所有登录用户均可搜索和使用": "Icon published; all signed-in users can search and use it",
|
||||
"图标已存入我的图标,仅自己可见": "Icon saved to your private library",
|
||||
"账户图标库": "Account icon library",
|
||||
"搜索共享图标和我的图标,选择后可在多个账户复用。直接上传默认仅自己可见。": "Search shared and private icons. Reuse a selected icon across accounts. Uploads are private by default.",
|
||||
"当前账户图标": "Current account icon",
|
||||
"已选择图标": "Selected icon",
|
||||
"移除图标": "Remove icon",
|
||||
"使用默认账户图标": "Use default account icon",
|
||||
"搜索图标名称": "Search icon names",
|
||||
"例如:工商银行、支付宝": "For example: ICBC, Alipay",
|
||||
"正在加载图标…": "Loading icons…",
|
||||
"共享": "Shared",
|
||||
"仅自己": "Private",
|
||||
"没有匹配图标,可以在下方上传。": "No matching icons. Upload one below.",
|
||||
"共": "Total ",
|
||||
"个图标": " icons",
|
||||
"上一页": "Previous",
|
||||
"下一页": "Next",
|
||||
"应用所选图标": "Apply selected icon",
|
||||
"上传我的图标 / 导入共享图标": "Upload private icon / Import shared icon",
|
||||
"图标名称(共享时必须含中文)": "Icon name (Chinese required when sharing)",
|
||||
"图片文件(PNG、JPG、WebP,最大 2 MB)": "Image file (PNG, JPG, WebP, up to 2 MB)",
|
||||
"发布到共享图标库": "Publish to shared library",
|
||||
"确认此图片可公开,所有用户均可搜索并复用;已发布图标会保留供他人使用": "I confirm this image can be public and reused by all users. Published icons remain available to others.",
|
||||
"正在上传…": "Uploading…",
|
||||
"导入共享图标": "Import shared icon",
|
||||
"上传私有图标": "Upload private icon",
|
||||
"服务响应异常": "Invalid server response",
|
||||
"语言": "Language",
|
||||
"借入到账": "Receive a loan",
|
||||
"借出付款": "Lend money",
|
||||
"收回借款": "Collect repayment",
|
||||
"偿还债务": "Repay debt",
|
||||
"借出(应收)": "Lent (receivable)",
|
||||
"借入(应付)": "Borrowed (payable)",
|
||||
"债务收款 / 还款": "Collect / repay debt",
|
||||
"账户与债务已同步更新": "Account and debt updated together",
|
||||
"请先创建启用的资产账户和借入或借出债务。": "Create an active asset account and a borrowed or lent debt first.",
|
||||
"收款账户": "Receiving account",
|
||||
"付款账户": "Paying account",
|
||||
"账户与债务同时记账并自动建立关联;这里填写本次金额,不是操作后的余额。": "The account and debt update together and are linked automatically. Enter the amount of this payment, not the resulting balance.",
|
||||
"本次账户金额": "Account payment amount",
|
||||
"本次债务本金": "Debt principal amount",
|
||||
"手续费 / 优惠": "Fee / discount",
|
||||
"手续费只影响账户余额:付款时额外扣除,收款时从到账金额扣除;负数表示优惠。": "Fees affect only the account: added to outgoing payments or deducted from receipts. Negative fees represent discounts.",
|
||||
"债务方向": "Debt direction",
|
||||
"已有债务填写剩余本金;新借入或借出可先填0,保存后通过账户联动操作记录资金。": "For existing debt, enter the remaining principal. For a new loan, start at 0 and then record the money through a linked account.",
|
||||
"收款手续费不能超过收款金额": "The receipt fee cannot exceed the receipt amount.",
|
||||
"请选择有效的资产账户和对应借入或借出债务": "Choose an active asset account and the matching borrowed or lent debt.",
|
||||
"收款或还款不能超过剩余债务": "Collection or repayment cannot exceed the remaining debt.",
|
||||
"变更后的金额超出支持范围": "The resulting balance exceeds the supported range.",
|
||||
"手续费优惠不能超过转出金额": "The discount cannot exceed the payment amount.",
|
||||
"转出账户余额不足(含手续费)": "The source account has insufficient funds, including fees.",
|
||||
"转账时间不能早于任一账户的最新余额记录,请以当前余额转账": "The payment time cannot precede either item’s latest balance record.",
|
||||
"项目不存在": "Item not found.",
|
||||
"历史记录不存在": "History record not found.",
|
||||
"请先恢复归档项目": "Restore this archived item first.",
|
||||
"转账及其之前的历史不可单独更正;请新增余额调整,保留转账双方一致": "Paired payments and their earlier history cannot be corrected separately. Add a balance adjustment to preserve both sides.",
|
||||
"余额调整时间不能早于已有转账;请使用当前时间调整": "Balance adjustments cannot precede an existing paired payment.",
|
||||
"账号或密码错误": "Incorrect username or password.",
|
||||
"未登录或会话已失效": "Not signed in, or your session has expired.",
|
||||
"服务暂时不可用,请稍后重试": "Service temporarily unavailable. Please try again.",
|
||||
"数据已存在或已被其他操作更新,请刷新后重试": "The data already exists or has changed. Refresh and retry.",
|
||||
"JSON 文件或请求格式无效": "Invalid JSON file or request format.",
|
||||
"转账请求标识已使用,请刷新后重试": "This payment request ID has been used. Refresh and retry.",
|
||||
"只能在自己的启用资产账户之间转账(隐藏账户须先解锁)": "Choose your own active items; unlock hidden items first.",
|
||||
"同币种转出与到账金额必须一致,手续费单独填写": "Amounts in the same currency must match. Enter the fee separately.",
|
||||
"日期范围超出限制": "The date range exceeds the limit.",
|
||||
"开始日期不能晚于结束日期": "The start date cannot follow the end date.",
|
||||
"请使用账户转账接口": "Use the paired payment form.",
|
||||
"手续费可为负数表示优惠,最多 16 位整数和 8 位小数": "The fee may be negative for a discount, with up to 16 integer digits and 8 decimals.",
|
||||
"尚未尝试更新": "No update attempted yet",
|
||||
"汇率更新正在进行": "Exchange rate update in progress",
|
||||
"正在更新汇率": "Updating exchange rates",
|
||||
"原币金额和已有汇率已保留": "Original balances and saved exchange rates have been preserved",
|
||||
"汇率更新失败,原币金额和已有汇率已保留,请稍后重试": "Exchange rate update failed. Original balances and saved rates are preserved. Please retry later.",
|
||||
"日期无效或在未来": "Invalid date or a future date",
|
||||
"业务时间无效或在未来(北京时间)": "Invalid or future business time (Beijing time)",
|
||||
"金额须为非负十进制字符串,最多 16 位整数和 8 位小数": "The amount must be a non-negative decimal with up to 16 integer digits and 8 decimals",
|
||||
"转出金额必须大于零": "The payment amount must be greater than zero",
|
||||
"到账金额必须大于零": "The receipt amount must be greater than zero",
|
||||
"不能向同一账户转账": "Choose two different items",
|
||||
"项目类型与资产负债属性不符": "The item type does not match its asset/liability side",
|
||||
"还款记录只能用于负债": "Repayment balance records are only available for liabilities",
|
||||
"还款后的欠款不能高于该业务日期的原欠款": "The remaining debt cannot exceed the previous debt at this business time",
|
||||
"请求来源不受信任": "Untrusted request origin",
|
||||
"会话已失效,请重新登录": "Your session expired. Please sign in again",
|
||||
"用户名已存在": "This username is already taken",
|
||||
"密码验证失败": "Password verification failed",
|
||||
"备份验证失败": "Backup validation failed",
|
||||
"转账关联无效": "Invalid paired payment links",
|
||||
"转账历史与双方金额不一致": "Paired payment history does not match its balances",
|
||||
"缺少配对转账记录": "Missing paired payment records",
|
||||
"需要先下载备份": "Download a backup first",
|
||||
"资金往来": "Money movements"
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
{
|
||||
"资产总览": "資產總覽",
|
||||
"账户": "賬戶",
|
||||
"独立资产": "獨立資產",
|
||||
"债务": "債務",
|
||||
"变化记录": "變化記錄",
|
||||
"设置与备份": "設置與備份",
|
||||
"银行账户": "銀行賬戶",
|
||||
"现金": "現金",
|
||||
"投资账户": "投資賬戶",
|
||||
"信用卡": "信用卡",
|
||||
"贷款账户": "貸款賬戶",
|
||||
"其他": "其他",
|
||||
"房产": "房產",
|
||||
"车辆": "車輛",
|
||||
"贵金属": "貴金屬",
|
||||
"其他资产": "其他資產",
|
||||
"房贷": "房貸",
|
||||
"车贷": "車貸",
|
||||
"个人借款": "個人借款",
|
||||
"其他债务": "其他債務",
|
||||
"备注": "備註",
|
||||
"清空本账号数据": "清空本賬號數據",
|
||||
"关闭": "關閉",
|
||||
"等待更多历史记录": "等待更多歷史記錄",
|
||||
"至少两个日期的完整余额和汇率记录,才能绘制趋势。": "至少兩個日期的完整餘額和匯率記錄,才能繪製趨勢。",
|
||||
"总资产": "總資產",
|
||||
"总负债": "總負債",
|
||||
"净资产": "淨資產",
|
||||
"资产、负债和净资产历史趋势": "資產、負債和淨資產歷史趨勢",
|
||||
"缺失汇率的日期未绘制,曲线在该处断开。": "缺失匯率的日期未繪製,曲線在該處斷開。",
|
||||
"操作失败": "操作失敗",
|
||||
"长时间无操作,已自动退出登录": "長時間無操作,已自動退出登錄",
|
||||
"新增": "新增",
|
||||
"WorthPath · 正在连接你的资产空间…": "WorthPath · 正在連接你的資產空間…",
|
||||
"让每一步积累,": "讓每一步積累,",
|
||||
"都有清晰的方向。": "都有清晰的方向。",
|
||||
"在一个空间里,连接你的账户、资产与债务。": "在一個空間裡,連接你的賬戶、資產與債務。",
|
||||
"看见当下,也看见净资产的每一次变化。": "看見當下,也看見淨資產的每一次變化。",
|
||||
"关注长期积累": "關注長期積累",
|
||||
"从第一份真实资产开始": "從第一份真實資產開始",
|
||||
"个人资产管理 · 多币种 · 历史追踪": "個人資產管理 · 多幣種 · 歷史追蹤",
|
||||
"开始你的资产之路": "開始你的資產之路",
|
||||
"创建账号": "創建賬號",
|
||||
"欢迎回来": "歡迎回來",
|
||||
"建立属于你的私人资产空间": "建立屬於你的私人資產空間",
|
||||
"登录以查看你的资产与负债": "登錄以查看你的資產與負債",
|
||||
"账号": "賬號",
|
||||
"输入账号(至少 3 个字符)": "輸入賬號(至少 3 個字符)",
|
||||
"密码": "密碼",
|
||||
"至少 10 个字符,最多 72 字节": "至少 10 個字符,最多 72 字節",
|
||||
"正在处理…": "正在處理…",
|
||||
"注册并进入": "註冊並進入",
|
||||
"登录": "登錄",
|
||||
"已有账号?": "已有賬號?",
|
||||
"还没有账号?": "還沒有賬號?",
|
||||
"个人数据按登录身份隔离": "個人數據按登錄身份隔離",
|
||||
"我的资产空间": "我的資產空間",
|
||||
"个人账户 ·": "個人賬戶 ·",
|
||||
"退出登录": "退出登錄",
|
||||
"个人财务 /": "個人財務 /",
|
||||
"本位币": "本位幣",
|
||||
"刷新数据": "刷新數據",
|
||||
"当前包含隐藏项目 · 验证 5 分钟后自动锁定": "當前包含隱藏項目 · 驗證 5 分鐘後自動鎖定",
|
||||
"当前不包含隐藏账户、资产和债务,净资产按此范围计算": "當前不包含隱藏賬戶、資產和債務,淨資產按此範圍計算",
|
||||
"隐藏项目已锁定": "隱藏項目已鎖定",
|
||||
"锁定隐藏项目": "鎖定隱藏項目",
|
||||
"显示隐藏资产(验证密码)": "顯示隱藏資產(驗證密碼)",
|
||||
"账户图标": "賬戶圖標",
|
||||
"你的财富全貌,从这里开始。": "你的財富全貌,從這裡開始。",
|
||||
"让数据始终可控,让换算清晰可靠。": "讓數據始終可控,讓換算清晰可靠。",
|
||||
"保留原币金额,追踪长期变化。": "保留原幣金額,追蹤長期變化。",
|
||||
"账户间转账": "賬戶間轉賬",
|
||||
"正在刷新数据…": "正在刷新數據…",
|
||||
"开始": "開始",
|
||||
"结束": "結束",
|
||||
"日(最多366天)": "日(最多366天)",
|
||||
"周(最多3年)": "周(最多3年)",
|
||||
"月(最多10年)": "月(最多10年)",
|
||||
"默认最近90天;周一至周日及自然月取期末余额,变化归因累计区间内每日变化,首尾不足周期按实际范围。": "默認最近90天;週一至週日及自然月取期末餘額,變化歸因累計區間內每日變化,首尾不足週期按實際範圍。",
|
||||
"缺少": "缺少",
|
||||
"到": "到",
|
||||
"的汇率。以下仅展示已能换算的部分,不能作为完整总额。": "的匯率。以下僅展示已能換算的部分,不能作為完整總額。",
|
||||
"设置汇率": "設置匯率",
|
||||
"(部分)": "(部分)",
|
||||
"总资产 − 总负债": "總資產 − 總負債",
|
||||
"账户资产 + 独立资产": "賬戶資產 + 獨立資產",
|
||||
"负债账户 + 独立债务": "負債賬戶 + 獨立債務",
|
||||
"净资产轨迹": "淨資產軌跡",
|
||||
"按业务日期": "按業務日期",
|
||||
"按当时可用汇率重放;更正历史后重新计算。归档项目仍计入。": "按當時可用匯率重放;更正歷史後重新計算。歸檔項目仍計入。",
|
||||
"资产与负债组成": "資產與負債組成",
|
||||
"个项目": "個項目",
|
||||
"从一个账户开始": "從一個賬戶開始",
|
||||
"添加银行、现金或投资账户,建立你的资产全貌。": "添加銀行、現金或投資賬戶,建立你的資產全貌。",
|
||||
"关联关系不会重复计入总额": "關聯關係不會重複計入總額",
|
||||
"最近变化": "最近變化",
|
||||
"查看全部 →": "查看全部 →",
|
||||
"· 汇率日期:": "· 匯率日期:",
|
||||
"尚无外币汇率": "尚無外幣匯率",
|
||||
"· 每日尝试更新,失败保留已有数据;旧日期汇率可能产生换算偏差。": "· 每日嘗試更新,失敗保留已有數據;舊日期匯率可能產生換算偏差。",
|
||||
"变化归因": "變化歸因",
|
||||
"日期": "日期",
|
||||
"余额 / 估值变化": "餘額 / 估值變化",
|
||||
"汇率变化": "匯率變化",
|
||||
"汇率不完整": "匯率不完整",
|
||||
"返回列表": "返回列表",
|
||||
"资产": "資產",
|
||||
"负债": "負債",
|
||||
"· 已归档": "· 已歸檔",
|
||||
"隐藏项目": "隱藏項目",
|
||||
"本位币:": "本位幣:",
|
||||
"缺少汇率": "缺少匯率",
|
||||
"暂无备注": "暫無備註",
|
||||
"币种和资产负债属性创建后固定。归档项目仍计入总览。": "幣種和資產負債屬性創建後固定。歸檔項目仍計入總覽。",
|
||||
"编辑资料": "編輯資料",
|
||||
"项目已恢复": "項目已恢復",
|
||||
"项目已归档": "項目已歸檔",
|
||||
"恢复项目": "恢復項目",
|
||||
"归档项目": "歸檔項目",
|
||||
"更新": "更新",
|
||||
"估值": "估值",
|
||||
"余额": "餘額",
|
||||
"关联资产与账户": "關聯資產與賬戶",
|
||||
"管理关联": "管理關聯",
|
||||
"只展示关系,不改变资产或债务的统计。": "只展示關係,不改變資產或債務的統計。",
|
||||
"尚未关联": "尚未關聯",
|
||||
"余额与估值历史": "餘額與估值歷史",
|
||||
"每次更新保留独立历史,同日按记录顺序展示。错误记录可单独更正,后续变化自动重算。": "每次更新保留獨立歷史,同日按記錄順序展示。錯誤記錄可單獨更正,後續變化自動重算。",
|
||||
"加载更早记录": "加載更早記錄",
|
||||
"折合": "摺合",
|
||||
"还没有": "還沒有",
|
||||
"添加真实数据后,这里将展示原币余额和历史变化。": "添加真實數據後,這裡將展示原幣餘額和歷史變化。",
|
||||
"账户转账": "賬戶轉賬",
|
||||
"· 手续费": "· 手續費",
|
||||
"暂无账户间转账记录。": "暫無賬戶間轉賬記錄。",
|
||||
"加载更早转账": "加載更早轉賬",
|
||||
"全部金额变化": "全部金額變化",
|
||||
"原币绝对余额记录;负债减少即还款。选择项目可查看详情并更正。": "原幣絕對餘額記錄;負債減少即還款。選擇項目可查看詳情並更正。",
|
||||
"设置分类": "設置分類",
|
||||
"个人与菜单设置": "個人與菜單設置",
|
||||
"图标库": "圖標庫",
|
||||
"每日汇率": "每日匯率",
|
||||
"备份与恢复": "備份與恢復",
|
||||
"账号:": "賬號:",
|
||||
"。切换本位币会重新换算整个历史,原币数据保留。": "。切換本位幣會重新換算整個歷史,原幣數據保留。",
|
||||
"个人设置已保存": "個人設置已保存",
|
||||
"选择需要显示的菜单项。设置入口始终保留,隐藏仅影响导航,不删除数据。": "選擇需要顯示的菜單項。設置入口始終保留,隱藏僅影響導航,不刪除數據。",
|
||||
"显示": "顯示",
|
||||
"显示备注": "顯示備註",
|
||||
"关闭后隐藏列表、详情及编辑表单中的备注,已有备注仍会保留在备份中。": "關閉後隱藏列表、詳情及編輯表單中的備註,已有備註仍會保留在備份中。",
|
||||
"无操作自动退出(分钟)": "無操作自動退出(分鐘)",
|
||||
"关闭自动退出": "關閉自動退出",
|
||||
"分钟": "分鐘",
|
||||
"0 为关闭;最长 1440 分钟。到时退出登录并清除页面数据,浏览器标签页保留。": "0 為關閉;最長 1440 分鐘。到時退出登錄並清除頁面數據,瀏覽器標籤頁保留。",
|
||||
"保存设置": "保存設置",
|
||||
"服务器每小时检查当日是否已尝试更新。外部请求固定下载公共汇率表,不发送个人数据。失败保留已有汇率,可稍后重试。": "服務器每小時檢查當日是否已嘗試更新。外部請求固定下載公共匯率表,不發送個人數據。失敗保留已有匯率,可稍後重試。",
|
||||
"汇率已更新至数据源最新可用日期": "匯率已更新至數據源最新可用日期",
|
||||
"立即更新": "立即更新",
|
||||
"公共参考汇率可能与银行成交价不同;休市日日期可能滞后。": "公共參考匯率可能與銀行成交價不同;休市日日期可能滯後。",
|
||||
"已保存汇率": "已保存匯率",
|
||||
"原币 → 本位币": "原幣 → 本位幣",
|
||||
"汇率": "匯率",
|
||||
"来源": "來源",
|
||||
"历史导入": "歷史導入",
|
||||
"没有保存的外币汇率": "沒有保存的外幣匯率",
|
||||
"添加外币项目后更新并保存自动汇率。": "添加外幣項目後更新並保存自動匯率。",
|
||||
"清除本账号全部账户、资产、债务、历史、私有图标和汇率(包括隐藏项目);保留登录账号、个人设置和已发布的共享图标。请先下载备份并确认文件已保存。": "清除本賬號全部賬戶、資產、債務、歷史、私有圖標和匯率(包括隱藏項目);保留登錄賬號、個人設置和已發佈的共享圖標。請先下載備份並確認文件已保存。",
|
||||
"第一步:下载备份": "第一步:下載備份",
|
||||
"备份尚未完成,请重新下载": "備份尚未完成,請重新下載",
|
||||
"备份已保存,进入下一步": "備份已保存,進入下一步",
|
||||
"确定清空": "确定清空",
|
||||
"输入“确定清空”以确认": "輸入“確定清空”以確認",
|
||||
"取消清空 / 重新下载": "取消清空 / 重新下載",
|
||||
"数据备份与恢复": "數據備份與恢復",
|
||||
"ZIP 内分文件保存可读 JSON,包括全部账户、资产、债务、历史、关联、币种、设置、汇率、图标及配对转账,不限制记录条数;不包含任何认证凭据。备份含个人财务信息,请妥善保管。": "ZIP 內分文件保存可讀 JSON,包括全部賬戶、資產、債務、歷史、關聯、幣種、設置、匯率、圖標及配對轉賬,不限制記錄條數;不包含任何認證憑據。備份含個人財務信息,請妥善保管。",
|
||||
"备份下载请求已发起,请检查浏览器下载列表": "備份下載請求已發起,請檢查瀏覽器下載列表",
|
||||
"下载我的数据备份": "下載我的數據備份",
|
||||
"选择备份并验证": "選擇備份並驗證",
|
||||
"ZIP 文件不能超过 512 MB(不限制记录条数)": "ZIP 文件不能超過 512 MB(不限制記錄條數)",
|
||||
"ZIP 验证失败": "ZIP 驗證失敗",
|
||||
"导入预览": "導入預覽",
|
||||
"个项目 ·": "個項目 ·",
|
||||
"条历史 ·": "條歷史 ·",
|
||||
"条汇率": "條匯率",
|
||||
"备份本位币": "備份本位幣",
|
||||
"/ 当前本位币": "/ 當前本位幣",
|
||||
"备份已完整导入": "備份已完整導入",
|
||||
"确认追加导入": "確認追加導入",
|
||||
"取消": "取消",
|
||||
"WorthPath · 记录你的积累,理解你的财富": "WorthPath · 記錄你的積累,理解你的財富",
|
||||
"第一次确认:清空本账号数据": "第一次確認:清空本賬號數據",
|
||||
"第二次确认:清空本账号数据": "第二次確認:清空本賬號數據",
|
||||
"将清空本账号全部财务数据及私有图标,请确认备份已保存。": "將清空本賬號全部財務數據及私有圖標,請確認備份已保存。",
|
||||
"这是最后一次确认。清空后只能通过备份恢复,其他用户的数据不受影响。": "這是最後一次確認。清空後只能通過備份恢復,其他用戶的數據不受影響。",
|
||||
"登录账号、个人设置和已发布的共享图标会保留。": "登錄賬號、個人設置和已發佈的共享圖標會保留。",
|
||||
"取消清空": "取消清空",
|
||||
"继续清空": "繼續清空",
|
||||
"本账号数据已清空": "本賬號數據已清空",
|
||||
"正在清空…": "正在清空…",
|
||||
"最终确认清空": "最終確認清空",
|
||||
"验证密码以显示隐藏资产": "驗證密碼以顯示隱藏資產",
|
||||
"编辑项目": "編輯項目",
|
||||
"更新余额 / 估值": "更新余額 / 估值",
|
||||
"更正历史记录": "更正歷史記錄",
|
||||
"管理债务关联": "管理債務關聯",
|
||||
"转账已保存,双方余额已更新": "轉賬已保存,雙方餘額已更新",
|
||||
"隐藏项目已解锁 5 分钟": "隱藏項目已解鎖 5 分鐘",
|
||||
"关联已更新": "關聯已更新",
|
||||
"金额历史已保存,总览已重新计算": "金額歷史已保存,總覽已重新計算",
|
||||
"项目资料已保存": "項目資料已保存",
|
||||
"项目已添加": "項目已添加",
|
||||
"登录密码": "登錄密碼",
|
||||
"隐藏此项目(密码验证后可查看和编辑)": "隱藏此項目(密碼驗證後可查看和編輯)",
|
||||
"名称": "名稱",
|
||||
"类别": "類別",
|
||||
"信用卡、贷款账户自动计入负债,其余账户计入资产。": "信用卡、貸款賬戶自動計入負債,其餘賬戶計入資產。",
|
||||
"原币币种": "原幣幣種",
|
||||
"变更后的绝对金额": "變更後的絕對金額",
|
||||
"业务时间(北京时间,精确到分钟)": "業務時間(北京時間,精確到分鐘)",
|
||||
"变更原因": "變更原因",
|
||||
"余额更新": "餘額更新",
|
||||
"估值更新": "估值更新",
|
||||
"还款(填写还款后欠款)": "還款(填寫還款後欠款)",
|
||||
"填写更新后的余额或剩余欠款,每次保存会新增历史。已有错误记录请在历史中更正。": "填寫更新後的餘額或剩餘欠款,每次保存會新增歷史。已有錯誤記錄請在歷史中更正。",
|
||||
"这将修改选中的历史金额并重算后续变化,请确认日期和余额。": "這將修改選中的歷史金額並重算後續變化,請確認日期和餘額。",
|
||||
"请先创建账户或独立资产。": "請先創建賬戶或獨立資產。",
|
||||
"正在保存…": "正在保存…",
|
||||
"确认更正": "確認更正",
|
||||
"保存": "保存",
|
||||
"项目 / 日期": "項目 / 日期",
|
||||
"变更前": "變更前",
|
||||
"变更后": "變更後",
|
||||
"变化额": "變化額",
|
||||
"操作": "操作",
|
||||
"初始记录": "初始記錄",
|
||||
"还款": "還款",
|
||||
"更正": "更正",
|
||||
"转账转出": "轉賬轉出",
|
||||
"转账到账": "轉賬到賬",
|
||||
"暂无变化记录": "暫無變化記錄",
|
||||
"添加项目或更新金额后,历史会自动保存在这里。": "添加項目或更新金額後,歷史會自動保存在這裡。",
|
||||
"请先创建至少两个启用中的资产账户。隐藏账户需验证密码后才能选择。": "請先創建至少兩個啟用中的資產賬戶。隱藏賬戶需驗證密碼後才能選擇。",
|
||||
"双方余额同时更新。正数手续费额外扣除,负数表示优惠抵扣(不超过转出金额);转账时间不能早于双方最新余额记录。": "雙方餘額同時更新。正數手續費額外扣除,負數表示優惠抵扣(不超過轉出金額);轉賬時間不能早於雙方最新余額記錄。",
|
||||
"转出账户": "轉出賬戶",
|
||||
"转入账户": "轉入賬戶",
|
||||
"转出金额(": "轉出金額(",
|
||||
"实际到账(": "實際到賬(",
|
||||
"跨币种请填写银行实际到账金额;本次转账不修改公共参考汇率。": "跨幣種請填寫銀行實際到賬金額;本次轉賬不修改公共參考匯率。",
|
||||
"手续费 / 优惠(": "手續費 / 優惠(",
|
||||
",优惠填负数)": ",優惠填負數)",
|
||||
"转账时间(北京时间)": "轉賬時間(北京時間)",
|
||||
"正在转账…": "正在轉賬…",
|
||||
"确认转账": "確認轉賬",
|
||||
"已选账户图标": "已選賬戶圖標",
|
||||
"更换图标": "更換圖標",
|
||||
"选择图标": "選擇圖標",
|
||||
"移除": "移除",
|
||||
"选择账户图标": "選擇賬戶圖標",
|
||||
"关闭图标选择": "關閉圖標選擇",
|
||||
"请选择图片": "請選擇圖片",
|
||||
"请填写名称;共享图标须含中文并确认公开": "請填寫名稱;共享圖標須含中文並確認公開",
|
||||
"图片不能超过 2 MB": "圖片不能超過 2 MB",
|
||||
"上传失败": "上傳失敗",
|
||||
"图标已发布,所有登录用户均可搜索和使用": "圖標已發佈,所有登錄用戶均可搜索和使用",
|
||||
"图标已存入我的图标,仅自己可见": "圖標已存入我的圖標,僅自己可見",
|
||||
"账户图标库": "賬戶圖標庫",
|
||||
"搜索共享图标和我的图标,选择后可在多个账户复用。直接上传默认仅自己可见。": "搜索共享圖標和我的圖標,選擇後可在多個賬戶複用。直接上傳默認僅自己可見。",
|
||||
"当前账户图标": "當前賬戶圖標",
|
||||
"已选择图标": "已選擇圖標",
|
||||
"移除图标": "移除圖標",
|
||||
"使用默认账户图标": "使用默認賬戶圖標",
|
||||
"搜索图标名称": "搜索圖標名稱",
|
||||
"例如:工商银行、支付宝": "例如:工商銀行、支付寶",
|
||||
"正在加载图标…": "正在加載圖標…",
|
||||
"共享": "共享",
|
||||
"仅自己": "僅自己",
|
||||
"没有匹配图标,可以在下方上传。": "沒有匹配圖標,可以在下方上傳。",
|
||||
"共": "共",
|
||||
"个图标": "個圖標",
|
||||
"上一页": "上一頁",
|
||||
"下一页": "下一頁",
|
||||
"应用所选图标": "應用所選圖標",
|
||||
"上传我的图标 / 导入共享图标": "上傳我的圖標 / 導入共享圖標",
|
||||
"图标名称(共享时必须含中文)": "圖標名稱(共享時必須含中文)",
|
||||
"图片文件(PNG、JPG、WebP,最大 2 MB)": "圖片文件(PNG、JPG、WebP,最大 2 MB)",
|
||||
"发布到共享图标库": "發佈到共享圖標庫",
|
||||
"确认此图片可公开,所有用户均可搜索并复用;已发布图标会保留供他人使用": "確認此圖片可公開,所有用戶均可搜索並複用;已發佈圖標會保留供他人使用",
|
||||
"正在上传…": "正在上傳…",
|
||||
"导入共享图标": "導入共享圖標",
|
||||
"上传私有图标": "上傳私有圖標",
|
||||
"服务响应异常": "服務響應異常",
|
||||
"语言": "語言",
|
||||
"借入到账": "借入到賬",
|
||||
"借出付款": "借出付款",
|
||||
"收回借款": "收回借款",
|
||||
"偿还债务": "償還債務",
|
||||
"借出(应收)": "借出(應收)",
|
||||
"借入(应付)": "借入(應付)",
|
||||
"债务收款 / 还款": "債務收款 / 還款",
|
||||
"账户与债务已同步更新": "賬戶與債務已同步更新",
|
||||
"请先创建启用的资产账户和借入或借出债务。": "請先創建啟用的資產賬戶和借入或借出債務。",
|
||||
"收款账户": "收款賬戶",
|
||||
"付款账户": "付款賬戶",
|
||||
"账户与债务同时记账并自动建立关联;这里填写本次金额,不是操作后的余额。": "賬戶與債務同時記賬並自動建立關聯;這裡填寫本次金額,不是操作後的餘額。",
|
||||
"本次账户金额": "本次賬戶金額",
|
||||
"本次债务本金": "本次債務本金",
|
||||
"手续费 / 优惠": "手續費 / 優惠",
|
||||
"手续费只影响账户余额:付款时额外扣除,收款时从到账金额扣除;负数表示优惠。": "手續費隻影響賬戶餘額:付款時額外扣除,收款時從到賬金額扣除;負數表示優惠。",
|
||||
"债务方向": "債務方向",
|
||||
"已有债务填写剩余本金;新借入或借出可先填0,保存后通过账户联动操作记录资金。": "已有債務填寫剩餘本金;新借入或借出可先填0,保存後通過賬戶聯動操作記錄資金。",
|
||||
"收款手续费不能超过收款金额": "收款手續費不能超過收款金額",
|
||||
"请选择有效的资产账户和对应借入或借出债务": "請選擇有效的資產賬戶和對應借入或借出債務",
|
||||
"收款或还款不能超过剩余债务": "收款或還款不能超過剩餘債務",
|
||||
"变更后的金额超出支持范围": "變更後的金額超出支持範圍",
|
||||
"手续费优惠不能超过转出金额": "手續費優惠不能超過轉出金額",
|
||||
"转出账户余额不足(含手续费)": "轉出賬戶餘額不足(含手續費)",
|
||||
"转账时间不能早于任一账户的最新余额记录,请以当前余额转账": "轉賬時間不能早於任一賬戶的最新余額記錄,請以當前餘額轉賬",
|
||||
"项目不存在": "項目不存在",
|
||||
"历史记录不存在": "歷史記錄不存在",
|
||||
"请先恢复归档项目": "請先恢復歸檔項目",
|
||||
"转账及其之前的历史不可单独更正;请新增余额调整,保留转账双方一致": "轉賬及其之前的歷史不可單獨更正;請新增餘額調整,保留轉賬雙方一致",
|
||||
"余额调整时间不能早于已有转账;请使用当前时间调整": "餘額調整時間不能早於已有轉賬;請使用當前時間調整",
|
||||
"账号或密码错误": "賬號或密碼錯誤",
|
||||
"未登录或会话已失效": "未登錄或會話已失效",
|
||||
"服务暂时不可用,请稍后重试": "服務暫時不可用,請稍後重試",
|
||||
"数据已存在或已被其他操作更新,请刷新后重试": "數據已存在或已被其他操作更新,請刷新後重試",
|
||||
"JSON 文件或请求格式无效": "JSON 文件或請求格式無效",
|
||||
"转账请求标识已使用,请刷新后重试": "轉賬請求標識已使用,請刷新後重試",
|
||||
"只能在自己的启用资产账户之间转账(隐藏账户须先解锁)": "只能在自己的啟用資產賬戶之間轉賬(隱藏賬戶須先解鎖)",
|
||||
"同币种转出与到账金额必须一致,手续费单独填写": "同幣種轉出與到賬金額必須一致,手續費單獨填寫",
|
||||
"日期范围超出限制": "日期範圍超出限制",
|
||||
"开始日期不能晚于结束日期": "開始日期不能晚於結束日期",
|
||||
"请使用账户转账接口": "請使用賬戶轉賬接口",
|
||||
"手续费可为负数表示优惠,最多 16 位整数和 8 位小数": "手續費可為負數表示優惠,最多 16 位整數和 8 位小數",
|
||||
"尚未尝试更新": "尚未嘗試更新",
|
||||
"汇率更新正在进行": "匯率更新正在進行",
|
||||
"正在更新汇率": "正在更新匯率",
|
||||
"原币金额和已有汇率已保留": "原幣金額和已有匯率已保留",
|
||||
"汇率更新失败,原币金额和已有汇率已保留,请稍后重试": "匯率更新失敗,原幣金額和已有匯率已保留,請稍後重試",
|
||||
"日期无效或在未来": "日期無效或在未來",
|
||||
"业务时间无效或在未来(北京时间)": "業務時間無效或在未來(北京時間)",
|
||||
"金额须为非负十进制字符串,最多 16 位整数和 8 位小数": "金額須為非負十進制字符串,最多 16 位整數和 8 位小數",
|
||||
"转出金额必须大于零": "轉出金額必須大於零",
|
||||
"到账金额必须大于零": "到賬金額必須大於零",
|
||||
"不能向同一账户转账": "不能向同一賬戶轉賬",
|
||||
"项目类型与资产负债属性不符": "項目類型與資產負債屬性不符",
|
||||
"还款记录只能用于负债": "還款記錄只能用於負債",
|
||||
"还款后的欠款不能高于该业务日期的原欠款": "還款後的欠款不能高於該業務日期的原欠款",
|
||||
"请求来源不受信任": "請求來源不受信任",
|
||||
"会话已失效,请重新登录": "會話已失效,請重新登錄",
|
||||
"用户名已存在": "用戶名已存在",
|
||||
"密码验证失败": "密碼驗證失敗",
|
||||
"备份验证失败": "備份驗證失敗",
|
||||
"转账关联无效": "轉賬關聯無效",
|
||||
"转账历史与双方金额不一致": "轉賬歷史與雙方金額不一致",
|
||||
"缺少配对转账记录": "缺少配對轉賬記錄",
|
||||
"需要先下载备份": "需要先下載備份",
|
||||
"资金往来": "資金往來"
|
||||
}
|
||||
@@ -1177,3 +1177,29 @@ footer {
|
||||
.notes-hidden label:has(.note-input) {
|
||||
display: none !important;
|
||||
}
|
||||
.language-picker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.language-picker select {
|
||||
width: auto;
|
||||
min-width: 7rem;
|
||||
padding: 0.4rem 0.65rem;
|
||||
}
|
||||
.auth-language {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 2;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.language-picker span {
|
||||
display: none;
|
||||
}
|
||||
.topbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user