diff --git a/assets/images/active_home_o.svg b/assets/images/active_home_o.svg
new file mode 100644
index 0000000..f0b32ed
--- /dev/null
+++ b/assets/images/active_home_o.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/images/active_server_o.svg b/assets/images/active_server_o.svg
new file mode 100644
index 0000000..4830726
--- /dev/null
+++ b/assets/images/active_server_o.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/images/active_settings_o.svg b/assets/images/active_settings_o.svg
new file mode 100644
index 0000000..b56dea8
--- /dev/null
+++ b/assets/images/active_settings_o.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/images/support_icons.png b/assets/images/support_icons.png
new file mode 100644
index 0000000..83a72d9
Binary files /dev/null and b/assets/images/support_icons.png differ
diff --git a/assets/lang/en.json b/assets/lang/en.json
new file mode 100644
index 0000000..612e72d
--- /dev/null
+++ b/assets/lang/en.json
@@ -0,0 +1,41 @@
+{
+
+ "app_name": "VPN Client",
+ "apps_selection": "App Selection",
+ "search": "Search",
+ "your_location": "Your Location",
+ "auto_select": "Auto Select",
+ "kazakhstan": "Kazakhstan",
+ "turkey": "Turkey",
+ "poland": "Poland",
+ "fastest": "Fastest",
+ "selected_server": "Selected server",
+ "server_selection": "Server selection",
+ "all_servers": "All servers",
+ "country_name": "Country name",
+ "all_apps": "All Applications",
+ "done": "Done",
+ "cancel": "Cancel",
+ "recently_searched": "Recently searched",
+ "nothing_found": "Nothing found",
+ "connected": "CONNECTED",
+ "disconnected": "DISCONNECTED",
+ "reconnecting": "RECONNECTING",
+ "connecting": "CONNECTING",
+ "disconnecting": "DISCONNECTING",
+ "settings": "Settings",
+ "version": "Version",
+ "connection": "Connection",
+ "not_connected": "Not connected",
+ "support": "Support",
+ "unavailable": "Unavailable",
+ "your_id": "Your ID",
+ "support_service": "Support Service",
+ "reset_settings": "Reset settings",
+ "connect": "Connect",
+ "are_you_sure_reset": "Are you sure you want to reset all connection settings?",
+ "reset": "Reset",
+ "connection_reset": "Connection settings have been reset",
+ "failed_open_telegram": "Failed to open Telegram bot",
+ "about_app": "About App"
+}
diff --git a/lib/l10n/app_ru.arb b/assets/lang/ru.json
similarity index 53%
rename from lib/l10n/app_ru.arb
rename to assets/lang/ru.json
index 067094e..1e302b1 100644
--- a/lib/l10n/app_ru.arb
+++ b/assets/lang/ru.json
@@ -1,5 +1,5 @@
{
- "@@locale": "ru",
+
"app_name": "VPN Клиент",
"apps_selection": "Выбор приложений",
"search": "Поиск",
@@ -22,5 +22,20 @@
"disconnected": "ОТКЛЮЧЕН",
"reconnecting": "Повторное подключение",
"connecting": "ПОДКЛЮЧЕНИЕ",
- "disconnecting": "ОТКЛЮЧЕНИЕ"
+ "disconnecting": "ОТКЛЮЧЕНИЕ",
+ "settings": "Настройки",
+ "version": "Версия",
+ "connection": "Подключение",
+ "not_connected": "Не подключено",
+ "support": "Поддержка",
+ "unavailable": "Недоступно",
+ "your_id": "Ваш ID",
+ "support_service": "Служба поддержки",
+ "reset_settings": "Сбросить настройки",
+ "connect": "Подключить",
+ "are_you_sure_reset": "Вы уверены, что хотите сбросить все настройки подключения?",
+ "reset": "Сбросить",
+ "connection_reset": "Настройки подключения сброшены",
+ "failed_open_telegram": "Не удалось открыть Telegram бот",
+ "about_app": "О приложении"
}
diff --git a/lib/l10n/app_th.arb b/assets/lang/th.json
similarity index 53%
rename from lib/l10n/app_th.arb
rename to assets/lang/th.json
index c258b47..de766dc 100644
--- a/lib/l10n/app_th.arb
+++ b/assets/lang/th.json
@@ -1,5 +1,5 @@
{
- "@@locale": "th",
+
"app_name": "VPN Client",
"apps_selection": "เลือกแอป",
"search": "ค้นหา",
@@ -22,5 +22,20 @@
"disconnected": "ไม่ได้เชื่อมต่อ",
"reconnecting": "กำลังเชื่อมต่อใหม่",
"connecting": "กำลังเชื่อมต่อ",
- "disconnecting": "กำลังตัดการเชื่อมต่อ"
+ "disconnecting": "กำลังตัดการเชื่อมต่อ",
+ "settings": "การตั้งค่า",
+ "version": "เวอร์ชัน",
+ "connection": "การเชื่อมต่อ",
+ "not_connected": "ไม่ได้เชื่อมต่อ",
+ "support": "การสนับสนุน",
+ "unavailable": "ไม่พร้อมใช้งาน",
+ "your_id": "รหัสของคุณ",
+ "support_service": "บริการสนับสนุน",
+ "reset_settings": "รีเซ็ตการตั้งค่า",
+ "connect": "เชื่อมต่อ",
+ "are_you_sure_reset": "คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตการตั้งค่าการเชื่อมต่อทั้งหมด?",
+ "reset": "รีเซ็ต",
+ "connection_reset": "รีเซ็ตการตั้งค่าการเชื่อมต่อแล้ว",
+ "failed_open_telegram": "ไม่สามารถเปิดบอท Telegram ได้",
+ "about_app": "เกี่ยวกับแอป"
}
diff --git a/lib/l10n/app_zh.arb b/assets/lang/zh.json
similarity index 56%
rename from lib/l10n/app_zh.arb
rename to assets/lang/zh.json
index fae8b5e..43128d3 100644
--- a/lib/l10n/app_zh.arb
+++ b/assets/lang/zh.json
@@ -1,5 +1,5 @@
{
- "@@locale": "zh",
+
"app_name": "VPN客户端",
"apps_selection": "应用选择",
"search": "搜索",
@@ -22,5 +22,20 @@
"disconnected": "已断开连接",
"reconnecting": "重新连接",
"connecting": "连接中",
- "disconnecting": "断开中"
+ "disconnecting": "断开中",
+ "settings": "设置",
+ "version": "版本",
+ "connection": "连接",
+ "not_connected": "未连接",
+ "support": "支持",
+ "unavailable": "不可用",
+ "your_id": "您的 ID",
+ "support_service": "客服服务",
+ "reset_settings": "重置设置",
+ "connect": "连接",
+ "are_you_sure_reset": "您确定要重置所有连接设置吗?",
+ "reset": "重置",
+ "connection_reset": "连接设置已重置",
+ "failed_open_telegram": "无法打开 Telegram 机器人",
+ "about_app": "关于应用"
}
diff --git a/l10n.yaml b/l10n.yaml
deleted file mode 100644
index d5830f6..0000000
--- a/l10n.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-synthetic-package: true
-arb-dir: lib/l10n
-template-arb-file: app_en.arb
-output-localization-file: app_localizations.dart
diff --git a/lib/design/colors.dart b/lib/design/colors.dart
index 2578f88..3cfc0ba 100644
--- a/lib/design/colors.dart
+++ b/lib/design/colors.dart
@@ -23,7 +23,7 @@ final ThemeData darkTheme = ThemeData(
);
final LinearGradient mainGradient = LinearGradient(
- colors: [Color(0xFF00C6FB), Color(0xFF005BEA)],
+ colors: [Color(0xFFFBB800), Color(0xFFEA7500)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
diff --git a/lib/design/images.dart b/lib/design/images.dart
index 2e011ea..e1b4212 100644
--- a/lib/design/images.dart
+++ b/lib/design/images.dart
@@ -2,7 +2,7 @@ import 'package:flutter_svg/flutter_svg.dart';
final SvgPicture homeIcon = SvgPicture.asset('assets/images/home.svg');
final SvgPicture activeHomeIcon = SvgPicture.asset(
- 'assets/images/active_home.svg',
+ 'assets/images/active_home_o.svg',
);
final SvgPicture appIcon = SvgPicture.asset('assets/images/app.svg');
final SvgPicture activeAppIcon = SvgPicture.asset(
@@ -10,8 +10,11 @@ final SvgPicture activeAppIcon = SvgPicture.asset(
);
final SvgPicture serverIcon = SvgPicture.asset('assets/images/server.svg');
final SvgPicture activeServerIcon = SvgPicture.asset(
- 'assets/images/active_server.svg',
+ 'assets/images/active_server_o.svg',
);
final SvgPicture settingsIcon = SvgPicture.asset('assets/images/settings.svg');
+final SvgPicture activeSettingsIcon = SvgPicture.asset(
+ 'assets/images/active_settings_o.svg',
+);
final SvgPicture speedIcon = SvgPicture.asset('assets/images/speed.svg');
final SvgPicture deFlag = SvgPicture.asset('assets/images/de.svg');
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
deleted file mode 100644
index 385ce8c..0000000
--- a/lib/l10n/app_en.arb
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "@@locale": "en",
- "app_name": "VPN Client",
- "apps_selection": "App Selection",
- "search": "Search",
- "your_location": "Your Location",
- "auto_select": "Auto Select",
- "kazakhstan": "Kazakhstan",
- "turkey": "Turkey",
- "poland": "Poland",
- "fastest": "Fastest",
- "selected_server": "Selected server",
- "server_selection": "Server selection",
- "all_servers": "All servers",
- "country_name": "Country name",
- "all_apps": "All Applications",
- "done": "Done",
- "cancel": "Cancel",
- "recently_searched": "Recently searched",
- "nothing_found": "Nothing found",
- "connected": "CONNECTED",
- "disconnected": "DISCONNECTED",
- "reconnecting": "RECONNECTING",
- "connecting": "CONNECTING",
- "disconnecting": "DISCONNECTING"
-}
diff --git a/lib/localization_service.dart b/lib/localization_service.dart
new file mode 100644
index 0000000..259fbbd
--- /dev/null
+++ b/lib/localization_service.dart
@@ -0,0 +1,32 @@
+import 'dart:convert';
+import 'package:flutter/services.dart';
+import 'package:flutter/material.dart';
+
+class LocalizationService {
+ static Map _localizedStrings = {};
+ static late Locale _currentLocale;
+
+ static Future load(Locale locale) async {
+ _currentLocale = locale;
+ String langCode = locale.languageCode;
+
+ // Try loading the file, fallback to English
+ try {
+ final String jsonString = await rootBundle.loadString(
+ 'assets/lang/$langCode.json',
+ );
+ _localizedStrings = json.decode(jsonString);
+ } catch (_) {
+ final String fallback = await rootBundle.loadString(
+ 'assets/lang/en.json',
+ );
+ _localizedStrings = json.decode(fallback);
+ }
+ }
+
+ static String to(String key) {
+ return _localizedStrings[key] ?? '[$key]';
+ }
+
+ static Locale get currentLocale => _currentLocale;
+}
diff --git a/lib/main.dart b/lib/main.dart
index 334992a..469fc36 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,18 +1,25 @@
import 'package:flutter/material.dart';
-import 'package:flutter_localizations/flutter_localizations.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
-
import 'package:vpn_client/pages/apps/apps_page.dart';
+import 'dart:ui' as ui;
import 'package:vpn_client/pages/main/main_page.dart';
+import 'package:vpn_client/pages/settings/setting_page.dart';
import 'package:vpn_client/pages/servers/servers_page.dart';
import 'package:vpn_client/theme_provider.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:vpn_client/vpn_state.dart';
+import 'package:vpn_client/localization_service.dart';
import 'design/colors.dart';
import 'nav_bar.dart';
-void main() {
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ Locale userLocale =
+ ui.PlatformDispatcher.instance.locale; // <-- Get the system locale
+ await LocalizationService.load(userLocale);
+
runApp(
MultiProvider(
providers: [
@@ -36,28 +43,23 @@ class App extends StatelessWidget {
final Locale? manualLocale = null; // ← use system by default
return MaterialApp(
+ localizationsDelegates: const [
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ ],
debugShowCheckedModeBanner: false,
title: 'VPN Client',
theme: lightTheme,
darkTheme: darkTheme,
locale: manualLocale,
- localeResolutionCallback: (locale, supportedLocales) {
+ localeResolutionCallback: (locale, _) {
if (locale == null) return const Locale('en');
// Check for exact match
- for (var supportedLocale in supportedLocales) {
- if (supportedLocale.languageCode == locale.languageCode &&
- (supportedLocale.countryCode == null ||
- supportedLocale.countryCode == locale.countryCode)) {
- return supportedLocale;
- }
- }
-
- // If Chinese variants are not supported, fallback to zh
- if (locale.languageCode == 'zh') {
- return supportedLocales.contains(const Locale('zh'))
- ? const Locale('zh')
- : const Locale('en');
+ final supported = ['en', 'ru', 'th', 'zh'];
+ if (supported.contains(locale.languageCode)) {
+ return Locale(locale.languageCode);
}
// Fallback to 'en' if not found
@@ -66,19 +68,6 @@ class App extends StatelessWidget {
themeMode: themeProvider.themeMode,
home: const MainScreen(),
-
- localizationsDelegates: const [
- AppLocalizations.delegate,
- GlobalMaterialLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate,
- GlobalCupertinoLocalizations.delegate,
- ],
- supportedLocales: const [
- Locale('en'),
- Locale('ru'),
- Locale('th'),
- Locale('zh'),
- ],
);
}
}
@@ -102,7 +91,7 @@ class _MainScreenState extends State {
ServersPage(onNavBarTap: _handleNavBarTap),
const MainPage(),
const PlaceholderPage(text: 'Speed Page'),
- const PlaceholderPage(text: 'Settings Page'),
+ SettingPage(onNavBarTap: _handleNavBarTap),
];
}
diff --git a/lib/nav_bar.dart b/lib/nav_bar.dart
index 4438d2e..0b3113b 100644
--- a/lib/nav_bar.dart
+++ b/lib/nav_bar.dart
@@ -27,7 +27,7 @@ class NavBarState extends State {
activeServerIcon,
activeHomeIcon,
speedIcon,
- settingsIcon,
+ activeSettingsIcon,
];
@override
diff --git a/lib/pages/apps/apps_page.dart b/lib/pages/apps/apps_page.dart
index 9e45e79..fdd99ba 100644
--- a/lib/pages/apps/apps_page.dart
+++ b/lib/pages/apps/apps_page.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:vpn_client/pages/apps/apps_list.dart';
import 'package:vpn_client/search_dialog.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:vpn_client/localization_service.dart';
class AppsPage extends StatefulWidget {
const AppsPage({super.key});
@@ -19,7 +19,7 @@ class AppsPageState extends State {
context: context,
builder: (BuildContext context) {
return SearchDialog(
- placeholder: AppLocalizations.of(context)!.app_name,
+ placeholder: LocalizationService.to('app_name'),
items: _apps,
type: 1,
);
@@ -40,7 +40,7 @@ class AppsPageState extends State {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- title: Text(AppLocalizations.of(context)!.apps_selection),
+ title: Text(LocalizationService.to('apps_selection')),
centerTitle: true,
titleTextStyle: TextStyle(
color: Theme.of(context).colorScheme.primary,
@@ -60,7 +60,7 @@ class AppsPageState extends State {
color: Theme.of(context).colorScheme.primary,
),
onPressed: () => _showSearchDialog(context),
- tooltip: AppLocalizations.of(context)!.search,
+ tooltip: LocalizationService.to('search'),
),
),
),
diff --git a/lib/pages/main/location_widget.dart b/lib/pages/main/location_widget.dart
index 889911f..53b724c 100644
--- a/lib/pages/main/location_widget.dart
+++ b/lib/pages/main/location_widget.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:vpn_client/localization_service.dart';
class LocationWidget extends StatelessWidget {
final Map? selectedServer;
@@ -27,7 +27,7 @@ class LocationWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- AppLocalizations.of(context)!.your_location,
+ LocalizationService.to('your_location'),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
diff --git a/lib/pages/main/main_btn.dart b/lib/pages/main/main_btn.dart
index 1a1b40f..7e5d82a 100644
--- a/lib/pages/main/main_btn.dart
+++ b/lib/pages/main/main_btn.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:vpn_client/design/colors.dart';
import 'package:flutter_v2ray/flutter_v2ray.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:vpn_client/localization_service.dart';
import 'package:vpn_client/vpn_state.dart';
final FlutterV2ray flutterV2ray = FlutterV2ray(
@@ -47,21 +47,16 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
super.dispose();
}
- String get connectionStatusText {
- final localizations = AppLocalizations.of(context)!;
+ String connectionStatusText(BuildContext context) {
final vpnState = Provider.of(context, listen: false);
- switch (vpnState.connectionStatus) {
- case ConnectionStatus.connected:
- return localizations.connected;
- case ConnectionStatus.disconnected:
- return localizations.disconnected;
- case ConnectionStatus.reconnecting:
- return localizations.reconnecting;
- case ConnectionStatus.disconnecting:
- return localizations.disconnecting;
- case ConnectionStatus.connecting:
- return localizations.connecting;
- }
+
+ return {
+ ConnectionStatus.connected: LocalizationService.to('connected'),
+ ConnectionStatus.disconnected: LocalizationService.to('disconnected'),
+ ConnectionStatus.reconnecting: LocalizationService.to('reconnecting'),
+ ConnectionStatus.disconnecting: LocalizationService.to('disconnecting'),
+ ConnectionStatus.connecting: LocalizationService.to('connecting'),
+ }[vpnState.connectionStatus]!;
}
Future _toggleConnection(BuildContext context) async {
@@ -160,7 +155,7 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
),
const SizedBox(height: 20),
Text(
- connectionStatusText,
+ connectionStatusText(context),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart
index f08a8e4..de76bd8 100644
--- a/lib/pages/main/main_page.dart
+++ b/lib/pages/main/main_page.dart
@@ -4,7 +4,7 @@ import 'dart:convert';
import 'package:vpn_client/pages/main/main_btn.dart';
import 'package:vpn_client/pages/main/location_widget.dart';
import 'package:vpn_client/pages/main/stat_bar.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:vpn_client/localization_service.dart';
class MainPage extends StatefulWidget {
const MainPage({super.key});
@@ -55,7 +55,7 @@ class MainPageState extends State {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- title: Text(AppLocalizations.of(context)!.app_name),
+ title: Text(LocalizationService.to('app_name')),
centerTitle: true,
titleTextStyle: TextStyle(
color: Theme.of(context).colorScheme.primary,
diff --git a/lib/pages/servers/servers_list.dart b/lib/pages/servers/servers_list.dart
index 7a89d8d..c4343ce 100644
--- a/lib/pages/servers/servers_list.dart
+++ b/lib/pages/servers/servers_list.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:vpn_client/pages/servers/servers_list_item.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:vpn_client/localization_service.dart';
import 'dart:convert';
class ServersList extends StatefulWidget {
@@ -62,25 +62,25 @@ class ServersListState extends State {
List