diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 869a26a88c..8f3fa1a417 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -1972,8 +1972,26 @@ export const ar: TranslationResources = { }, duration: { days: "{{value}} يوم", + days_zero: "{{value}} يوم", + days_one: "يوم واحد", + days_two: "يومين", + days_few: "{{value}} أيام", + days_many: "{{value}} يومًا", + days_other: "{{value}} يوم", hours: "{{value}} ساعة", + hours_zero: "{{value}} ساعة", + hours_one: "ساعة واحدة", + hours_two: "ساعتين", + hours_few: "{{value}} ساعات", + hours_many: "{{value}} ساعة", + hours_other: "{{value}} ساعة", minutes: "{{value}} دقيقة", + minutes_zero: "{{value}} دقيقة", + minutes_one: "دقيقة واحدة", + minutes_two: "دقيقتين", + minutes_few: "{{value}} دقائق", + minutes_many: "{{value}} دقيقة", + minutes_other: "{{value}} دقيقة", }, timing: { resettingNow: "تتم إعادة التعيين الآن", @@ -1982,8 +2000,26 @@ export const ar: TranslationResources = { runsOutIn: "ينفد خلال {{duration}}", justNow: "الآن", daysAgo: "قبل {{value}} يومًا", + daysAgo_zero: "قبل {{value}} يوم", + daysAgo_one: "قبل يوم واحد", + daysAgo_two: "قبل يومين", + daysAgo_few: "قبل {{value}} أيام", + daysAgo_many: "قبل {{value}} يومًا", + daysAgo_other: "قبل {{value}} يوم", hoursAgo: "قبل {{value}} ساعة", + hoursAgo_zero: "قبل {{value}} ساعة", + hoursAgo_one: "قبل ساعة واحدة", + hoursAgo_two: "قبل ساعتين", + hoursAgo_few: "قبل {{value}} ساعات", + hoursAgo_many: "قبل {{value}} ساعة", + hoursAgo_other: "قبل {{value}} ساعة", minutesAgo: "قبل {{value}} دقيقة", + minutesAgo_zero: "قبل {{value}} دقيقة", + minutesAgo_one: "قبل دقيقة واحدة", + minutesAgo_two: "قبل دقيقتين", + minutesAgo_few: "قبل {{value}} دقائق", + minutesAgo_many: "قبل {{value}} دقيقة", + minutesAgo_other: "قبل {{value}} دقيقة", updated: "تم التحديث {{relativeTime}}", }, }, diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index 13544c4d09..f2f16e36e4 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -2913,4 +2913,18 @@ type WidenStringLeaves = { [K in keyof T]: T[K] extends string ? string : WidenStringLeaves; }; -export type TranslationResources = WidenStringLeaves; +type TranslationResourcesBase = WidenStringLeaves; +type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other"; +type DurationPluralKey = `${"days" | "hours" | "minutes"}_${PluralCategory}`; +type AgoPluralKey = `${"daysAgo" | "hoursAgo" | "minutesAgo"}_${PluralCategory}`; + +// i18next plural categories differ by locale, so locale catalogs may add the +// explicit v4 suffixes they need while retaining the canonical fallback keys. +export type TranslationResources = TranslationResourcesBase & { + providerUsage: TranslationResourcesBase["providerUsage"] & { + duration: TranslationResourcesBase["providerUsage"]["duration"] & + Partial>; + timing: TranslationResourcesBase["providerUsage"]["timing"] & + Partial>; + }; +}; diff --git a/packages/app/src/usage/format.test.ts b/packages/app/src/usage/format.test.ts index 939628e26d..27ea022148 100644 --- a/packages/app/src/usage/format.test.ts +++ b/packages/app/src/usage/format.test.ts @@ -51,4 +51,20 @@ describe("formatAmount", () => { expect(formatRunsOutLabel(daysFromNow, NOW)).toBe("s’épuise dans 1\u202f234 j"); expect(formatAgo(daysAgo, NOW)).toBe("il y a 1\u202f234 j"); }); + + it("selects Arabic plural forms for relative durations", async () => { + await i18n.changeLanguage("ar"); + const twoHoursFromNow = new Date(NOW + 2 * 60 * 60 * 1000).toISOString(); + const threeHoursFromNow = new Date(NOW + 3 * 60 * 60 * 1000).toISOString(); + const twoDaysAgo = new Date(NOW - 2 * 24 * 60 * 60 * 1000).toISOString(); + + expect(formatResetLabel(twoHoursFromNow)).toBe("تتم إعادة التعيين خلال ساعتين"); + expect(formatRunsOutLabel(threeHoursFromNow)).toBe("ينفد خلال 3 ساعات"); + expect(formatAgo(twoDaysAgo)).toBe("قبل يومين"); + }); + + it("uses locale-aware compact notation for token balances", () => { + expect(formatAmount(1_234, "tokens", "en")).toBe("1.2K"); + expect(formatAmount(1_234, "tokens", "ar")).toBe("1.2\u00a0ألف"); + }); }); diff --git a/packages/app/src/usage/format.ts b/packages/app/src/usage/format.ts index c99a7a07a8..4b0a1c67e9 100644 --- a/packages/app/src/usage/format.ts +++ b/packages/app/src/usage/format.ts @@ -1,4 +1,3 @@ -import { formatTokenCount } from "@/components/context-window-meter.utils"; import { i18n } from "@/i18n/i18next"; import type { UsageDisplayAs } from "./preferences"; import type { UsageBalanceUnit } from "./types";