Files
pangolin/client/lib/l10n/app_text.dart
T
wangjia b461a476a2 feat(client): 免费版连接倒计时 + 到点切断 + 灰按钮 + 看广告加时(#21 前端)
四端共享 Dart 实现,配合 #21 后端账户级卡控:

- me.dart: 加 quota_cap_min(当日总额度 = daily + 看广告 bonus,进度条分母)。
- quota_provider: total 取 quotaCapMin;isExhausted;markExhausted(倒计时归零本地置耗尽
  + 登录态拉 me 校准);watchAd() async 调 /ads/unlock(占位 ad_token=uuid)→ 刷新 me,返回 granted。
- connection_provider: 连接时锁定 _freeRemainingSec(会员 null 不倒计时);复用 elapsed 计时器
  每 tick 算 freeCountdown,归零 → _onFreeQuotaExhausted(主动切断不报节点异常 + markExhausted);
  倒计时用墙上时钟(后台漏跳回前台补上、准时切);连接遇后端 QUOTA_EXHAUSTED 兜底置耗尽。
- connect_button: enabled/onDisabledTap —— 额度耗尽 off 态灰化(锁图标),点击弹加时。
- quota_card: 三态(连接倒计时 mm:ss / 未连接剩余分钟 / 耗尽今日已用完);移动「看广告加时」、桌面「升级会员」。
- ad_reward_dialog(新): 移动端占位广告(播放中→3s→加时→奖励);桌面版硬 10 分钟提示去移动端/升级。
- l10n(zh/en): 倒计时/已用完/看广告加时/占位广告/桌面升级 双语。
- 测试: quota isExhausted/markExhausted;连接倒计时归零自动切断+置耗尽(注入时钟);
  额度卡三态 widget;/me 契约含 quota_cap_min;golden 更新(Linux 权威基线 quota_low/exhausted)。
- 文档: docs/free-quota-ad.html + 登记 docs/index.html。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 00:33:21 +08:00

214 lines
6.4 KiB
Dart

// app_text.dart — 文案契约(l10n 资源层)
//
// 产品任一时刻只显示中文【或】英文(设计铁律 §6 单语言显示),由
// `localeProvider` 段控切换。所有界面文案必须经此处取用,禁止在组件内
// 写死中/英字面量。zh / en 两份资源分别在 strings_zh.dart / strings_en.dart。
//
// 红线词约束(设计铁律 §13):资源文件不得出现 VPN / 翻墙 / 科学上网 /
// 突破封锁 / 自由穿越 / Go anywhere。CI `ci/scan-redline.sh` 对本目录做扫描。
//
// 注意:这是手写资源(非 flutter gen-l10n 代码生成),保证离线可编译;
// 接口形态与 .arb 一致,后续可平滑迁移到官方 l10n 工具链。
/// 受支持语言。单显——任一时刻只渲染其一。
enum AppLang { zh, en }
/// 全部界面文案的抽象契约。zh / en 各实现一份。
abstract class AppText {
const AppText();
AppLang get lang;
// ── 品牌 / 连接状态 ──
String get brand;
String get online;
String get offline;
String get capOff;
String get capConnecting;
String get capOn;
String get connectNow; // 连接键 off 态圆内文字
String get secure; // 连接键圆内「已加密」
// ── 底部 / 侧栏导航 ──
String get tabConnect;
String get tabServers;
String get tabStats;
String get tabMe;
// ── 连接页 ──
String get currentNode;
String get download;
String get upload;
String get latency;
// ── 免费额度卡 ──
String get quotaToday;
String get minutes;
String get quotaFree;
String get watchAd;
String get adUnlocked;
// 免费版 10 分钟卡控 + 看广告加时(累加式)
String get quotaLeftLabel; // 连接期倒计时前缀「剩余」
String get quotaUsedUp; // 额度卡:今日已用完
String get watchAdMore; // 「看广告加时」CTA
String get quotaExhaustedNotice; // 倒计时归零自动切断提示
String get adPlaying; // 占位广告:播放中
String adRewarded(int minutes); // 占位广告:已加 N 分钟
String get adFailed; // 看广告加时失败
String get quotaDesktopTitle; // 桌面版额度耗尽弹窗标题
String get quotaDesktopBody; // 桌面版额度耗尽弹窗正文
String get gotIt; // 知道了
// ── 节点页 ──
String get chooseNode;
String get searchPh;
String get smartSelect;
// 已连接时点其他节点 → 切换确认(防误触)
String nodeSwitchTitle(String name);
String get nodeSwitchBody;
String get nodeSwitchConfirm;
// 连通看门狗:当前节点数据面不可用时的提示
String get nodeUnhealthySwitched; // 智能选择已自动切换
String get nodeUnhealthyError; // 手动节点:断开并提示
String get smartSub;
String get recommended;
// ── 统计页 ──
String get statsTitle;
String get trafficMonth;
String get avgPing;
String get durMonth;
String get weekTraffic;
List<String> get days7;
String get byDevice;
String get noDeviceUsage;
// 统计页重设计:周期(月/周/日) · 指标(下行/上行/时长) · 折线 · 设备下拉
String get periodMonth;
String get periodWeek;
String get periodToday;
String get statDown;
String get statUp;
String get statDuration;
String get chartTwoWeeks;
String get allDevices;
// ── 账户页 ──
String get meTitle;
String get proMember;
String get freePlanName;
String get expires;
String get upgradeBtn;
String get accInfoTitle;
String get accEmail;
String get accPassword;
String get accChange;
String get myDevices;
String get devicesSub;
String get thisDevice;
String get remove;
// 设备数超上限挡板(#16):%s = 套餐设备上限数
String get deviceLimitTitle;
String get deviceLimitDesc; // "你的套餐最多 %s 台设备,移除一台以继续。"
String get deviceLimitRemoveOldest;
// 设备管理(P6):在线状态 / 操作 / 危险确认
String get devOnline;
String get devOffline;
String get devLastOnline; // 「最后在线」前缀(离线设备,取 last_seen)
String get devNeverLogin;
String get devForceLogout;
String get devClearLogin;
String get devRename;
String get devRenameHint;
String get devSave;
String get devForceLogoutConfirm; // 弹窗正文(含设备名占位 %s)
String get devClearLoginConfirm;
String get devCancel;
// 本设备被其他设备强制下线时的提示框。
String get sessionRevokedTitle;
String get sessionRevokedBody;
String get sessionRevokedOk;
String get redeemEntry;
String get contactEntry;
String get signOut;
String get language;
String get darkAppearance;
String get stateOn;
String get followLight;
String get protocol;
String get settingsTitle;
String get autostart;
String get autostartSub;
String get autoConnect;
String get autoConnectSub;
String autoConnectingTo(String name); // 自动连接 toast
String get smartRoute;
String get smartRouteSub;
String get killSwitch;
String get killSwitchSub;
String get checkUpdate;
// ── 套餐选择 ──
String get choosePlan;
String get freePlan;
String get proPlan;
String get teamPlan;
String get perMonth;
String get current;
String get upgrade;
String get choose;
String get mostPopular;
List<String> get featsFree;
List<String> get featsPro;
List<String> get featsTeam;
// ── 兑换 & 购买 ──
String get redeemTitle;
String get redeemCodeTitle;
String get redeemPh;
String get redeemBtn;
String get redeemOk;
String get buyTitle;
String get buySub;
String get chStore;
String get chEmail;
// ── 联系我们 ──
String get contactTitle;
String get contactIntro;
String get contactEmail;
String get contactStore;
String get contactHoursTitle;
String get contactHours;
// ── 登录 / 注册 ──
String get authTagline;
String get tabLogin;
String get tabRegister;
String get emailLabel;
String get emailPh;
String get pwLabel;
String get pwPh;
String get setPwPh;
String get codeLabel;
String codeSentTo(String email);
String get sendCode;
String get resend;
String get doLogin;
String get doNext;
String get doCreate;
String get forgotPw;
String get tos;
// ── 首次引导 ──
String get obSkip;
String get obNext;
String get obAllow;
String get obStart;
String get ob1Title;
String get ob1Sub;
String get ob2Title;
String get ob2Sub;
String get ob3Title;
String get ob3Sub;
}