Skip to content

Commit ddc7860

Browse files
mapleafgoclaude
andcommitted
feat: 实现完整国际化(i18n)支持,中英双语实时切换
- 使用 slang 库实现类型安全的国际化,支持 context-free 的 t.xxx 访问 - 新增 l10nBuilder 辅助函数,利用 SignalBuilder 的 dependencies 参数统一处理 locale 响应 - 所有 UI 文本、托盘菜单、对话框、深度链接提示均支持中英双语 - 设置页新增语言切换(跟随系统/中文/English),持久化存储 - 确保 LocaleSettings 在 appLocale 之前更新,避免 effects 时序问题 - 缓存合并后内核配置(lastMergedConfig signal) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 028d383 commit ddc7860

29 files changed

Lines changed: 2354 additions & 254 deletions

build.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,19 @@ targets:
1010
reflectable:
1111
generate_for:
1212
- no/files
13+
14+
slang_build_runner:
15+
options:
16+
base_locale: zh
17+
fallback_strategy: base_locale
18+
input_directory: lib/i18n
19+
input_file_pattern: .i18n.json
20+
output_directory: lib/i18n
21+
output_file_name: strings.g.dart
22+
lazy: false
23+
locale_handling: true
24+
flutter_integration: true
25+
translate_var: t
26+
enum_name: AppLocale
27+
key_case: camel
28+
string_interpolation: dart

lib/data/local/app_settings_storage.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class AppStoredConfig {
6161
final String? themeMode;
6262
final String? ignoredVersion;
6363
final bool autoCheckUpdate;
64+
final String? locale;
6465

6566
AppStoredConfig({
6667
this.selectedFile,
@@ -71,6 +72,7 @@ class AppStoredConfig {
7172
this.themeMode,
7273
this.ignoredVersion,
7374
this.autoCheckUpdate = true,
75+
this.locale,
7476
}) : subUA = subUA ?? Defaults.subUA;
7577

7678
factory AppStoredConfig.fromJson(Map<String, dynamic> json) =>
@@ -86,6 +88,7 @@ class AppStoredConfig {
8688
themeMode: json['theme-mode'] as String?,
8789
ignoredVersion: json['ignored-version'] as String?,
8890
autoCheckUpdate: json['auto-check-update'] as bool? ?? true,
91+
locale: json['locale'] as String?,
8992
);
9093

9194
Map<String, dynamic> toJson() => {
@@ -97,6 +100,7 @@ class AppStoredConfig {
97100
if (themeMode != null) 'theme-mode': themeMode,
98101
if (ignoredVersion != null) 'ignored-version': ignoredVersion,
99102
if (!autoCheckUpdate) 'auto-check-update': autoCheckUpdate,
103+
if (locale != null) 'locale': locale,
100104
};
101105

102106
factory AppStoredConfig.empty() => AppStoredConfig(
@@ -113,6 +117,7 @@ class AppStoredConfig {
113117
String? themeMode,
114118
Object? ignoredVersion = _sentinel,
115119
bool? autoCheckUpdate,
120+
String? locale,
116121
}) =>
117122
AppStoredConfig(
118123
selectedFile: selectedFile ?? this.selectedFile,
@@ -125,5 +130,6 @@ class AppStoredConfig {
125130
? this.ignoredVersion
126131
: ignoredVersion as String?,
127132
autoCheckUpdate: autoCheckUpdate ?? this.autoCheckUpdate,
133+
locale: locale ?? this.locale,
128134
);
129135
}

lib/i18n/en.i18n.json

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
{
2+
"nav": {
3+
"home": "Home",
4+
"proxies": "Proxies",
5+
"profiles": "Profiles",
6+
"settings": "Settings"
7+
},
8+
"home": {
9+
"speed": "Speed",
10+
"connections": "Connections",
11+
"connectionCount": "connections",
12+
"memory": "Memory Usage",
13+
"trafficTotal": "Total Traffic",
14+
"outboundMode": "Outbound Mode",
15+
"proxyMode": "Proxy Mode",
16+
"systemProxy": "System Proxy",
17+
"waitingCore": "Waiting for core...",
18+
"pleaseAddProfile": "Add a profile first",
19+
"enable": "Enable",
20+
"copy": "Copy"
21+
},
22+
"mode": {
23+
"rule": "Rule",
24+
"global": "Global",
25+
"direct": "Direct"
26+
},
27+
"proxies": {
28+
"title": "Proxies",
29+
"sort": "Sort",
30+
"speedTest": "Speed Test",
31+
"empty": "No Proxies",
32+
"emptyHint": "Proxies will appear here after adding a profile",
33+
"sortTitle": "Sort By",
34+
"sortDefault": "Default",
35+
"sortByName": "By Name",
36+
"sortByDelay": "By Delay",
37+
"expandGroups": "Expand Groups",
38+
"selectGroup": "Select Group",
39+
"switchFailed": "Failed to switch proxy: $error"
40+
},
41+
"profiles": {
42+
"title": "Profiles",
43+
"add": "Add",
44+
"addSubscription": "Add Profile",
45+
"fromFile": "From File",
46+
"fromUrl": "From URL",
47+
"edit": "Edit",
48+
"remove": "Remove",
49+
"update": "Update",
50+
"updating": "Updating...",
51+
"confirmDelete": "Confirm Delete",
52+
"confirmDeleteMessage": "Delete \"$name\"?",
53+
"configExists": "Profile \"$name\" already exists",
54+
"fileCopyFailed": "File copy failed: $error",
55+
"urlValidationFailed": "URL validation failed: $error",
56+
"updateFailed": "Update failed: $error",
57+
"importFailed": "Import failed: $error",
58+
"configValidationFailed": "Config validation failed: $validation",
59+
"subscriptionExists": "Subscription already exists",
60+
"inputUrl": "Enter Profile URL",
61+
"pleaseInput": "Please enter",
62+
"name": "Name",
63+
"updateIntervalHours": "Update Interval (hours)",
64+
"empty": "No Profiles",
65+
"emptyHint": "Tap the button below to add a profile",
66+
"copy": "Copy"
67+
},
68+
"settings": {
69+
"title": "Settings",
70+
"sectionCore": "Core",
71+
"proxyService": "Proxy Service",
72+
"proxyServiceDesc": "Enable HTTP/SOCKS5 mixed proxy port",
73+
"allowLan": "Allow LAN",
74+
"allowLanDesc": "Allow other LAN devices to use this proxy",
75+
"port": "Port",
76+
"portDesc": "Local port for proxy service",
77+
"portValidationError": "Enter a port between 1-65535",
78+
"notSet": "Not set",
79+
"ipv6": "IPv6",
80+
"ipv6Desc": "Support IPv6 for proxy connections",
81+
"outboundMode": "Outbound Mode",
82+
"outboundModeDesc": "Control traffic routing strategy",
83+
"clashApi": "Clash API",
84+
"clashApiDesc": "Expose proxy status and control interface",
85+
"apiAddress": "API Address",
86+
"logLevel": "Log Level",
87+
"logLevelDesc": "Lower level = more detailed, use Debug for troubleshooting",
88+
"logDebug": "Debug",
89+
"logInfo": "Info",
90+
"logWarning": "Warning",
91+
"logError": "Error",
92+
"sectionGeneral": "General",
93+
"subUserAgent": "Subscription User-Agent",
94+
"subUaDialogTitle": "Subscription User-Agent",
95+
"subUaDialogDesc": "User-Agent for subscription updates",
96+
"inputUa": "Enter User-Agent",
97+
"defaultValue": "Default",
98+
"delayTestUrl": "Delay Test URL",
99+
"delayTestUrlDesc": "Target URL for latency testing",
100+
"ruleSetProxy": "Rule-Set Proxy",
101+
"ruleSetProxyDesc": "Proxy address for downloading rule-set",
102+
"autoCheckUpdate": "Check for Updates on Startup",
103+
"autoCheckUpdateDesc": "Automatically check for new versions on startup",
104+
"sectionAppearance": "Appearance",
105+
"theme": "Theme",
106+
"themeDesc": "Change app appearance",
107+
"themeSystem": "System Default",
108+
"themeLight": "Light",
109+
"themeDark": "Dark",
110+
"language": "Language",
111+
"languageDesc": "Change app display language",
112+
"languageSystem": "System Default",
113+
"sectionOther": "Other",
114+
"about": "About",
115+
"aboutDesc": "Version info and related links"
116+
},
117+
"about": {
118+
"title": "About",
119+
"officialWebsite": "Website",
120+
"sourceRepo": "Source Code",
121+
"kernelVersion": "Kernel Version",
122+
"loading": "Loading...",
123+
"version": "Version",
124+
"newVersionFound": "Update Available",
125+
"versionInfo": "Current: $current\nLatest: $latest",
126+
"ignore": "Ignore",
127+
"goDownload": "Download",
128+
"removeElevation": "Remove Elevation",
129+
"removeElevationConfirm": "Confirm Remove Elevation",
130+
"removeElevationDesc": "This will remove the TUN elevation service and restart the core in built-in mode (TUN unavailable). You will need to re-elevate next time you use TUN.",
131+
"removeElevationWinDesc": "Remove Windows service and switch to built-in core",
132+
"removeElevationMacDesc": "Remove elevated core and switch to built-in core",
133+
"elevationRemoved": "Elevation removed, switched to built-in core",
134+
"operationFailed": "Operation failed",
135+
"exportLog": "Export Log",
136+
"logNotInitialized": "Log not initialized",
137+
"logFileNotExist": "Log file not found",
138+
"logExported": "Log exported",
139+
"exportFailed": "Export failed",
140+
"easterEgg": "You found me!"
141+
},
142+
"dialogs": {
143+
"error": "Error",
144+
"cancel": "Cancel",
145+
"confirm": "OK",
146+
"delete": "Delete",
147+
"import": "Import",
148+
"copy": "Copy"
149+
},
150+
"tray": {
151+
"showWindow": "Show Window",
152+
"tunMode": "TUN Mode",
153+
"systemProxy": "System Proxy",
154+
"exit": "Exit"
155+
},
156+
"core": {
157+
"connectionFailed": "Core connection failed: $error",
158+
"initTimeout": "Core initialization timed out",
159+
"initFailed": "Core initialization failed: $error",
160+
"reconnectCore": "Reconnect Core",
161+
"restartCore": "Restart Core",
162+
"reconnectMessage": "Core is disconnected. Try reconnecting?",
163+
"restartMessage": "Restart core service?",
164+
"reconnect": "Reconnect",
165+
"restart": "Restart",
166+
"coreState": "Core Status",
167+
"coreDisconnected": "Core disconnected, tap to reconnect",
168+
"modeSwitchFailed": "Failed to switch mode: $error",
169+
"elevationFailed": "Elevation failed, please retry"
170+
},
171+
"deepLink": {
172+
"importSubscription": "Import Profile",
173+
"confirmImport": "Import profile \"$name\"?",
174+
"confirmImportNoName": "Import this profile?",
175+
"importSuccess": "Profile imported successfully",
176+
"importFailed": "Profile import failed: $error"
177+
},
178+
"startup": {
179+
"newVersionFound": "Update Available",
180+
"currentAndLatest": "Current: $current\nLatest: $latest",
181+
"ignoreThisTime": "Dismiss",
182+
"ignoreThisVersion": "Ignore This Version",
183+
"goDownload": "Download"
184+
},
185+
"common": {
186+
"close": "Close"
187+
}
188+
}

0 commit comments

Comments
 (0)