fix(subscription): display Codex free-plan 30-day quota window (#3651)

Codex free accounts are metered on a rolling 30-day secondary window
(limit_window_seconds = 2,592,000) instead of the weekly window used by
paid plans. The backend already maps this correctly to the tier name
"30_day", but the frontend TIER_I18N_KEYS whitelist had no entry for it,
so SubscriptionQuotaView filtered the tier out. When that was the only
surviving tier (as happens for free accounts), the whole quota footer
rendered nothing — free accounts showed no remaining quota or reset time
while paid accounts worked.

- Add "30_day" -> subscription.thirtyDay to TIER_I18N_KEYS
- Add the thirtyDay label to all four locales (zh/en/ja/zh-TW)
- Add a unit test locking in window_seconds_to_tier_name mappings,
  including the 30-day case

Fixes #3651
This commit is contained in:
SaladDay
2026-07-01 16:56:01 +00:00
parent 8d1b3306d0
commit 3023dc14e0
6 changed files with 24 additions and 0 deletions
+18
View File
@@ -1340,3 +1340,21 @@ fn now_millis() -> i64 {
.unwrap_or_default()
.as_millis() as i64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn window_seconds_map_to_expected_tier_names() {
// 官方特例窗口
assert_eq!(window_seconds_to_tier_name(18000), "five_hour");
assert_eq!(window_seconds_to_tier_name(604800), "seven_day");
// Codex 免费方案的次要窗口是 30 天(30 * 24 * 3600 = 2_592_000 秒)。
// 前端 TIER_I18N_KEYS 需要有对应的 "30_day" 才能展示,见 #3651。
assert_eq!(window_seconds_to_tier_name(2_592_000), "30_day");
// 其他窗口按小时/天回退命名
assert_eq!(window_seconds_to_tier_name(3600), "1_hour");
assert_eq!(window_seconds_to_tier_name(86400), "1_day");
}
}
@@ -27,6 +27,8 @@ export const TIER_I18N_KEYS: Record<string, string> = {
seven_day: "subscription.sevenDay",
seven_day_opus: "subscription.sevenDayOpus",
seven_day_sonnet: "subscription.sevenDaySonnet",
// Codex 免费方案的次要窗口是 30 天(付费方案为 7 天)
"30_day": "subscription.thirtyDay",
// Gemini 模型分类
gemini_pro: "subscription.geminiPro",
gemini_flash: "subscription.geminiFlash",
+1
View File
@@ -2903,6 +2903,7 @@
"sevenDay": "7-Day",
"sevenDayOpus": "7-Day (Opus)",
"sevenDaySonnet": "7-Day (Sonnet)",
"thirtyDay": "30-Day",
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",
+1
View File
@@ -2903,6 +2903,7 @@
"sevenDay": "7日間",
"sevenDayOpus": "7日間(Opus)",
"sevenDaySonnet": "7日間(Sonnet)",
"thirtyDay": "30日間",
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",
+1
View File
@@ -2875,6 +2875,7 @@
"sevenDay": "7 天",
"sevenDayOpus": "7 天 (Opus)",
"sevenDaySonnet": "7 天 (Sonnet)",
"thirtyDay": "30 天",
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",
+1
View File
@@ -2903,6 +2903,7 @@
"sevenDay": "7天",
"sevenDayOpus": "7天(Opus)",
"sevenDaySonnet": "7天(Sonnet)",
"thirtyDay": "30天",
"geminiPro": "Pro",
"geminiFlash": "Flash",
"geminiFlashLite": "Flash Lite",