From 6fcd93bb5a72aed6f0136f99506e1f51e7764d6d Mon Sep 17 00:00:00 2001 From: Michael Wu Date: Tue, 6 Oct 2026 05:34:49 +0900 Subject: [PATCH] fix(app): address usage localization review --- .../app/e2e/browser/usage-settings.spec.ts | 14 +------ .../app/e2e/support/helpers/usage-reports.ts | 17 ++++++++- packages/app/src/usage/card.tsx | 5 ++- packages/app/src/usage/copy.ts | 37 ++++++++++++++----- packages/app/src/usage/format.test.ts | 2 +- packages/app/src/usage/format.ts | 4 +- 6 files changed, 53 insertions(+), 26 deletions(-) diff --git a/packages/app/e2e/browser/usage-settings.spec.ts b/packages/app/e2e/browser/usage-settings.spec.ts index ea98df9d0c..a6d48daf80 100644 --- a/packages/app/e2e/browser/usage-settings.spec.ts +++ b/packages/app/e2e/browser/usage-settings.spec.ts @@ -4,6 +4,7 @@ import { gotoAppShell, openSettings } from "../support/helpers/app"; import { getServerId } from "../support/helpers/server-id"; import { openSettingsHostSection } from "../support/helpers/settings"; import { + expectUsageBalanceInsideCard, installUsageReportsFixture, type UsageReportsFixture, } from "../support/helpers/usage-reports"; @@ -163,18 +164,7 @@ test.describe("usage settings", () => { await openSettings(page); await openSettingsHostSection(page, serverId, "usage"); - const card = page.getByTestId("usage-card"); - const value = page.getByTestId("provider-usage-balance-credits-value"); - const reset = page.getByTestId("provider-usage-balance-credits-reset"); - await expect(value).toBeVisible({ timeout: 10_000 }); - await expect(reset).toHaveText(/resets \d+d/); - await expect(reset).toBeVisible(); - const [cardBox, valueBox] = await Promise.all([card.boundingBox(), value.boundingBox()]); - expect(cardBox).not.toBeNull(); - expect(valueBox).not.toBeNull(); - expect((valueBox?.x ?? 0) + (valueBox?.width ?? 0)).toBeLessThanOrEqual( - (cardBox?.x ?? 0) + (cardBox?.width ?? 0), - ); + await expectUsageBalanceInsideCard(page, "credits"); }); test("asks to update a host without usage support and never calls it", async ({ page }) => { diff --git a/packages/app/e2e/support/helpers/usage-reports.ts b/packages/app/e2e/support/helpers/usage-reports.ts index 86b74c6ea3..eaa3aa7352 100644 --- a/packages/app/e2e/support/helpers/usage-reports.ts +++ b/packages/app/e2e/support/helpers/usage-reports.ts @@ -1,4 +1,4 @@ -import type { Page } from "@playwright/test"; +import { expect, type Page } from "@playwright/test"; import type { UsageReportEntry } from "@getpaseo/protocol/messages"; import { daemonWsRoutePattern, wsRoutePatternForPort } from "./daemon-port"; @@ -13,6 +13,21 @@ export interface UsageReportsFixture { waitForListRequests(count: number): Promise; } +export async function expectUsageBalanceInsideCard(page: Page, balanceId: string): Promise { + const card = page.getByTestId("usage-card"); + const value = page.getByTestId(`provider-usage-balance-${balanceId}-value`); + const reset = page.getByTestId(`provider-usage-balance-${balanceId}-reset`); + await expect(value).toBeVisible({ timeout: 10_000 }); + await expect(reset).toHaveText(/resets \d+d/); + await expect(reset).toBeVisible(); + const [cardBox, valueBox] = await Promise.all([card.boundingBox(), value.boundingBox()]); + expect(cardBox).not.toBeNull(); + expect(valueBox).not.toBeNull(); + expect((valueBox?.x ?? 0) + (valueBox?.width ?? 0)).toBeLessThanOrEqual( + (cardBox?.x ?? 0) + (cardBox?.width ?? 0), + ); +} + interface UsageReportsFixtureOptions { /** * Successive `usage.list_reports` responses; the last one repeats. `{ error }` fails that diff --git a/packages/app/src/usage/card.tsx b/packages/app/src/usage/card.tsx index a1936e0f97..e6f9199394 100644 --- a/packages/app/src/usage/card.tsx +++ b/packages/app/src/usage/card.tsx @@ -13,6 +13,7 @@ import { StatusBadge } from "@/components/ui/status-badge"; import { useIsCompactFormFactor } from "@/constants/layout"; import { isNative } from "@/constants/platform"; import { useCompactTimeAgo } from "@/hooks/use-time-ago"; +import { i18n } from "@/i18n/i18next"; import { UsageBalanceBar } from "./balance-bar"; import { usageCopy } from "./copy"; import type { UsageDisplay } from "./display"; @@ -24,7 +25,9 @@ import { UsageWindowBar } from "./window-bar"; function statusText(report: UsageReport): string | null { if (report.status === "available") return null; - return report.status === "error" ? "Error" : "Unavailable"; + return report.status === "error" + ? i18n.t("providerUsage.status.error") + : i18n.t("providerUsage.status.unavailable"); } function reportContent(report: UsageReport) { diff --git a/packages/app/src/usage/copy.ts b/packages/app/src/usage/copy.ts index 639272283b..3fddde64fb 100644 --- a/packages/app/src/usage/copy.ts +++ b/packages/app/src/usage/copy.ts @@ -1,4 +1,5 @@ import type { UsageProblem } from "@getpaseo/protocol/messages"; +import { i18n } from "@/i18n/i18next"; import { formatCompactTimeAgo, formatCompactTimeAgoAsProse } from "@/utils/time"; // User-facing copy for the usage surfaces, kept in one file so localization is a @@ -13,23 +14,41 @@ export const usageCopy = { const ago = formatCompactTimeAgoAsProse(formatCompactTimeAgo(new Date(problem.expiresAt), now)); return `Login expired ${ago}. ${remedy}`; }, - title: "Usage", - planUsage: "Plan usage", + get title() { + return i18n.t("providerUsage.title"); + }, + get planUsage() { + return i18n.t("providerUsage.title"); + }, options: "Settings", - refresh: "Refresh", - refreshAll: "Refresh all", - refreshing: "Refreshing...", + get refresh() { + return i18n.t("providerUsage.refresh"); + }, + get refreshAll() { + return i18n.t("providerUsage.refresh"); + }, + get refreshing() { + return i18n.t("providerUsage.refreshing"); + }, refreshFailed: "Unable to refresh usage", updated: "Updated", - loading: "Loading usage...", - empty: "No usage data", + get loading() { + return i18n.t("providerUsage.loading"); + }, + get empty() { + return i18n.t("providerUsage.empty"); + }, noHosts: "No connected hosts", - errorTitle: "Unable to load usage", + get errorTitle() { + return i18n.t("providerUsage.errorTitle"); + }, agentError: (reason: string) => `Unable to load usage: ${reason}`, hostUnavailable: (host: string) => `Connect to ${host} to see usage`, hostUpgradeRequired: (host: string) => `Update ${host} to see usage`, clientUnavailable: "Host connection is not ready", - retry: "Try again", + get retry() { + return i18n.t("providerUsage.retry"); + }, pin: "Pin", unpin: "Unpin", displayAs: "Percentages", diff --git a/packages/app/src/usage/format.test.ts b/packages/app/src/usage/format.test.ts index 80a63c7c72..f92d13df1f 100644 --- a/packages/app/src/usage/format.test.ts +++ b/packages/app/src/usage/format.test.ts @@ -79,7 +79,7 @@ describe("formatAmount", () => { await i18n.changeLanguage("ko"); expect(formatProviderUsageLabel("session", "Session")).toBe("세션"); - expect(formatProviderUsageLabel("five_hour", "Session")).toBe("세션"); + expect(formatProviderUsageLabel("five_hour", "5-hour limit")).toBe("5-hour limit"); expect(formatProviderUsageLabel("credits", "Credits")).toBe("크레딧"); expect(formatProviderUsageLabel("custom_limit", "Custom limit")).toBe("Custom limit"); }); diff --git a/packages/app/src/usage/format.ts b/packages/app/src/usage/format.ts index 5cfa0b0f36..af5c10f7d8 100644 --- a/packages/app/src/usage/format.ts +++ b/packages/app/src/usage/format.ts @@ -4,7 +4,6 @@ import type { UsageBalanceUnit } from "./types"; const providerUsageLabelKeys = { session: "providerUsage.labels.session", - five_hour: "providerUsage.labels.session", weekly: "providerUsage.labels.weekly", monthly: "providerUsage.labels.monthly", code_review: "providerUsage.labels.codeReview", @@ -22,7 +21,8 @@ export function clampPct(value: number): number { } export function formatPct(value: number, locale?: string): string { - return new Intl.NumberFormat(locale, { + const resolvedLocale = locale ?? i18n.resolvedLanguage; + return new Intl.NumberFormat(resolvedLocale, { style: "percent", maximumFractionDigits: 0, }).format(clampPct(value) / 100);