feat(i18n): l10n codegen — strings.json 单源生成 6 份 strings_*.dart

② 单源 codegen 落地:
- strings.json 补全 lists 段(days7/featsFree/featsPro/featsTeam),
  之前 extraction 漏了 4 个 List<String> getter;现 246 getter+4 list+5 method×6 语。
- design/codegen/gen_l10n_dart.mjs:strings.json → strings_{zh,en,ja,ko,ru,es}.dart,
  getter 转义 \$、method 保留 $var 插值,dart format 定型(保证漂移闸零 diff)。
- app_text.dart 保持手写(enum/abstract 声明/AppLangMisc 逻辑),编译器强制 concrete⊆abstract 兜底。
- 往返无损:生成的 Dart 重新抽取 == strings.json 逐字节一致;flutter analyze 0 issue、flutter test 244 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A79VtQA1BwTuQN1ThpvYpo
This commit is contained in:
wangjia
2026-07-27 08:47:44 +08:00
parent 9ab550d398
commit f76e3cdffd
9 changed files with 662 additions and 372 deletions
+65 -59
View File
@@ -1,8 +1,7 @@
// strings_en.dart — English copy resources (single-language display)
// strings_en.dart — English文案(自动生成 · 勿手改)
//
// Red-line words forbidden — see design/CLAUDE.md §1 rule 13 (no censorship-circumvention terms).
// Positioning: network acceleration / experience optimization + privacy.
// Plan numbers follow design/CLAUDE.md §7.
// GENERATED by design/codegen/gen_l10n_dart.mjs from design/i18n/strings.json.
// 改文案:改 strings.json 后重跑 codegen;新增 key 需同步 app_text.dart 抽象声明。
import 'app_text.dart';
class StringsEn extends AppText {
@@ -27,7 +26,6 @@ class StringsEn extends AppText {
String get connectNow => 'CONNECT';
@override
String get secure => 'SECURE';
@override
String get tabConnect => 'Connect';
@override
@@ -36,7 +34,6 @@ class StringsEn extends AppText {
String get tabStats => 'Stats';
@override
String get tabMe => 'Account';
@override
String get currentNode => 'Current node';
@override
@@ -45,7 +42,6 @@ class StringsEn extends AppText {
String get upload => 'Up';
@override
String get latency => 'Ping';
@override
String get quotaToday => 'Left today';
@override
@@ -65,20 +61,19 @@ class StringsEn extends AppText {
@override
String get watchAdMore => 'Watch ad for more time';
@override
String get quotaExhaustedNotice => "You've used today's free time. Upgrade to keep connecting.";
String get quotaExhaustedNotice =>
'You\'ve used today\'s free time. Upgrade to keep connecting.';
@override
String get adPlaying => 'Ad playing…';
@override
String adRewarded(int minutes) => '+$minutes min added';
@override
String get adFailed => 'Failed to add time, please retry';
@override
String get quotaDesktopTitle => 'Daily free time used up';
@override
String get quotaDesktopBody => 'Watch ads in the mobile app to add time, or upgrade for unlimited access.';
String get quotaDesktopBody =>
'Watch ads in the mobile app to add time, or upgrade for unlimited access.';
@override
String get gotIt => 'Got it';
@override
String get chooseNode => 'Choose server';
@override
@@ -86,22 +81,22 @@ class StringsEn extends AppText {
@override
String get smartSelect => 'Smart select';
@override
String nodeSwitchTitle(String name) => 'Switch to $name?';
@override
String get nodeSwitchBody => 'This disconnects the current link and reconnects to this node.';
String get nodeSwitchBody =>
'This disconnects the current link and reconnects to this node.';
@override
String get nodeSwitchConfirm => 'Switch';
@override
String get nodeUnhealthySwitched => 'Current node unreachable — switched automatically';
String get nodeUnhealthySwitched =>
'Current node unreachable — switched automatically';
@override
String get nodeUnhealthyError => 'Current node is unreachable — reconnect or switch node';
String get nodeUnhealthyError =>
'Current node is unreachable — reconnect or switch node';
@override
String get nodeReconnecting => 'Network unstable, reconnecting…';
@override
String get smartSub => 'Picks the best node for your network';
@override
String get recommended => 'Recommended';
@override
String get statsTitle => 'Statistics';
@override
@@ -113,8 +108,6 @@ class StringsEn extends AppText {
@override
String get weekTraffic => 'This week (GB)';
@override
List<String> get days7 => const ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
@override
String get byDevice => 'By device';
@override
String get noDeviceUsage => 'No device usage yet';
@@ -134,7 +127,6 @@ class StringsEn extends AppText {
String get chartTwoWeeks => 'Last 2 weeks';
@override
String get allDevices => 'All devices';
@override
String get meTitle => 'Account';
@override
@@ -156,7 +148,8 @@ class StringsEn extends AppText {
@override
String get deviceLimitTitle => 'Device limit reached';
@override
String get deviceLimitDesc => 'Your plan allows up to %s devices. Remove one to continue.';
String get deviceLimitDesc =>
'Your plan allows up to %s devices. Remove one to continue.';
@override
String get deviceLimitRemoveOldest => 'Remove least recently used';
@override
@@ -186,15 +179,18 @@ class StringsEn extends AppText {
@override
String get devSave => 'Save';
@override
String get devForceLogoutConfirm => 'This revokes the device\'s login session — it will be kicked offline and must sign in again. The device stays in the list.';
String get devForceLogoutConfirm =>
'This revokes the device\'s login session — it will be kicked offline and must sign in again. The device stays in the list.';
@override
String get devClearLoginConfirm => 'This removes the device from the list entirely and revokes its login session and data-plane credential. The device must sign in again to be used.';
String get devClearLoginConfirm =>
'This removes the device from the list entirely and revokes its login session and data-plane credential. The device must sign in again to be used.';
@override
String get devCancel => 'Cancel';
@override
String get sessionRevokedTitle => 'This device was signed out';
@override
String get sessionRevokedBody => 'Your account signed this device out from another device. Please sign in again.';
String get sessionRevokedBody =>
'Your account signed this device out from another device. Please sign in again.';
@override
String get sessionRevokedOk => 'Sign in again';
@override
@@ -224,8 +220,6 @@ class StringsEn extends AppText {
@override
String get autoConnectSub => 'Connect automatically on launch';
@override
String autoConnectingTo(String name) => 'Auto-connecting to $name';
@override
String get smartRoute => 'Smart routing';
@override
String get smartRouteSub => 'Accelerate abroad, direct at home';
@@ -238,9 +232,8 @@ class StringsEn extends AppText {
@override
String get webUserCenter => 'User Center (Web)';
@override
String updateAvailableTitle(String version) => 'New version v$version available';
@override
String get updateNotesFallback => 'This update includes fixes and stability improvements.';
String get updateNotesFallback =>
'This update includes fixes and stability improvements.';
@override
String get updateLater => 'Later';
@override
@@ -249,7 +242,6 @@ class StringsEn extends AppText {
String get updateUpToDate => 'You are up to date';
@override
String get updateCheckFailed => 'Update check failed, try again later';
@override
String get freePlan => 'Free';
@override
@@ -266,20 +258,6 @@ class StringsEn extends AppText {
String get choose => 'Choose';
@override
String get mostPopular => 'Popular';
@override
List<String> get featsFree => const [
'1 basic node only',
'10 min per day',
'Watch an ad to start',
'7-day free trial on sign-up',
];
@override
List<String> get featsPro =>
const ['80+ locations', 'Unlimited · fast', '5 devices', 'Streaming optimized'];
@override
List<String> get featsTeam =>
const ['Everything in Pro', '10 seats', 'Central billing', 'Priority support'];
@override
String get redeemTitle => 'Redeem & buy';
@override
@@ -298,7 +276,6 @@ class StringsEn extends AppText {
String get chStore => 'Self-serve store';
@override
String get chEmail => 'Email support';
@override
String get contactTitle => 'Contact us';
@override
@@ -314,7 +291,6 @@ class StringsEn extends AppText {
String get contactHoursTitle => 'Support hours';
@override
String get contactHours => 'Daily 9:00 24:00 (GMT+8)';
@override
String get authTagline => 'Fast · Stable · Effortless';
@override
@@ -334,8 +310,6 @@ class StringsEn extends AppText {
@override
String get codeLabel => 'Verification code';
@override
String codeSentTo(String email) => 'Code sent to $email';
@override
String get sendCode => 'Send code';
@override
String get resend => 'Resend';
@@ -349,7 +323,6 @@ class StringsEn extends AppText {
String get forgotPw => 'Forgot password?';
@override
String get tos => 'By continuing you agree to our Terms & Privacy Policy';
@override
String get obSkip => 'Skip';
@override
@@ -365,12 +338,12 @@ class StringsEn extends AppText {
@override
String get ob2Title => 'Allow network setup';
@override
String get ob2Sub => 'The system will ask to add a network profile to build the encrypted tunnel.';
String get ob2Sub =>
'The system will ask to add a network profile to build the encrypted tunnel.';
@override
String get ob3Title => 'Private by design';
@override
String get ob3Sub => 'End-to-end encrypted. We keep zero browsing logs.';
@override
String get purchaseTitle => 'Purchase';
@override
@@ -400,15 +373,18 @@ class StringsEn extends AppText {
@override
String get payNetworkLabel => 'Network';
@override
String get payExactAmountHint => 'Send the exact amount; activates automatically';
String get payExactAmountHint =>
'Send the exact amount; activates automatically';
@override
String get copied => 'Copied';
@override
String get openAlipay => 'Pay with Alipay';
@override
String get openAlipayHint => 'Return after paying; this page refreshes automatically';
String get openAlipayHint =>
'Return after paying; this page refreshes automatically';
@override
String get openAlipayFailed => "Couldn't open Alipay; check it's installed or try again";
String get openAlipayFailed =>
'Couldn\'t open Alipay; check it\'s installed or try again';
@override
String get awaitingPayment => 'Awaiting payment';
@override
@@ -433,8 +409,6 @@ class StringsEn extends AppText {
String get payUnsupportedRenderType => 'Unsupported, switch payment method';
@override
String get payNoActiveOrder => 'No active order';
// ── 支付渠道 switch + 订单 ──
@override
String get payChannel => 'Payment channel';
@override
@@ -473,8 +447,6 @@ class StringsEn extends AppText {
String get orderRebuy => 'Buy again';
@override
String get payMethodNezha => 'Nazhapay';
// ── 邀请 / 通知 / 设置分组 / 订单过期(Spec ②③占位) ──
@override
String get inviteEntry => 'Invite friends, earn membership';
@override
@@ -545,4 +517,38 @@ class StringsEn extends AppText {
String get promoBadge => 'Limited-time offer';
@override
String get promoOriginalPrice => 'Was';
@override
List<String> get days7 => const ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
@override
List<String> get featsFree => const [
'1 basic node only',
'10 min per day',
'Watch an ad to start',
'7-day free trial on sign-up',
];
@override
List<String> get featsPro => const [
'80+ locations',
'Unlimited · fast',
'5 devices',
'Streaming optimized',
];
@override
List<String> get featsTeam => const [
'Everything in Pro',
'10 seats',
'Central billing',
'Priority support',
];
@override
String adRewarded(int minutes) => '+$minutes min added';
@override
String nodeSwitchTitle(String name) => 'Switch to $name?';
@override
String autoConnectingTo(String name) => 'Auto-connecting to $name';
@override
String updateAvailableTitle(String version) =>
'New version v$version available';
@override
String codeSentTo(String email) => 'Code sent to $email';
}