diff --git a/CHANGELOG.md b/CHANGELOG.md index bda4ba2..3a56123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ The format is based on Keep a Changelog and the project follows Semantic Version - The interface language can be chosen in Settings. Automatic still follows the browser language. Popup, sidebar, and Settings update without a reload. +- Language choices show the English name and the name from the active translation. + The English interface keeps English names only. +- Accent colors, protection labels, and X-Ray labels follow the active interface + language. Long About link labels stay inside their buttons. - Experimental: every subframe in a tab now uses the top-frame runtime snapshot (locale, timezone, fingerprint, geolocation) so iframe and worker realms do not diverge from the page. Per-tab header rules apply to all requests in that diff --git a/src/ui/i18n/en-sections/options.ts b/src/ui/i18n/en-sections/options.ts index ec2d14b..5f6360f 100644 --- a/src/ui/i18n/en-sections/options.ts +++ b/src/ui/i18n/en-sections/options.ts @@ -28,6 +28,8 @@ export const optionsPage = { description: `Choose which browser protections ${BRAND_DISPLAY_NAME} uses by default. More specific settings can override individual protections, while Trusted Sites can disable them for matching sites.`, disabledNote: `Browser protections are off everywhere. Turn them back on to choose what ${BRAND_DISPLAY_NAME} should spoof by default.`, copyLinkLabel: "browser protections", + interceptsApis: (count: number) => + `Intercepts ${count} browser ${count === 1 ? "API" : "APIs"}`, items: { geolocation: { label: "Geolocation", diff --git a/src/ui/i18n/es-sections/options.ts b/src/ui/i18n/es-sections/options.ts index 2ecad92..b670cc9 100644 --- a/src/ui/i18n/es-sections/options.ts +++ b/src/ui/i18n/es-sections/options.ts @@ -28,6 +28,8 @@ export const optionsPage = { description: `Elige qué protecciones del navegador usa ${BRAND_DISPLAY_NAME} de forma predeterminada. Las opciones más específicas pueden sustituir protecciones concretas, mientras que Sitios de confianza puede desactivarlas en los sitios coincidentes.`, disabledNote: `Las protecciones del navegador están desactivadas en todas partes. Vuelve a activarlas para elegir qué ${BRAND_DISPLAY_NAME} debería simular por defecto.`, copyLinkLabel: "protecciones del navegador", + interceptsApis: (count: number) => + `Intercepta ${count} ${count === 1 ? "API del navegador" : "APIs del navegador"}`, items: { geolocation: { label: "Geolocalización", diff --git a/src/ui/i18n/pt-sections/options.ts b/src/ui/i18n/pt-sections/options.ts index 214c0fd..815211f 100644 --- a/src/ui/i18n/pt-sections/options.ts +++ b/src/ui/i18n/pt-sections/options.ts @@ -28,6 +28,8 @@ export const optionsPage = { description: `Escolha quais proteções do navegador o ${BRAND_DISPLAY_NAME} usa por padrão. Configurações mais específicas podem substituir proteções individuais, enquanto Sites Confiáveis podem desativá-las para sites correspondentes.`, disabledNote: `As proteções do navegador estão desativadas em todos os lugares. Ative-as novamente para escolher o que o ${BRAND_DISPLAY_NAME} deve simular por padrão.`, copyLinkLabel: "proteções do navegador", + interceptsApis: (count: number) => + `Intercepta ${count} ${count === 1 ? "API do navegador" : "APIs do navegador"}`, items: { geolocation: { label: "Geolocalização", diff --git a/src/ui/i18n/ru-sections/options.ts b/src/ui/i18n/ru-sections/options.ts index 74b366b..56f8640 100644 --- a/src/ui/i18n/ru-sections/options.ts +++ b/src/ui/i18n/ru-sections/options.ts @@ -29,6 +29,7 @@ export const optionsPage = { description: `Выберите, какие средства защиты браузера ${BRAND_DISPLAY_NAME} использует по умолчанию. Более конкретные настройки могут переопределять отдельные средства защиты, а «Доверенные сайты» могут отключать их для соответствующих сайтов.`, disabledNote: `Защита браузера на всех сайтах отключена. Включите её, чтобы настроить подмену данных по умолчанию.`, copyLinkLabel: "защита браузера", + interceptsApis: (count: number) => `Перехватывает ${count} API браузера`, items: { geolocation: { label: "Геолокация", diff --git a/src/ui/i18n/uk-sections/options.ts b/src/ui/i18n/uk-sections/options.ts index f3ea280..09ecf0c 100644 --- a/src/ui/i18n/uk-sections/options.ts +++ b/src/ui/i18n/uk-sections/options.ts @@ -29,6 +29,7 @@ export const optionsPage = { description: `Виберіть, який захист браузера ${BRAND_DISPLAY_NAME} використовує за замовчуванням. Конкретніші налаштування можуть перевизначити окремі механізми захисту, тоді як довірені сайти можуть вимкнути їх для відповідних сайтів.`, disabledNote: `Захист браузера скрізь вимкнено. Увімкніть його, щоб налаштувати підміну даних за замовчуванням.`, copyLinkLabel: "механізми захисту браузера", + interceptsApis: (count: number) => `Перехоплює ${count} API браузера`, items: { geolocation: { label: "Геолокація", diff --git a/src/ui/options/components/onboarding/WelcomeWizard.tsx b/src/ui/options/components/onboarding/WelcomeWizard.tsx index 190baa9..0178fb3 100644 --- a/src/ui/options/components/onboarding/WelcomeWizard.tsx +++ b/src/ui/options/components/onboarding/WelcomeWizard.tsx @@ -27,7 +27,7 @@ import { PAGE_ANCHORS } from "@/ui/options/navigation"; import { useSettings } from "@/ui/options/state/SettingsContext"; import { sendMessageOrThrow } from "@/ui/shared/runtime-messaging"; import { getThemeAccentTokens } from "@/ui/shared/theme"; -import { THEME_ACCENT_OPTIONS } from "@/ui/shared/theme-accent-options"; +import { getThemeAccentOptions } from "@/ui/shared/theme-accent-options"; import { useTheme } from "@/ui/shared/ThemeProvider"; const DEFAULT_PRESET_IDS = [ @@ -371,18 +371,20 @@ const setWizardContrast = async ( await theme.setHighContrast(checked); }; -const getAccentView = (theme: Theme) => ({ - color: `hsl(${getThemeAccentTokens(theme.accentPreset, theme.theme, theme.highContrast).primary})`, - label: - THEME_ACCENT_OPTIONS.find((option) => option.preset === theme.accentPreset) - ?.label ?? - THEME_ACCENT_OPTIONS[0]?.label ?? - theme.accentPreset, - options: THEME_ACCENT_OPTIONS.map((option) => ({ - ...option, - color: `hsl(${getThemeAccentTokens(option.preset, theme.theme, theme.highContrast).primary})`, - })), -}); +const getAccentView = (theme: Theme) => { + const accentOptions = getThemeAccentOptions(); + return { + color: `hsl(${getThemeAccentTokens(theme.accentPreset, theme.theme, theme.highContrast).primary})`, + label: + accentOptions.find((option) => option.preset === theme.accentPreset)?.label ?? + accentOptions[0]?.label ?? + theme.accentPreset, + options: accentOptions.map((option) => ({ + ...option, + color: `hsl(${getThemeAccentTokens(option.preset, theme.theme, theme.highContrast).primary})`, + })), + }; +}; export const WelcomeWizard = ({ onComplete }: { onComplete: () => void }) => { const settings = useSettings(); diff --git a/src/ui/options/components/tabs/AboutTab.tsx b/src/ui/options/components/tabs/AboutTab.tsx index fa0a694..73c586d 100644 --- a/src/ui/options/components/tabs/AboutTab.tsx +++ b/src/ui/options/components/tabs/AboutTab.tsx @@ -479,14 +479,14 @@ const AboutSidebar = () => {

{t.about.projectLinks.description}

-
+
{projectLinks.map((link) => (
), }, -}; +}); export const buildSpoofingSurfaces = ({ browserTarget, @@ -155,9 +155,10 @@ export const buildSpoofingSurfaces = ({ featureFlags?: FeatureFlags; nativeTemporalApi?: boolean; sharedSpoofing: SharedSpoofingConfig | undefined; -}): SpoofingSurface[] => - CONFIGURABLE_SURFACES.map((surface) => { - const copy = SURFACE_COPY[surface.key]; +}): SpoofingSurface[] => { + const surfaceCopy = getSurfaceCopy(); + return CONFIGURABLE_SURFACES.map((surface) => { + const copy = surfaceCopy[surface.key]; return { key: surface.key, anchorId: SURFACE_ANCHORS[surface.key], @@ -176,6 +177,7 @@ export const buildSpoofingSurfaces = ({ ), } satisfies SpoofingSurface; }).filter((surface) => surface.supported !== false); +}; export const renderOsmConsentState = (osmConsent: "unknown" | "granted" | "denied") => { if (osmConsent === "unknown") return t.advanced.privacy.osmConsent.stateUnknown; diff --git a/src/ui/options/interface-language-label.test.ts b/src/ui/options/interface-language-label.test.ts new file mode 100644 index 0000000..10d7035 --- /dev/null +++ b/src/ui/options/interface-language-label.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; + +import { languageOptionLabel } from "./interface-language-label"; + +describe("languageOptionLabel", () => { + it("keeps the English interface on English names", () => { + expect(languageOptionLabel("en", "Spanish", "Spanish")).toBe("Spanish"); + expect(languageOptionLabel("en", "Automatic", "Automatic")).toBe("Automatic"); + }); + + it("shows the English name and the active translation", () => { + expect(languageOptionLabel("es", "Spanish", "Español")).toBe("Spanish (Español)"); + expect(languageOptionLabel("uk", "Ukrainian", "Українська")).toBe( + "Ukrainian (Українська)", + ); + expect(languageOptionLabel("ru", "Automatic", "Автоматически")).toBe( + "Automatic (Автоматически)", + ); + }); +}); diff --git a/src/ui/options/interface-language-label.ts b/src/ui/options/interface-language-label.ts new file mode 100644 index 0000000..de2ffdb --- /dev/null +++ b/src/ui/options/interface-language-label.ts @@ -0,0 +1,8 @@ +import type { UiLocale } from "@/shared/ui-locale"; + +export const languageOptionLabel = ( + activeLocale: UiLocale, + englishName: string, + localizedName: string, +): string => + activeLocale === "en" ? englishName : `${englishName} (${localizedName})`; diff --git a/src/ui/shared/surface-method-labels.ts b/src/ui/shared/surface-method-labels.ts index 5628d59..8dcb0dd 100644 --- a/src/ui/shared/surface-method-labels.ts +++ b/src/ui/shared/surface-method-labels.ts @@ -1,7 +1,7 @@ import type { SpoofingSurfaceMethodId } from "@/shared/spoofing-surfaces"; import { t } from "@/ui/i18n"; -export const SURFACE_METHOD_LABELS: Record = { +export const getSurfaceMethodLabels = (): Record => ({ "geolocation.getCurrentPosition": t.sidebar.accessed.methods.geolocationGetCurrentPosition, "geolocation.watchPosition": t.sidebar.accessed.methods.geolocationWatchPosition, @@ -91,4 +91,4 @@ export const SURFACE_METHOD_LABELS: Record = { "worker.constructor": t.sidebar.accessed.methods.workerConstructor, "serviceWorker.register": t.sidebar.accessed.methods.serviceWorkerRegister, "sharedWorker.constructor": t.sidebar.accessed.methods.sharedWorkerConstructor, -}; +}); diff --git a/src/ui/shared/theme-accent-options.test.ts b/src/ui/shared/theme-accent-options.test.ts new file mode 100644 index 0000000..ddebbeb --- /dev/null +++ b/src/ui/shared/theme-accent-options.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; + +import { accentOptionLabel } from "./theme-accent-options"; + +import { applyUiLocalePreference } from "@/ui/i18n"; + +describe("accentOptionLabel", () => { + it("follows the active interface language", () => { + applyUiLocalePreference("en"); + expect(accentOptionLabel("teal")).toBe("Teal"); + + applyUiLocalePreference("es"); + expect(accentOptionLabel("teal")).toBe("Verde azulado"); + expect(accentOptionLabel("purple")).toBe("Morado"); + + applyUiLocalePreference("en"); + }); +}); diff --git a/src/ui/shared/theme-accent-options.ts b/src/ui/shared/theme-accent-options.ts index 8146301..8bd8cb5 100644 --- a/src/ui/shared/theme-accent-options.ts +++ b/src/ui/shared/theme-accent-options.ts @@ -6,21 +6,31 @@ export type ThemeAccentOption = { label: string; }; -export const ACCENT_OPTION_LABELS: Record = { - teal: t.advanced.display.accentColor.options.teal, - blue: t.advanced.display.accentColor.options.blue, - green: t.advanced.display.accentColor.options.green, - yellow: t.advanced.display.accentColor.options.yellow, - orange: t.advanced.display.accentColor.options.orange, - red: t.advanced.display.accentColor.options.red, - pink: t.advanced.display.accentColor.options.pink, - purple: t.advanced.display.accentColor.options.purple, - gray: t.advanced.display.accentColor.options.gray, +export const accentOptionLabel = (preset: ThemeAccentPreset): string => { + switch (preset) { + case "teal": + return t.advanced.display.accentColor.options.teal; + case "blue": + return t.advanced.display.accentColor.options.blue; + case "green": + return t.advanced.display.accentColor.options.green; + case "yellow": + return t.advanced.display.accentColor.options.yellow; + case "orange": + return t.advanced.display.accentColor.options.orange; + case "red": + return t.advanced.display.accentColor.options.red; + case "pink": + return t.advanced.display.accentColor.options.pink; + case "purple": + return t.advanced.display.accentColor.options.purple; + case "gray": + return t.advanced.display.accentColor.options.gray; + } }; -export const THEME_ACCENT_OPTIONS: ThemeAccentOption[] = THEME_ACCENT_PRESETS.map( - (preset) => ({ +export const getThemeAccentOptions = (): ThemeAccentOption[] => + THEME_ACCENT_PRESETS.map((preset) => ({ preset, - label: ACCENT_OPTION_LABELS[preset], - }), -); + label: accentOptionLabel(preset), + })); diff --git a/src/ui/sidebar/modules/xray/XRayAccessAccordion.tsx b/src/ui/sidebar/modules/xray/XRayAccessAccordion.tsx index 7e395f5..4daf252 100644 --- a/src/ui/sidebar/modules/xray/XRayAccessAccordion.tsx +++ b/src/ui/sidebar/modules/xray/XRayAccessAccordion.tsx @@ -5,7 +5,7 @@ import { XRayStatusDot } from "./XRayRows"; import type { SpoofingSurfaceMethodId } from "@/shared/spoofing-surfaces"; import type { XRaySurfaceCategory } from "@/shared/types"; import { t } from "@/ui/i18n"; -import { SURFACE_METHOD_LABELS } from "@/ui/shared/surface-method-labels"; +import { getSurfaceMethodLabels } from "@/ui/shared/surface-method-labels"; type CategoryStatus = "accessed" | "connecting" | "armed" | "off" | "failed"; @@ -37,20 +37,35 @@ const getCategoryStatus = ( } }; -const CATEGORY_LABELS: Record = { - geolocation: t.sidebar.accessed.categories.geolocation, - timeLocale: t.sidebar.accessed.categories.timeLocale, - canvas: t.sidebar.accessed.categories.canvas, - webGL: t.sidebar.accessed.categories.webGL, - audio: t.sidebar.accessed.categories.audio, - navigator: t.sidebar.accessed.categories.navigator, - screen: t.sidebar.accessed.categories.screen, - clientHints: t.sidebar.accessed.categories.clientHints, - battery: t.sidebar.accessed.categories.battery, - webRTC: t.sidebar.accessed.categories.webRTC, - worker: t.sidebar.accessed.categories.worker, - serviceWorker: t.sidebar.accessed.categories.serviceWorker, - sharedWorker: t.sidebar.accessed.categories.sharedWorker, +const categoryLabel = (category: XRaySurfaceCategory): string => { + switch (category) { + case "geolocation": + return t.sidebar.accessed.categories.geolocation; + case "timeLocale": + return t.sidebar.accessed.categories.timeLocale; + case "canvas": + return t.sidebar.accessed.categories.canvas; + case "webGL": + return t.sidebar.accessed.categories.webGL; + case "audio": + return t.sidebar.accessed.categories.audio; + case "navigator": + return t.sidebar.accessed.categories.navigator; + case "screen": + return t.sidebar.accessed.categories.screen; + case "clientHints": + return t.sidebar.accessed.categories.clientHints; + case "battery": + return t.sidebar.accessed.categories.battery; + case "webRTC": + return t.sidebar.accessed.categories.webRTC; + case "worker": + return t.sidebar.accessed.categories.worker; + case "serviceWorker": + return t.sidebar.accessed.categories.serviceWorker; + case "sharedWorker": + return t.sidebar.accessed.categories.sharedWorker; + } }; const STATUS_DOT: Record = { @@ -61,12 +76,19 @@ const STATUS_DOT: Record = { off: "bg-muted-foreground/30", }; -const STATUS_LABEL: Record = { - failed: t.sidebar.accessed.failed, - accessed: t.sidebar.accessed.accessed, - connecting: t.sidebar.accessed.connecting, - armed: t.sidebar.accessed.armed, - off: t.sidebar.accessed.off, +const statusLabel = (status: CategoryStatus): string => { + switch (status) { + case "failed": + return t.sidebar.accessed.failed; + case "accessed": + return t.sidebar.accessed.accessed; + case "connecting": + return t.sidebar.accessed.connecting; + case "armed": + return t.sidebar.accessed.armed; + case "off": + return t.sidebar.accessed.off; + } }; export const XRayAccessAccordion = ({ @@ -75,74 +97,77 @@ export const XRayAccessAccordion = ({ }: { assessments: SurfaceAssessment[]; surfaceSyncPending: boolean; -}) => ( -
-

- {t.sidebar.accessed.title} -

-

- {t.sidebar.accessed.description} -

-
- {assessments - .filter((assessment) => assessment.applicability === "applicable") - .map((assessment) => { - const cat = assessment.key; - const status = getCategoryStatus(assessment, surfaceSyncPending); - const count = assessment.activity.queryCount; - const visibleMethods = Object.entries(assessment.activity.methodCounts) - .map(([id, methodCount]) => ({ - id: id as SpoofingSurfaceMethodId, - count: methodCount, - })) - .filter( - (method): method is { id: SpoofingSurfaceMethodId; count: number } => - typeof method.count === "number" && method.count > 0, - ); - const row = ( -
- - {CATEGORY_LABELS[cat]} - -
- +}) => { + const methodLabels = getSurfaceMethodLabels(); + return ( +
+

+ {t.sidebar.accessed.title} +

+

+ {t.sidebar.accessed.description} +

+
+ {assessments + .filter((assessment) => assessment.applicability === "applicable") + .map((assessment) => { + const cat = assessment.key; + const status = getCategoryStatus(assessment, surfaceSyncPending); + const count = assessment.activity.queryCount; + const visibleMethods = Object.entries(assessment.activity.methodCounts) + .map(([id, methodCount]) => ({ + id: id as SpoofingSurfaceMethodId, + count: methodCount, + })) + .filter( + (method): method is { id: SpoofingSurfaceMethodId; count: number } => + typeof method.count === "number" && method.count > 0, + ); + const row = ( +
- {status === "accessed" && cat === "serviceWorker" - ? t.sidebar.accessed.blocked - : STATUS_LABEL[status]} - {status === "accessed" && count != null && count > 0 - ? ` (${count})` - : ""} + {categoryLabel(cat)} +
+ + + {status === "accessed" && cat === "serviceWorker" + ? t.sidebar.accessed.blocked + : statusLabel(status)} + {status === "accessed" && count != null && count > 0 + ? ` (${count})` + : ""} + +
-
- ); - if (visibleMethods.length === 0) { - return
{row}
; - } - return ( -
- - {row} - -
- {visibleMethods.map((method) => ( -
- - {SURFACE_METHOD_LABELS[method.id as SpoofingSurfaceMethodId]} - - - {method.count} - -
- ))} -
-
- ); - })} -
-
-); + ); + if (visibleMethods.length === 0) { + return
{row}
; + } + return ( +
+ + {row} + +
+ {visibleMethods.map((method) => ( +
+ + {methodLabels[method.id]} + + + {method.count} + +
+ ))} +
+
+ ); + })} +
+ + ); +}; diff --git a/src/ui/sidebar/modules/xray/XRaySurfacesSection.tsx b/src/ui/sidebar/modules/xray/XRaySurfacesSection.tsx index 7a44096..60a354c 100644 --- a/src/ui/sidebar/modules/xray/XRaySurfacesSection.tsx +++ b/src/ui/sidebar/modules/xray/XRaySurfacesSection.tsx @@ -273,20 +273,29 @@ const openOptionsAnchor = async (anchorId: string): Promise => { }); }; -const SHARED_WORKER_LABELS: Record = { - "native-compatibility": t.sidebar.surfaces.sharedWorkerNativeCompatibility, - "blob-wrapper-dedup-disabled": t.sidebar.surfaces.sharedWorkerBlobSpoofing, - "response-rewrite-preserved-identity": - t.sidebar.surfaces.sharedWorkerResponseRewriteSpoofing, - "response-rewrite-cache-sensitive": - t.sidebar.surfaces.sharedWorkerResponseRewriteCacheSensitive, - "module-rewrite-unsupported": t.sidebar.surfaces.sharedWorkerModuleUnsupported, - "identity-conflict": t.sidebar.surfaces.sharedWorkerIdentityConflict, - "response-rewrite-unavailable": t.sidebar.surfaces.sharedWorkerRewriteUnavailable, - "blocked-strict": t.sidebar.surfaces.sharedWorkerBlockedStrict, - "strict-rewrite-required": t.sidebar.surfaces.sharedWorkerStrictRewriteRequired, - "strict-blocked-cache-sensitive": - t.sidebar.surfaces.sharedWorkerStrictBlockedCacheSensitive, +const sharedWorkerLabel = (status: SharedWorkerStatus): string => { + switch (status) { + case "native-compatibility": + return t.sidebar.surfaces.sharedWorkerNativeCompatibility; + case "blob-wrapper-dedup-disabled": + return t.sidebar.surfaces.sharedWorkerBlobSpoofing; + case "response-rewrite-preserved-identity": + return t.sidebar.surfaces.sharedWorkerResponseRewriteSpoofing; + case "response-rewrite-cache-sensitive": + return t.sidebar.surfaces.sharedWorkerResponseRewriteCacheSensitive; + case "module-rewrite-unsupported": + return t.sidebar.surfaces.sharedWorkerModuleUnsupported; + case "identity-conflict": + return t.sidebar.surfaces.sharedWorkerIdentityConflict; + case "response-rewrite-unavailable": + return t.sidebar.surfaces.sharedWorkerRewriteUnavailable; + case "blocked-strict": + return t.sidebar.surfaces.sharedWorkerBlockedStrict; + case "strict-rewrite-required": + return t.sidebar.surfaces.sharedWorkerStrictRewriteRequired; + case "strict-blocked-cache-sensitive": + return t.sidebar.surfaces.sharedWorkerStrictBlockedCacheSensitive; + } }; const ProfileRow = ({ @@ -359,7 +368,7 @@ export const XRaySurfacesSection = ({ } - value={SHARED_WORKER_LABELS[resolvedWorkerStatus]} + value={sharedWorkerLabel(resolvedWorkerStatus)} /> {displayedProfileLabel ? ( diff --git a/src/ui/sidebar/modules/xray/XRayWhySection.tsx b/src/ui/sidebar/modules/xray/XRayWhySection.tsx index 43727ce..be4fc03 100644 --- a/src/ui/sidebar/modules/xray/XRayWhySection.tsx +++ b/src/ui/sidebar/modules/xray/XRayWhySection.tsx @@ -60,7 +60,7 @@ function toDisplaySteps(steps: RuleResolutionStep[]): DisplayStep[] { continue; } - const label = SHORT_LABELS[step.source as DisplaySource]; + const label = sourceShortLabel(step.source as DisplaySource); result.push({ source: step.source as DisplaySource, status: step.status, @@ -74,12 +74,19 @@ function toDisplaySteps(steps: RuleResolutionStep[]): DisplayStep[] { } // ── Label maps ──────────────────────────────────────────────────────────────── -const SHORT_LABELS: Record = { - "trusted-site": t.sidebar.why.sourcesShort.trustedSite, - "domain-rule": t.sidebar.why.sourcesShort.domainRule, - container: t.sidebar.why.sourcesShort.container, - fallback: t.sidebar.why.sourcesShort.fallback, - none: t.sidebar.why.sourcesShort.none, +const sourceShortLabel = (source: DisplaySource): string => { + switch (source) { + case "trusted-site": + return t.sidebar.why.sourcesShort.trustedSite; + case "domain-rule": + return t.sidebar.why.sourcesShort.domainRule; + case "container": + return t.sidebar.why.sourcesShort.container; + case "fallback": + return t.sidebar.why.sourcesShort.fallback; + case "none": + return t.sidebar.why.sourcesShort.none; + } }; // ── Visual styles ───────────────────────────────────────────────────────────── @@ -146,7 +153,7 @@ export const XRayWhySection = ({ LABEL_STYLE[step.status], )} > - {SHORT_LABELS[step.source]} + {sourceShortLabel(step.source)}