diff --git a/ui/flutter/lib/i18n/messages.dart b/ui/flutter/lib/i18n/messages.dart new file mode 100644 index 00000000..b0386d6d --- /dev/null +++ b/ui/flutter/lib/i18n/messages.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +Locale toLocale(String key) { + final arr = key.split('_'); + return Locale(arr[0], arr[1]); +} + +final messages = _Messages(); +const fallbackLocale = Locale('en', 'US'); + +class _Messages extends Translations { + @override + Map> get keys => { + 'zh_CN': { + 'error': '错误', + 'confirm': '确认', + 'cancel': '取消', + 'home.task': '任务', + 'home.setting': '设置', + 'create.title': '创建任务', + 'create.downloadLink': '下载链接', + 'create.downloadLinkValid': '请输入下载链接', + 'create.downloadLinkHit': '请输入下载链接,支持 HTTP/HTTPS/MAGNET', + 'create.download': '下载', + 'create.error.noStoragePermission': '需要开启存储权限', + 'create.selectDir': '选择目录', + 'setting.title': '设置', + 'setting.basic': '基础设置', + 'setting.theme': '主题', + 'setting.themeSystem': '跟随系统', + 'setting.themeLight': '明亮主题', + 'setting.themeDark': '暗黑主题', + 'setting.downloadDir': '下载目录', + 'setting.downloadDirValid': '请选择下载目录', + 'setting.connections': '连接数', + 'setting.locale': '语言', + 'setting.locale.zh_CN': '中文(简体)', + 'setting.locale.en_US': '英文(美国)', + 'task.deleteTask': '删除任务', + 'task.deleteTaskTip': '保留已下载的文件', + 'task.delete': '删除', + }, + 'en_US': { + 'error': 'Error', + 'confirm': 'Confirm', + 'cancel': 'Cancel', + 'home.task': 'Task', + 'home.setting': 'Setting', + 'create.title': 'Create Task', + 'create.downloadLink': 'Download Link', + 'create.downloadLinkValid': 'Please enter the download link', + 'create.downloadLinkHit': + 'Please enter the download link, HTTP/HTTPS/MAGNET supported', + 'create.download': 'Download', + 'create.error.noStoragePermission': 'Storage permission required', + 'create.selectDir': 'Select Directory', + 'setting.title': 'Setting', + 'setting.basic': 'Basic', + 'setting.theme': 'Theme', + 'setting.themeSystem': 'System', + 'setting.themeLight': 'Light', + 'setting.themeDark': 'Dark', + 'setting.downloadDir': 'Download Directory', + 'setting.downloadDirValid': 'Please select the download directory', + 'setting.connections': 'Connections', + 'setting.locale': 'Language', + 'setting.locale.zh_CN': 'Chinese(Simplified)', + 'setting.locale.en_US': 'English(US)', + 'task.deleteTask': 'Delete Task', + 'task.deleteTaskTip': 'Keep downloaded files', + 'task.delete': 'Delete', + }, + }; +} diff --git a/ui/flutter/lib/main.dart b/ui/flutter/lib/main.dart index 0cc2d331..0397b350 100644 --- a/ui/flutter/lib/main.dart +++ b/ui/flutter/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'core/libgopeed_boot.dart'; +import 'i18n/messages.dart'; import 'routes/router.dart'; import 'setting/setting.dart'; import 'theme/theme.dart'; @@ -24,6 +25,9 @@ void main() async { theme: GopeedTheme.light, darkTheme: GopeedTheme.dark, themeMode: Setting.instance.themeMode, + translations: messages, + locale: Setting.instance.locale, + fallbackLocale: fallbackLocale, getPages: Routes.routes, )); } diff --git a/ui/flutter/lib/pages/create/create_view.dart b/ui/flutter/lib/pages/create/create_view.dart index 0796a0eb..b082a80e 100644 --- a/ui/flutter/lib/pages/create/create_view.dart +++ b/ui/flutter/lib/pages/create/create_view.dart @@ -30,13 +30,13 @@ class CreateView extends GetView { icon: const Icon(Icons.arrow_back), onPressed: () => Get.rootDelegate.offNamed(Routes.task)), actions: [Container()], - title: const Text("创建任务"), + title: Text('create.title'.tr), ), body: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0), child: Form( key: _resolveFormKey, - autovalidateMode: AutovalidateMode.always, //开启自动校验 + autovalidateMode: AutovalidateMode.always, child: Column( children: [ TextFormField( @@ -44,12 +44,14 @@ class CreateView extends GetView { controller: _urlController, minLines: 1, maxLines: 15, - decoration: const InputDecoration( - hintText: "请输入下载链接,支持 HTTP/HTTPS/MAGNET", - labelText: "下载链接", - icon: Icon(Icons.link)), + decoration: InputDecoration( + hintText: 'create.downloadLinkHit'.tr, + labelText: 'create.downloadLink'.tr, + icon: const Icon(Icons.link)), validator: (v) { - return v!.trim().isNotEmpty ? null : "请输入下载链接"; + return v!.trim().isNotEmpty + ? null + : 'create.downloadLinkValid'.tr; }), // 登录按钮 Center( @@ -71,14 +73,14 @@ class CreateView extends GetView { )); await _showResolveDialog(res); } catch (e) { - Get.snackbar("错误", e.toString()); + Get.snackbar('error'.tr, e.toString()); } finally { _confirmController.reset(); } } }, controller: _confirmController, - child: const Text("确认"), + child: Text('confirm'.tr), ), )), ), @@ -142,7 +144,7 @@ class CreateView extends GetView { onPressed: () { Get.back(); }, - child: const Text("取消"), + child: Text('cancel'.tr), ), ), ConstrainedBox( @@ -158,7 +160,8 @@ class CreateView extends GetView { downloadController.start(); if (Util.isAndroid()) { if (!await Permission.storage.request().isGranted) { - Get.snackbar("错误", "需要开启存储权限"); + Get.snackbar('error'.tr, + 'create.error.noStoragePermission'.tr); return; } } @@ -179,14 +182,14 @@ class CreateView extends GetView { Get.rootDelegate.offNamed(Routes.task); } } catch (e) { - Get.snackbar("错误", e.toString()); + Get.snackbar('error'.tr, e.toString()); rethrow; } finally { downloadController.reset(); } }, controller: downloadController, - child: const Text("下载"), + child: Text('create.download'.tr), ), ), ], diff --git a/ui/flutter/lib/pages/home/home_view.dart b/ui/flutter/lib/pages/home/home_view.dart index cae1ace9..9b386358 100644 --- a/ui/flutter/lib/pages/home/home_view.dart +++ b/ui/flutter/lib/pages/home/home_view.dart @@ -37,11 +37,11 @@ class HomeView extends GetView { AnimatedBarItems( icon: const Icon(Icons.list), selectedColor: Get.theme.primaryColor, - title: const Text('任务')), + title: Text('home.task'.tr)), AnimatedBarItems( icon: const Icon(Icons.settings), selectedColor: Get.theme.primaryColor, - title: const Text('设置')), + title: Text('home.setting'.tr)), ], iconSize: 32, barAnimation: BarAnimation.fade, diff --git a/ui/flutter/lib/pages/setting/setting_controller.dart b/ui/flutter/lib/pages/setting/setting_controller.dart index 9ee7a286..86fc93c4 100644 --- a/ui/flutter/lib/pages/setting/setting_controller.dart +++ b/ui/flutter/lib/pages/setting/setting_controller.dart @@ -5,4 +5,8 @@ import '../../setting/setting.dart'; class SettingController extends GetxController { final setting = Setting.instance.obs; final tapStatues = {}.obs; + + void clearTapStatus() { + tapStatues.clear(); + } } diff --git a/ui/flutter/lib/pages/setting/setting_view.dart b/ui/flutter/lib/pages/setting/setting_view.dart index b7d49626..446f88a1 100644 --- a/ui/flutter/lib/pages/setting/setting_view.dart +++ b/ui/flutter/lib/pages/setting/setting_view.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:gopeed/i18n/messages.dart'; import '../../setting/setting.dart'; import '../../widget/directory_selector.dart'; @@ -21,26 +22,26 @@ class SettingView extends GetView { return Scaffold( appBar: AppBar( - title: const Text('设置'), + title: Text('setting.title'.tr), centerTitle: true, ), body: Column( - // 左对齐 crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("基本设置", style: Get.textTheme.titleLarge), + Text('setting.basic'.tr, style: Get.textTheme.titleLarge), const SizedBox(height: 10), Obx(() => Card( child: Column( children: _buildConfigItems([ _ConfigItem( - "主题", + 'setting.theme'.tr, () => _getThemeName(controller.setting.value.themeMode), () => DropdownButton( value: controller.setting.value.themeMode, isDense: true, onChanged: (value) { controller.setting.value.themeMode = value!; + controller.clearTapStatus(); Get.changeThemeMode(value); debounceSave(); @@ -52,8 +53,8 @@ class SettingView extends GetView { )) .toList(), )), - _ConfigItem( - "下载目录", () => controller.setting.value.downloadDir, () { + _ConfigItem('setting.downloadDir'.tr, + () => controller.setting.value.downloadDir, () { final downloadDirController = TextEditingController( text: controller.setting.value.downloadDir); downloadDirController.addListener(() { @@ -61,6 +62,7 @@ class SettingView extends GetView { controller.setting.value.downloadDir) { controller.setting.value.downloadDir = downloadDirController.text; + controller.clearTapStatus(); debounceSave(); } @@ -70,7 +72,7 @@ class SettingView extends GetView { showLabel: false, ); }), - _ConfigItem("连接数", + _ConfigItem('setting.connections'.tr, () => controller.setting.value.connections.toString(), () { final connectionsController = TextEditingController( @@ -95,6 +97,27 @@ class SettingView extends GetView { ], ); }), + _ConfigItem( + 'setting.locale'.tr, + () => _getLocaleName(controller.setting.value.locale), + () => DropdownButton( + value: controller.setting.value.locale, + isDense: true, + onChanged: (value) { + controller.setting.value.locale = value!; + controller.clearTapStatus(); + Get.updateLocale(value); + + debounceSave(); + }, + items: _getLocales() + .map((e) => DropdownMenuItem( + value: toLocale(e + .substring(_settingLocaleKey.length)), + child: Text(e.tr), + )) + .toList(), + )), ]), ))), ], @@ -130,13 +153,26 @@ class SettingView extends GetView { String _getThemeName(ThemeMode themeMode) { switch (themeMode) { case ThemeMode.system: - return "跟随系统"; + return 'setting.themeSystem'.tr; case ThemeMode.light: - return "明亮主题"; + return 'setting.themeLight'.tr; case ThemeMode.dark: - return "暗黑主题"; + return 'setting.themeDark'.tr; } } + + final _settingLocaleKey = 'setting.locale.'; + + List _getLocales() { + return messages.keys[fallbackLocale.toString()]!.entries + .where((e) => e.key.startsWith(_settingLocaleKey)) + .map((e) => e.key) + .toList(); + } + + String _getLocaleName(Locale locale) { + return '$_settingLocaleKey${locale.toString()}'.tr; + } } class _ConfigItem { diff --git a/ui/flutter/lib/pages/task/task_view.dart b/ui/flutter/lib/pages/task/task_view.dart index db44271d..61d8e089 100644 --- a/ui/flutter/lib/pages/task/task_view.dart +++ b/ui/flutter/lib/pages/task/task_view.dart @@ -65,8 +65,6 @@ class Item extends StatelessWidget { @override Widget build(BuildContext context) { - var controller = Get.find(); - return Card( elevation: 4.0, child: InkWell( @@ -153,22 +151,23 @@ class Item extends StatelessWidget { context: Get.context!, barrierDismissible: false, builder: (_) => AlertDialog( - title: const Text("删除任务"), + title: Text('task.deleteTask'.tr), content: Obx(() => CheckboxListTile( value: keep.value, - title: Text("保留已下载的文件", style: Get.textTheme.bodyText1), + title: Text('task.deleteTaskTip'.tr, + style: Get.textTheme.bodyText1), onChanged: (v) { keep.value = v!; })), actions: [ TextButton( - child: const Text("取消"), - onPressed: () => Get.back(), //关闭对话框 + child: Text('cancel'.tr), + onPressed: () => Get.back(), ), TextButton( - child: const Text( - "删除", - style: TextStyle(color: Colors.redAccent), + child: Text( + 'task.delete'.tr, + style: const TextStyle(color: Colors.redAccent), ), onPressed: () async { try { @@ -176,7 +175,7 @@ class Item extends StatelessWidget { Get.find().loadTab(); Get.back(); } catch (e) { - Get.snackbar("错误", e.toString()); + Get.snackbar('error'.tr, e.toString()); } }, ), diff --git a/ui/flutter/lib/setting/setting.dart b/ui/flutter/lib/setting/setting.dart index 7bea5471..0dbce289 100644 --- a/ui/flutter/lib/setting/setting.dart +++ b/ui/flutter/lib/setting/setting.dart @@ -1,8 +1,11 @@ import 'package:external_path/external_path.dart'; import 'package:flutter/material.dart'; +import 'package:gopeed/i18n/messages.dart'; import '../api/api.dart'; import 'package:path_provider/path_provider.dart'; +import 'dart:ui' as ui; + import '../api/model/server_config.dart'; import '../util/util.dart'; @@ -12,14 +15,7 @@ class Setting { int connections = 0; String downloadDir = ""; ThemeMode themeMode = ThemeMode.system; - - Setting({ - required this.host, - required this.port, - required this.connections, - required this.downloadDir, - required this.themeMode, - }); + Locale locale = ui.window.locale; // singleton pattern static Setting? _instance; @@ -38,11 +34,13 @@ class Setting { connections = config.connections; downloadDir = config.downloadDir; final themeMode = config.extra?['themeMode']; - if (themeMode == null) { - this.themeMode = ThemeMode.system; - } else { + if (themeMode != null) { this.themeMode = ThemeMode.values.byName(themeMode); } + final locale = config.extra?['locale']; + if (locale != null) { + this.locale = toLocale(locale); + } if (Util.isWeb()) { downloadDir = "./"; @@ -69,6 +67,7 @@ class Setting { downloadDir: downloadDir, extra: { 'themeMode': themeMode.name, + 'locale': locale.toString(), }, ); await putConfig(config); diff --git a/ui/flutter/lib/widget/directory_selector.dart b/ui/flutter/lib/widget/directory_selector.dart index 0605f637..366277df 100644 --- a/ui/flutter/lib/widget/directory_selector.dart +++ b/ui/flutter/lib/widget/directory_selector.dart @@ -26,12 +26,12 @@ class _DirectorySelectorState extends State { readOnly: true, controller: widget.controller, decoration: widget.showLabel - ? const InputDecoration( - labelText: "下载目录", + ? InputDecoration( + labelText: 'setting.downloadDir'.tr, ) : null, validator: (v) { - return v!.trim().isNotEmpty ? null : "请选择下载目录"; + return v!.trim().isNotEmpty ? null : 'setting.downloadDirValid'.tr; }, )), IconButton( diff --git a/ui/flutter/lib/widget/file_list_view.dart b/ui/flutter/lib/widget/file_list_view.dart index 66e496d5..dcc81b13 100644 --- a/ui/flutter/lib/widget/file_list_view.dart +++ b/ui/flutter/lib/widget/file_list_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import '../util/util.dart'; import '../api/model/resource.dart'; @@ -29,7 +30,8 @@ class _FileListViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding(padding: EdgeInsets.only(top: 10)), - Text("选择文件", style: TextStyle(color: themeData.hintColor)), + Text('create.selectDir'.tr, + style: TextStyle(color: themeData.hintColor)), Expanded( child: Container( margin: const EdgeInsets.only(top: 10),