diff --git a/lib/app.dart b/lib/app.dart index fb1fd822..971ece48 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:toolbox/data/res/build_data.dart'; import 'package:toolbox/data/store/setting.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/home.dart'; @@ -30,6 +32,13 @@ class MyApp extends StatelessWidget { final primaryColor = Color(value); final textStyle = TextStyle(color: primaryColor); return MaterialApp( + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, title: BuildData.name, debugShowCheckedModeBanner: false, theme: ThemeData( diff --git a/lib/core/utils.dart b/lib/core/utils.dart index cfd42da3..c6534941 100644 --- a/lib/core/utils.dart +++ b/lib/core/utils.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:toolbox/core/persistant_store.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/view/widget/card_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:toolbox/core/extension/stringx.dart'; @@ -82,3 +83,17 @@ void setTransparentNavigationBar(BuildContext context) { )); } } + +String tabTitleName(BuildContext context, int i) { + final s = S.of(context); + switch (i) { + case 0: + return s.server; + case 1: + return s.convert; + case 2: + return s.ping; + default: + return ''; + } +} diff --git a/lib/data/res/font_style.dart b/lib/data/res/font_style.dart new file mode 100644 index 00000000..61227688 --- /dev/null +++ b/lib/data/res/font_style.dart @@ -0,0 +1,3 @@ +import 'package:flutter/material.dart'; + +const TextStyle size18 = TextStyle(fontSize: 18); \ No newline at end of file diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart new file mode 100644 index 00000000..2b808161 --- /dev/null +++ b/lib/generated/intl/messages_all.dart @@ -0,0 +1,66 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that looks up messages for specific locales by +// delegating to the appropriate library. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:implementation_imports, file_names, unnecessary_new +// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering +// ignore_for_file:argument_type_not_assignable, invalid_assignment +// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases +// ignore_for_file:comment_references + +import 'dart:async'; + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; +import 'package:intl/src/intl_helpers.dart'; + +import 'messages_en.dart' as messages_en; +import 'messages_zh.dart' as messages_zh; + +typedef Future LibraryLoader(); +Map _deferredLibraries = { + 'en': () => new Future.value(null), + 'zh': () => new Future.value(null), +}; + +MessageLookupByLibrary? _findExact(String localeName) { + switch (localeName) { + case 'en': + return messages_en.messages; + case 'zh': + return messages_zh.messages; + default: + return null; + } +} + +/// User programs should call this before using [localeName] for messages. +Future initializeMessages(String localeName) async { + var availableLocale = Intl.verifiedLocale( + localeName, (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); + if (availableLocale == null) { + return new Future.value(false); + } + var lib = _deferredLibraries[availableLocale]; + await (lib == null ? new Future.value(false) : lib()); + initializeInternalMessageLookup(() => new CompositeMessageLookup()); + messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); + return new Future.value(true); +} + +bool _messagesExistFor(String locale) { + try { + return _findExact(locale) != null; + } catch (e) { + return false; + } +} + +MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { + var actualLocale = + Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); + if (actualLocale == null) return null; + return _findExact(actualLocale); +} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart new file mode 100644 index 00000000..5c792c89 --- /dev/null +++ b/lib/generated/intl/messages_en.dart @@ -0,0 +1,140 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a en locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'en'; + + static String m0(rainSunMeGithub) => + "\nThanks ${rainSunMeGithub} for participating in the test.\n\nAll rights reserved."; + + static String m1(code) => "request failed, status code: ${code}"; + + static String m2(myGithub) => "\nMade with ❤️ by ${myGithub}"; + + static String m3(server) => "Are you sure to delete server [${server}]?"; + + static String m4(build) => "Found: v1.0.${build}, click to update"; + + static String m5(build) => "Current: v1.0.${build}"; + + static String m6(build) => "Current: v1.0.${build}, is up to date"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "aboutThanks": m0, + "addAServer": MessageLookupByLibrary.simpleMessage("add a server"), + "addPrivateKey": + MessageLookupByLibrary.simpleMessage("Add private key"), + "appPrimaryColor": + MessageLookupByLibrary.simpleMessage("App primary color"), + "attention": MessageLookupByLibrary.simpleMessage("Attention"), + "cancel": MessageLookupByLibrary.simpleMessage("Cancel"), + "choose": MessageLookupByLibrary.simpleMessage("Choose"), + "chooseDestination": + MessageLookupByLibrary.simpleMessage("Choose destination"), + "choosePrivateKey": + MessageLookupByLibrary.simpleMessage("Choose private key"), + "clear": MessageLookupByLibrary.simpleMessage("Clear"), + "close": MessageLookupByLibrary.simpleMessage("Close"), + "convert": MessageLookupByLibrary.simpleMessage("Convert"), + "copy": MessageLookupByLibrary.simpleMessage("Copy"), + "currentMode": MessageLookupByLibrary.simpleMessage("Current Mode"), + "debug": MessageLookupByLibrary.simpleMessage("Debug"), + "decode": MessageLookupByLibrary.simpleMessage("Decode"), + "delete": MessageLookupByLibrary.simpleMessage("Delete"), + "edit": MessageLookupByLibrary.simpleMessage("Edit"), + "encode": MessageLookupByLibrary.simpleMessage("Encode"), + "exampleName": MessageLookupByLibrary.simpleMessage("Example name"), + "export": MessageLookupByLibrary.simpleMessage("Export"), + "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage( + "These fields must not be empty."), + "go": MessageLookupByLibrary.simpleMessage("Go"), + "host": MessageLookupByLibrary.simpleMessage("Host"), + "httpFailedWithCode": m1, + "import": MessageLookupByLibrary.simpleMessage("Import"), + "importAndExport": + MessageLookupByLibrary.simpleMessage("Import and Export"), + "keyAuth": MessageLookupByLibrary.simpleMessage("Key Auth"), + "launchPage": MessageLookupByLibrary.simpleMessage("Launch page"), + "license": MessageLookupByLibrary.simpleMessage("License"), + "loss": MessageLookupByLibrary.simpleMessage("Loss"), + "madeWithLove": m2, + "max": MessageLookupByLibrary.simpleMessage("max"), + "min": MessageLookupByLibrary.simpleMessage("min"), + "ms": MessageLookupByLibrary.simpleMessage("ms"), + "name": MessageLookupByLibrary.simpleMessage("Name"), + "noResult": MessageLookupByLibrary.simpleMessage("No result"), + "noSavedPrivateKey": + MessageLookupByLibrary.simpleMessage("No saved private keys."), + "noSavedSnippet": + MessageLookupByLibrary.simpleMessage("No saved snippets."), + "noServerAvailable": + MessageLookupByLibrary.simpleMessage("No server available."), + "ok": MessageLookupByLibrary.simpleMessage("OK"), + "ping": MessageLookupByLibrary.simpleMessage("Ping"), + "pingAvg": MessageLookupByLibrary.simpleMessage("Avg:"), + "pingInputIP": MessageLookupByLibrary.simpleMessage( + "Please input a target IP/domain."), + "plzEnterHost": + MessageLookupByLibrary.simpleMessage("Please enter host."), + "plzEnterPwd": + MessageLookupByLibrary.simpleMessage("Please enter password."), + "plzSelectKey": + MessageLookupByLibrary.simpleMessage("Please select a key."), + "port": MessageLookupByLibrary.simpleMessage("Port"), + "privateKey": MessageLookupByLibrary.simpleMessage("Private Key"), + "pwd": MessageLookupByLibrary.simpleMessage("Password"), + "result": MessageLookupByLibrary.simpleMessage("Result"), + "run": MessageLookupByLibrary.simpleMessage("Run"), + "save": MessageLookupByLibrary.simpleMessage("Save"), + "second": MessageLookupByLibrary.simpleMessage("s"), + "server": MessageLookupByLibrary.simpleMessage("Server"), + "serverTabConnecting": + MessageLookupByLibrary.simpleMessage("Connecting..."), + "serverTabEmpty": MessageLookupByLibrary.simpleMessage( + "There is no server.\nClick the fab to add one."), + "serverTabFailed": MessageLookupByLibrary.simpleMessage("Failed"), + "serverTabLoading": MessageLookupByLibrary.simpleMessage("Loading..."), + "serverTabPlzSave": MessageLookupByLibrary.simpleMessage( + "Please \'save\' this private key again."), + "serverTabUnkown": + MessageLookupByLibrary.simpleMessage("Unknown state"), + "setting": MessageLookupByLibrary.simpleMessage("Setting"), + "snippet": MessageLookupByLibrary.simpleMessage("Snippet"), + "start": MessageLookupByLibrary.simpleMessage("Start"), + "stop": MessageLookupByLibrary.simpleMessage("Stop"), + "sureToDeleteServer": m3, + "ttl": MessageLookupByLibrary.simpleMessage("TTL"), + "unknown": MessageLookupByLibrary.simpleMessage("unknown"), + "unkownConvertMode": + MessageLookupByLibrary.simpleMessage("Unknown convert mode"), + "updateIntervalEqual0": MessageLookupByLibrary.simpleMessage( + "You set to 0, will not update automatically.\nYou can pull to refresh manually."), + "updateServerStatusInterval": MessageLookupByLibrary.simpleMessage( + "Server status update interval"), + "upsideDown": MessageLookupByLibrary.simpleMessage("Upside Down"), + "urlOrJson": MessageLookupByLibrary.simpleMessage("URL or JSON"), + "user": MessageLookupByLibrary.simpleMessage("User"), + "versionHaveUpdate": m4, + "versionUnknownUpdate": m5, + "versionUpdated": m6, + "willTakEeffectImmediately": + MessageLookupByLibrary.simpleMessage("Will take effect immediately") + }; +} diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart new file mode 100644 index 00000000..fe424fcd --- /dev/null +++ b/lib/generated/intl/messages_zh.dart @@ -0,0 +1,123 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a zh locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'zh'; + + static String m0(rainSunMeGithub) => + "\n感谢 ${rainSunMeGithub} 参与软件测试。\n\n保留所有权利。"; + + static String m1(code) => "请求失败, 状态码: ${code}"; + + static String m2(myGithub) => "\n用❤️制作 by ${myGithub}"; + + static String m3(server) => "你确定要删除服务器 [${server}] 吗?"; + + static String m4(build) => "找到新版本:v1.0.${build}, 点击更新"; + + static String m5(build) => "当前:v1.0.${build}"; + + static String m6(build) => "当前:v1.0.${build}, 已是最新版本"; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "aboutThanks": m0, + "addAServer": MessageLookupByLibrary.simpleMessage("添加服务器"), + "addPrivateKey": MessageLookupByLibrary.simpleMessage("添加一个私钥"), + "appPrimaryColor": MessageLookupByLibrary.simpleMessage("App主要色"), + "attention": MessageLookupByLibrary.simpleMessage("注意"), + "cancel": MessageLookupByLibrary.simpleMessage("取消"), + "choose": MessageLookupByLibrary.simpleMessage("选择"), + "chooseDestination": MessageLookupByLibrary.simpleMessage("选择目标"), + "choosePrivateKey": MessageLookupByLibrary.simpleMessage("选择私钥"), + "clear": MessageLookupByLibrary.simpleMessage("清除"), + "close": MessageLookupByLibrary.simpleMessage("关闭"), + "convert": MessageLookupByLibrary.simpleMessage("转换"), + "copy": MessageLookupByLibrary.simpleMessage("复制到剪切板"), + "currentMode": MessageLookupByLibrary.simpleMessage("当前模式"), + "debug": MessageLookupByLibrary.simpleMessage("调试"), + "decode": MessageLookupByLibrary.simpleMessage("解码"), + "delete": MessageLookupByLibrary.simpleMessage("删除"), + "edit": MessageLookupByLibrary.simpleMessage("编辑"), + "encode": MessageLookupByLibrary.simpleMessage("编码"), + "exampleName": MessageLookupByLibrary.simpleMessage("名称示例"), + "export": MessageLookupByLibrary.simpleMessage("导出"), + "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage("这些输入框不能为空。"), + "go": MessageLookupByLibrary.simpleMessage("开始"), + "host": MessageLookupByLibrary.simpleMessage("主机"), + "httpFailedWithCode": m1, + "import": MessageLookupByLibrary.simpleMessage("导入"), + "importAndExport": MessageLookupByLibrary.simpleMessage("导入或导出"), + "keyAuth": MessageLookupByLibrary.simpleMessage("公钥认证"), + "launchPage": MessageLookupByLibrary.simpleMessage("启动页"), + "license": MessageLookupByLibrary.simpleMessage("开源证书"), + "loss": MessageLookupByLibrary.simpleMessage("丢包率"), + "madeWithLove": m2, + "max": MessageLookupByLibrary.simpleMessage("最大"), + "min": MessageLookupByLibrary.simpleMessage("最小"), + "ms": MessageLookupByLibrary.simpleMessage("毫秒"), + "name": MessageLookupByLibrary.simpleMessage("名称"), + "noResult": MessageLookupByLibrary.simpleMessage("无结果"), + "noSavedPrivateKey": MessageLookupByLibrary.simpleMessage("没有已保存的私钥。"), + "noSavedSnippet": MessageLookupByLibrary.simpleMessage("没有已保存的代码片段。"), + "noServerAvailable": MessageLookupByLibrary.simpleMessage("没有可用的服务器。"), + "ok": MessageLookupByLibrary.simpleMessage("好"), + "ping": MessageLookupByLibrary.simpleMessage("Ping"), + "pingAvg": MessageLookupByLibrary.simpleMessage("平均:"), + "pingInputIP": MessageLookupByLibrary.simpleMessage("请输入目标IP或域名"), + "plzEnterHost": MessageLookupByLibrary.simpleMessage("请输入主机"), + "plzEnterPwd": MessageLookupByLibrary.simpleMessage("请输入密码"), + "plzSelectKey": MessageLookupByLibrary.simpleMessage("请选择私钥"), + "port": MessageLookupByLibrary.simpleMessage("端口"), + "privateKey": MessageLookupByLibrary.simpleMessage("私钥"), + "pwd": MessageLookupByLibrary.simpleMessage("密码"), + "result": MessageLookupByLibrary.simpleMessage("结果"), + "run": MessageLookupByLibrary.simpleMessage("运行"), + "save": MessageLookupByLibrary.simpleMessage("保存"), + "second": MessageLookupByLibrary.simpleMessage("秒"), + "server": MessageLookupByLibrary.simpleMessage("服务器"), + "serverTabConnecting": MessageLookupByLibrary.simpleMessage("连接中..."), + "serverTabEmpty": + MessageLookupByLibrary.simpleMessage("现在没有服务器。\n点击右下方按钮来添加。"), + "serverTabFailed": MessageLookupByLibrary.simpleMessage("失败"), + "serverTabLoading": MessageLookupByLibrary.simpleMessage("加载中..."), + "serverTabPlzSave": MessageLookupByLibrary.simpleMessage("请再次保存该私钥"), + "serverTabUnkown": MessageLookupByLibrary.simpleMessage("未知状态"), + "setting": MessageLookupByLibrary.simpleMessage("设置"), + "snippet": MessageLookupByLibrary.simpleMessage("代码片段"), + "start": MessageLookupByLibrary.simpleMessage("开始"), + "stop": MessageLookupByLibrary.simpleMessage("停止"), + "sureToDeleteServer": m3, + "ttl": MessageLookupByLibrary.simpleMessage("缓存时间"), + "unknown": MessageLookupByLibrary.simpleMessage("未知"), + "unkownConvertMode": MessageLookupByLibrary.simpleMessage("未知转换模式"), + "updateIntervalEqual0": MessageLookupByLibrary.simpleMessage( + "你设置为0,服务器状态不会自动刷新。\n你可以手动下拉刷新。"), + "updateServerStatusInterval": + MessageLookupByLibrary.simpleMessage("服务器状态刷新间隔"), + "upsideDown": MessageLookupByLibrary.simpleMessage("上下交换"), + "urlOrJson": MessageLookupByLibrary.simpleMessage("链接或JSON"), + "user": MessageLookupByLibrary.simpleMessage("用户"), + "versionHaveUpdate": m4, + "versionUnknownUpdate": m5, + "versionUpdated": m6, + "willTakEeffectImmediately": + MessageLookupByLibrary.simpleMessage("更改将会立即生效") + }; +} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart new file mode 100644 index 00000000..28de461c --- /dev/null +++ b/lib/generated/l10n.dart @@ -0,0 +1,859 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'intl/messages_all.dart'; + +// ************************************************************************** +// Generator: Flutter Intl IDE plugin +// Made by Localizely +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars +// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each +// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes + +class S { + S(); + + static S? _current; + + static S get current { + assert(_current != null, + 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); + return _current!; + } + + static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); + + static Future load(Locale locale) { + final name = (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); + final localeName = Intl.canonicalizedLocale(name); + return initializeMessages(localeName).then((_) { + Intl.defaultLocale = localeName; + final instance = S(); + S._current = instance; + + return instance; + }); + } + + static S of(BuildContext context) { + final instance = S.maybeOf(context); + assert(instance != null, + 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); + return instance!; + } + + static S? maybeOf(BuildContext context) { + return Localizations.of(context, S); + } + + /// `Server` + String get server { + return Intl.message( + 'Server', + name: 'server', + desc: '', + args: [], + ); + } + + /// `Convert` + String get convert { + return Intl.message( + 'Convert', + name: 'convert', + desc: '', + args: [], + ); + } + + /// `Ping` + String get ping { + return Intl.message( + 'Ping', + name: 'ping', + desc: '', + args: [], + ); + } + + /// `Debug` + String get debug { + return Intl.message( + 'Debug', + name: 'debug', + desc: '', + args: [], + ); + } + + /// `add a server` + String get addAServer { + return Intl.message( + 'add a server', + name: 'addAServer', + desc: '', + args: [], + ); + } + + /// `Setting` + String get setting { + return Intl.message( + 'Setting', + name: 'setting', + desc: '', + args: [], + ); + } + + /// `License` + String get license { + return Intl.message( + 'License', + name: 'license', + desc: '', + args: [], + ); + } + + /// `Snippet` + String get snippet { + return Intl.message( + 'Snippet', + name: 'snippet', + desc: '', + args: [], + ); + } + + /// `Private Key` + String get privateKey { + return Intl.message( + 'Private Key', + name: 'privateKey', + desc: '', + args: [], + ); + } + + /// `\nMade with ❤️ by {myGithub}` + String madeWithLove(Object myGithub) { + return Intl.message( + '\nMade with ❤️ by $myGithub', + name: 'madeWithLove', + desc: '', + args: [myGithub], + ); + } + + /// `\nThanks {rainSunMeGithub} for participating in the test.\n\nAll rights reserved.` + String aboutThanks(Object rainSunMeGithub) { + return Intl.message( + '\nThanks $rainSunMeGithub for participating in the test.\n\nAll rights reserved.', + name: 'aboutThanks', + desc: '', + args: [rainSunMeGithub], + ); + } + + /// `There is no server.\nClick the fab to add one.` + String get serverTabEmpty { + return Intl.message( + 'There is no server.\nClick the fab to add one.', + name: 'serverTabEmpty', + desc: '', + args: [], + ); + } + + /// `Loading...` + String get serverTabLoading { + return Intl.message( + 'Loading...', + name: 'serverTabLoading', + desc: '', + args: [], + ); + } + + /// `Please 'save' this private key again.` + String get serverTabPlzSave { + return Intl.message( + 'Please \'save\' this private key again.', + name: 'serverTabPlzSave', + desc: '', + args: [], + ); + } + + /// `Failed` + String get serverTabFailed { + return Intl.message( + 'Failed', + name: 'serverTabFailed', + desc: '', + args: [], + ); + } + + /// `Unknown state` + String get serverTabUnkown { + return Intl.message( + 'Unknown state', + name: 'serverTabUnkown', + desc: '', + args: [], + ); + } + + /// `Connecting...` + String get serverTabConnecting { + return Intl.message( + 'Connecting...', + name: 'serverTabConnecting', + desc: '', + args: [], + ); + } + + /// `Decode` + String get decode { + return Intl.message( + 'Decode', + name: 'decode', + desc: '', + args: [], + ); + } + + /// `Encode` + String get encode { + return Intl.message( + 'Encode', + name: 'encode', + desc: '', + args: [], + ); + } + + /// `Current Mode` + String get currentMode { + return Intl.message( + 'Current Mode', + name: 'currentMode', + desc: '', + args: [], + ); + } + + /// `Unknown convert mode` + String get unkownConvertMode { + return Intl.message( + 'Unknown convert mode', + name: 'unkownConvertMode', + desc: '', + args: [], + ); + } + + /// `Copy` + String get copy { + return Intl.message( + 'Copy', + name: 'copy', + desc: '', + args: [], + ); + } + + /// `Upside Down` + String get upsideDown { + return Intl.message( + 'Upside Down', + name: 'upsideDown', + desc: '', + args: [], + ); + } + + /// `Avg:` + String get pingAvg { + return Intl.message( + 'Avg:', + name: 'pingAvg', + desc: '', + args: [], + ); + } + + /// `unknown` + String get unknown { + return Intl.message( + 'unknown', + name: 'unknown', + desc: '', + args: [], + ); + } + + /// `min` + String get min { + return Intl.message( + 'min', + name: 'min', + desc: '', + args: [], + ); + } + + /// `max` + String get max { + return Intl.message( + 'max', + name: 'max', + desc: '', + args: [], + ); + } + + /// `ms` + String get ms { + return Intl.message( + 'ms', + name: 'ms', + desc: '', + args: [], + ); + } + + /// `TTL` + String get ttl { + return Intl.message( + 'TTL', + name: 'ttl', + desc: '', + args: [], + ); + } + + /// `Loss` + String get loss { + return Intl.message( + 'Loss', + name: 'loss', + desc: '', + args: [], + ); + } + + /// `No result` + String get noResult { + return Intl.message( + 'No result', + name: 'noResult', + desc: '', + args: [], + ); + } + + /// `Please input a target IP/domain.` + String get pingInputIP { + return Intl.message( + 'Please input a target IP/domain.', + name: 'pingInputIP', + desc: '', + args: [], + ); + } + + /// `Clear` + String get clear { + return Intl.message( + 'Clear', + name: 'clear', + desc: '', + args: [], + ); + } + + /// `Start` + String get start { + return Intl.message( + 'Start', + name: 'start', + desc: '', + args: [], + ); + } + + /// `App primary color` + String get appPrimaryColor { + return Intl.message( + 'App primary color', + name: 'appPrimaryColor', + desc: '', + args: [], + ); + } + + /// `Server status update interval` + String get updateServerStatusInterval { + return Intl.message( + 'Server status update interval', + name: 'updateServerStatusInterval', + desc: '', + args: [], + ); + } + + /// `Will take effect immediately` + String get willTakEeffectImmediately { + return Intl.message( + 'Will take effect immediately', + name: 'willTakEeffectImmediately', + desc: '', + args: [], + ); + } + + /// `Launch page` + String get launchPage { + return Intl.message( + 'Launch page', + name: 'launchPage', + desc: '', + args: [], + ); + } + + /// `Current: v1.0.{build}, is up to date` + String versionUpdated(Object build) { + return Intl.message( + 'Current: v1.0.$build, is up to date', + name: 'versionUpdated', + desc: '', + args: [build], + ); + } + + /// `Current: v1.0.{build}` + String versionUnknownUpdate(Object build) { + return Intl.message( + 'Current: v1.0.$build', + name: 'versionUnknownUpdate', + desc: '', + args: [build], + ); + } + + /// `Found: v1.0.{build}, click to update` + String versionHaveUpdate(Object build) { + return Intl.message( + 'Found: v1.0.$build, click to update', + name: 'versionHaveUpdate', + desc: '', + args: [build], + ); + } + + /// `s` + String get second { + return Intl.message( + 's', + name: 'second', + desc: '', + args: [], + ); + } + + /// `You set to 0, will not update automatically.\nYou can pull to refresh manually.` + String get updateIntervalEqual0 { + return Intl.message( + 'You set to 0, will not update automatically.\nYou can pull to refresh manually.', + name: 'updateIntervalEqual0', + desc: '', + args: [], + ); + } + + /// `Edit` + String get edit { + return Intl.message( + 'Edit', + name: 'edit', + desc: '', + args: [], + ); + } + + /// `No saved private keys.` + String get noSavedPrivateKey { + return Intl.message( + 'No saved private keys.', + name: 'noSavedPrivateKey', + desc: '', + args: [], + ); + } + + /// `Name` + String get name { + return Intl.message( + 'Name', + name: 'name', + desc: '', + args: [], + ); + } + + /// `Password` + String get pwd { + return Intl.message( + 'Password', + name: 'pwd', + desc: '', + args: [], + ); + } + + /// `Save` + String get save { + return Intl.message( + 'Save', + name: 'save', + desc: '', + args: [], + ); + } + + /// `Delete` + String get delete { + return Intl.message( + 'Delete', + name: 'delete', + desc: '', + args: [], + ); + } + + /// `These fields must not be empty.` + String get fieldMustNotEmpty { + return Intl.message( + 'These fields must not be empty.', + name: 'fieldMustNotEmpty', + desc: '', + args: [], + ); + } + + /// `Import and Export` + String get importAndExport { + return Intl.message( + 'Import and Export', + name: 'importAndExport', + desc: '', + args: [], + ); + } + + /// `Choose` + String get choose { + return Intl.message( + 'Choose', + name: 'choose', + desc: '', + args: [], + ); + } + + /// `Import` + String get import { + return Intl.message( + 'Import', + name: 'import', + desc: '', + args: [], + ); + } + + /// `Export` + String get export { + return Intl.message( + 'Export', + name: 'export', + desc: '', + args: [], + ); + } + + /// `OK` + String get ok { + return Intl.message( + 'OK', + name: 'ok', + desc: '', + args: [], + ); + } + + /// `Cancel` + String get cancel { + return Intl.message( + 'Cancel', + name: 'cancel', + desc: '', + args: [], + ); + } + + /// `URL or JSON` + String get urlOrJson { + return Intl.message( + 'URL or JSON', + name: 'urlOrJson', + desc: '', + args: [], + ); + } + + /// `Go` + String get go { + return Intl.message( + 'Go', + name: 'go', + desc: '', + args: [], + ); + } + + /// `request failed, status code: {code}` + String httpFailedWithCode(Object code) { + return Intl.message( + 'request failed, status code: $code', + name: 'httpFailedWithCode', + desc: '', + args: [code], + ); + } + + /// `Run` + String get run { + return Intl.message( + 'Run', + name: 'run', + desc: '', + args: [], + ); + } + + /// `No saved snippets.` + String get noSavedSnippet { + return Intl.message( + 'No saved snippets.', + name: 'noSavedSnippet', + desc: '', + args: [], + ); + } + + /// `Choose destination` + String get chooseDestination { + return Intl.message( + 'Choose destination', + name: 'chooseDestination', + desc: '', + args: [], + ); + } + + /// `No server available.` + String get noServerAvailable { + return Intl.message( + 'No server available.', + name: 'noServerAvailable', + desc: '', + args: [], + ); + } + + /// `Result` + String get result { + return Intl.message( + 'Result', + name: 'result', + desc: '', + args: [], + ); + } + + /// `Close` + String get close { + return Intl.message( + 'Close', + name: 'close', + desc: '', + args: [], + ); + } + + /// `Attention` + String get attention { + return Intl.message( + 'Attention', + name: 'attention', + desc: '', + args: [], + ); + } + + /// `Are you sure to delete server [{server}]?` + String sureToDeleteServer(Object server) { + return Intl.message( + 'Are you sure to delete server [$server]?', + name: 'sureToDeleteServer', + desc: '', + args: [server], + ); + } + + /// `Host` + String get host { + return Intl.message( + 'Host', + name: 'host', + desc: '', + args: [], + ); + } + + /// `Port` + String get port { + return Intl.message( + 'Port', + name: 'port', + desc: '', + args: [], + ); + } + + /// `User` + String get user { + return Intl.message( + 'User', + name: 'user', + desc: '', + args: [], + ); + } + + /// `Key Auth` + String get keyAuth { + return Intl.message( + 'Key Auth', + name: 'keyAuth', + desc: '', + args: [], + ); + } + + /// `Add private key` + String get addPrivateKey { + return Intl.message( + 'Add private key', + name: 'addPrivateKey', + desc: '', + args: [], + ); + } + + /// `Choose private key` + String get choosePrivateKey { + return Intl.message( + 'Choose private key', + name: 'choosePrivateKey', + desc: '', + args: [], + ); + } + + /// `Please enter host.` + String get plzEnterHost { + return Intl.message( + 'Please enter host.', + name: 'plzEnterHost', + desc: '', + args: [], + ); + } + + /// `Please enter password.` + String get plzEnterPwd { + return Intl.message( + 'Please enter password.', + name: 'plzEnterPwd', + desc: '', + args: [], + ); + } + + /// `Please select a key.` + String get plzSelectKey { + return Intl.message( + 'Please select a key.', + name: 'plzSelectKey', + desc: '', + args: [], + ); + } + + /// `Example name` + String get exampleName { + return Intl.message( + 'Example name', + name: 'exampleName', + desc: '', + args: [], + ); + } + + /// `Stop` + String get stop { + return Intl.message( + 'Stop', + name: 'stop', + desc: '', + args: [], + ); + } +} + +class AppLocalizationDelegate extends LocalizationsDelegate { + const AppLocalizationDelegate(); + + List get supportedLocales { + return const [ + Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'zh'), + ]; + } + + @override + bool isSupported(Locale locale) => _isSupported(locale); + @override + Future load(Locale locale) => S.load(locale); + @override + bool shouldReload(AppLocalizationDelegate old) => false; + + bool _isSupported(Locale locale) { + for (var supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale.languageCode) { + return true; + } + } + return false; + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 00000000..eba0f1af --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,80 @@ +{ + "server": "Server", + "convert": "Convert", + "ping": "Ping", + "debug": "Debug", + "addAServer": "add a server", + "setting": "Setting", + "license": "License", + "snippet": "Snippet", + "privateKey": "Private Key", + "madeWithLove": "\nMade with ❤️ by {myGithub}", + "aboutThanks": "\nThanks {rainSunMeGithub} for participating in the test.\n\nAll rights reserved.", + "serverTabEmpty": "There is no server.\nClick the fab to add one.", + "serverTabLoading": "Loading...", + "serverTabPlzSave": "Please 'save' this private key again.", + "serverTabFailed": "Failed", + "serverTabUnkown": "Unknown state", + "serverTabConnecting": "Connecting...", + "decode": "Decode", + "encode": "Encode", + "currentMode": "Current Mode", + "unkownConvertMode": "Unknown convert mode", + "copy": "Copy", + "upsideDown": "Upside Down", + "pingAvg": "Avg:", + "unknown": "unknown", + "min": "min", + "max": "max", + "ms": "ms", + "ttl": "TTL", + "loss": "Loss", + "noResult": "No result", + "pingInputIP": "Please input a target IP/domain.", + "clear": "Clear", + "start": "Start", + "appPrimaryColor": "App primary color", + "updateServerStatusInterval": "Server status update interval", + "willTakEeffectImmediately": "Will take effect immediately", + "launchPage": "Launch page", + "versionUpdated": "Current: v1.0.{build}, is up to date", + "versionUnknownUpdate": "Current: v1.0.{build}", + "versionHaveUpdate": "Found: v1.0.{build}, click to update", + "second": "s", + "updateIntervalEqual0": "You set to 0, will not update automatically.\nYou can pull to refresh manually.", + "edit": "Edit", + "noSavedPrivateKey": "No saved private keys.", + "name": "Name", + "pwd": "Password", + "save": "Save", + "delete": "Delete", + "fieldMustNotEmpty": "These fields must not be empty.", + "importAndExport": "Import and Export", + "choose": "Choose", + "import": "Import", + "export": "Export", + "ok": "OK", + "cancel": "Cancel", + "urlOrJson": "URL or JSON", + "go": "Go", + "httpFailedWithCode": "request failed, status code: {code}", + "run": "Run", + "noSavedSnippet": "No saved snippets.", + "chooseDestination": "Choose destination", + "noServerAvailable": "No server available.", + "result": "Result", + "close": "Close", + "attention": "Attention", + "sureToDeleteServer": "Are you sure to delete server [{server}]?", + "host": "Host", + "port": "Port", + "user": "User", + "keyAuth": "Key Auth", + "addPrivateKey": "Add private key", + "choosePrivateKey": "Choose private key", + "plzEnterHost": "Please enter host.", + "plzEnterPwd": "Please enter password.", + "plzSelectKey": "Please select a key.", + "exampleName": "Example name", + "stop": "Stop" +} \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb new file mode 100644 index 00000000..496fdf0f --- /dev/null +++ b/lib/l10n/intl_zh.arb @@ -0,0 +1,80 @@ +{ + "server": "服务器", + "convert": "转换", + "ping": "Ping", + "debug": "调试", + "addAServer": "添加服务器", + "setting": "设置", + "license": "开源证书", + "snippet": "代码片段", + "privateKey": "私钥", + "madeWithLove": "\n用❤️制作 by {myGithub}", + "aboutThanks": "\n感谢 {rainSunMeGithub} 参与软件测试。\n\n保留所有权利。", + "serverTabEmpty": "现在没有服务器。\n点击右下方按钮来添加。", + "serverTabLoading": "加载中...", + "serverTabPlzSave": "请再次保存该私钥", + "serverTabFailed": "失败", + "serverTabUnkown": "未知状态", + "serverTabConnecting": "连接中...", + "decode": "解码", + "encode": "编码", + "currentMode": "当前模式", + "unkownConvertMode": "未知转换模式", + "copy": "复制到剪切板", + "upsideDown": "上下交换", + "pingAvg": "平均:", + "unknown": "未知", + "min": "最小", + "max": "最大", + "ms": "毫秒", + "ttl": "缓存时间", + "loss": "丢包率", + "noResult": "无结果", + "pingInputIP": "请输入目标IP或域名", + "clear": "清除", + "start": "开始", + "appPrimaryColor": "App主要色", + "updateServerStatusInterval": "服务器状态刷新间隔", + "willTakEeffectImmediately": "更改将会立即生效", + "launchPage": "启动页", + "versionUpdated": "当前:v1.0.{build}, 已是最新版本", + "versionUnknownUpdate": "当前:v1.0.{build}", + "versionHaveUpdate": "找到新版本:v1.0.{build}, 点击更新", + "second": "秒", + "updateIntervalEqual0": "你设置为0,服务器状态不会自动刷新。\n你可以手动下拉刷新。", + "edit": "编辑", + "noSavedPrivateKey": "没有已保存的私钥。", + "name": "名称", + "pwd": "密码", + "save": "保存", + "delete": "删除", + "fieldMustNotEmpty": "这些输入框不能为空。", + "importAndExport": "导入或导出", + "choose": "选择", + "import": "导入", + "export": "导出", + "ok": "好", + "cancel": "取消", + "urlOrJson": "链接或JSON", + "go": "开始", + "httpFailedWithCode": "请求失败, 状态码: {code}", + "run": "运行", + "noSavedSnippet": "没有已保存的代码片段。", + "chooseDestination": "选择目标", + "noServerAvailable": "没有可用的服务器。", + "result": "结果", + "close": "关闭", + "attention": "注意", + "sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?", + "host": "主机", + "port": "端口", + "user": "用户", + "keyAuth": "公钥认证", + "addPrivateKey": "添加一个私钥", + "choosePrivateKey": "选择私钥", + "plzEnterHost": "请输入主机", + "plzEnterPwd": "请输入密码", + "plzSelectKey": "请选择私钥", + "exampleName": "名称示例", + "stop": "停止" +} \ No newline at end of file diff --git a/lib/view/page/convert.dart b/lib/view/page/convert.dart index 36537712..85652bf6 100644 --- a/lib/view/page/convert.dart +++ b/lib/view/page/convert.dart @@ -4,6 +4,7 @@ import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; @@ -20,13 +21,8 @@ class _ConvertPageState extends State late TextEditingController _textEditingControllerResult; late MediaQueryData _media; late ThemeData _theme; + late S s; - static const List _typeOption = [ - 'base64 decode', - 'base64 encode', - 'URL encode', - 'URL decode' - ]; int _typeOptionIndex = 0; @override @@ -41,6 +37,7 @@ class _ConvertPageState extends State super.didChangeDependencies(); _media = MediaQuery.of(context); _theme = Theme.of(context); + s = S.of(context); } @override @@ -67,7 +64,7 @@ class _ConvertPageState extends State showSnackBar(context, Text('Error: \n$e')); } }, - tooltip: 'convert', + tooltip: s.convert, child: const Icon(Icons.send), ), ); @@ -85,7 +82,7 @@ class _ConvertPageState extends State case 3: return Uri.decodeFull(text); default: - return 'Unknown Convert Method'; + return s.unkownConvertMode; } } @@ -97,6 +94,14 @@ class _ConvertPageState extends State } Widget _buildTypeOption() { + final decode = s.decode; + final encode = s.encode; + final List _typeOption = [ + 'Base64 $decode', + 'Base64 $encode', + 'URL $encode', + 'URL $decode' + ]; return RoundRectCard( ExpansionTile( tilePadding: const EdgeInsets.only(left: 7, right: 27), @@ -106,7 +111,7 @@ class _ConvertPageState extends State TextButton( style: ButtonStyle( foregroundColor: MaterialStateProperty.all(primaryColor)), - child: const Icon(Icons.change_circle), + child: Icon(Icons.change_circle, semanticLabel: s.upsideDown), onPressed: () { final temp = _textEditingController.text; _textEditingController.text = _textEditingControllerResult.text; @@ -116,7 +121,7 @@ class _ConvertPageState extends State TextButton( style: ButtonStyle( foregroundColor: MaterialStateProperty.all(primaryColor)), - child: const Icon(Icons.copy), + child: Icon(Icons.copy, semanticLabel: s.copy), onPressed: () => FlutterClipboard.copy( _textEditingControllerResult.text == '' ? ' ' @@ -137,11 +142,11 @@ class _ConvertPageState extends State fontSize: 16.0, fontWeight: FontWeight.w500, color: primaryColor)), - const Text( - 'Current Mode', + Text( + s.currentMode, textScaleFactor: 1.0, textAlign: TextAlign.right, - style: TextStyle(fontSize: 9.0, color: Colors.grey), + style: const TextStyle(fontSize: 9.0, color: Colors.grey), ) ], ), diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index 30d8669f..211e1b94 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -11,10 +11,12 @@ import 'package:toolbox/data/model/app/navigation_item.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/build_data.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/data/res/icon/common.dart'; import 'package:toolbox/data/res/tab.dart'; import 'package:toolbox/data/res/url.dart'; import 'package:toolbox/data/store/setting.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/convert.dart'; import 'package:toolbox/view/page/debug.dart'; @@ -44,6 +46,7 @@ class _MyHomePageState extends State late final AdvancedDrawerController _advancedDrawerController; late int _selectIndex; late double _width; + late S s; @override void initState() { @@ -58,6 +61,7 @@ class _MyHomePageState extends State @override void didChangeDependencies() { super.didChangeDependencies(); + s = S.of(context); _width = MediaQuery.of(context).size.width; } @@ -115,11 +119,11 @@ class _MyHomePageState extends State drawer: _buildDrawer(), child: Scaffold( appBar: AppBar( - title: Text(tabItems[_selectIndex].title), + title: Text(tabTitleName(context, _selectIndex), style: size18), actions: [ IconButton( icon: const Icon(Icons.developer_mode, size: 23), - tooltip: 'Debug', + tooltip: s.debug, onPressed: () => AppRoute(const DebugPage(), 'Debug Page').go(context), ), @@ -171,7 +175,7 @@ class _MyHomePageState extends State borderRadius: const BorderRadius.all(Radius.circular(50))), child: IconButton( icon: Icon(item.icon), - tooltip: item.title, + tooltip: tabTitleName(context, idx), splashRadius: width / 3.3, padding: const EdgeInsets.only(left: 17, right: 17), onPressed: () { @@ -218,36 +222,35 @@ class _MyHomePageState extends State children: [ ListTile( leading: const Icon(Icons.settings), - title: const Text('Setting'), + title: Text(s.setting), onTap: () => AppRoute(const SettingPage(), 'Setting').go(context), ), ListTile( leading: const Icon(Icons.vpn_key), - title: const Text('Private Key'), + title: Text(s.privateKey), onTap: () => AppRoute( const StoredPrivateKeysPage(), 'private key list') .go(context), ), ListTile( leading: const Icon(Icons.snippet_folder), - title: const Text('Snippet'), + title: Text(s.snippet), onTap: () => AppRoute(const SnippetListPage(), 'snippet list') .go(context), ), AboutListTile( icon: const Icon(Icons.text_snippet), - child: const Text('Licences'), + child: Text(s.license), applicationName: BuildData.name, applicationVersion: _buildVersionStr(), applicationIcon: _buildIcon(), - aboutBoxChildren: const [ + aboutBoxChildren: [ UrlText( - text: '\nMade with ❤️ by $myGithub', + text: s.madeWithLove(myGithub), replace: 'LollipopKit'), UrlText( - text: - '\nThanks $rainSunMeGithub for participating in the test.\n\nAll rights reserved.', + text: s.aboutThanks(rainSunMeGithub), replace: 'RainSunMe', ), ], diff --git a/lib/view/page/ping.dart b/lib/view/page/ping.dart index 961d277c..7dbc7659 100644 --- a/lib/view/page/ping.dart +++ b/lib/view/page/ping.dart @@ -4,6 +4,7 @@ import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/model/server/ping_result.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; @@ -20,6 +21,10 @@ class _PingPageState extends State late TextEditingController _textEditingController; late MediaQueryData _media; final List _results = []; + late S s; + static const summaryTextStyle = TextStyle( + fontSize: 12, + ); @override void initState() { @@ -31,6 +36,7 @@ class _PingPageState extends State void didChangeDependencies() { super.didChangeDependencies(); _media = MediaQuery.of(context); + s = S.of(context); } @override @@ -62,37 +68,39 @@ class _PingPageState extends State } Widget _buildResultItem(PingResult result) { + final unknown = s.unknown; + final ms = s.ms; return RoundRectCard(ListTile( contentPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17), title: Text(result.serverName, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: primaryColor)), - subtitle: Text(_buildPingSummary(result)), + subtitle: Text(_buildPingSummary(result, unknown, ms), style: summaryTextStyle,), trailing: Text( - 'Avg: ' + - (result.statistic?.avg?.toStringAsFixed(2) ?? 'unkown') + - ' ms', + s.pingAvg + + (result.statistic?.avg?.toStringAsFixed(2) ?? s.unknown) + + ' $ms', style: TextStyle(fontSize: 14, color: primaryColor)), )); } - String _buildPingSummary(PingResult result) { - final ip = result.ip ?? 'unkown'; + String _buildPingSummary(PingResult result, String unknown, String ms) { + final ip = result.ip ?? unknown; if (result.results == null || result.results!.isEmpty) { - return '$ip - no results'; + return '$ip - ${s.noResult}'; } - final ttl = result.results?.first.ttl ?? 'unkown'; - final loss = result.statistic?.loss ?? 'unkown'; - final min = result.statistic?.min ?? 'unkown'; - final max = result.statistic?.max ?? 'unkown'; - return '$ip\nttl: $ttl, loss: $loss%\nmin: $min ms, max: $max ms'; + final ttl = result.results?.first.ttl ?? unknown; + final loss = result.statistic?.loss ?? unknown; + final min = result.statistic?.min ?? unknown; + final max = result.statistic?.max ?? unknown; + return '$ip\n${s.ttl}: $ttl, ${s.loss}: $loss%\n${s.min}: $min $ms, ${s.max}: $max $ms'; } Future doPing() async { _results.clear(); final target = _textEditingController.text.trim(); if (target.isEmpty) { - showSnackBar(context, const Text('Please input a target')); + showSnackBar(context, Text(s.pingInputIP)); return; } @@ -119,12 +127,12 @@ class _PingPageState extends State style: ButtonStyle( foregroundColor: MaterialStateProperty.all(primaryColor)), child: Row( - children: const [ - Icon(Icons.delete), - SizedBox( + children: [ + const Icon(Icons.delete), + const SizedBox( width: 7, ), - Text('Clear') + Text(s.clear) ], ), onPressed: () { @@ -136,12 +144,12 @@ class _PingPageState extends State style: ButtonStyle( foregroundColor: MaterialStateProperty.all(primaryColor)), child: Row( - children: const [ - Icon(Icons.play_arrow), - SizedBox( + children: [ + const Icon(Icons.play_arrow), + const SizedBox( width: 7, ), - Text('Start') + Text(s.start) ], ), onPressed: () { diff --git a/lib/view/page/private_key/edit.dart b/lib/view/page/private_key/edit.dart index 17044830..e410f5a1 100644 --- a/lib/view/page/private_key/edit.dart +++ b/lib/view/page/private_key/edit.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:toolbox/data/provider/private_key.dart'; +import 'package:toolbox/data/res/font_style.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/widget/input_decoration.dart'; @@ -25,6 +27,7 @@ class _PrivateKeyEditPageState extends State late PrivateKeyProvider _provider; late Widget loading; + late S s; @override void initState() { @@ -33,12 +36,19 @@ class _PrivateKeyEditPageState extends State loading = const SizedBox(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + s = S.of(context); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Edit'), actions: [ + appBar: AppBar(title: Text(s.edit, style: size18), actions: [ widget.info != null ? IconButton( + tooltip: s.delete, onPressed: () { _provider.delInfo(widget.info!); Navigator.of(context).pop(); @@ -52,7 +62,7 @@ class _PrivateKeyEditPageState extends State TextField( controller: nameController, keyboardType: TextInputType.text, - decoration: buildDecoration('Name', icon: Icons.info), + decoration: buildDecoration(s.name, icon: Icons.info), ), TextField( controller: keyController, @@ -61,14 +71,14 @@ class _PrivateKeyEditPageState extends State maxLines: 10, keyboardType: TextInputType.text, enableSuggestions: false, - decoration: buildDecoration('Private Key', icon: Icons.vpn_key), + decoration: buildDecoration(s.privateKey, icon: Icons.vpn_key), ), TextField( controller: pwdController, autocorrect: false, keyboardType: TextInputType.text, obscureText: true, - decoration: buildDecoration('Password', icon: Icons.password), + decoration: buildDecoration(s.pwd, icon: Icons.password), ), SizedBox(height: MediaQuery.of(context).size.height * 0.1), loading @@ -76,13 +86,14 @@ class _PrivateKeyEditPageState extends State ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.save), + tooltip: s.save, onPressed: () async { final name = nameController.text; final key = keyController.text; final pwd = pwdController.text; if (name.isEmpty || key.isEmpty) { showSnackBar( - context, const Text('Name and Key must not be empty.')); + context, Text(s.fieldMustNotEmpty)); return; } FocusScope.of(context).unfocus(); diff --git a/lib/view/page/private_key/list.dart b/lib/view/page/private_key/list.dart index 943902fe..4e6b635a 100644 --- a/lib/view/page/private_key/list.dart +++ b/lib/view/page/private_key/list.dart @@ -3,7 +3,9 @@ import 'package:provider/provider.dart'; import 'package:toolbox/core/route.dart'; import 'package:toolbox/data/provider/private_key.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/data/res/padding.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/view/page/private_key/edit.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; @@ -16,11 +18,19 @@ class StoredPrivateKeysPage extends StatefulWidget { class _PrivateKeyListState extends State { final _textStyle = TextStyle(color: primaryColor); + late S s; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + s = S.of(context); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Private Keys'), + title: Text(s.privateKey, style: size18), ), body: Consumer( builder: (_, key, __) { @@ -46,14 +56,14 @@ class _PrivateKeyListState extends State { 'private key edit page') .go(context), child: Text( - 'Edit', + s.edit, style: _textStyle, )) ], ), )); }) - : const Center(child: Text('No saved private keys.')); + : Center(child: Text(s.noSavedPrivateKey)); }, ), floatingActionButton: FloatingActionButton( diff --git a/lib/view/page/server/detail.dart b/lib/view/page/server/detail.dart index 99362c32..b5476e1e 100644 --- a/lib/view/page/server/detail.dart +++ b/lib/view/page/server/detail.dart @@ -6,6 +6,7 @@ import 'package:toolbox/data/model/server/server.dart'; import 'package:toolbox/data/model/server/server_status.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/data/res/icon/linux_icons.dart'; import 'package:toolbox/data/res/padding.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; @@ -43,7 +44,7 @@ class _ServerDetailPageState extends State Widget _buildMainPage(ServerInfo si) { return Scaffold( appBar: AppBar( - title: Text(si.info.name), + title: Text(si.info.name, style: size18), ), body: ListView( padding: const EdgeInsets.all(13), diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index 4162169c..a8e7521a 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -8,7 +8,9 @@ import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:toolbox/data/provider/private_key.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/data/store/private_key.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/private_key/edit.dart'; import 'package:toolbox/view/widget/input_decoration.dart'; @@ -32,6 +34,8 @@ class _ServerEditPageState extends State with AfterLayoutMixin { late ServerProvider _serverProvider; + late S s; + bool usePublicKey = false; int _pubKeyIndex = -1; @@ -43,10 +47,16 @@ class _ServerEditPageState extends State with AfterLayoutMixin { _serverProvider = locator(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + s = S.of(context); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Edit'), actions: [ + appBar: AppBar(title: Text(s.edit, style: size18), actions: [ widget.spi != null ? IconButton( onPressed: () { @@ -54,7 +64,7 @@ class _ServerEditPageState extends State with AfterLayoutMixin { context, 'Attention', Text( - 'Are you sure to delete server [${widget.spi!.name}]'), + s.sureToDeleteServer(widget.spi!.name)), [ TextButton( onPressed: () { @@ -62,13 +72,13 @@ class _ServerEditPageState extends State with AfterLayoutMixin { Navigator.of(context).pop(); Navigator.of(context).pop(); }, - child: const Text( - 'Yes', - style: TextStyle(color: Colors.red), + child: Text( + s.ok, + style: const TextStyle(color: Colors.red), )), TextButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('No')) + child: Text(s.cancel)) ]); }, icon: const Icon(Icons.delete)) @@ -84,20 +94,20 @@ class _ServerEditPageState extends State with AfterLayoutMixin { controller: nameController, keyboardType: TextInputType.text, decoration: - buildDecoration('Name', icon: Icons.info, hint: 'Example'), + buildDecoration(s.name, icon: Icons.info, hint: s.exampleName), ), TextField( controller: ipController, keyboardType: TextInputType.text, autocorrect: false, enableSuggestions: false, - decoration: buildDecoration('Host', + decoration: buildDecoration(s.host, icon: Icons.storage, hint: 'example.com'), ), TextField( controller: portController, keyboardType: TextInputType.number, - decoration: buildDecoration('Port', + decoration: buildDecoration(s.port, icon: Icons.format_list_numbered, hint: '22'), ), TextField( @@ -105,13 +115,13 @@ class _ServerEditPageState extends State with AfterLayoutMixin { keyboardType: TextInputType.text, autocorrect: false, enableSuggestions: false, - decoration: buildDecoration('User', + decoration: buildDecoration(s.user, icon: Icons.account_box, hint: 'root'), ), const SizedBox(height: 7), Row( children: [ - const Text('Key Auth'), + Text(s.keyAuth), Switch( value: usePublicKey, onChanged: (val) => setState(() => usePublicKey = val)), @@ -122,8 +132,8 @@ class _ServerEditPageState extends State with AfterLayoutMixin { controller: passwordController, obscureText: true, keyboardType: TextInputType.text, - decoration: buildDecoration('Pwd', - icon: Icons.password, hint: 'Password'), + decoration: buildDecoration(s.pwd, + icon: Icons.password, hint: s.pwd), onSubmitted: (_) => {}, ) : const SizedBox(), @@ -143,7 +153,7 @@ class _ServerEditPageState extends State with AfterLayoutMixin { ) .toList(); tiles.add(ListTile( - title: const Text('Add a Private Key'), + title: Text(s.addPrivateKey), contentPadding: EdgeInsets.zero, trailing: IconButton( icon: const Icon(Icons.add), @@ -157,9 +167,9 @@ class _ServerEditPageState extends State with AfterLayoutMixin { iconColor: primaryColor, tilePadding: EdgeInsets.zero, childrenPadding: EdgeInsets.zero, - title: const Text( - 'Choose Key', - style: TextStyle(fontSize: 14), + title: Text( + s.choosePrivateKey, + style: const TextStyle(fontSize: 14), ), children: tiles, ); @@ -172,15 +182,15 @@ class _ServerEditPageState extends State with AfterLayoutMixin { child: const Icon(Icons.send), onPressed: () { if (ipController.text == '') { - showSnackBar(context, const Text('Please enter host.')); + showSnackBar(context, Text(s.plzEnterHost)); return; } if (!usePublicKey && passwordController.text == '') { - showSnackBar(context, const Text('Please enter password.')); + showSnackBar(context, Text(s.plzEnterPwd)); return; } if (usePublicKey && _pubKeyIndex == -1) { - showSnackBar(context, const Text('Please select a private key.')); + showSnackBar(context, Text(s.plzSelectKey)); return; } if (usernameController.text == '') { diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 69e6ae05..f506ac42 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -15,6 +15,7 @@ import 'package:toolbox/data/model/server/server_status.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/color.dart'; import 'package:toolbox/data/store/setting.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/apt.dart'; import 'package:toolbox/view/page/docker.dart'; @@ -39,6 +40,7 @@ class _ServerPageState extends State late RefreshController _refreshController; late ServerProvider _serverProvider; + late S s; @override void initState() { @@ -53,6 +55,7 @@ class _ServerPageState extends State _media = MediaQuery.of(context); _theme = Theme.of(context); _primaryColor = primaryColor; + s = S.of(context); } @override @@ -62,9 +65,9 @@ class _ServerPageState extends State locator().serverStatusUpdateInterval.fetch() != 0; final child = Consumer(builder: (_, pro, __) { if (pro.servers.isEmpty) { - return const Center( + return Center( child: Text( - 'There is no server.\nClick the fab to add one.', + s.serverTabEmpty, textAlign: TextAlign.center, ), ); @@ -99,7 +102,7 @@ class _ServerPageState extends State onPressed: () => AppRoute(const ServerEditPage(), 'Add server info page') .go(context), - tooltip: 'add a server', + tooltip: s.addAServer, heroTag: 'server page fab', child: const Icon(Icons.add), ), @@ -302,7 +305,7 @@ class _ServerPageState extends State case ServerConnectionState.connected: if (temp == '') { if (upTime == '') { - return 'Loading...'; + return s.serverTabLoading; } else { return upTime; } @@ -314,17 +317,17 @@ class _ServerPageState extends State } } case ServerConnectionState.connecting: - return 'Connecting...'; + return s.serverTabConnecting; case ServerConnectionState.failed: if (failedInfo == null) { - return 'Failed'; + return s.serverTabFailed; } if (failedInfo.contains('encypted')) { - return 'Please "save" this private key again.'; + return s.serverTabPlzSave; } return failedInfo; default: - return 'Unknown State'; + return s.serverTabUnkown; } } diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index b2410d70..241b1721 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -2,13 +2,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_material_color_picker/flutter_material_color_picker.dart'; import 'package:provider/provider.dart'; import 'package:toolbox/core/update.dart'; +import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/provider/app.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/build_data.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/data/res/padding.dart'; import 'package:toolbox/data/res/tab.dart'; import 'package:toolbox/data/store/setting.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; @@ -29,6 +32,7 @@ class _SettingPageState extends State { late final ServerProvider _serverProvider; late MediaQueryData _media; late ThemeData _theme; + late S s; @override void didChangeDependencies() { @@ -36,6 +40,7 @@ class _SettingPageState extends State { priColor = primaryColor; _media = MediaQuery.of(context); _theme = Theme.of(context); + s = S.of(context); } @override @@ -51,7 +56,7 @@ class _SettingPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Setting'), + title: Text(s.setting, style: size18), ), body: ListView( padding: const EdgeInsets.all(17), @@ -70,12 +75,12 @@ class _SettingPageState extends State { String display; if (app.newestBuild != null) { if (app.newestBuild! > BuildData.build) { - display = 'Found: v1.0.${app.newestBuild}, click to update'; + display = s.versionHaveUpdate(app.newestBuild!); } else { - display = 'Current: v1.0.${BuildData.build},is up to date'; + display = s.versionUpdated(BuildData.build); } } else { - display = 'Current: v1.0.${BuildData.build}'; + display = s.versionUnknownUpdate(BuildData.build); } return ListTile( contentPadding: roundRectCardPadding, @@ -94,16 +99,16 @@ class _SettingPageState extends State { tilePadding: roundRectCardPadding, childrenPadding: roundRectCardPadding, textColor: priColor, - title: const Text( - 'Server status update interval', + title: Text( + s.updateServerStatusInterval, style: textStyle, textAlign: TextAlign.start, ), - subtitle: const Text( - 'Will take effect immediately.', - style: TextStyle(color: Colors.grey), + subtitle: Text( + s.willTakEeffectImmediately, + style: const TextStyle(color: Colors.grey, fontSize: 13), ), - trailing: Text('${_intervalValue.toInt()} s'), + trailing: Text('${_intervalValue.toInt()} ${s.second}'), children: [ Slider( thumbColor: priColor, @@ -120,16 +125,16 @@ class _SettingPageState extends State { _store.serverStatusUpdateInterval.put(val.toInt()); _serverProvider.startAutoRefresh(); }, - label: '${_intervalValue.toInt()} seconds', + label: '${_intervalValue.toInt()} ${s.second}', divisions: 10, ), const SizedBox( height: 3, ), _intervalValue == 0.0 - ? const Text( - 'You set to 0, will not update automatically.\nYou can pull to refresh manually.', - style: TextStyle(color: Colors.grey), + ? Text( + s.updateIntervalEqual0, + style: const TextStyle(color: Colors.grey, fontSize: 12), textAlign: TextAlign.center, ) : const SizedBox(), @@ -156,8 +161,8 @@ class _SettingPageState extends State { width: 27, ), ), - title: const Text( - 'App primary color', + title: Text( + s.appPrimaryColor, style: textStyle, )); } @@ -186,14 +191,14 @@ class _SettingPageState extends State { textColor: priColor, tilePadding: roundRectCardPadding, childrenPadding: roundRectCardPadding, - title: const Text( - 'Launch page', + title: Text( + s.launchPage, style: textStyle, ), trailing: ConstrainedBox( constraints: BoxConstraints(maxWidth: _media.size.width * 0.35), child: Text( - tabs[_launchPageIdx], + tabTitleName(context, _launchPageIdx), style: textStyle, textAlign: TextAlign.right, ), @@ -202,7 +207,7 @@ class _SettingPageState extends State { .map((e) => ListTile( contentPadding: EdgeInsets.zero, title: Text( - e, + tabTitleName(context, tabs.indexOf(e)), style: TextStyle( fontSize: 14, color: _theme.textTheme.bodyText2!.color!.withAlpha(177)), diff --git a/lib/view/page/snippet/edit.dart b/lib/view/page/snippet/edit.dart index 5270214a..f7edcb71 100644 --- a/lib/view/page/snippet/edit.dart +++ b/lib/view/page/snippet/edit.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:toolbox/core/utils.dart'; import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/provider/snippet.dart'; +import 'package:toolbox/data/res/font_style.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/widget/input_decoration.dart'; @@ -21,23 +23,31 @@ class _SnippetEditPageState extends State final scriptController = TextEditingController(); late SnippetProvider _provider; + late S s; @override void initState() { super.initState(); _provider = locator(); } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + s = S.of(context); + } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Edit'), actions: [ + appBar: AppBar(title: Text(s.edit, style: size18), actions: [ widget.snippet != null ? IconButton( onPressed: () { _provider.del(widget.snippet!); Navigator.of(context).pop(); }, + tooltip: s.delete, icon: const Icon(Icons.delete)) : const SizedBox() ]), @@ -47,7 +57,7 @@ class _SnippetEditPageState extends State TextField( controller: nameController, keyboardType: TextInputType.text, - decoration: buildDecoration('Name', icon: Icons.info), + decoration: buildDecoration(s.name, icon: Icons.info), ), TextField( controller: scriptController, @@ -56,7 +66,7 @@ class _SnippetEditPageState extends State maxLines: 10, keyboardType: TextInputType.text, enableSuggestions: false, - decoration: buildDecoration('Snippet', icon: Icons.code), + decoration: buildDecoration(s.snippet, icon: Icons.code), ), ], ), @@ -66,7 +76,7 @@ class _SnippetEditPageState extends State final name = nameController.text; final script = scriptController.text; if (name.isEmpty || script.isEmpty) { - showSnackBar(context, const Text('Two fields must not be empty.')); + showSnackBar(context, Text(s.fieldMustNotEmpty)); return; } final snippet = Snippet(name, script); diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index 784668db..9be6d2fd 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -8,7 +8,9 @@ import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/provider/snippet.dart'; import 'package:toolbox/data/res/color.dart'; +import 'package:toolbox/data/res/font_style.dart'; import 'package:toolbox/data/res/padding.dart'; +import 'package:toolbox/generated/l10n.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/page/snippet/edit.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; @@ -28,14 +30,24 @@ class _SnippetListPageState extends State { final _exportFieldController = TextEditingController(); final _textStyle = TextStyle(color: primaryColor); + + late S s; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + s = S.of(context); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Snippet List'), + title: Text(s.snippet, style: size18), actions: [ IconButton( onPressed: () => _showImportExport(), + tooltip: s.importAndExport, icon: const Icon(Icons.import_export)), ], ), @@ -51,17 +63,17 @@ class _SnippetListPageState extends State { Future _showImportExport() async { await showRoundDialog( context, - 'Choose', + s.choose, Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( - title: const Text('Import'), + title: Text(s.import), leading: const Icon(Icons.download), onTap: () => _showImportDialog(), ), ListTile( - title: const Text('Export'), + title: Text(s.export), leading: const Icon(Icons.file_upload), onTap: () => _showExportDialog(), ), @@ -75,7 +87,7 @@ class _SnippetListPageState extends State { _exportFieldController.text = locator().export; await showRoundDialog( context, - 'Export', + s.export, TextField( decoration: const InputDecoration( labelText: 'JSON', @@ -85,7 +97,7 @@ class _SnippetListPageState extends State { ), [ TextButton( - child: const Text('OK'), + child: Text(s.ok), onPressed: () => Navigator.pop(context), ), ]); @@ -95,10 +107,10 @@ class _SnippetListPageState extends State { Navigator.of(context).pop(); await showRoundDialog( context, - 'Import', + s.import, TextField( - decoration: const InputDecoration( - labelText: 'Url or JSON', + decoration: InputDecoration( + labelText: s.urlOrJson, ), maxLines: 2, controller: _importFieldController, @@ -106,18 +118,18 @@ class _SnippetListPageState extends State { [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel')), + child: Text(s.cancel)), TextButton( onPressed: () async => await _import(_importFieldController.text.trim()), - child: const Text('GO'), + child: Text('GO'), ) ]); } Future _import(String text) async { if (text.isEmpty) { - showSnackBar(context, const Text('field can not be empty')); + showSnackBar(context, Text(s.fieldMustNotEmpty)); return; } final snippetProvider = locator(); @@ -125,7 +137,7 @@ class _SnippetListPageState extends State { final resp = await Dio().get(text); if (resp.statusCode != 200) { showSnackBar( - context, Text('request failed, status code: ${resp.statusCode}')); + context, Text(s.httpFailedWithCode(resp.statusCode ?? '-1'))); return; } for (final snippet in getSnippetList(resp.data)) { @@ -166,7 +178,7 @@ class _SnippetListPageState extends State { 'snippet edit page') .go(context), child: Text( - 'Edit', + s.edit, style: _textStyle, )), TextButton( @@ -179,7 +191,7 @@ class _SnippetListPageState extends State { run(context, snippet); }, child: Text( - 'Run', + s.run, style: _textStyle, )) ]) @@ -187,16 +199,16 @@ class _SnippetListPageState extends State { ), )); }) - : const Center(child: Text('No saved snippets.')); + : Center(child: Text(s.noSavedSnippet)); }, ); } void _showRunDialog(Snippet snippet) { - showRoundDialog(context, 'Choose destination', + showRoundDialog(context, s.chooseDestination, Consumer(builder: (_, provider, __) { if (provider.servers.isEmpty) { - return const Text('No server available'); + return Text(s.noServerAvailable); } _selectedIndex = provider.servers.first.info; return SizedBox( @@ -235,10 +247,10 @@ class _SnippetListPageState extends State { }), [ TextButton( onPressed: () async => run(context, snippet), - child: const Text('Run')), + child: Text(s.run)), TextButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel')), + child: Text(s.cancel)), ]); } @@ -246,11 +258,11 @@ class _SnippetListPageState extends State { final result = await locator() .runSnippet(widget.spi ?? _selectedIndex, snippet); if (result != null) { - showRoundDialog(context, 'Result', + showRoundDialog(context, s.result, Text(result, style: const TextStyle(fontSize: 13)), [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('Close')) + child: Text(s.close)) ]); } } diff --git a/pubspec.lock b/pubspec.lock index 5f2447d0..7091b3d1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -199,6 +199,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_material_color_picker: dependency: "direct main" description: @@ -279,6 +284,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.3" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f1a40a86..c0ee6348 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,8 @@ environment: dependencies: flutter: sdk: flutter - + flutter_localizations: + sdk: flutter provider: ^6.0.0 get_it: ^7.2.0 hive: ^2.0.0 @@ -39,14 +40,14 @@ dependencies: extended_image: ^6.0.3 url_launcher: ^6.0.9 countly_flutter: - git: + git: url: https://github.com/Countly/countly-sdk-flutter-bridge.git ref: master dartssh2: ^2.3.1-pre logging: ^1.0.2 flutter_material_color_picker: ^1.1.0+2 - circle_chart: - git: + circle_chart: + git: url: https://github.com/LollipopKit/circle_chart ref: main # path: ../circle_chart @@ -57,7 +58,6 @@ dependencies: dropdown_button2: ^1.1.1 flutter_advanced_drawer: ^1.3.0 - dev_dependencies: flutter_native_splash: ^2.1.6 flutter_test: @@ -72,7 +72,6 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec - # The following section is specific to Flutter. flutter: @@ -93,13 +92,10 @@ flutter: - assets/linux/opensuse.png # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a @@ -119,7 +115,6 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages - flutter_native_splash: # This package generates native code to customize Flutter's default white native splash screen # with background color and splash image. @@ -127,17 +122,14 @@ flutter_native_splash: # flutter pub run flutter_native_splash:create # To restore Flutter's default white splash screen, run the following command in the terminal: # flutter pub run flutter_native_splash:remove - # color or background_image is the only required parameter. Use color to set the background # of your splash screen to a solid color. Use background_image to set the background of your # splash screen to a png image. This is useful for gradients. The image will be stretch to the # size of the app. Only one parameter can be used, color and background_image cannot both be set. color: "#ffffff" #background_image: "assets/background.png" - # Optional parameters are listed below. To enable a parameter, uncomment the line by removing # the leading # character. - # The image parameter allows you to specify an image used in the splash screen. It must be a # png file and should be sized for 4x pixel density. image: assets/app_icon.png @@ -150,13 +142,11 @@ flutter_native_splash: color_dark: "#121212" #background_image_dark: "assets/dark-background.png" #image_dark: assets/splash-invert.png - # The android, ios and web parameters can be used to disable generating a splash screen on a given # platform. #android: false #ios: false #web: false - # The position of the splash image can be set with android_gravity, ios_content_mode, and # web_image_mode parameters. All default to center. # @@ -174,7 +164,6 @@ flutter_native_splash: # # web_image_mode can be one of the following modes: center, contain, stretch, and cover. #web_image_mode: center - # To hide the notification bar, use the fullscreen parameter. Has no affect in web since web # has no notification bar. Defaults to false. # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads. @@ -182,13 +171,13 @@ flutter_native_splash: # WidgetsFlutterBinding.ensureInitialized(); # SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]); #fullscreen: true - # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s) # with the info_plist_files parameter. Remove only the # characters in the three lines below, # do not remove any spaces: #info_plist_files: # - 'ios/Runner/Info-Debug.plist' # - 'ios/Runner/Info-Release.plist' - # To enable support for Android 12, set the following parameter to true. Defaults to false. - #android12: true \ No newline at end of file + #android12: true +flutter_intl: + enabled: true