Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions frontend/src/lib/displayText.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";

import {
englishDisplayText,
frameworkDisplayName,
hasLocalizedText
} from "./displayText";

const localizedIdentity = String.fromCharCode(0x8eab, 0x4efd, 0x9274, 0x5225);
const localizedAudit = String.fromCharCode(0x5b89, 0x5168, 0x5ba1, 0x8ba1);

describe("display text normalization", () => {
it("uses English framework fallbacks for known localized overlays", () => {
expect(
frameworkDisplayName(
"china-mlps2",
"China Multi-Level Protection Scheme 2.0 (dengbao)"
)
).toBe("China Multi-Level Protection Scheme 2.0");
});

it("removes localized suffixes from mixed framework citations", () => {
expect(englishDisplayText("GB/T 22239-2019 §7.1.4.1 -- identity")).toBe(
"GB/T 22239-2019 §7.1.4.1 -- identity"
);
expect(
englishDisplayText(`GB/T 22239-2019 §7.1.4.1 -- ${localizedIdentity}`)
).toBe("GB/T 22239-2019 §7.1.4.1");
});

it("detects localized text", () => {
expect(hasLocalizedText("Security audit")).toBe(false);
expect(hasLocalizedText(localizedAudit)).toBe(true);
});
});
33 changes: 33 additions & 0 deletions frontend/src/lib/displayText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const cjkPattern = /[\p{Script=Han}\u3000-\u303f\uff00-\uffef]/u;
const cjkParentheticalPattern = /\s*[\(\[][^)\]]*[\p{Script=Han}][^)\]]*[\)\]]/gu;
const cjkRunPattern = /[\p{Script=Han}]+/gu;

const frameworkLabels: Record<string, string> = {
"china-mlps2": "China Multi-Level Protection Scheme 2.0"
};

export function frameworkDisplayName(
frameworkId: string,
value?: string | null
) {
return frameworkLabels[frameworkId] ?? englishDisplayText(value, frameworkId);
}

export function englishDisplayText(value?: string | null, fallback = "") {
if (!value) return fallback;
if (!cjkPattern.test(value)) return value;

const cleaned = value
.replace(cjkParentheticalPattern, "")
.replace(cjkRunPattern, "")
.replace(/\s+([,.;:])/g, "$1")
.replace(/[-\u2013\u2014:\uff1a\uff1b;,\s]+$/g, "")
.replace(/\s{2,}/g, " ")
.trim();

return cleaned || fallback;
}

export function hasLocalizedText(value?: string | null) {
return Boolean(value && cjkPattern.test(value));
}
42 changes: 33 additions & 9 deletions frontend/src/pages/Governance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
type GovernanceProject,
type PallasLens
} from "../lib/governanceApi";
import { englishDisplayText, frameworkDisplayName } from "../lib/displayText";
import { cn } from "../lib/utils";

const riskTiers = ["low", "medium", "high", "critical"];
Expand Down Expand Up @@ -203,8 +204,15 @@ export default function Governance() {
onChange={() => toggleFramework(overlay.id)}
type="checkbox"
/>
<span className="min-w-0 flex-1 truncate">{overlay.label ?? overlay.id}</span>
<span className="text-xs text-muted">{overlay.region_group}</span>
<span className="min-w-0 flex-1 truncate">
{frameworkDisplayName(
overlay.id,
overlay.label ?? overlay.name
)}
</span>
<span className="text-xs text-muted">
{englishDisplayText(overlay.region_group, "Framework")}
</span>
</label>
))}
</div>
Expand Down Expand Up @@ -354,16 +362,24 @@ function ControlsPanel({
<span className="text-xs text-muted">{control.control_id}</span>
</div>
<div>
<div className="text-sm font-medium text-text">{control.title}</div>
<div className="text-sm font-medium text-text">
{englishDisplayText(control.title, control.control_id)}
</div>
<p className="mt-1 text-sm leading-5 text-muted">
{control.normalized_requirement}
{englishDisplayText(
control.normalized_requirement,
"Control requirement"
)}
</p>
</div>
<div className="grid gap-2 md:grid-cols-[1fr_auto]">
<Textarea
minLength={1}
onChange={(event) => onDraftChange(control.control_id, event.target.value)}
placeholder={control.expected_evidence[0] ?? "Evidence note"}
placeholder={englishDisplayText(
control.expected_evidence[0],
"Evidence note"
)}
value={evidenceDraft[control.control_id] ?? ""}
/>
<Button
Expand Down Expand Up @@ -408,13 +424,17 @@ function LensPanel({ lens, loading }: { lens: PallasLens | null; loading: boolea
{lens.posture}
</Badge>
</div>
<p className="mt-2 text-sm leading-5 text-muted">{lens.summary}</p>
<p className="mt-2 text-sm leading-5 text-muted">
{englishDisplayText(lens.summary, "Governance readiness summary")}
</p>
</div>
<div className="grid gap-3">
{lens.dimensions.map((dimension) => (
<div key={dimension.key} className="grid gap-1.5">
<div className="flex justify-between gap-3 text-xs">
<span className="text-muted">{dimension.label}</span>
<span className="text-muted">
{englishDisplayText(dimension.label, dimension.key)}
</span>
<span className="font-medium text-text">{dimension.score}</span>
</div>
<div className="h-2 overflow-hidden rounded bg-panel2">
Expand All @@ -434,9 +454,13 @@ function LensPanel({ lens, loading }: { lens: PallasLens | null; loading: boolea
>
<div className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-accent" aria-hidden="true" />
<div className="truncate text-sm font-medium text-text">{action.action}</div>
<div className="truncate text-sm font-medium text-text">
{englishDisplayText(action.action, action.title)}
</div>
</div>
<div className="mt-1 text-xs leading-5 text-muted">
{englishDisplayText(action.reason, "Action recommended")}
</div>
<div className="mt-1 text-xs leading-5 text-muted">{action.reason}</div>
</div>
))}
</div>
Expand Down