From b0eceb2ffa39ab4a239116b2bbf0a2e31929965c Mon Sep 17 00:00:00 2001 From: MiaoMint <1981324730@qq.com> Date: Sun, 9 Jul 2023 00:31:55 +0800 Subject: [PATCH] =?UTF-8?q?feat!:=20i18n=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/i18n/en.json | 111 +++++++++++++++++ assets/i18n/zh.json | 116 ++++++++++++++++++ lib/main.dart | 7 ++ lib/pages/detail/view.dart | 28 ++++- .../widgets/detail_appbar_flexible_space.dart | 3 +- .../detail/widgets/detail_continue_play.dart | 26 +++- lib/pages/detail/widgets/detail_episodes.dart | 12 +- .../widgets/detail_favorite_button.dart | 21 ++-- lib/pages/extension/view.dart | 58 +++++---- .../extension/widgets/extension_tile.dart | 33 ++--- lib/pages/extension_repo/view.dart | 28 +++-- .../widgets/extension_card.dart | 30 ++--- lib/pages/home/view.dart | 13 +- lib/pages/home/widgets/home_favorites.dart | 9 +- lib/pages/home/widgets/home_recent.dart | 7 +- lib/pages/main/controller.dart | 32 +++-- lib/pages/main/view.dart | 37 +++--- lib/pages/search/view.dart | 35 +++--- lib/pages/search/widgets/search_all_tile.dart | 5 +- lib/pages/settings/view.dart | 95 +++++++++----- ...put_tile.dart => settings_input_tile.dart} | 7 +- .../widgets/settings_radios_tile.dart | 89 ++++++++++++++ ...ch_tile.dart => settings_switch_tile.dart} | 12 +- .../{setting_tile.dart => settings_tile.dart} | 8 +- lib/pages/watch/view.dart | 29 +++-- lib/pages/watch/widgets/video_player.dart | 25 ++-- lib/utils/extension.dart | 20 ++- lib/utils/i18n.dart | 36 ++++++ lib/utils/miru_storage.dart | 2 + pubspec.lock | 24 ++++ pubspec.yaml | 4 +- 31 files changed, 739 insertions(+), 223 deletions(-) create mode 100644 assets/i18n/en.json create mode 100644 assets/i18n/zh.json rename lib/pages/settings/widgets/{setting_input_tile.dart => settings_input_tile.dart} (91%) create mode 100644 lib/pages/settings/widgets/settings_radios_tile.dart rename lib/pages/settings/widgets/{setting_switch_tile.dart => settings_switch_tile.dart} (76%) rename lib/pages/settings/widgets/{setting_tile.dart => settings_tile.dart} (88%) create mode 100644 lib/utils/i18n.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json new file mode 100644 index 00000000..fee9bdc7 --- /dev/null +++ b/assets/i18n/en.json @@ -0,0 +1,111 @@ +{ + "common": { + "home": "Home", + "search": "Search", + "extension": "Extensions", + "extension-repo": "Extension Repository", + "settings": "Settings", + "no-extension": "No installed extensions", + "no-result": "No relevant results", + "cancel": "Cancel", + "confirm": "Confirm", + "close": "Close", + "copied": "Copied to clipboard", + "uninstall": "Uninstall", + "install": "Install", + "repo": "Repository", + "unset": "Not set", + "extension-missing": "Extension {package} missing", + "error": "Error" + }, + + "home": { + "continue-watching": "Continue Watching", + "favorite": "Favorite", + "no-record": "No favorites or viewing records" + }, + + "search": { + "hint-text": "Please use search wisely!~", + "all": "All" + }, + + "extension": { + "import": { + "title": "Import Extension", + "url-label": "Extension URL", + "tips": "You can import extensions through a link,\nor click on the extension directory below\nand place the extension file there.", + "extension-dir": "Extension Directory", + "import-by-url": "Import by URL" + }, + "error-dialog": "Error Message", + "installed": "Installed", + "edit-code": "Edit Code" + }, + + "extension-repo": { + "error": "An error occurred!", + "error-tips": "Please check your network connection\nor the repository address", + "retry": "Retry", + "empty": "Repository is empty", + "upgrade": "Update" + }, + + "settings": { + "repo-url": "Extension Repository URL", + "repo-url-subtitle": "Get the repository URL for extensions", + "tmdb-key": "TMDB API Key", + "tmdb-key-subtitle": "Get API Key for TMDB metadata", + "upgrade": "Update Software", + "upgrade-subtitle": "version: {version}", + "upgrade-training": "Check for Updates", + "auto-check-update": "Automatically Check for Updates", + "auto-check-update-subtitle": "Check for updates on each startup", + "language": "Language", + "language-subtitle": "Change the language of the software", + "extension-log": "Extension Log Window", + "extension-log-subtitle": "Used for debugging extensions", + "about": "About", + "official-site": "Official Website", + "official-site-training": "Visit the official website", + "source-code": "Open Source", + "source-code-training": "Go to Star", + "license": "License", + "license-subtitle": "License" + }, + + "detail": { + "favorite": "Favorite", + "favorited": "Favorited", + "episodes": "Episodes", + "no-episodes": "No episodes available", + "continue-watching": "Continue watching {episode}", + "watch-now": "Watch Now", + "total-episodes": "Total {total} episodes", + "overview": "Overview", + "cast": "Cast" + }, + + "video": { + "already-first": "Already on the first episode", + "already-last": "Already on the last episode", + "play-complete": "Playback complete" + }, + + "upgrade": { + "check-update": "Check for Updates", + "new-version": "New version {version} detected", + "download": "Go to Update", + "no-update": "Already up to date", + "not-now": "Not now", + "error": "Failed to check for updates, network error occurred" + }, + + "extension-install-error": "Failed to install extension", + + "extension-type": { + "video": "Video", + "novel": "Novel", + "comics": "Comics" + } +} diff --git a/assets/i18n/zh.json b/assets/i18n/zh.json new file mode 100644 index 00000000..895d5954 --- /dev/null +++ b/assets/i18n/zh.json @@ -0,0 +1,116 @@ +{ + "languages": { + "en": "English", + "zh": "中文" + }, + + "common": { + "home": "首页", + "search": "搜索", + "extension": "扩展", + "extension-repo": "扩展仓库", + "settings": "设置", + "no-extension": "未安装任何扩展", + "no-result": "未找到相关结果", + "cancel": "取消", + "confirm": "确定", + "close": "关闭", + "copied": "已复制到剪贴板", + "uninstall": "卸载", + "install": "安装", + "repo": "仓库", + "unset": "未设置", + "extension-missing": "扩展 {package} 丢失", + "error": "错误" + }, + + "home": { + "continue-watching": "继续观看", + "favorite": "收藏", + "no-record": "暂无收藏和观看记录" + }, + + "search": { + "hint-text": "请善用搜索哦!~", + "all": "全部" + }, + + "extension": { + "import": { + "title": "导入扩展", + "url-label": "扩展地址", + "tips": "你可以通过链接导入扩展,\n或者点击下方的扩展目录,将扩展文件\n放入其中。", + "extension-dir": "扩展目录", + "import-by-url": "通过链接导入" + }, + "error-dialog": "错误信息", + "installed": "已安装", + "edit-code": "编辑代码" + }, + + "extension-repo": { + "error": "发生错误了!", + "error-tips": "请检查网络连接或者仓库地址是否正确", + "retry": "重试", + "empty": "仓库为空", + "upgrade": "更新" + }, + + "settings": { + "repo-url": "扩展仓库地址", + "repo-url-subtitle": "获取扩展的仓库地址", + "tmdb-key": "TMDB API Key", + "tmdb-key-subtitle": "获取 TMDB 元数据的 API Key", + "upgrade": "更新软件", + "upgrade-subtitle": "当前版本:{version}", + "upgrade-training": "检查更新", + "auto-check-update": "自动检查更新", + "auto-check-update-subtitle": "每次启动时检查更新", + "language": "语言", + "language-subtitle": "选择软件的语言", + "extension-log": "扩展日志窗口", + "extension-log-subtitle": "用于调试扩展", + "about": "关于", + "official-site": "官方网站", + "official-site-training": "访问官网", + "source-code": "开源", + "source-code-training": "前往 Star", + "license": "许可", + "license-subtitle": "许可" + }, + + "detail": { + "favorite": "收藏", + "favorited": "已收藏", + "episodes": "剧集", + "no-episodes": "暂无剧集", + "continue-watching": "继续观看 {episode}", + "watch-now": "立即观看", + "total-episodes": "共 {total} 集", + "overview": "概览", + "cast": "演员" + }, + + "video": { + "already-first": "当前已经是第一集", + "already-last": "当前已经是最后一集", + "play-complete": "播放完成" + }, + + "upgrade": { + "check-update": "检查更新", + "new-version": "检测到新版本 {version}", + "download": "前往更新", + "no-update": "已经是最新版本", + "not-now": "暂不更新", + "error": "检查更新失败,网络出现异常" + }, + + "extension-install-error": "扩展安装错误", + + "extension-type": { + "video": "影视", + "novel": "小说", + "comics": "漫画" + } +} diff --git a/lib/main.dart b/lib/main.dart index d0be368e..621a1c58 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,7 @@ import 'package:miru_app/pages/main/view.dart'; import 'package:miru_app/pages/search/view.dart'; import 'package:miru_app/pages/settings/view.dart'; import 'package:miru_app/utils/extension.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_storage.dart'; import 'package:miru_app/utils/package_info.dart'; import 'package:miru_app/widgets/platform_widget.dart'; @@ -83,6 +84,9 @@ class MainApp extends StatelessWidget { theme: ThemeData(useMaterial3: true), darkTheme: ThemeData.dark(useMaterial3: true), home: const AndroidMainPage(), + localizationsDelegates: [ + I18nUtils.flutterI18nDelegate, + ], ); } @@ -101,6 +105,9 @@ class MainApp extends StatelessWidget { visualDensity: VisualDensity.standard, fontFamily: "Microsoft Yahei", ), + localizationsDelegates: [ + I18nUtils.flutterI18nDelegate, + ], ); } diff --git a/lib/pages/detail/view.dart b/lib/pages/detail/view.dart index 06a21737..4abcbdc3 100644 --- a/lib/pages/detail/view.dart +++ b/lib/pages/detail/view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:get/get.dart'; import 'package:miru_app/pages/detail/controller.dart'; import 'package:miru_app/pages/detail/widgets/detail_appbar_flexible_space.dart'; @@ -8,6 +9,7 @@ import 'package:miru_app/pages/detail/widgets/detail_background_color.dart'; import 'package:miru_app/pages/detail/widgets/detail_episodes.dart'; import 'package:miru_app/pages/detail/widgets/detail_favorite_button.dart'; import 'package:miru_app/utils/extension.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/cache_network_image.dart'; import 'package:miru_app/widgets/platform_widget.dart'; import 'package:miru_app/widgets/progress_ring.dart'; @@ -47,7 +49,13 @@ class _DetailPageState extends State { if (!ExtensionUtils.extensions.containsKey(widget.package)) { return Scaffold( body: Center( - child: Text('扩展 ${widget.package} 丢失'), + child: Text(FlutterI18n.translate( + context, + 'common.extension-missing', + translationParams: { + 'package': widget.package, + }, + )), ), ); } @@ -75,11 +83,11 @@ class _DetailPageState extends State { controller: c.scrollController, ), flexibleSpace: const DetailAppbarflexibleSpace(), - bottom: const TabBar( + bottom: TabBar( tabs: [ - Tab(text: "剧集"), - Tab(text: "概览"), - Tab(text: "演员"), + Tab(text: 'detail.episodes'.i18n), + Tab(text: 'detail.overview'.i18n), + Tab(text: 'detail.cast'.i18n), ], ), expandedHeight: 400, @@ -116,7 +124,15 @@ class _DetailPageState extends State { return Obx(() { if (!ExtensionUtils.extensions.containsKey(widget.package)) { return Center( - child: Text('扩展 ${widget.package} 丢失'), + child: Text( + FlutterI18n.translate( + context, + 'common.extension-missing', + translationParams: { + 'package': widget.package, + }, + ), + ), ); } if (c.error.value.isNotEmpty) { diff --git a/lib/pages/detail/widgets/detail_appbar_flexible_space.dart b/lib/pages/detail/widgets/detail_appbar_flexible_space.dart index 9d8c6d28..e0931748 100644 --- a/lib/pages/detail/widgets/detail_appbar_flexible_space.dart +++ b/lib/pages/detail/widgets/detail_appbar_flexible_space.dart @@ -131,13 +131,14 @@ class _DetailAppbarflexibleSpaceState extends State { child: Row( children: [ Expanded( - flex: 2, + flex: 4, child: DetailContinuePlay(), ), SizedBox( width: 10, ), Expanded( + flex: 3, child: DetailFavoriteButton(), ) ], diff --git a/lib/pages/detail/widgets/detail_continue_play.dart b/lib/pages/detail/widgets/detail_continue_play.dart index 4af89498..effdb840 100644 --- a/lib/pages/detail/widgets/detail_continue_play.dart +++ b/lib/pages/detail/widgets/detail_continue_play.dart @@ -1,7 +1,9 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:get/get.dart'; import 'package:miru_app/pages/detail/controller.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; class DetailContinuePlay extends StatefulWidget { @@ -19,7 +21,7 @@ class _DetailContinuePlayState extends State { final noEpisodes = FilledButton.icon( onPressed: () {}, icon: const Icon(Icons.play_arrow), - label: const Text("无剧集"), + label: Text('detail.no-episodes'.i18n), style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.grey), foregroundColor: MaterialStateProperty.all(Colors.white), @@ -46,7 +48,15 @@ class _DetailContinuePlayState extends State { ); }, icon: const Icon(Icons.play_arrow), - label: Text("继续观看 ${history!.episodeTitle}"), + label: Text( + FlutterI18n.translate( + context, + 'detail.continue-watching', + translationParams: { + 'episode': history!.episodeTitle, + }, + ), + ), style: ButtonStyle( minimumSize: MaterialStateProperty.all( const Size(double.infinity, 50), @@ -65,7 +75,7 @@ class _DetailContinuePlayState extends State { ); }, icon: const Icon(Icons.play_arrow), - label: const Text("立即观看"), + label: Text('detail.watch-now'.i18n), style: ButtonStyle( minimumSize: MaterialStateProperty.all( const Size(double.infinity, 50), @@ -95,7 +105,15 @@ class _DetailContinuePlayState extends State { children: [ const Icon(fluent.FluentIcons.play), const SizedBox(width: 5), - Text("继续观看 ${history.episodeTitle}") + Text( + FlutterI18n.translate( + context, + 'detail.continue-watching', + translationParams: { + 'episode': history.episodeTitle, + }, + ), + ) ], ), ); diff --git a/lib/pages/detail/widgets/detail_episodes.dart b/lib/pages/detail/widgets/detail_episodes.dart index 4ee43cde..3ab721fc 100644 --- a/lib/pages/detail/widgets/detail_episodes.dart +++ b/lib/pages/detail/widgets/detail_episodes.dart @@ -1,10 +1,12 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:get/get.dart'; import 'package:miru_app/models/extension.dart'; import 'package:miru_app/pages/detail/controller.dart'; import 'package:miru_app/pages/detail/widgets/detail_continue_play.dart'; import 'package:miru_app/pages/detail/widgets/detail_tile.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; class DetailEpisodes extends StatefulWidget { @@ -55,7 +57,13 @@ class _DetailEpisodesState extends State { Container( margin: const EdgeInsets.only(left: 16, top: 10, bottom: 10), child: Text( - "共 ${episodes[selectEpGroup].urls.length} 集", + FlutterI18n.translate( + context, + 'detail.total-episodes', + translationParams: { + 'total': episodes[selectEpGroup].urls.length.toString(), + }, + ), style: const TextStyle( fontSize: 18, ), @@ -87,7 +95,7 @@ class _DetailEpisodesState extends State { Widget _buildDesktopEpisodes(BuildContext context) { return DetailTile( - title: "剧集", + title: 'detail.episodes'.i18n, trailing: Row( children: [ const DetailContinuePlay(), diff --git a/lib/pages/detail/widgets/detail_favorite_button.dart b/lib/pages/detail/widgets/detail_favorite_button.dart index 15eaf6fd..2eafe210 100644 --- a/lib/pages/detail/widgets/detail_favorite_button.dart +++ b/lib/pages/detail/widgets/detail_favorite_button.dart @@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:miru_app/pages/detail/controller.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; class DetailFavoriteButton extends StatefulWidget { @@ -21,7 +22,9 @@ class _DetailFavoriteButtonState extends State { final isFavorite = c.isFavorite.value; return OutlinedButton.icon( icon: Icon(isFavorite ? Icons.favorite : Icons.favorite_border), - label: Text(isFavorite ? "已收藏" : "收藏"), + label: Text( + isFavorite ? 'detail.favorited'.i18n : 'detail.favorite'.i18n, + ), style: ButtonStyle( minimumSize: MaterialStateProperty.all( const Size(double.infinity, 50), @@ -56,15 +59,15 @@ class _DetailFavoriteButtonState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: isFavorite - ? const [ - Text("已收藏"), - SizedBox(width: 8), - Icon(fluent.FluentIcons.favorite_star_fill) + ? [ + Text('detail.favorited'.i18n), + const SizedBox(width: 8), + const Icon(fluent.FluentIcons.favorite_star_fill) ] - : const [ - Text("收藏"), - SizedBox(width: 8), - Icon(fluent.FluentIcons.favorite_star) + : [ + Text('detail.favorite'.i18n), + const SizedBox(width: 8), + const Icon(fluent.FluentIcons.favorite_star) ], ), ), diff --git a/lib/pages/extension/view.dart b/lib/pages/extension/view.dart index 84e62b12..d1af5fe8 100644 --- a/lib/pages/extension/view.dart +++ b/lib/pages/extension/view.dart @@ -9,6 +9,7 @@ import 'package:miru_app/pages/extension/controller.dart'; import 'package:miru_app/pages/extension/widgets/extension_tile.dart'; import 'package:miru_app/pages/extension_repo/view.dart'; import 'package:miru_app/utils/extension.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/router.dart'; import 'package:miru_app/widgets/button.dart'; import 'package:miru_app/widgets/messenger.dart'; @@ -36,15 +37,15 @@ class _ExtensionPageState extends State { String url = ''; showPlatformDialog( context: context, - title: "导入扩展", + title: 'extension.import.title'.i18n, content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ PlatformWidget( androidWidget: TextField( - decoration: const InputDecoration( - labelText: "扩展地址", + decoration: InputDecoration( + labelText: 'extension.import.url-label'.i18n, hintText: "https://example.com/extension.js", ), onChanged: (value) { @@ -52,19 +53,19 @@ class _ExtensionPageState extends State { }, ), desktopWidget: fluent.TextBox( - placeholder: "扩展地址", + placeholder: 'extension.import.url-label'.i18n, onChanged: (value) { url = value; }, ), ), const SizedBox(height: 16), - const Row( + Row( children: [ - Icon(fluent.FluentIcons.error), - SizedBox(width: 8), + const Icon(fluent.FluentIcons.error), + const SizedBox(width: 8), Text( - "你可以通过链接导入扩展,\n或者点击下方的扩展目录,将扩展文件\n放入其中。", + "extension.import.tips".i18n, softWrap: true, ) ], @@ -76,7 +77,7 @@ class _ExtensionPageState extends State { onPressed: () { RouterUtils.pop(); }, - child: const Text("取消"), + child: Text('common.cancel'.i18n), ), PlatformFilledButton( onPressed: () async { @@ -89,22 +90,22 @@ class _ExtensionPageState extends State { // ignore: use_build_context_synchronously showPlatformSnackbar( context: context, - title: '扩展目录', - content: '已复制到剪贴板', + title: 'extension.import.extension-dir'.i18n, + content: 'common.copied'.i18n, ); return; } final uri = Uri.directory(dir); await launchUrl(uri); }, - child: const Text("扩展目录"), + child: Text('extension.import.extension-dir'.i18n), ), PlatformFilledButton( onPressed: () async { RouterUtils.pop(); await ExtensionUtils.install(url, context); }, - child: const Text("通过链接导入"), + child: Text('extension.import.import-by-url'.i18n), ), ], ); @@ -114,7 +115,7 @@ class _ExtensionPageState extends State { _loadErrorDialog() { showPlatformDialog( context: context, - title: "错误信息", + title: 'extension.error-dialog'.i18n, content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, @@ -146,7 +147,7 @@ class _ExtensionPageState extends State { onPressed: () { RouterUtils.pop(); }, - child: const Text("确定"), + child: Text('common.confirm'.i18n), ), ], ); @@ -158,11 +159,11 @@ class _ExtensionPageState extends State { length: 2, child: Scaffold( appBar: AppBar( - title: const Text("扩展"), - bottom: const TabBar( + title: Text('common.extension'.i18n), + bottom: TabBar( tabs: [ - Tab(text: "已安装"), - Tab(text: "仓库"), + Tab(text: 'extension.installed'.i18n), + Tab(text: 'common.repo'.i18n), ], ), actions: [ @@ -185,12 +186,12 @@ class _ExtensionPageState extends State { ListView( children: [ if (c.extensions.isEmpty) - const SizedBox( + SizedBox( height: 300, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("未安装任何扩展"), + Text('common.no-extension'.i18n), ], ), ), @@ -213,9 +214,12 @@ class _ExtensionPageState extends State { children: [ Row( children: [ - const Text( - "扩展", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + Text( + 'common.extension'.i18n, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), const Spacer(), // 错误按钮 @@ -241,10 +245,12 @@ class _ExtensionPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text("未安装任何扩展"), + Text('common.no-extension'.i18n), const SizedBox(height: 8), fluent.FilledButton( - child: const Text("扩展仓库"), + child: Text( + 'common.extension-repo'.i18n, + ), onPressed: () { router.push('/extension_repo'); }, diff --git a/lib/pages/extension/widgets/extension_tile.dart b/lib/pages/extension/widgets/extension_tile.dart index efc9ccd0..32ad73ec 100644 --- a/lib/pages/extension/widgets/extension_tile.dart +++ b/lib/pages/extension/widgets/extension_tile.dart @@ -4,6 +4,7 @@ import 'package:get/get.dart'; import 'package:miru_app/models/extension.dart'; import 'package:miru_app/pages/code_edit/view.dart'; import 'package:miru_app/utils/extension.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/cache_network_image.dart'; import 'package:miru_app/widgets/messenger.dart'; import 'package:miru_app/widgets/platform_widget.dart'; @@ -22,17 +23,6 @@ class _ExtensionTileState extends State { final fluent.FlyoutController moreFlyoutController = fluent.FlyoutController(); - _extensionTypeToString(ExtensionType type) { - switch (type) { - case ExtensionType.bangumi: - return '影视'; - case ExtensionType.fikushon: - return '小说'; - case ExtensionType.manga: - return '漫画'; - } - } - Widget _buildAndroid(BuildContext context) { return ListTile( leading: SizedBox( @@ -47,7 +37,7 @@ class _ExtensionTileState extends State { ), title: Text(widget.extension.name), subtitle: Text( - '${widget.extension.version} ${_extensionTypeToString(widget.extension.type)} ', + '${widget.extension.version} ${ExtensionUtils.typeToString(widget.extension.type)} ', ), onTap: () { showPlatformSnackbar(context: context, title: '😣', content: '还没写的('); @@ -63,7 +53,7 @@ class _ExtensionTileState extends State { children: [ ListTile( leading: const Icon(Icons.code), - title: const Text('编辑代码'), + title: Text('extension.edit-code'.i18n), onTap: () async { Get.back(); Get.to(CodeEditPage(extension: widget.extension)); @@ -71,7 +61,7 @@ class _ExtensionTileState extends State { ), ListTile( leading: const Icon(Icons.delete), - title: const Text('卸载'), + title: Text('common.uninstall'.i18n), onTap: () { ExtensionUtils.uninstall(widget.extension.package); Get.back(); @@ -120,12 +110,15 @@ class _ExtensionTileState extends State { const Spacer(), Text(widget.extension.version), const Spacer(), - Text(_extensionTypeToString(widget.extension.type)), + Text(ExtensionUtils.typeToString(widget.extension.type)), const Spacer(), fluent.Button( - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 2), - child: Text("设置"), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 2, + ), + child: Text('common.settings'.i18n), ), onPressed: () { fluent.displayInfoBar(context, builder: (builder, colse) { @@ -149,7 +142,7 @@ class _ExtensionTileState extends State { items: [ fluent.MenuFlyoutItem( leading: const Icon(fluent.FluentIcons.code), - text: const Text('编辑代码'), + text: Text('extension.edit-code'.i18n), onPressed: () async { fluent.Flyout.of(context).close(); // final window = @@ -168,7 +161,7 @@ class _ExtensionTileState extends State { ), fluent.MenuFlyoutItem( leading: const Icon(fluent.FluentIcons.delete), - text: const Text('卸载'), + text: Text('common.uninstall'.i18n), onPressed: () { ExtensionUtils.uninstall(widget.extension.package); fluent.Flyout.of(context).close(); diff --git a/lib/pages/extension_repo/view.dart b/lib/pages/extension_repo/view.dart index 95d658df..48a07b5b 100644 --- a/lib/pages/extension_repo/view.dart +++ b/lib/pages/extension_repo/view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:miru_app/pages/extension_repo/controller.dart'; import 'package:miru_app/pages/extension_repo/widgets/extension_card.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; import 'package:miru_app/widgets/progress_ring.dart'; @@ -31,14 +32,14 @@ class _ExtensionRepoPageState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text("发生错误了!"), - const Text( - "请检查仓库地址是否设置正确,或者网络是否能正常联通", - style: TextStyle(fontSize: 12), + Text('extension-repo.error'.i18n), + Text( + 'extension-repo.error-tips'.i18n, + style: const TextStyle(fontSize: 12), ), const SizedBox(height: 8), FilledButton( - child: const Text("重试"), + child: Text('extension-repo.retry'.i18n), onPressed: () { c.onRefresh(); }) @@ -46,7 +47,7 @@ class _ExtensionRepoPageState extends State { )); } if (c.extensions.isEmpty) { - return const Center(child: Text("仓库为空")); + return Center(child: Text('extension-repo.empty'.i18n)); } return PlatformBuildWidget( androidBuilder: (context) => ListView( @@ -54,7 +55,7 @@ class _ExtensionRepoPageState extends State { .map((e) => ExtensionCard( key: ValueKey(e['package']), name: e['name'], - icon: e['icon'] ?? 'https://github.com/miru-project.png', + icon: e['icon'], version: e['version'], package: e['package'], )) @@ -70,7 +71,7 @@ class _ExtensionRepoPageState extends State { .map((e) => ExtensionCard( key: ValueKey(e['package']), name: e['name'], - icon: e['icon'] ?? 'https://github.com/miru-project.png', + icon: e['icon'], version: e['version'], package: e['package'], )) @@ -92,16 +93,19 @@ class _ExtensionRepoPageState extends State { children: [ Row( children: [ - const Text( - "仓库", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + Text( + 'common.extension-repo'.i18n, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), const Spacer(), SizedBox( width: 200, child: fluent.TextBox( controller: TextEditingController(text: c.search.value), - placeholder: "搜索", + placeholder: 'common.search'.i18n, onChanged: (value) { if (value.isEmpty) { c.onRefresh(); diff --git a/lib/pages/extension_repo/widgets/extension_card.dart b/lib/pages/extension_repo/widgets/extension_card.dart index 780c1151..3c0f2727 100644 --- a/lib/pages/extension_repo/widgets/extension_card.dart +++ b/lib/pages/extension_repo/widgets/extension_card.dart @@ -1,6 +1,7 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; import 'package:miru_app/utils/extension.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_storage.dart'; import 'package:miru_app/widgets/cache_network_image.dart'; import 'package:miru_app/widgets/platform_widget.dart'; @@ -14,7 +15,7 @@ class ExtensionCard extends StatefulWidget { required this.icon, required this.package, }) : super(key: key); - final String icon; + final String? icon; final String name; final String version; final String package; @@ -26,13 +27,14 @@ class ExtensionCard extends StatefulWidget { class _ExtensionCardState extends State { bool isLoading = false; bool isInstall = false; - bool hasUpdate = false; + bool hasUpgrade = false; + late String icon = widget.icon ?? ''; @override void initState() { setState(() { isInstall = ExtensionUtils.extensions.containsKey(widget.package); - hasUpdate = isInstall && + hasUpgrade = isInstall && ExtensionUtils.extensions[widget.package]!.extension.version != widget.version; }); @@ -66,7 +68,7 @@ class _ExtensionCardState extends State { width: 35, height: 35, child: CacheNetWorkImage( - widget.icon, + icon, fit: BoxFit.contain, fallback: const Icon(Icons.extension), ), @@ -85,9 +87,9 @@ class _ExtensionCardState extends State { child: ProgressRing(), ) else if (isInstall) ...[ - if (hasUpdate) + if (hasUpgrade) FilledButton( - child: const Text("更新"), + child: Text('extension-repo.upgrade'.i18n), onPressed: () async { await _install(); setState(() {}); @@ -96,7 +98,7 @@ class _ExtensionCardState extends State { const SizedBox(width: 8), if (isInstall) TextButton( - child: const Text("卸载"), + child: Text('common.uninstall'.i18n), onPressed: () async { await ExtensionUtils.uninstall(widget.package); setState(() { @@ -109,7 +111,7 @@ class _ExtensionCardState extends State { onPressed: () async { await _install(); }, - child: const Text("安装"), + child: Text('common.install'.i18n), ) ], ), @@ -125,7 +127,7 @@ class _ExtensionCardState extends State { SizedBox( height: 120, child: CacheNetWorkImage( - widget.icon, + icon, width: double.infinity, height: double.infinity, fit: BoxFit.cover, @@ -140,7 +142,7 @@ class _ExtensionCardState extends State { borderRadius: BorderRadius.circular(8), ), child: CacheNetWorkImage( - widget.icon, + icon, width: 64, height: 64, fit: BoxFit.contain, @@ -170,9 +172,9 @@ class _ExtensionCardState extends State { child: ProgressRing(), ) else if (isInstall) ...[ - if (hasUpdate) + if (hasUpgrade) fluent.FilledButton( - child: const Text("更新"), + child: Text('extension-repo.upgrade'.i18n), onPressed: () async { await _install(); setState(() {}); @@ -181,7 +183,7 @@ class _ExtensionCardState extends State { const SizedBox(width: 8), if (isInstall) fluent.FilledButton( - child: const Text("卸载"), + child: Text('common.uninstall'.i18n), onPressed: () async { await ExtensionUtils.uninstall(widget.package); setState(() { @@ -194,7 +196,7 @@ class _ExtensionCardState extends State { onPressed: () async { await _install(); }, - child: const Text("安装"), + child: Text('common.install'.i18n), ) ], ), diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index c0741f88..b1467a49 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:miru_app/pages/home/controller.dart'; import 'package:miru_app/pages/home/widgets/home_favorites.dart'; import 'package:miru_app/pages/home/widgets/home_recent.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; class HomePage extends StatefulWidget { @@ -28,19 +29,19 @@ class _HomePageState extends State { child: Obx( () { if (c.resents.isEmpty && c.favorites.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 200), - Image( + const SizedBox(height: 200), + const Image( image: AssetImage("assets/icon/logo.png"), width: 100, ), - SizedBox(height: 16), + const SizedBox(height: 16), Text( - "暂无收藏和观看记录", + "no record".i18n, ), ], ), @@ -73,7 +74,7 @@ class _HomePageState extends State { Widget _buildAndroidHome(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text("首页"), + title: Text("common.home".i18n), ), body: _buildContent(), ); diff --git a/lib/pages/home/widgets/home_favorites.dart b/lib/pages/home/widgets/home_favorites.dart index b529d032..a16ac512 100644 --- a/lib/pages/home/widgets/home_favorites.dart +++ b/lib/pages/home/widgets/home_favorites.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:miru_app/models/favorite.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/bangumi_card.dart'; class HomeFavorites extends StatefulWidget { @@ -20,11 +21,13 @@ class _HomeFavoritesState extends State { Widget build(BuildContext context) { return Column( children: [ - const Row( + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("收藏", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + Text( + "home.favorite".i18n, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), // IconButton( // icon: const Icon(FluentIcons.filter), // onPressed: () { diff --git a/lib/pages/home/widgets/home_recent.dart b/lib/pages/home/widgets/home_recent.dart index e3da039b..e1a55ecf 100644 --- a/lib/pages/home/widgets/home_recent.dart +++ b/lib/pages/home/widgets/home_recent.dart @@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; import 'package:miru_app/models/history.dart'; import 'package:miru_app/pages/home/widgets/home_resent_card.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; class HomeRecent extends StatefulWidget { @@ -51,9 +52,9 @@ class _HomeRecentState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - "继续观看", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + Text( + "home.continue-watching".i18n, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), Row( children: [ diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 6d391b7e..22af21e5 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -4,8 +4,10 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:get/get.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/package_info.dart'; import 'package:miru_app/utils/router.dart'; import 'package:miru_app/widgets/button.dart'; @@ -34,7 +36,13 @@ class MainController extends GetxController { Get.to( Scaffold( appBar: AppBar( - title: Text('检测到新版本$remoteVersion'), + title: Text(FlutterI18n.translate( + context, + 'upgrate.new-version', + translationParams: { + 'version': remoteVersion, + }, + )), ), body: Padding( padding: const EdgeInsets.all(16), @@ -52,7 +60,7 @@ class MainController extends GetxController { mode: LaunchMode.externalApplication, ); }, - child: const Text('前往更新'), + child: Text('upgrade.download'.i18n), ), ) ], @@ -65,14 +73,20 @@ class MainController extends GetxController { } showPlatformDialog( context: context, - title: '检测到新版本$remoteVersion', + title: FlutterI18n.translate( + context, + 'upgrate.new-version', + translationParams: { + 'version': remoteVersion, + }, + ), content: Markdown(data: res.data['body']), actions: [ PlatformTextButton( onPressed: () { RouterUtils.pop(); }, - child: const Text('关闭'), + child: Text('common.not-now'.i18n), ), PlatformFilledButton( onPressed: () { @@ -82,7 +96,7 @@ class MainController extends GetxController { mode: LaunchMode.externalApplication, ); }, - child: const Text('前往更新'), + child: Text('upgrade.download'.i18n), ) ], ); @@ -92,8 +106,8 @@ class MainController extends GetxController { } showPlatformSnackbar( context: context, - title: '检查更新', - content: "当前已是最新版本", + title: 'upgrade.check-update'.i18n, + content: "upgrade.no-update".i18n, ); } } catch (e) { @@ -102,8 +116,8 @@ class MainController extends GetxController { } showPlatformSnackbar( context: context, - title: '检查更新', - content: "检查更新失败,网络出现异常", + title: 'upgrade.check-update'.i18n, + content: 'upgrade.error'.i18n, ); } } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 0023df60..e6f22442 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -8,6 +8,7 @@ import 'package:miru_app/pages/home/view.dart'; import 'package:miru_app/pages/main/controller.dart'; import 'package:miru_app/pages/search/view.dart'; import 'package:miru_app/pages/settings/view.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_storage.dart'; import 'package:window_manager/window_manager.dart'; @@ -89,7 +90,7 @@ class _DesktopMainPageState extends State { fluent.PaneItemSeparator(), fluent.PaneItem( icon: const Icon(fluent.FluentIcons.repo), - title: const Text('扩展仓库'), + title: Text('common.extension-repo'.i18n), body: const ExtensionPage(), onTap: () { router.go('/extension_repo'); @@ -97,7 +98,7 @@ class _DesktopMainPageState extends State { ), fluent.PaneItem( icon: const Icon(fluent.FluentIcons.settings), - title: const Text('设置'), + title: Text('common.settings'.i18n), body: const SettingsPage(), onTap: () { router.go('/settings'); @@ -107,7 +108,7 @@ class _DesktopMainPageState extends State { items: [ fluent.PaneItem( icon: const Icon(fluent.FluentIcons.home), - title: const Text('首页'), + title: Text('common.home'.i18n), body: const HomePage(), onTap: () { router.go('/'); @@ -115,7 +116,7 @@ class _DesktopMainPageState extends State { ), fluent.PaneItem( icon: const Icon(fluent.FluentIcons.search), - title: const Text('搜索'), + title: Text('common.search'.i18n), body: const SearchPage(), onTap: () { router.go('/search'); @@ -123,7 +124,7 @@ class _DesktopMainPageState extends State { ), fluent.PaneItem( icon: const Icon(fluent.FluentIcons.add_in), - title: const Text('扩展'), + title: Text('common.extension'.i18n), body: const ExtensionPage(), onTap: () { router.go('/extension'); @@ -165,26 +166,26 @@ class _AndroidMainPageState extends fluent.State { return Obx(() => Scaffold( body: pages[c.selectedTab.value], bottomNavigationBar: NavigationBar( - destinations: const [ + destinations: [ NavigationDestination( - icon: Icon(Icons.home_outlined), - selectedIcon: Icon(Icons.home), - label: "首页", + icon: const Icon(Icons.home_outlined), + selectedIcon: const Icon(Icons.home), + label: "common.home".i18n, ), NavigationDestination( - icon: Icon(Icons.search_outlined), - label: "探索", - selectedIcon: Icon(Icons.search), + icon: const Icon(Icons.search_outlined), + label: "common.search".i18n, + selectedIcon: const Icon(Icons.search), ), NavigationDestination( - icon: Icon(Icons.apps_outlined), - label: "扩展", - selectedIcon: Icon(Icons.apps), + icon: const Icon(Icons.apps_outlined), + label: "common.extension".i18n, + selectedIcon: const Icon(Icons.apps), ), NavigationDestination( - icon: Icon(Icons.settings_outlined), - label: "设置", - selectedIcon: Icon(Icons.settings), + icon: const Icon(Icons.settings_outlined), + label: "common.settings".i18n, + selectedIcon: const Icon(Icons.settings), ), ], selectedIndex: c.selectedTab.value, diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index b09515cd..8f127070 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -5,6 +5,7 @@ import 'package:miru_app/main.dart'; import 'package:miru_app/pages/main/controller.dart'; import 'package:miru_app/pages/search/controller.dart'; import 'package:miru_app/pages/search/widgets/search_all_extension.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/bangumi_card.dart'; import 'package:miru_app/widgets/cache_network_image.dart'; import 'package:miru_app/widgets/platform_widget.dart'; @@ -30,7 +31,7 @@ class _SearchPageState extends State { return Obx(() { return Scaffold( appBar: AppBar( - title: const Text("搜索"), + title: Text('common.search'.i18n), ), body: (c.runtimeList.isEmpty) ? SizedBox( @@ -39,10 +40,10 @@ class _SearchPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text("未安装任何扩展"), + Text('common.no-extension'.i18n), const SizedBox(height: 8), FilledButton( - child: const Text("扩展仓库"), + child: Text("common.extension-repo".i18n), onPressed: () { Get.find().selectedTab.value = 2; }, @@ -62,13 +63,13 @@ class _SearchPageState extends State { child: TextField( controller: TextEditingController(text: c.search.value), - decoration: const InputDecoration( - border: OutlineInputBorder( + decoration: InputDecoration( + border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(20)), ), - hintText: "请善用搜索哦!~", - prefixIcon: Icon(Icons.search), + hintText: "search.hint-text".i18n, + prefixIcon: const Icon(Icons.search), ), onChanged: (value) { if (value.isEmpty) { @@ -90,7 +91,7 @@ class _SearchPageState extends State { children: [ ChoiceChip( avatar: const Icon(Icons.extension), - label: const Text('全部'), + label: Text('search.all'.i18n), selected: c.selectIndex.value == (-1), onSelected: (value) { if (value) { @@ -167,8 +168,8 @@ class _SearchPageState extends State { final data = snapshot.data; if (data != null && data.isEmpty) { - return const Center( - child: Text("没有数据"), + return Center( + child: Text("common.no-result".i18n), ); } return LayoutBuilder( @@ -212,10 +213,10 @@ class _SearchPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text("未安装任何扩展"), + Text('common.no-extension'.i18n), const SizedBox(height: 8), fluent.FilledButton( - child: const Text("扩展仓库"), + child: Text("common.extension-repo".i18n), onPressed: () { router.push('/extension_repo'); }, @@ -259,9 +260,9 @@ class _SearchPageState extends State { c.selectIndex.value = -1; } }, - child: const Row( + child: Row( children: [ - Text("全部"), + Text("search.all".i18n), ], ), ), @@ -304,7 +305,7 @@ class _SearchPageState extends State { child: fluent.TextBox( controller: TextEditingController(text: c.search.value), - placeholder: "请善用搜索哦!~", + placeholder: "search.hint-text".i18n, onChanged: (value) { if (value.isEmpty) { c.search.value = ''; @@ -357,8 +358,8 @@ class _SearchPageState extends State { final data = snapshot.data; if (data != null && data.isEmpty) { - return const Center( - child: Text("没有数据"), + return Center( + child: Text('common.no-result'.i18n), ); } diff --git a/lib/pages/search/widgets/search_all_tile.dart b/lib/pages/search/widgets/search_all_tile.dart index 02ae4d78..98026919 100644 --- a/lib/pages/search/widgets/search_all_tile.dart +++ b/lib/pages/search/widgets/search_all_tile.dart @@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; import 'package:miru_app/pages/search/widgets/search_all_tile_title.dart'; import 'package:miru_app/utils/extension_runtime.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/bangumi_card.dart'; import 'package:miru_app/widgets/platform_widget.dart'; import 'package:miru_app/widgets/progress_ring.dart'; @@ -71,7 +72,7 @@ class _SearchAllTileState extends State { final data = snapshot.data; if (snapshot.data != null && snapshot.data!.isEmpty) { - return const Text("无结果"); + return Text('common.no_result'.i18n); } return ListView.builder( @@ -150,7 +151,7 @@ class _SearchAllTileState extends State { final data = snapshot.data; if (snapshot.data != null && snapshot.data!.isEmpty) { - return const Text("无结果"); + return Text("common.no_result".i18n); } return ListView.builder( diff --git a/lib/pages/settings/view.dart b/lib/pages/settings/view.dart index 83f44fe7..8c3850c5 100644 --- a/lib/pages/settings/view.dart +++ b/lib/pages/settings/view.dart @@ -2,13 +2,16 @@ import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:get/get.dart'; import 'package:miru_app/pages/extension_repo/controller.dart'; import 'package:miru_app/pages/main/controller.dart'; import 'package:miru_app/pages/settings/controller.dart'; -import 'package:miru_app/pages/settings/widgets/setting_input_tile.dart'; -import 'package:miru_app/pages/settings/widgets/setting_switch_tile.dart'; -import 'package:miru_app/pages/settings/widgets/setting_tile.dart'; +import 'package:miru_app/pages/settings/widgets/settings_input_tile.dart'; +import 'package:miru_app/pages/settings/widgets/settings_radios_tile.dart'; +import 'package:miru_app/pages/settings/widgets/settings_switch_tile.dart'; +import 'package:miru_app/pages/settings/widgets/settings_tile.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_storage.dart'; import 'package:miru_app/utils/package_info.dart'; import 'package:miru_app/widgets/button.dart'; @@ -36,9 +39,9 @@ class _SettingsPageState extends State { return ListView( children: [ if (!Platform.isAndroid) ...[ - const Text( - "设置", - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + Text( + 'common.settings'.i18n, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), ], @@ -47,10 +50,10 @@ class _SettingsPageState extends State { androidWidget: Icon(Icons.link), desktopWidget: Icon(fluent.FluentIcons.repo, size: 24), ), - title: "扩展仓库地址", + title: 'settings.repo-url'.i18n, buildSubtitle: () { if (!Platform.isAndroid) { - return "获取扩展的仓库地址"; + return 'settings.repo-url-subtitle'.i18n; } return MiruStorage.getSetting(SettingKey.miruRepoUrl); }, @@ -67,14 +70,14 @@ class _SettingsPageState extends State { desktopWidget: Icon(fluent.FluentIcons.key_phrase_extraction, size: 24), ), - title: "TMDB API Key", + title: 'settings.tmdb-key'.i18n, buildSubtitle: () { if (!Platform.isAndroid) { - return "获取TMDB API Key"; + return 'settings.tmdb-key-subtitle'.i18n; } final key = MiruStorage.getSetting(SettingKey.tmdbKay) as String; if (key.isEmpty) { - return "未设置"; + return 'common.unset'.i18n; } // 替换为*号 return key.replaceAll(RegExp(r"."), '*'); @@ -85,13 +88,19 @@ class _SettingsPageState extends State { text: MiruStorage.getSetting(SettingKey.tmdbKay), ), const SizedBox(height: 8), - SettingTile( + SettingsTile( icon: const PlatformWidget( androidWidget: Icon(Icons.update), desktopWidget: Icon(fluent.FluentIcons.update_restore, size: 24), ), - title: "更新软件", - buildSubtitle: () => "当前版本: ${packageInfo.version}", + title: 'settings.upgrade'.i18n, + buildSubtitle: () => FlutterI18n.translate( + context, + 'settings.upgrade-subtitle', + translationParams: { + 'version': packageInfo.version, + }, + ), trailing: PlatformWidget( androidWidget: TextButton( onPressed: () { @@ -100,7 +109,7 @@ class _SettingsPageState extends State { showSnackbar: true, ); }, - child: const Text("检查更新"), + child: Text('settings.upgrade-training'.i18n), ), desktopWidget: fluent.FilledButton( onPressed: () { @@ -109,33 +118,53 @@ class _SettingsPageState extends State { showSnackbar: true, ); }, - child: const Text("检查更新"), + child: Text('settings.upgrade-training'.i18n), ), ), ), const SizedBox(height: 8), - SettingSwitchTile( + SettingsSwitchTile( icon: const PlatformWidget( androidWidget: Icon(Icons.autorenew_sharp), desktopWidget: Icon(fluent.FluentIcons.auto_deploy_settings, size: 24), ), - title: "自动检查更新", - buildSubtitle: () => "启动时检查更新", + title: 'settings.auto-check-update'.i18n, + buildSubtitle: () => 'settings.auto-check-update-subtitle'.i18n, buildValue: () => MiruStorage.getSetting(SettingKey.autoCheckUpdate), onChanged: (value) { MiruStorage.setSetting(SettingKey.autoCheckUpdate, value); }, ), const SizedBox(height: 8), + SettingsRadiosTile( + icon: const PlatformWidget( + androidWidget: Icon(Icons.language), + desktopWidget: Icon(fluent.FluentIcons.locale_language, size: 24), + ), + title: 'settings.language'.i18n, + itemNameValue: { + 'languages.en'.i18n: 'en', + 'languages.zh'.i18n: 'zh', + }, + buildSubtitle: () => 'settings.language-subtitle'.i18n, + applyValue: (value) { + MiruStorage.setSetting(SettingKey.language, value); + I18nUtils.changeLanguage(value); + }, + buildGroupValue: () { + return MiruStorage.getSetting(SettingKey.language); + }, + ), + const SizedBox(height: 8), if (!Platform.isAndroid) Obx( () { final value = c.extensionLogWindowId.value != -1; - return SettingSwitchTile( + return SettingsSwitchTile( icon: const Icon(fluent.FluentIcons.bug), - title: "扩展日志窗口", - buildSubtitle: () => "用于调试扩展", + title: 'settings.extension-log'.i18n, + buildSubtitle: () => 'settings.extension-log-subtitle'.i18n, buildValue: () => value, onChanged: (value) { c.toggleExtensionLogWindow(value); @@ -144,17 +173,17 @@ class _SettingsPageState extends State { }, ), const SizedBox(height: 8), - const ListTitle(title: "关于"), + ListTitle(title: 'settings.about'.i18n), const SizedBox(height: 8), - SettingTile( + SettingsTile( icon: const PlatformWidget( androidWidget: Icon(Icons.web), desktopWidget: Icon(fluent.FluentIcons.live_site, size: 24), ), - title: '官方网站', + title: 'settings.official-site'.i18n, buildSubtitle: () => 'https://miru.js.org', trailing: PlatformTextButton( - child: const Text('打开官网'), + child: Text('settings.official-site-training'.i18n), onPressed: () { launchUrl( Uri.parse('https://miru.js.org'), @@ -164,15 +193,15 @@ class _SettingsPageState extends State { ), ), const SizedBox(height: 8), - SettingTile( + SettingsTile( icon: const PlatformWidget( androidWidget: Icon(Icons.code), desktopWidget: Icon(fluent.FluentIcons.code, size: 24), ), - title: '开源', + title: 'settings.source-code'.i18n, buildSubtitle: () => 'miru-project/miru-app', trailing: PlatformTextButton( - child: const Text('前往 Star'), + child: Text('settings.source-code-training'.i18n), onPressed: () { launchUrl( Uri.parse('https://github.com/miru-project/miru-app'), @@ -183,10 +212,10 @@ class _SettingsPageState extends State { ), if (Platform.isAndroid) ...[ const SizedBox(height: 8), - SettingTile( + SettingsTile( icon: const Icon(Icons.library_books), - title: '许可', - buildSubtitle: () => '许可证', + title: 'settings.license'.i18n, + buildSubtitle: () => 'settings.license-subtitle'.i18n, trailing: const Icon(Icons.chevron_right), onTap: () { Get.to(LicensePage( @@ -208,7 +237,7 @@ class _SettingsPageState extends State { Widget _buildAndroid(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text("设置"), + title: Text('common.settings'.i18n), ), body: _buildContent(), ); diff --git a/lib/pages/settings/widgets/setting_input_tile.dart b/lib/pages/settings/widgets/settings_input_tile.dart similarity index 91% rename from lib/pages/settings/widgets/setting_input_tile.dart rename to lib/pages/settings/widgets/settings_input_tile.dart index 6d33302f..0af9b3f4 100644 --- a/lib/pages/settings/widgets/setting_input_tile.dart +++ b/lib/pages/settings/widgets/settings_input_tile.dart @@ -1,6 +1,7 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; -import 'package:miru_app/pages/settings/widgets/setting_tile.dart'; +import 'package:miru_app/pages/settings/widgets/settings_tile.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/widgets/platform_widget.dart'; class SettingsIntpuTile extends fluent.StatefulWidget { @@ -46,7 +47,7 @@ class _SettingsIntpuTileState extends fluent.State { onPressed: () { Navigator.pop(context); }, - child: const Text("完成"), + child: Text('common.confirm'.i18n), ), ], ); @@ -57,7 +58,7 @@ class _SettingsIntpuTileState extends fluent.State { } Widget _buildDesktop(BuildContext context) { - return SettingTile( + return SettingsTile( icon: widget.icon, title: widget.title, buildSubtitle: widget.buildSubtitle, diff --git a/lib/pages/settings/widgets/settings_radios_tile.dart b/lib/pages/settings/widgets/settings_radios_tile.dart new file mode 100644 index 00000000..a2e24c57 --- /dev/null +++ b/lib/pages/settings/widgets/settings_radios_tile.dart @@ -0,0 +1,89 @@ +import 'package:fluent_ui/fluent_ui.dart' as fluent; +import 'package:flutter/material.dart'; +import 'package:miru_app/pages/settings/widgets/settings_tile.dart'; +import 'package:miru_app/widgets/platform_widget.dart'; + +class SettingsRadiosTile extends fluent.StatefulWidget { + const SettingsRadiosTile({ + Key? key, + required this.icon, + required this.title, + this.buildSubtitle, + required this.itemNameValue, + required this.applyValue, + required this.buildGroupValue, + }) : super(key: key); + final Widget icon; + final String title; + final String Function()? buildSubtitle; + final Function(T value) applyValue; + final Map itemNameValue; + final T Function() buildGroupValue; + + @override + fluent.State> createState() => + _SettingsRadiosTileState(); +} + +class _SettingsRadiosTileState extends fluent.State> { + Widget _buildAndroid(BuildContext context) { + return SettingsTile( + icon: widget.icon, + title: widget.title, + buildSubtitle: widget.buildSubtitle, + trailing: const Icon(Icons.chevron_right), + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(widget.title), + scrollable: true, + content: Column( + children: [ + for (final item in widget.itemNameValue.entries) + RadioListTile( + title: Text(item.key), + value: item.value, + groupValue: widget.buildGroupValue(), + onChanged: (value) { + Navigator.pop(context); + widget.applyValue(value as T); + }, + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildDesktop(BuildContext context) { + return SettingsTile( + icon: widget.icon, + title: widget.title, + buildSubtitle: widget.buildSubtitle, + trailing: fluent.ComboBox( + items: [ + for (final item in widget.itemNameValue.entries) + fluent.ComboBoxItem( + value: item.value, + child: Text(item.key), + ) + ], + value: widget.buildGroupValue(), + onChanged: (value) { + widget.applyValue(value as T); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return PlatformBuildWidget( + androidBuilder: _buildAndroid, + desktopBuilder: _buildDesktop, + ); + } +} diff --git a/lib/pages/settings/widgets/setting_switch_tile.dart b/lib/pages/settings/widgets/settings_switch_tile.dart similarity index 76% rename from lib/pages/settings/widgets/setting_switch_tile.dart rename to lib/pages/settings/widgets/settings_switch_tile.dart index cff615c5..65040e46 100644 --- a/lib/pages/settings/widgets/setting_switch_tile.dart +++ b/lib/pages/settings/widgets/settings_switch_tile.dart @@ -1,10 +1,10 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; -import 'package:miru_app/pages/settings/widgets/setting_tile.dart'; +import 'package:miru_app/pages/settings/widgets/settings_tile.dart'; import 'package:miru_app/widgets/platform_widget.dart'; -class SettingSwitchTile extends fluent.StatefulWidget { - const SettingSwitchTile({ +class SettingsSwitchTile extends fluent.StatefulWidget { + const SettingsSwitchTile({ Key? key, required this.icon, required this.title, @@ -19,13 +19,13 @@ class SettingSwitchTile extends fluent.StatefulWidget { final Function(bool) onChanged; @override - fluent.State createState() => _SettingSwitchTileState(); + fluent.State createState() => _SettingsSwitchTileState(); } -class _SettingSwitchTileState extends fluent.State { +class _SettingsSwitchTileState extends fluent.State { @override Widget build(BuildContext context) { - return SettingTile( + return SettingsTile( icon: widget.icon, title: widget.title, buildSubtitle: widget.buildSubtitle, diff --git a/lib/pages/settings/widgets/setting_tile.dart b/lib/pages/settings/widgets/settings_tile.dart similarity index 88% rename from lib/pages/settings/widgets/setting_tile.dart rename to lib/pages/settings/widgets/settings_tile.dart index 4a9c8582..1fccb567 100644 --- a/lib/pages/settings/widgets/setting_tile.dart +++ b/lib/pages/settings/widgets/settings_tile.dart @@ -2,8 +2,8 @@ import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; import 'package:miru_app/widgets/platform_widget.dart'; -class SettingTile extends StatefulWidget { - const SettingTile({ +class SettingsTile extends StatefulWidget { + const SettingsTile({ Key? key, required this.icon, required this.title, @@ -18,10 +18,10 @@ class SettingTile extends StatefulWidget { final Widget? trailing; @override - State createState() => _SettingTileState(); + State createState() => _SettingsTileState(); } -class _SettingTileState extends State { +class _SettingsTileState extends State { Widget _buildAndroid(BuildContext context) { return ListTile( leading: widget.icon, diff --git a/lib/pages/watch/view.dart b/lib/pages/watch/view.dart index af1b96c9..98fc61de 100644 --- a/lib/pages/watch/view.dart +++ b/lib/pages/watch/view.dart @@ -2,10 +2,13 @@ import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart' as fluent; import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:get/get.dart'; import 'package:miru_app/main.dart'; import 'package:miru_app/models/extension.dart'; import 'package:miru_app/pages/watch/widgets/video_player.dart'; import 'package:miru_app/utils/extension.dart'; +import 'package:miru_app/utils/i18n.dart'; class WatchPage extends StatelessWidget { const WatchPage({ @@ -33,23 +36,35 @@ class WatchPage extends StatelessWidget { builder: (context) { if (Platform.isAndroid) { return AlertDialog( - title: const Text("错误"), - content: Text("扩展 $package 丢失"), + title: Text('common.error'.i18n), + content: Text(FlutterI18n.translate( + context, + 'common.extension-missing', + translationParams: { + 'package': package, + }, + )), actions: [ TextButton( - onPressed: () => router.pop(), - child: const Text("确定"), + onPressed: () => Get.back(), + child: Text('common.confirm'.i18n), ), ], ); } return fluent.ContentDialog( - title: const Text("错误"), - content: Text("扩展 $package 丢失"), + title: Text('common.error'.i18n), + content: Text(FlutterI18n.translate( + context, + 'common.extension-missing', + translationParams: { + 'package': package, + }, + )), actions: [ fluent.Button( onPressed: () => router.pop(), - child: const Text("确定"), + child: Text('common.confirm'.i18n), ), ], ); diff --git a/lib/pages/watch/widgets/video_player.dart b/lib/pages/watch/widgets/video_player.dart index ac6c69da..a3860ed1 100644 --- a/lib/pages/watch/widgets/video_player.dart +++ b/lib/pages/watch/widgets/video_player.dart @@ -12,6 +12,7 @@ import 'package:miru_app/models/history.dart'; import 'package:miru_app/pages/home/controller.dart'; import 'package:miru_app/utils/database.dart'; import 'package:miru_app/utils/extension_runtime.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_directory.dart'; import 'package:miru_app/widgets/platform_widget.dart'; import 'package:miru_app/widgets/progress_ring.dart'; @@ -45,10 +46,7 @@ class VideoPlayer extends StatefulWidget { class _VideoPlayerState extends State { late final player = Player(); - late final controller = VideoController( - player, - configuration: const VideoControllerConfiguration(), - ); + late final controller = VideoController(player); late final ScreenshotController screenshotController = ScreenshotController(); late int playerIndex = widget.playerIndex; bool isPlaying = false; @@ -99,13 +97,13 @@ class _VideoPlayerState extends State { player.streams.completed.listen((event) { if (playerIndex == widget.playList.length - 1) { if (Platform.isAndroid) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("播放完毕"), + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('video.play-complete'.i18n), )); return; } fluent.displayInfoBar(context, builder: ((context, close) { - return const fluent.InfoBar(title: Text("播放完毕")); + return fluent.InfoBar(title: Text('video.play-complete'.i18n)); })); return; } @@ -289,8 +287,8 @@ class _VideoPlayerState extends State { ), onPressed: () { if (playerIndex == 0) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("当前是第一集"), + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('video.already-first'.i18n), )); return; } @@ -315,8 +313,8 @@ class _VideoPlayerState extends State { ), onPressed: () { if (playerIndex == widget.playList.length - 1) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("当前最后一集"), + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('video.already-last'.i18n), )); return; } @@ -380,7 +378,8 @@ class _VideoPlayerState extends State { onPressed: () { if (playerIndex == 0) { fluent.displayInfoBar(context, builder: ((context, close) { - return const fluent.InfoBar(title: Text("当前是第一集")); + return fluent.InfoBar( + title: Text('video.already-first'.i18n)); })); return; } @@ -399,7 +398,7 @@ class _VideoPlayerState extends State { onPressed: () { if (playerIndex == widget.playList.length - 1) { fluent.displayInfoBar(context, builder: ((context, close) { - return const fluent.InfoBar(title: Text("当前是最后一集")); + return fluent.InfoBar(title: Text('video.already-last'.i18n)); })); return; } diff --git a/lib/utils/extension.dart b/lib/utils/extension.dart index d0182a31..1b3d7e43 100644 --- a/lib/utils/extension.dart +++ b/lib/utils/extension.dart @@ -10,6 +10,7 @@ import 'package:miru_app/pages/extension/controller.dart'; import 'package:miru_app/pages/search/controller.dart'; import 'package:miru_app/pages/settings/controller.dart'; import 'package:miru_app/utils/extension_runtime.dart'; +import 'package:miru_app/utils/i18n.dart'; import 'package:miru_app/utils/miru_directory.dart'; import 'package:miru_app/utils/router.dart'; import 'package:miru_app/widgets/button.dart'; @@ -49,7 +50,7 @@ class ExtensionUtils { // 如果文件名和包名不一致,抛出异常 final ext = ExtensionUtils.parseExtension(content); if (path.basenameWithoutExtension(extension.path) != ext.package) { - throw Exception("文件名和包名不一致"); + throw Exception("Inconsistency between file name and package name"); } exts[ext.package] = await ExtensionRuntime().initRuntime(ext); } catch (e) { @@ -81,7 +82,7 @@ class ExtensionUtils { try { final res = await Dio().get(url); if (res.data == null) { - throw Exception("似乎不是扩展"); + throw Exception("Does not seem to be an extension"); } final ext = ExtensionUtils.parseExtension(res.data!); final savePath = path.join(await getExtensionsDir, '${ext.package}.js'); @@ -90,11 +91,11 @@ class ExtensionUtils { } catch (e) { showPlatformDialog( context: context, - title: "安装错误", + title: 'extension-install-error'.i18n, content: Text(e.toString()), actions: [ PlatformButton( - child: const Text("关闭"), + child: Text('common.close'.i18n), onPressed: () { RouterUtils.pop(); }, @@ -105,6 +106,17 @@ class ExtensionUtils { } } + static String typeToString(ExtensionType type) { + switch (type) { + case ExtensionType.bangumi: + return 'extension-type.video'.i18n; + case ExtensionType.fikushon: + return 'extension-type.novel'.i18n; + case ExtensionType.manga: + return 'extension-type.comics'.i18n; + } + } + static addLog( Extension ext, ExtensionLogLevel level, diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart new file mode 100644 index 00000000..d0c8b1f9 --- /dev/null +++ b/lib/utils/i18n.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:flutter_i18n/loaders/decoders/json_decode_strategy.dart'; +import 'package:get/get.dart'; +import 'package:miru_app/main.dart'; +import 'package:miru_app/utils/miru_storage.dart'; + +final _context = + Platform.isAndroid ? Get.context! : rootNavigatorKey.currentContext!; + +class I18nUtils { + static final flutterI18nDelegate = FlutterI18nDelegate( + translationLoader: FileTranslationLoader( + useCountryCode: false, + fallbackFile: 'zh', + basePath: 'assets/i18n', + forcedLocale: Locale(MiruStorage.getSetting(SettingKey.language)), + decodeStrategies: [JsonDecodeStrategy()], + ), + ); + +// 获取当前语言 + static Locale? get currentLanguage => FlutterI18n.currentLocale(_context); + +// 切换语言 + static Future changeLanguage(String locale) async { + await FlutterI18n.refresh(_context, Locale(locale)); + await Get.forceAppUpdate(); + } +} + +extension I18nString on String { + String get i18n => FlutterI18n.translate(_context, this); +} diff --git a/lib/utils/miru_storage.dart b/lib/utils/miru_storage.dart index 48655b59..fa4ccc79 100644 --- a/lib/utils/miru_storage.dart +++ b/lib/utils/miru_storage.dart @@ -25,6 +25,7 @@ class MiruStorage { await _initSetting(SettingKey.miruRepoUrl, "https://miru-repo.0n0.dev"); await _initSetting(SettingKey.tmdbKay, ""); await _initSetting(SettingKey.autoCheckUpdate, true); + await _initSetting(SettingKey.language, 'en'); } static _initSetting(String key, dynamic value) async { @@ -46,4 +47,5 @@ class SettingKey { static String miruRepoUrl = "MiruRepoUrl"; static String tmdbKay = 'TMDBKey'; static String autoCheckUpdate = 'AutoCheckUpdate'; + static String language = 'Language'; } diff --git a/pubspec.lock b/pubspec.lock index df931b96..c87eb095 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -342,6 +342,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + flutter_i18n: + dependency: "direct main" + description: + name: flutter_i18n + sha256: b71fe887697686368c93e4d2257ecdb3c35fe38686a6fbf3902d6355c3f20363 + url: "https://pub.dev" + source: hosted + version: "0.33.0" flutter_js: dependency: "direct main" description: @@ -1074,6 +1082,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.5" + toml: + dependency: transitive + description: + name: toml + sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44" + url: "https://pub.dev" + source: hosted + version: "0.14.0" tuple: dependency: transitive description: @@ -1258,6 +1274,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.0" + xml2json: + dependency: transitive + description: + name: xml2json + sha256: c8cb35b83cce879c2ea86951fd257f4e765b0030a0298b35cf94f2b3d0f32095 + url: "https://pub.dev" + source: hosted + version: "5.3.6" xxh3: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 28fe01d3..0e1e20fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: miru_app description: A new Flutter project. publish_to: "none" -version: 1.1.1 +version: 1.2.0 environment: sdk: ">=3.0.3 <4.0.0" @@ -18,6 +18,7 @@ dependencies: flutter_animate: ^4.1.1+1 flutter_code_editor: ^0.3.0 flutter_highlight: ^0.7.0 + flutter_i18n: ^0.33.0 flutter_js: ^0.7.1 flutter_markdown: ^0.6.16 get: ^4.6.5 @@ -56,4 +57,5 @@ dev_dependencies: flutter: uses-material-design: true assets: + - assets/i18n/ - assets/icon/logo.png