mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
fix(app): address usage localization review
This commit is contained in:
@@ -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 }) => {
|
||||
|
||||
@@ -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<void>;
|
||||
}
|
||||
|
||||
export async function expectUsageBalanceInsideCard(page: Page, balanceId: string): Promise<void> {
|
||||
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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user