fix(i18n): preserve remaining Vietnamese localization (#7935)

* fix(i18n): preserve remaining Vietnamese localization

* chore(quality): rebaseline file-size cap for 9 dashboard components (i18n wiring)

Restoring the Vietnamese localization on 9 dashboard components (useTranslations
wiring + t()/tc() call-site swaps for previously hardcoded strings) grows each
file by a small, irreducible amount. Bumps the frozen file-size-baseline.json
caps to match, with a justification entry per the project's own ratchet policy.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(i18n): wire weekday localization + add missing qwen CLI description

Two gaps left by this PR's own new contract tests, caught while
reconciling the branch against the release tip:

- CostOverviewTab.tsx added formatWeekdayLabel() but never called it;
  the Weekly Usage Pattern chart still showed raw English day
  abbreviations regardless of locale. Now maps weeklyPattern rows
  through it before handing them to WeeklyPatternCard.
- cliTools.toolDescriptions was missing an entry for "qwen" (a
  baseUrlSupport:"full" tool) in both en.json and vi.json, failing
  the PR's own cli-catalog-display-contract.test.ts.

Covered by the PR's existing tests/unit/dashboard-localization-contract.test.ts
and tests/unit/cli-catalog-display-contract.test.ts (both now pass).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* i18n(vi): backfill the 4 proxySubscription keys #7299 added to en.json

#7299 (proxy subscriptions) merged while this branch was rebasing, adding
settings.proxySubscriptionsTab and settings.proxySubscription.error.{LOCAL_CORE_ENDPOINT_INVALID,
NEEDS_CORE_NOT_CONFIGURED,NO_USABLE_NODES} to en.json. This PR's own
i18n-vi-completeness contract asserts full en↔vi key parity, so the merge of the
current release tip surfaced them as missing. Adds the Vietnamese translations,
keeping the SS/VMess/Trojan/VLESS/SOCKS5 technical terms verbatim.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: nguyenha935 <208228297+nguyenha935@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: nguyenha935 <nguyenha935@users.noreply.github.com>
This commit is contained in:
nguyenha935
2026-07-21 23:41:02 +07:00
committed by GitHub
parent eab59d4048
commit 4012bac41d
207 changed files with 12021 additions and 5221 deletions

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect, useCallback, useRef, type ReactNode } from "react";
import { useTranslations } from "next-intl";
import Card from "./Card";
import Button from "./Button";
import DistributeProxiesButton from "./DistributeProxiesButton";
@@ -92,13 +93,16 @@ export default function NoAuthAccountCard({
generateAccountId,
generateApiKey,
dataKey = "fingerprints",
description = "Ready to use — no signup needed. Add accounts for rate-limit rotation.",
addLabel = "Add Account",
description,
addLabel,
enabled = true,
savingEnabled = false,
onEnabledChange,
providerProxyControl,
}: NoAuthAccountCardProps) {
const t = useTranslations("noAuthProvider");
const resolvedDescription = description || t("accountDescription");
const resolvedAddLabel = addLabel || t("addAccount");
const [connections, setConnections] = useState<Connection[]>([]);
const [loading, setLoading] = useState(true);
const [adding, setAdding] = useState(false);
@@ -144,8 +148,12 @@ export default function NoAuthAccountCard({
}, []);
useEffect(() => {
void fetchConnections();
void fetchSavedProxies();
const loadTimer = window.setTimeout(() => {
void fetchConnections();
void fetchSavedProxies();
}, 0);
return () => window.clearTimeout(loadTimer);
}, [fetchConnections, fetchSavedProxies]);
useEffect(() => {
@@ -176,14 +184,14 @@ export default function NoAuthAccountCard({
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: providerId,
name: `${providerName} Account 1`,
name: t("accountName", { provider: providerName, number: 1 }),
...(apiKey ? { apiKey } : {}),
providerSpecificData: { [dataKey]: [accountId] },
}),
});
if (!res.ok) {
const errData = await res.json().catch(() => ({}));
throw new Error(errData?.error || `Failed to create connection (${res.status})`);
throw new Error(errData?.error || t("createConnectionFailed"));
}
} else {
const updated = [...allAccountIds, accountId];
@@ -194,7 +202,7 @@ export default function NoAuthAccountCard({
providerSpecificData: { [dataKey]: updated },
}),
});
if (!res.ok) throw new Error("Failed to update connection");
if (!res.ok) throw new Error(t("updateConnectionFailed"));
}
await fetchConnections();
} catch (err) {
@@ -302,11 +310,11 @@ export default function NoAuthAccountCard({
if (!conn || allAccountIds.length === 0) return;
const proxiesRes = await fetch("/api/settings/proxies");
if (!proxiesRes.ok) throw new Error("Failed to fetch proxies");
if (!proxiesRes.ok) throw new Error(t("fetchProxiesFailed"));
const proxiesData = await proxiesRes.json();
const savedProxies = (proxiesData?.items || []).filter((p: any) => p.status === "active");
if (savedProxies.length === 0) {
throw new Error("No saved proxies found. Add proxies in Settings → Proxy first.");
throw new Error(t("noSavedProxiesError"));
}
// #5217 (Gap 1): distribute stores by-id references too, so editing a pool
@@ -323,7 +331,7 @@ export default function NoAuthAccountCard({
providerSpecificData: { accountProxies: updatedProxies },
}),
});
if (!res.ok) throw new Error("Failed to update connection");
if (!res.ok) throw new Error(t("updateConnectionFailed"));
await fetchConnections();
};
@@ -336,8 +344,8 @@ export default function NoAuthAccountCard({
<span className="material-symbols-outlined text-[20px]">lock_open</span>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium">No authentication required</p>
<p className="text-xs text-text-muted">{description}</p>
<p className="text-sm font-medium">{t("title")}</p>
<p className="text-xs text-text-muted">{resolvedDescription}</p>
</div>
</div>
<div className="flex w-full flex-wrap items-center justify-end gap-2 sm:w-auto sm:flex-nowrap">
@@ -354,7 +362,7 @@ export default function NoAuthAccountCard({
<div className="border-t border-border pt-3 mt-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-sm font-medium">
Accounts ({loading ? "..." : allAccountIds.length})
{t("accounts", { count: loading ? "..." : allAccountIds.length })}
</span>
<div className="flex items-center justify-end gap-2">
{!loading && allAccountIds.length > 0 && (
@@ -365,14 +373,14 @@ export default function NoAuthAccountCard({
/>
)}
<Button size="sm" icon="add" onClick={handleAddAccount} disabled={adding || !enabled}>
{adding ? "Adding..." : addLabel}
{adding ? t("adding") : resolvedAddLabel}
</Button>
</div>
</div>
{!loading && allAccountIds.length === 0 && (
<p className="text-xs text-text-muted py-2">
Using auto-generated account. Click &quot;{addLabel}&quot; for rate-limit rotation.
{t("autoGeneratedAccount", { addLabel: resolvedAddLabel })}
</p>
)}
@@ -405,9 +413,11 @@ export default function NoAuthAccountCard({
title={
proxy
? `Proxy: ${proxy.type}://${proxy.host}:${proxy.port}`
: "Configure proxy"
: t("configureProxy")
}
aria-label={
proxy ? t("proxyConfigured", { host: proxy.host }) : t("configureProxy")
}
aria-label={proxy ? `Proxy configured: ${proxy.host}` : "Configure proxy"}
>
<span
className="material-symbols-outlined text-[16px]"
@@ -420,7 +430,7 @@ export default function NoAuthAccountCard({
type="button"
onClick={() => handleRemoveAccount(id)}
className="shrink-0 rounded p-1 text-text-muted opacity-0 transition-colors hover:bg-red-500/10 hover:text-red-500 group-hover:opacity-100"
aria-label="Remove account"
aria-label={t("removeAccount")}
>
<span className="material-symbols-outlined text-[16px]">delete</span>
</button>
@@ -437,7 +447,9 @@ export default function NoAuthAccountCard({
className="w-80 max-w-full rounded-lg border border-black/10 bg-surface p-4 shadow-lg dark:border-white/10"
>
<p className="mb-3 text-sm font-medium">
Proxy for Account {allAccountIds.indexOf(proxyAccountId) + 1}
{t("proxyForAccount", {
number: allAccountIds.indexOf(proxyAccountId) + 1,
})}
</p>
<div className="space-y-3">
{/* #5217 (Gap 1): pick a pre-saved Proxy Pool entry by reference,
@@ -452,7 +464,7 @@ export default function NoAuthAccountCard({
: "text-text-muted hover:text-text-main"
}`}
>
Saved
{t("saved")}
</button>
<button
type="button"
@@ -463,7 +475,7 @@ export default function NoAuthAccountCard({
: "text-text-muted hover:text-text-main"
}`}
>
Custom
{t("custom")}
</button>
</div>
@@ -474,9 +486,7 @@ export default function NoAuthAccountCard({
className="w-full rounded-md border border-black/10 bg-bg px-2.5 py-1.5 text-xs dark:border-white/10"
>
<option value="">
{savedProxies.length === 0
? "No saved proxies — add one in Settings → Proxy"
: "Direct (no proxy)"}
{savedProxies.length === 0 ? t("noSavedProxies") : t("directConnection")}
</option>
{savedProxies.map((p) => (
<option key={p.id} value={p.id}>
@@ -502,14 +512,14 @@ export default function NoAuthAccountCard({
type="text"
value={proxyHost}
onChange={(e) => setProxyHost(e.target.value)}
placeholder="Host"
placeholder={t("host")}
className="flex-1 rounded-md border border-black/10 bg-bg px-2.5 py-1.5 text-xs dark:border-white/10"
/>
<input
type="text"
value={proxyPort}
onChange={(e) => setProxyPort(e.target.value)}
placeholder="Port"
placeholder={t("port")}
className="w-16 rounded-md border border-black/10 bg-bg px-2.5 py-1.5 text-xs dark:border-white/10"
/>
</div>
@@ -517,14 +527,14 @@ export default function NoAuthAccountCard({
type="text"
value={proxyUsername}
onChange={(e) => setProxyUsername(e.target.value)}
placeholder="Username (optional)"
placeholder={t("usernameOptional")}
className="w-full rounded-md border border-black/10 bg-bg px-2.5 py-1.5 text-xs dark:border-white/10"
/>
<input
type="password"
value={proxyPassword}
onChange={(e) => setProxyPassword(e.target.value)}
placeholder="Password (optional)"
placeholder={t("passwordOptional")}
className="w-full rounded-md border border-black/10 bg-bg px-2.5 py-1.5 text-xs dark:border-white/10"
/>
</>
@@ -534,14 +544,14 @@ export default function NoAuthAccountCard({
onClick={() => setProxyAccountId(null)}
className="rounded-md px-3 py-1.5 text-xs text-text-muted transition-colors hover:bg-black/5 hover:text-text-main dark:hover:bg-white/5"
>
Cancel
{t("cancel")}
</button>
<button
onClick={handleSaveProxy}
disabled={savingProxy}
className="rounded-md bg-primary/10 px-3 py-1.5 text-xs text-primary transition-colors hover:bg-primary/20 disabled:opacity-50"
>
{savingProxy ? "Saving..." : "Save"}
{savingProxy ? t("saving") : t("save")}
</button>
</div>
</div>