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
+81 -68
View File
@@ -1,8 +1,7 @@
// strings_ru.dart — русские текстовые ресурсы (отображение на одном языке)
// strings_ru.dart — Русский文案(自动生成 · 勿手改)
//
// Запрещённые слова: термины обхода цензуры. Список см. design/CLAUDE.md §1 правило 13.
// Позиционирование: ускорение сети / оптимизация опыта + приватность.
// Номера тарифов соответствуют 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 StringsRu extends AppText {
@@ -27,7 +26,6 @@ class StringsRu extends AppText {
String get connectNow => 'ПОДКЛЮЧИТЬ';
@override
String get secure => 'ЗАЩИЩЕНО';
@override
String get tabConnect => 'Подключение';
@override
@@ -36,7 +34,6 @@ class StringsRu extends AppText {
String get tabStats => 'Статистика';
@override
String get tabMe => 'Аккаунт';
@override
String get currentNode => 'Текущий узел';
@override
@@ -45,7 +42,6 @@ class StringsRu extends AppText {
String get upload => 'Отдача';
@override
String get latency => 'Пинг';
@override
String get quotaToday => 'Осталось сегодня';
@override
@@ -65,20 +61,19 @@ class StringsRu extends AppText {
@override
String get watchAdMore => 'Смотрите рекламу, чтобы добавить время';
@override
String get quotaExhaustedNotice => 'Дневной бесплатный лимит исчерпан. Оформите подписку, чтобы продолжить подключение.';
String get quotaExhaustedNotice =>
'Дневной бесплатный лимит исчерпан. Оформите подписку, чтобы продолжить подключение.';
@override
String get adPlaying => 'Реклама воспроизводится…';
@override
String adRewarded(int minutes) => '+$minutes мин добавлено';
@override
String get adFailed => 'Не удалось добавить время, попробуйте ещё раз';
@override
String get quotaDesktopTitle => 'Дневной бесплатный лимит исчерпан';
@override
String get quotaDesktopBody => 'Смотрите рекламу в мобильном приложении, чтобы добавить время, или оформите подписку для безлимитного доступа.';
String get quotaDesktopBody =>
'Смотрите рекламу в мобильном приложении, чтобы добавить время, или оформите подписку для безлимитного доступа.';
@override
String get gotIt => 'Понятно';
@override
String get chooseNode => 'Выбор сервера';
@override
@@ -86,22 +81,22 @@ class StringsRu extends AppText {
@override
String get smartSelect => 'Умный выбор';
@override
String nodeSwitchTitle(String name) => 'Переключиться на $name?';
@override
String get nodeSwitchBody => 'Текущее соединение будет разорвано и восстановлено через этот узел.';
String get nodeSwitchBody =>
'Текущее соединение будет разорвано и восстановлено через этот узел.';
@override
String get nodeSwitchConfirm => 'Переключить';
@override
String get nodeUnhealthySwitched => 'Текущий узел недоступен — переключение выполнено автоматически';
String get nodeUnhealthySwitched =>
'Текущий узел недоступен — переключение выполнено автоматически';
@override
String get nodeUnhealthyError => 'Текущий узел недоступен — переподключитесь или смените узел';
String get nodeUnhealthyError =>
'Текущий узел недоступен — переподключитесь или смените узел';
@override
String get nodeReconnecting => 'Сеть нестабильна, переподключение…';
@override
String get smartSub => 'Подбирает лучший узел для вашей сети';
@override
String get recommended => 'Рекомендуется';
@override
String get statsTitle => 'Статистика';
@override
@@ -113,8 +108,6 @@ class StringsRu extends AppText {
@override
String get weekTraffic => 'На этой неделе (ГБ)';
@override
List<String> get days7 => const ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
@override
String get byDevice => 'По устройствам';
@override
String get noDeviceUsage => 'Пока нет данных по устройствам';
@@ -134,7 +127,6 @@ class StringsRu extends AppText {
String get chartTwoWeeks => 'Последние 2 недели';
@override
String get allDevices => 'Все устройства';
@override
String get meTitle => 'Аккаунт';
@override
@@ -156,7 +148,8 @@ class StringsRu extends AppText {
@override
String get deviceLimitTitle => 'Достигнут лимит устройств';
@override
String get deviceLimitDesc => 'Ваш тариф допускает до %s устройств. Удалите одно, чтобы продолжить.';
String get deviceLimitDesc =>
'Ваш тариф допускает до %s устройств. Удалите одно, чтобы продолжить.';
@override
String get deviceLimitRemoveOldest => 'Удалить давно неиспользуемое';
@override
@@ -186,15 +179,18 @@ class StringsRu extends AppText {
@override
String get devSave => 'Сохранить';
@override
String get devForceLogoutConfirm => 'Это завершит сеанс входа устройства — оно будет отключено и потребует повторного входа. Устройство останется в списке.';
String get devForceLogoutConfirm =>
'Это завершит сеанс входа устройства — оно будет отключено и потребует повторного входа. Устройство останется в списке.';
@override
String get devClearLoginConfirm => 'Это полностью удалит устройство из списка и отзовёт его сеанс входа и учётные данные передачи данных. Чтобы снова использовать устройство, нужно войти заново.';
String get devClearLoginConfirm =>
'Это полностью удалит устройство из списка и отзовёт его сеанс входа и учётные данные передачи данных. Чтобы снова использовать устройство, нужно войти заново.';
@override
String get devCancel => 'Отмена';
@override
String get sessionRevokedTitle => 'Это устройство было отключено';
@override
String get sessionRevokedBody => 'Вход с этого устройства завершён с другого устройства вашего аккаунта. Пожалуйста, войдите снова.';
String get sessionRevokedBody =>
'Вход с этого устройства завершён с другого устройства вашего аккаунта. Пожалуйста, войдите снова.';
@override
String get sessionRevokedOk => 'Войти снова';
@override
@@ -224,11 +220,10 @@ class StringsRu extends AppText {
@override
String get autoConnectSub => 'Подключаться автоматически при запуске';
@override
String autoConnectingTo(String name) => 'Автоподключение к $name';
@override
String get smartRoute => 'Умная маршрутизация';
@override
String get smartRouteSub => 'Ускорение для зарубежных ресурсов, прямой доступ для локальных';
String get smartRouteSub =>
'Ускорение для зарубежных ресурсов, прямой доступ для локальных';
@override
String get killSwitch => 'Kill Switch';
@override
@@ -238,9 +233,8 @@ class StringsRu extends AppText {
@override
String get webUserCenter => 'Личный кабинет (веб)';
@override
String updateAvailableTitle(String version) => 'Доступна новая версия v$version';
@override
String get updateNotesFallback => 'Это обновление включает исправления и улучшения стабильности.';
String get updateNotesFallback =>
'Это обновление включает исправления и улучшения стабильности.';
@override
String get updateLater => 'Позже';
@override
@@ -248,8 +242,8 @@ class StringsRu extends AppText {
@override
String get updateUpToDate => 'У вас установлена последняя версия';
@override
String get updateCheckFailed => 'Не удалось проверить обновления, попробуйте позже';
String get updateCheckFailed =>
'Не удалось проверить обновления, попробуйте позже';
@override
String get freePlan => 'Бесплатный';
@override
@@ -266,20 +260,6 @@ class StringsRu extends AppText {
String get choose => 'Выбрать';
@override
String get mostPopular => 'Популярный';
@override
List<String> get featsFree => const [
'Только 1 базовый узел',
'10 минут в день',
'Смотрите рекламу, чтобы начать',
'7 дней бесплатно при регистрации',
];
@override
List<String> get featsPro =>
const ['80+ локаций', 'Безлимитно · быстро', '5 устройств', 'Оптимизация для стриминга'];
@override
List<String> get featsTeam =>
const ['Всё из Pro', '10 мест', 'Единый счёт', 'Приоритетная поддержка'];
@override
String get redeemTitle => 'Активация и покупка';
@override
@@ -293,12 +273,12 @@ class StringsRu extends AppText {
@override
String get buyTitle => 'Где купить';
@override
String get buySub => 'Оплата внутри приложения недоступна. Получить код можно через:';
String get buySub =>
'Оплата внутри приложения недоступна. Получить код можно через:';
@override
String get chStore => 'Магазин самообслуживания';
@override
String get chEmail => 'Поддержка по эл. почте';
@override
String get contactTitle => 'Связаться с нами';
@override
@@ -314,7 +294,6 @@ class StringsRu extends AppText {
String get contactHoursTitle => 'Часы поддержки';
@override
String get contactHours => 'Ежедневно 9:00 24:00 (GMT+8)';
@override
String get authTagline => 'Быстро · Стабильно · Без усилий';
@override
@@ -334,8 +313,6 @@ class StringsRu extends AppText {
@override
String get codeLabel => 'Код подтверждения';
@override
String codeSentTo(String email) => 'Код отправлен на $email';
@override
String get sendCode => 'Отправить код';
@override
String get resend => 'Отправить снова';
@@ -348,8 +325,8 @@ class StringsRu extends AppText {
@override
String get forgotPw => 'Забыли пароль?';
@override
String get tos => 'Продолжая, вы принимаете наши Условия и Политику конфиденциальности';
String get tos =>
'Продолжая, вы принимаете наши Условия и Политику конфиденциальности';
@override
String get obSkip => 'Пропустить';
@override
@@ -361,17 +338,17 @@ class StringsRu extends AppText {
@override
String get ob1Title => 'Подключение по всему миру';
@override
String get ob1Sub => '80+ локаций, автоматический выбор самого быстрого маршрута, надёжная стабильность.';
String get ob1Sub =>
'80+ локаций, автоматический выбор самого быстрого маршрута, надёжная стабильность.';
@override
String get ob2Title => 'Разрешите настройку сети';
@override
String get ob2Sub => 'Система запросит добавление сетевого профиля для создания зашифрованного туннеля.';
String get ob2Sub =>
'Система запросит добавление сетевого профиля для создания зашифрованного туннеля.';
@override
String get ob3Title => 'Приватность по умолчанию';
@override
String get ob3Sub => 'Сквозное шифрование. Мы не храним журналы просмотров.';
// ── 购买 / 支付 ──
@override
String get purchaseTitle => 'Покупка тарифа';
@override
@@ -401,15 +378,18 @@ class StringsRu extends AppText {
@override
String get payNetworkLabel => 'Сеть';
@override
String get payExactAmountHint => 'Отправьте точную сумму — активация произойдёт автоматически';
String get payExactAmountHint =>
'Отправьте точную сумму — активация произойдёт автоматически';
@override
String get copied => 'Скопировано';
@override
String get openAlipay => 'Оплатить через Alipay';
@override
String get openAlipayHint => 'Вернитесь после оплаты — страница обновится автоматически';
String get openAlipayHint =>
'Вернитесь после оплаты — страница обновится автоматически';
@override
String get openAlipayFailed => 'Не удалось открыть Alipay. Проверьте, установлен ли он, или повторите';
String get openAlipayFailed =>
'Не удалось открыть Alipay. Проверьте, установлен ли он, или повторите';
@override
String get awaitingPayment => 'Ожидание оплаты';
@override
@@ -429,13 +409,13 @@ class StringsRu extends AppText {
@override
String get qrNotSupported => 'Скопируйте и откройте в Alipay';
@override
String get orderCreateFailed => 'Не удалось создать заказ, повторите попытку позже';
String get orderCreateFailed =>
'Не удалось создать заказ, повторите попытку позже';
@override
String get payUnsupportedRenderType => 'Не поддерживается, смените способ оплаты';
String get payUnsupportedRenderType =>
'Не поддерживается, смените способ оплаты';
@override
String get payNoActiveOrder => 'Нет активных заказов';
// ── 支付渠道 switch + 订单 ──
@override
String get payChannel => 'Способ оплаты';
@override
@@ -474,12 +454,11 @@ class StringsRu extends AppText {
String get orderRebuy => 'Купить снова';
@override
String get payMethodNezha => 'Nazhapay';
// ── 邀请 / 通知 / 设置分组 / 订单过期(Spec ②③占位) ──
@override
String get inviteEntry => 'Приглашайте друзей и получайте членство';
@override
String get inviteEntrySub => 'Друг регистрируется · оба получают дни подписки';
String get inviteEntrySub =>
'Друг регистрируется · оба получают дни подписки';
@override
String get inviteTitle => 'Пригласить друзей';
@override
@@ -546,4 +525,38 @@ class StringsRu extends AppText {
String get promoBadge => 'Спецпредложение';
@override
String get promoOriginalPrice => 'Было';
@override
List<String> get days7 => const ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
@override
List<String> get featsFree => const [
'Только 1 базовый узел',
'10 минут в день',
'Смотрите рекламу, чтобы начать',
'7 дней бесплатно при регистрации',
];
@override
List<String> get featsPro => const [
'80+ локаций',
'Безлимитно · быстро',
'5 устройств',
'Оптимизация для стриминга',
];
@override
List<String> get featsTeam => const [
'Всё из Pro',
'10 мест',
'Единый счёт',
'Приоритетная поддержка',
];
@override
String adRewarded(int minutes) => '+$minutes мин добавлено';
@override
String nodeSwitchTitle(String name) => 'Переключиться на $name?';
@override
String autoConnectingTo(String name) => 'Автоподключение к $name';
@override
String updateAvailableTitle(String version) =>
'Доступна новая версия v$version';
@override
String codeSentTo(String email) => 'Код отправлен на $email';
}