feat(client/i18n): B1 硬编码文案 6 语化 + B2 语言持久化
B2 持久化:localeProvider 由 StateProvider 改 StateNotifier(LocaleNotifier), 默认英文,选择存 shared_preferences(key pg_lang),重启保留;新增 appTextFor(lang) helper 供非 Consumer 场景复用。两处切换 .state= 改 .set()。 B1 硬编码 UI 文案 6 语化(AppLangMisc 扩展,exhaustive switch 不会漏): - 不可用 / 流媒体优化 / 节点未就绪 / 连接失败 / 加载失败 / 暂无设备 / 改邮箱 - 相对时间(刚刚/X分钟前/…)relativeTime(Duration) - 修 auth_screen 错误消息 bug:原 _errorZh 永远显示中文(连英文用户都中招)→ 按当前语言取 messageZh/En(非中文显英文) 仍回退英文(服务端数据,需服务端多语字段):节点/套餐名(nameZh/nameEn)、 API 错误消息(messageZh/En)。flutter analyze 仅 2 个既有 withOpacity info。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// app_providers.dart — 语言 / 主题 / 套餐视角等基础状态(Riverpod)
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../l10n/app_text.dart';
|
||||
import '../l10n/strings_en.dart';
|
||||
@@ -12,12 +13,10 @@ import '../l10n/strings_zh.dart';
|
||||
import 'account_providers.dart';
|
||||
import 'auth_provider.dart';
|
||||
|
||||
/// 当前语言(单显)。设置/账户页切换。默认英文(国际化默认语种)。
|
||||
final localeProvider = StateProvider<AppLang>((ref) => AppLang.en);
|
||||
|
||||
/// 由语言派生的文案资源——UI 一律通过它取文案,不写死字面量。
|
||||
final appTextProvider = Provider<AppText>((ref) {
|
||||
switch (ref.watch(localeProvider)) {
|
||||
/// AppLang → 文案资源实例。供 [appTextProvider] 与「拿不到 Consumer 的场景」
|
||||
/// (model / 非 Consumer 的 tile,只有 AppLang)复用,避免各处重写 switch。
|
||||
AppText appTextFor(AppLang lang) {
|
||||
switch (lang) {
|
||||
case AppLang.zh:
|
||||
return const StringsZh();
|
||||
case AppLang.en:
|
||||
@@ -31,7 +30,48 @@ final appTextProvider = Provider<AppText>((ref) {
|
||||
case AppLang.es:
|
||||
return const StringsEs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 当前语言(单显)。默认英文(国际化默认语种);用户选择持久化到
|
||||
/// shared_preferences(key `pg_lang`,存枚举 name),重启保留 —— 原来无持久化,
|
||||
/// 切了语言重启会丢。设置/账户页经 `.notifier).set(lang)` 切换。
|
||||
class LocaleNotifier extends StateNotifier<AppLang> {
|
||||
LocaleNotifier() : super(AppLang.en) {
|
||||
_load();
|
||||
}
|
||||
static const _key = 'pg_lang';
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final saved = (await SharedPreferences.getInstance()).getString(_key);
|
||||
if (saved != null) {
|
||||
for (final l in AppLang.values) {
|
||||
if (l.name == saved) {
|
||||
state = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
/* 读失败保持默认 en */
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> set(AppLang lang) async {
|
||||
state = lang;
|
||||
try {
|
||||
await (await SharedPreferences.getInstance()).setString(_key, lang.name);
|
||||
} catch (_) {
|
||||
/* 持久化失败忽略,本次会话仍生效 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final localeProvider =
|
||||
StateNotifierProvider<LocaleNotifier, AppLang>((ref) => LocaleNotifier());
|
||||
|
||||
/// 由语言派生的文案资源——UI 一律通过它取文案,不写死字面量。
|
||||
final appTextProvider = Provider<AppText>((ref) => appTextFor(ref.watch(localeProvider)));
|
||||
|
||||
/// 主题模式。默认跟随系统;设置页可显式切深色。
|
||||
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.system);
|
||||
|
||||
@@ -203,7 +203,8 @@ class ConnectionController extends StateNotifier<ConnectionState> {
|
||||
state = const ConnectionState(phase: VpnPhase.connecting);
|
||||
|
||||
final node = _ref.read(effectiveNodeProvider);
|
||||
final zh = _ref.read(localeProvider) == AppLang.zh;
|
||||
final lang = _ref.read(localeProvider);
|
||||
final zh = lang == AppLang.zh; // 仅用于服务端 e.messageZh/En 的二选一(下方)
|
||||
logLine('Connect', '_connect node=${node.code} uuid=${node.uuid.isEmpty ? "EMPTY" : "ok"} '
|
||||
'selected=${_ref.read(selectedNodeCodeProvider)} nodes=${(_ref.read(nodesProvider).valueOrNull ?? const []).length}');
|
||||
|
||||
@@ -212,7 +213,7 @@ class ConnectionController extends StateNotifier<ConnectionState> {
|
||||
if (mounted) {
|
||||
state = ConnectionState(
|
||||
phase: VpnPhase.off,
|
||||
error: zh ? '节点尚未就绪,请稍候重试' : 'Nodes not ready, please retry',
|
||||
error: lang.nodesNotReady,
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -244,7 +245,7 @@ class ConnectionController extends StateNotifier<ConnectionState> {
|
||||
if (mounted) {
|
||||
state = ConnectionState(
|
||||
phase: VpnPhase.off,
|
||||
error: zh ? '连接失败,请重试' : 'Connection failed, please retry',
|
||||
error: lang.connectFailed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user