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

@@ -6,10 +6,6 @@
// (parent owns the state + persistence). All mutations go through the pure
// `compressionPipelineModel` so invariants (valid intensity, non-empty pipeline) hold.
//
// Hydration note: this lives under the combos screen, which deliberately uses NO
// `useTranslations` (an earlier redesign failed to hydrate on the production build with a
// page-level `useTranslations`). Strings are literal English to match `CompressionHub`.
import {
DndContext,
closestCenter,
@@ -26,6 +22,7 @@ import {
sortableKeyboardCoordinates,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useTranslations } from "next-intl";
import {
allowedIntensities,
addLayer,
@@ -57,6 +54,7 @@ function SortableRow(props: {
onPatch: (patch: Partial<PipelineStep>) => void;
onRemove: () => void;
}) {
const t = useTranslations("contextCombos");
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: props.id,
});
@@ -74,7 +72,7 @@ function SortableRow(props: {
>
<button
type="button"
aria-label="Drag to reorder step"
aria-label={t("dragToReorder")}
data-testid={`pipeline-drag-${props.index}`}
className="cursor-grab rounded-lg border border-border px-2 py-2 text-sm text-text-muted"
{...attributes}
@@ -83,7 +81,7 @@ function SortableRow(props: {
</button>
<select
aria-label="Engine"
aria-label={t("engine")}
value={props.step.engine}
onChange={(event) => props.onPatch({ engine: event.target.value })}
className={SELECT_CLASS}
@@ -95,7 +93,7 @@ function SortableRow(props: {
))}
</select>
<select
aria-label="Intensity"
aria-label={t("intensity")}
value={props.step.intensity ?? ""}
onChange={(event) => props.onPatch({ intensity: event.target.value })}
className={SELECT_CLASS}
@@ -113,13 +111,14 @@ function SortableRow(props: {
data-testid={`pipeline-remove-${props.index}`}
className="rounded-lg border border-border px-3 py-2 text-sm text-text-main disabled:opacity-50"
>
Remove
{t("removeStep")}
</button>
</div>
);
}
export function CompressionPipelineEditor({ steps, onChange, engineIntensities }: Props) {
const t = useTranslations("contextCombos");
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
@@ -141,14 +140,14 @@ export function CompressionPipelineEditor({ steps, onChange, engineIntensities }
return (
<div className="space-y-3" data-testid="compression-pipeline-editor">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-text-main">Pipeline</h3>
<h3 className="text-sm font-semibold text-text-main">{t("pipeline")}</h3>
<button
type="button"
data-testid="pipeline-add-step"
onClick={() => onChange(addLayer(steps, { engine: firstEngine }, engineIntensities))}
className="rounded-lg border border-border px-3 py-1.5 text-xs text-text-main"
>
Add step
{t("addStep")}
</button>
</div>
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>

View File

@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useLocale, useTranslations } from "next-intl";
import type { EngineConfigField } from "@omniroute/open-sse/services/compression/engines/types";
import { EngineConfigForm } from "@/shared/components/compression/EngineConfigForm";
@@ -63,10 +64,9 @@ interface PreviewResult {
// ── Default preview sample ────────────────────────────────────────────────
const PREVIEW_SAMPLE =
"The quick brown fox jumps over the lazy dog. " +
"This is a sample message used to preview compression. " +
"It contains enough text to show meaningful token savings.";
const ENGINE_ICON_ALIASES: Record<string, string> = {
brain: "psychology",
};
// ── Sub-components ────────────────────────────────────────────────────────
@@ -79,7 +79,11 @@ function StatCard({ label, value }: { label: string; value: string }) {
);
}
function renderDiffSegment(segment: PreviewDiffSegment, index: number) {
function renderDiffSegment(
segment: PreviewDiffSegment,
index: number,
translateLabel: (label: string) => string
) {
const label = segment.type ?? "change";
const text =
segment.value ??
@@ -93,7 +97,7 @@ function renderDiffSegment(segment: PreviewDiffSegment, index: number) {
return (
<div key={`${label}-${index}`} className="rounded border border-border bg-background p-2">
<span className="mr-2 rounded bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-text-muted">
{label}
{translateLabel(label)}
</span>
<span className="whitespace-pre-wrap break-words text-text">{text}</span>
</div>
@@ -103,6 +107,8 @@ function renderDiffSegment(segment: PreviewDiffSegment, index: number) {
// ── Main component ────────────────────────────────────────────────────────
export function EngineConfigPage({ engineId }: { engineId: string }) {
const locale = useLocale();
const t = useTranslations("compressionEngineConfig");
// ── Data state ──────────────────────────────────────────────────────────
const [engine, setEngine] = useState<EngineEntry | null>(null);
const [configState, setConfigState] = useState<Record<string, unknown>>({});
@@ -111,7 +117,7 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
const [loading, setLoading] = useState(true);
// ── Preview state ───────────────────────────────────────────────────────
const [previewText, setPreviewText] = useState(PREVIEW_SAMPLE);
const [previewText, setPreviewText] = useState(() => t("previewSample"));
const [preview, setPreview] = useState<PreviewResult | null>(null);
const [previewError, setPreviewError] = useState<string | null>(null);
const [previewLoading, setPreviewLoading] = useState(false);
@@ -147,7 +153,7 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
if (enginesData) {
foundEngine = enginesData.engines?.find((e) => e.id === engineId) ?? null;
} else {
setLoadError("Failed to load engine information.");
setLoadError(t("loadFailed"));
}
// Detailed config lives in the engine's settings sub-object (when it has one);
@@ -174,7 +180,7 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
return () => {
cancelled = true;
};
}, [engineId]);
}, [engineId, t]);
// ── Handlers ─────────────────────────────────────────────────────────────
@@ -200,10 +206,10 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
body: JSON.stringify({ [subKey]: detail }),
});
if (!res.ok) {
setSaveError("Failed to save configuration.");
setSaveError(t("saveFailed"));
}
} catch {
setSaveError("Failed to save configuration.");
setSaveError(t("saveFailed"));
} finally {
setSaving(false);
}
@@ -226,10 +232,10 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
const data = (await res.json()) as PreviewResult;
setPreview(data);
} else {
setPreviewError("Preview failed.");
setPreviewError(t("previewFailed"));
}
} catch {
setPreviewError("Preview failed.");
setPreviewError(t("previewFailed"));
} finally {
setPreviewLoading(false);
}
@@ -239,20 +245,48 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
if (loading) {
return (
<div className="flex items-center justify-center p-12 text-text-muted text-sm">Loading</div>
<div className="flex items-center justify-center p-12 text-text-muted text-sm">
{t("loading")}
</div>
);
}
if (!engine) {
return (
<div className="p-6 text-sm text-text-muted">
{loadError ?? `Engine "${engineId}" not found.`}
{loadError ?? t("engineNotFound", { engine: engineId })}
</div>
);
}
const subtitle = engine.metadata?.description ?? engine.description;
const visibleConfigSchema = engine.configSchema.filter((field) => field.key !== "enabled");
const engineNameKey = `engines.${engineId}.name`;
const engineDescriptionKey = `engines.${engineId}.description`;
const engineName = t.has(engineNameKey) ? t(engineNameKey) : engine.name;
const rawSubtitle = engine.metadata?.description ?? engine.description;
const subtitle = t.has(engineDescriptionKey) ? t(engineDescriptionKey) : rawSubtitle;
const visibleConfigSchema = engine.configSchema
.filter((field) => field.key !== "enabled")
.map((field) => {
const engineFieldPrefix = `engineFields.${engineId}.${field.key}`;
const fieldPrefix = `fields.${field.key}`;
const labelKey = t.has(`${engineFieldPrefix}.label`)
? `${engineFieldPrefix}.label`
: `${fieldPrefix}.label`;
const descriptionKey = t.has(`${engineFieldPrefix}.description`)
? `${engineFieldPrefix}.description`
: `${fieldPrefix}.description`;
return {
...field,
label: t.has(labelKey) ? t(labelKey) : field.label,
description:
field.description && t.has(descriptionKey) ? t(descriptionKey) : field.description,
options: field.options?.map((option) => {
const optionKey = `options.${field.key}.${option.value}`;
return { ...option, label: t.has(optionKey) ? t(optionKey) : option.label };
}),
};
});
// Only engines with a dedicated settings sub-object can persist their detail here.
const persistable = Boolean(SETTINGS_SUBOBJECT[engineId]);
@@ -266,10 +300,10 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
className="material-symbols-outlined text-[28px] leading-none text-text-muted"
aria-hidden="true"
>
{engine.icon}
{ENGINE_ICON_ALIASES[engine.icon] || engine.icon}
</span>
)}
<h1 className="text-2xl font-bold text-text">{engine.name}</h1>
<h1 className="text-2xl font-bold text-text">{engineName}</h1>
</div>
{subtitle && <p className="text-sm text-text-muted">{subtitle}</p>}
</div>
@@ -283,17 +317,17 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
{/* ── Panel pointer (on/off + level live there now) ── */}
<div className="flex flex-col gap-1 rounded-lg border border-border bg-surface p-4">
<p className="text-xs text-text-muted" data-testid="panel-pointer-notice">
Turn this layer on/off and set its level in{" "}
{t("panelPointerPrefix")}{" "}
<a href="/dashboard/context/settings" className="underline hover:text-text">
Compression Settings
{t("compressionSettings")}
</a>
. This page edits its detailed configuration only.
{t("panelPointerSuffix")}
</p>
</div>
{/* ── Config form ── */}
<div className="flex flex-col gap-3 rounded-lg border border-border bg-surface p-4">
<h2 className="text-sm font-semibold text-text">Configuration</h2>
<h2 className="text-sm font-semibold text-text">{t("configuration")}</h2>
{visibleConfigSchema.length > 0 ? (
<EngineConfigForm
schema={visibleConfigSchema}
@@ -301,7 +335,7 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
onChange={setConfigState}
/>
) : (
<p className="text-sm text-text-muted">No additional configuration.</p>
<p className="text-sm text-text-muted">{t("noAdditionalConfiguration")}</p>
)}
<div className="flex items-center gap-3 pt-1">
{persistable ? (
@@ -310,12 +344,11 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
disabled={saving}
className="px-4 py-1.5 rounded bg-primary text-primary-foreground text-sm font-medium disabled:opacity-50"
>
{saving ? "Saving..." : "Save"}
{saving ? t("saving") : t("save")}
</button>
) : (
<p className="text-xs text-text-muted" data-testid="no-detail-store-notice">
This layer is configured by the global settings; there is no per-engine override to
save here yet.
{t("globalSettingsOnly")}
</p>
)}
{saveError && <p className="text-xs text-destructive">{saveError}</p>}
@@ -324,12 +357,12 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
{/* ── Live preview ── */}
<div className="flex flex-col gap-3 rounded-lg border border-border bg-surface p-4">
<h2 className="text-sm font-semibold text-text">Preview</h2>
<h2 className="text-sm font-semibold text-text">{t("preview")}</h2>
<textarea
className="border border-border rounded px-3 py-2 text-sm text-text bg-background resize-y min-h-[80px]"
value={previewText}
onChange={(e) => setPreviewText(e.target.value)}
aria-label="Preview input"
aria-label={t("previewInput")}
/>
<div className="flex items-center gap-3">
<button
@@ -337,7 +370,7 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
disabled={previewLoading}
className="px-4 py-1.5 rounded bg-primary text-primary-foreground text-sm font-medium disabled:opacity-50"
>
{previewLoading ? "Processing..." : "Preview"}
{previewLoading ? t("processing") : t("preview")}
</button>
</div>
{previewError && <p className="text-xs text-destructive">{previewError}</p>}
@@ -345,19 +378,22 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
<div className="flex flex-col gap-3 pt-1 text-sm">
<div className="flex flex-wrap gap-4">
<span className="text-text-muted">
Original tokens: <strong className="text-text">{preview.originalTokens}</strong>
{t("originalTokens")}:{" "}
<strong className="text-text">{preview.originalTokens}</strong>
</span>
<span className="text-text-muted">
Compressed tokens: <strong className="text-text">{preview.compressedTokens}</strong>
{t("compressedTokens")}:{" "}
<strong className="text-text">{preview.compressedTokens}</strong>
</span>
<span className="text-text-muted">
Savings: <strong className="text-primary">{preview.savingsPct.toFixed(1)}%</strong>
{t("savings")}:{" "}
<strong className="text-primary">{preview.savingsPct.toFixed(1)}%</strong>
</span>
</div>
<div className="grid gap-3 md:grid-cols-2">
<div className="flex flex-col gap-1">
<h3 className="text-xs font-semibold uppercase tracking-wide text-text-muted">
Original
{t("original")}
</h3>
<pre className="max-h-72 overflow-auto rounded border border-border bg-background p-3 whitespace-pre-wrap break-words text-text">
{preview.original ?? ""}
@@ -365,7 +401,7 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
</div>
<div className="flex flex-col gap-1">
<h3 className="text-xs font-semibold uppercase tracking-wide text-text-muted">
Compressed
{t("compressed")}
</h3>
<pre className="max-h-72 overflow-auto rounded border border-border bg-background p-3 whitespace-pre-wrap break-words text-text">
{preview.compressed ?? ""}
@@ -375,10 +411,15 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
{preview.diff && preview.diff.length > 0 && (
<div className="flex flex-col gap-2" data-testid="compression-preview-diff">
<h3 className="text-xs font-semibold uppercase tracking-wide text-text-muted">
Diff
{t("diff")}
</h3>
<div className="flex max-h-72 flex-col gap-2 overflow-auto rounded border border-border p-2">
{preview.diff.map(renderDiffSegment)}
{preview.diff.map((segment, index) =>
renderDiffSegment(segment, index, (label) => {
const key = `diffLabels.${label}`;
return t.has(key) ? t(key) : label;
})
)}
</div>
</div>
)}
@@ -388,20 +429,23 @@ export function EngineConfigPage({ engineId }: { engineId: string }) {
{/* ── Analytics strip ── */}
<div className="flex flex-col gap-3 rounded-lg border border-border bg-surface p-4">
<h2 className="text-sm font-semibold text-text">Last 7 days</h2>
<h2 className="text-sm font-semibold text-text">{t("last7Days")}</h2>
{analytics && analytics.runs === 0 ? (
<p className="text-sm text-text-muted">No data yet</p>
<p className="text-sm text-text-muted">{t("noDataYet")}</p>
) : analytics ? (
<div className="grid grid-cols-3 gap-3">
<StatCard label="Runs" value={analytics.runs.toLocaleString()} />
<StatCard label="Tokens saved" value={analytics.tokensSaved.toLocaleString()} />
<StatCard label={t("runs")} value={analytics.runs.toLocaleString(locale)} />
<StatCard
label="Average savings"
label={t("tokensSaved")}
value={analytics.tokensSaved.toLocaleString(locale)}
/>
<StatCard
label={t("averageSavings")}
value={`${analytics.avgSavingsPercent.toFixed(1)}%`}
/>
</div>
) : (
<p className="text-sm text-text-muted">No data yet</p>
<p className="text-sm text-text-muted">{t("noDataYet")}</p>
)}
</div>
</div>