Compare commits

...

31 Commits

Author SHA1 Message Date
lollipopkit🏳️‍⚧️
c4c0fdf6ff add: cloudflared/Cloudflare Tunnel support
Fixes #949
2025-11-22 17:16:52 +08:00
lollipopkit🏳️‍⚧️
84921de7a7 add: store exes & presets 2025-11-01 23:47:37 +08:00
lollipopkit🏳️‍⚧️
12c8543352 add: cloudflared/Cloudflare Tunnel support
Fixes #949
2025-10-31 00:30:58 +08:00
lollipopkit🏳️‍⚧️
92a4601335 opt.: disable it on iOS 2025-10-25 20:37:14 +08:00
lollipopkit🏳️‍⚧️
b6ab8f1db5 feat: proxy cmd support
Fixes #949
2025-10-25 20:35:47 +08:00
Korb
ffda27d057 add: fdroid Russian metadata translation (#947)
* Create ru/short_description.txt

* Create ru/full_description.txt
2025-10-23 02:24:04 +08:00
lollipopkit🏳️‍⚧️
c548b4ef48 fix: container parsing (#948) 2025-10-23 02:21:14 +08:00
lollipopkit🏳️‍⚧️
70040c5840 bump: v1270 2025-10-20 09:32:07 +08:00
lollipopkit🏳️‍⚧️
5272324be6 feat: prompt user on host key verification (#943) 2025-10-20 09:31:20 +08:00
lollipopkit🏳️‍⚧️
8cbb48ed67 feat: support windows clipboard shortcuts (#941)
Fixes #902
2025-10-20 00:56:33 +08:00
lollipopkit🏳️‍⚧️
03720fa322 Merge branch 'main' of github.com:lollipopkit/flutter_server_box 2025-10-20 00:35:07 +08:00
lollipopkit🏳️‍⚧️
0b51719070 fix: synthesize hardware backspace repeat (#940) 2025-10-20 00:34:52 +08:00
lollipopkit🏳️‍⚧️
a84231393d opt.: ask ai hint 2025-10-19 23:38:08 +08:00
lollipopkit🏳️‍⚧️
d6c2cafce7 opt.: ssh disconnection helper (#937) 2025-10-19 13:40:17 +08:00
lollipopkit🏳️‍⚧️
729b76177e feat: ask ai (#936)
* feat: ask ai in ssh terminal
Fixes #934

* new(ask_ai): settings

* fix: app hot reload

* new: l10n

* chore: deps.

* opt.
2025-10-18 01:15:43 +08:00
lollipopkit🏳️‍⚧️
860c11d4a8 bump: v1262 2025-10-10 09:18:38 +08:00
lollipopkit🏳️‍⚧️
bd949288ed fix: code editor tool bar (#933) 2025-10-10 09:14:41 +08:00
lollipopkit🏳️‍⚧️
bb3e3b4848 opt.: no Tag Switcher on desktop (#932) 2025-10-08 21:21:23 +08:00
lollipopkit🏳️‍⚧️
3307fca620 fix: cant sort servers order (#930) 2025-10-08 17:35:07 +08:00
lollipopkit🏳️‍⚧️
da8517bcf7 migrate: riverpod 3 2025-10-08 17:03:13 +08:00
lollipopkit🏳️‍⚧️
f68c4a851b feat: discover local ssh server (#921) 2025-09-19 23:29:01 +08:00
lollipopkit🏳️‍⚧️
17db393c12 bump: v1256 2025-09-15 02:35:42 +08:00
lollipopkit🏳️‍⚧️
275581cfa3 fix: notification permission (#914) 2025-09-14 22:34:01 +08:00
lollipopkit🏳️‍⚧️
d7168ea1ff fix: version code err caused by Flutter (#913) 2025-09-14 14:23:17 +08:00
lollipopkit🏳️‍⚧️
fd2bf08f78 bump: v1253 2025-09-09 13:32:17 +08:00
lollipopkit🏳️‍⚧️
98e13c39cf fix: android channel invoke 2025-09-09 13:30:37 +08:00
lollipopkit🏳️‍⚧️
e70abeef04 bump: v1251 2025-09-09 13:14:01 +08:00
lollipopkit🏳️‍⚧️
194774d6fb opt.: system detect logic to avoid creating useless file (#905) 2025-09-09 13:10:40 +08:00
lollipopkit🏳️‍⚧️
640d61bab9 fix: holding Backspace doesnt work on desktop (#903) 2025-09-08 14:06:35 +08:00
lollipopkit🏳️‍⚧️
7f4cf22cc9 fix: rm camera perm on mac 2025-09-08 12:37:30 +08:00
lollipopkit🏳️‍⚧️
05a927753f feat: stop all servers in noti center (#901) 2025-09-06 14:04:53 +08:00
140 changed files with 9856 additions and 3708 deletions

View File

@@ -23,19 +23,6 @@ jobs:
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: 'stable' channel: 'stable'
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: |
${{ env.PUB_CACHE }}
~/.pub-cache
${{ runner.tool_cache }}/flutter
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pub-
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get

141
LICENSE
View File

@@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
@@ -7,17 +7,15 @@
Preamble Preamble
The GNU General Public License is a free, copyleft license for The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works. software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast, to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the software for all its users.
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you price. Our General Public Licenses are designed to make sure that you
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things. free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you Developers that use our General Public Licenses protect your rights
these rights or asking you to surrender the rights. Therefore, you have with two steps: (1) assert copyright on the software, and (2) offer
certain responsibilities if you distribute copies of the software, or if you this License which gives you legal permission to copy, distribute
you modify it: responsibilities to respect the freedom of others. and/or modify the software.
For example, if you distribute copies of such a program, whether A secondary benefit of defending all users' freedom is that
gratis or for a fee, you must pass on to the recipients the same improvements made in alternate versions of the program, if they
freedoms that you received. You must make sure that they, too, receive receive widespread use, become available for other developers to
or can get the source code. And you must show them these terms so they incorporate. Many developers of free software are heartened and
know their rights. encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps: The GNU Affero General Public License is designed specifically to
(1) assert copyright on the software, and (2) offer you this License ensure that, in such cases, the modified source code becomes available
giving you legal permission to copy, distribute and/or modify it. to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains An older license, called the Affero General Public License and
that there is no warranty for this free software. For both users' and published by Affero, was designed to accomplish similar goals. This is
authors' sake, the GPL requires that modified versions be marked as a different license, not a version of the Affero GPL, but Affero has
changed, so that their problems will not be attributed erroneously to released a new version of the Affero GPL which permits relicensing under
authors of previous versions. this license.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
@@ -72,7 +60,7 @@ modification follow.
0. Definitions. 0. Definitions.
"This License" refers to version 3 of the GNU General Public License. "This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks. works, such as semiconductor masks.
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program. License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License. 13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work, License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License, but the work with which it is combined will remain governed by version
section 13, concerning interaction through a network will apply to the 3 of the GNU General Public License.
combination as such.
14. Revised Versions of this License. 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will the GNU Affero General Public License from time to time. Such new versions
be similar in spirit to the present version, but may differ in detail to will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns. address new problems or concerns.
Each version is given a distinguishing version number. If the Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation. by the Free Software Foundation.
If the Program specifies that a proxy can decide which future If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you public statement of acceptance of a version permanently authorizes you
to choose that version for the Program. to choose that version for the Program.
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author> Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If your software can interact with users remotely through a computer
notice like this when it starts in an interactive mode: network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
<program> Copyright (C) <year> <name of author> interface could display a "Source" link that leads users to an archive
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. of the code. There are many ways you could offer source, and different
This is free software, and you are welcome to redistribute it solutions will be better for different programs; see section 13 for the
under certain conditions; type `show c' for details. specific requirements.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -5,7 +5,7 @@ English | [简体中文](README_zh.md)
<div align="center"> <div align="center">
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a> <a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/donate-me-pink"></a>
<img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan"> <img alt="lang" src="https://img.shields.io/badge/lang-dart-cyan">
<img alt="license" src="https://img.shields.io/badge/license-GPLv3-yellow"> <img alt="license" src="https://img.shields.io/badge/license-AGPLv3-yellow">
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a> <a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
</div> </div>
@@ -85,4 +85,4 @@ If I forgot to add your name to the contributors list, please add a comment in t
## 📝 License ## 📝 License
`GPL v3 lollipopkit` `AGPL v3 lollipopkit & all contributors`

View File

@@ -5,7 +5,7 @@
<div align="center"> <div align="center">
<a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a> <a href="https://cdn.lpkt.cn/donate"><img alt="donate" src="https://img.shields.io/badge/捐赠-我-pink"></a>
<img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan"> <img alt="语言" src="https://img.shields.io/badge/语言-dart-cyan">
<img alt="license" src="https://img.shields.io/badge/证书-GPLv3-yellow"> <img alt="license" src="https://img.shields.io/badge/证书-AGPLv3-yellow">
<a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a> <a href="https://deepwiki.com/lollipopkit/flutter_server_box"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
</div> </div>
@@ -86,4 +86,4 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel
## 📝 协议 ## 📝 协议
`GPL v3 lollipopkit` `AGPL v3 lollipopkit & 所有贡献者`

View File

@@ -113,7 +113,7 @@ android.applicationVariants.all { variant ->
variant.outputs.each { output -> variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) { if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode output.versionCodeOverride = variant.versionCode * 100 + abiVersionCode
} }
} }
} }

View File

@@ -2,6 +2,8 @@ package tech.lolli.toolbox
import android.app.* import android.app.*
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
@@ -16,8 +18,7 @@ class ForegroundService : Service() {
var isRunning: Boolean = false var isRunning: Boolean = false
} }
private val chanId = "ForegroundServiceChannel" private val chanId = "ForegroundServiceChannel"
private val GROUP_KEY = "ssh_sessions_group" private val NOTIFICATION_ID = 1000
private val SUMMARY_ID = 1000
private val ACTION_STOP_FOREGROUND = "ACTION_STOP_FOREGROUND" private val ACTION_STOP_FOREGROUND = "ACTION_STOP_FOREGROUND"
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS" private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION" private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
@@ -49,19 +50,22 @@ class ForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
try { try {
// Check notification permission for Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
androidx.core.content.ContextCompat.checkSelfPermission( androidx.core.content.ContextCompat.checkSelfPermission(
this, android.Manifest.permission.POST_NOTIFICATIONS this, android.Manifest.permission.POST_NOTIFICATIONS
) != android.content.pm.PackageManager.PERMISSION_GRANTED ) != android.content.pm.PackageManager.PERMISSION_GRANTED
) { ) {
Log.w("ForegroundService", "Notification permission denied. Stopping service.") Log.w("ForegroundService", "Notification permission denied. Stopping service gracefully.")
stopForegroundService() // Don't call stopForegroundService() here as we haven't started foreground yet
stopSelf()
return START_NOT_STICKY return START_NOT_STICKY
} }
if (intent == null) { if (intent == null) {
Log.w("ForegroundService", "onStartCommand called with null intent") Log.w("ForegroundService", "onStartCommand called with null intent")
stopForegroundService() // Don't call stopForegroundService() here as we haven't started foreground yet
stopSelf()
return START_NOT_STICKY return START_NOT_STICKY
} }
@@ -70,6 +74,9 @@ class ForegroundService : Service() {
return when (action) { return when (action) {
ACTION_STOP_FOREGROUND -> { ACTION_STOP_FOREGROUND -> {
// Notify Flutter to stop all connections before stopping service
val stopAllIntent = Intent("tech.lolli.toolbox.STOP_ALL_CONNECTIONS")
sendBroadcast(stopAllIntent)
clearAll() clearAll()
stopForegroundService() stopForegroundService()
START_NOT_STICKY START_NOT_STICKY
@@ -81,7 +88,7 @@ class ForegroundService : Service() {
} }
else -> { else -> {
// Default bring up foreground with placeholder // Default bring up foreground with placeholder
ensureForeground(createSummaryNotification(0, emptyList())) ensureForeground(createMergedNotification(0, emptyList(), emptyList()))
START_STICKY START_STICKY
} }
} }
@@ -99,37 +106,67 @@ class ForegroundService : Service() {
private fun createNotificationChannel() { private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NotificationManager::class.java) try {
if (manager == null) { val manager = getSystemService(NotificationManager::class.java)
Log.e("ForegroundService", "Failed to get NotificationManager") if (manager == null) {
return Log.e("ForegroundService", "Failed to get NotificationManager")
return
}
val serviceChannel = NotificationChannel(
chanId,
"ForegroundServiceChannel",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "For foreground service"
}
manager.createNotificationChannel(serviceChannel)
Log.d("ForegroundService", "Notification channel created successfully")
} catch (e: Exception) {
logError("Failed to create notification channel", e)
} }
val serviceChannel = NotificationChannel(
chanId,
"ForegroundServiceChannel",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "For foreground service"
}
manager.createNotificationChannel(serviceChannel)
} }
} }
private fun ensureForeground(notification: Notification) { private fun ensureForeground(notification: Notification) {
try { try {
// Double-check notification permission before starting foreground service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
androidx.core.content.ContextCompat.checkSelfPermission(
this, android.Manifest.permission.POST_NOTIFICATIONS
) != android.content.pm.PackageManager.PERMISSION_GRANTED
) {
Log.w("ForegroundService", "Cannot start foreground service without notification permission")
stopSelf()
return
}
if (!isFgStarted) { if (!isFgStarted) {
startForeground(SUMMARY_ID, notification) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
startForeground(NOTIFICATION_ID, notification)
}
isFgStarted = true isFgStarted = true
Log.d("ForegroundService", "Foreground service started successfully")
} else { } else {
val nm = getSystemService(NotificationManager::class.java) val nm = getSystemService(NotificationManager::class.java)
nm?.notify(SUMMARY_ID, notification) if (nm != null) {
nm.notify(NOTIFICATION_ID, notification)
} else {
Log.w("ForegroundService", "NotificationManager is null, cannot update notification")
}
} }
} catch (e: SecurityException) {
logError("Security exception when starting foreground service (likely missing permission)", e)
stopSelf()
} catch (e: Exception) { } catch (e: Exception) {
logError("Failed to start/update foreground", e) logError("Failed to start/update foreground", e)
// Don't stop the service for other exceptions, just log them
} }
} }
private fun createSummaryNotification(count: Int, lines: List<String>): Notification {
private fun createMergedNotification(count: Int, lines: List<String>, sessions: List<SessionItem>): Notification {
val notificationIntent = Intent(this, MainActivity::class.java) val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
@@ -140,24 +177,66 @@ class ForegroundService : Service() {
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this, chanId) Notification.Builder(this, chanId)
} else { } else {
@Suppress("DEPRECATION")
Notification.Builder(this) Notification.Builder(this)
} }
val inbox = Notification.InboxStyle() // Use the earliest session's start time for chronometer
lines.forEach { inbox.addLine(it) } val earliestStartTime = sessions.minOfOrNull { it.startWhen } ?: System.currentTimeMillis()
return builder val title = when (count) {
.setContentTitle("SSH sessions: $count active") 0 -> "Server Box"
.setContentText(if (lines.isNotEmpty()) lines.first() else "Running") 1 -> sessions.first().title
else -> "SSH sessions: $count active"
}
val contentText = when (count) {
0 -> "Ready for connections"
1 -> {
val session = sessions.first()
"${session.subtitle} · ${session.status}"
}
else -> "Multiple SSH connections active"
}
// For multiple sessions, show details in expanded view
val style = if (count > 1) {
val inbox = Notification.InboxStyle()
val maxLines = 5
val displayLines = if (lines.size > maxLines) {
lines.take(maxLines) + "...and ${lines.size - maxLines} more"
} else {
lines
}
displayLines.forEach { inbox.addLine(it) }
inbox.setBigContentTitle(title)
inbox
} else {
null
}
val notification = builder
.setContentTitle(title)
.setContentText(contentText)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setStyle(inbox) .setWhen(earliestStartTime)
.setUsesChronometer(true)
.setOngoing(true) .setOngoing(true)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setGroup(GROUP_KEY)
.setGroupSummary(true)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_delete, "Stop", stopPending) .addAction(
.build() Notification.Action.Builder(
Icon.createWithResource(this, android.R.drawable.ic_delete),
"Stop All",
stopPending
).build()
)
if (style != null) {
notification.setStyle(style)
}
return notification.build()
} }
private fun handleUpdateSessions(payload: String) { private fun handleUpdateSessions(payload: String) {
@@ -192,71 +271,21 @@ class ForegroundService : Service() {
return return
} }
// Build per-session notifications // Cancel any existing individual notifications (we only show merged notification now)
val currentIds = mutableSetOf<Int>() val toCancel = postedIds.toSet()
val summaryLines = mutableListOf<String>()
sessions.forEach { s ->
// Assign a stable, collision-resistant id per session for this service lifecycle
val nid = notificationIdMap.getOrPut(s.id) { nextNotificationId.getAndIncrement() }
currentIds.add(nid)
summaryLines.add("${s.title}: ${s.status}")
val disconnectIntent = Intent(this, MainActivity::class.java).apply {
action = ACTION_DISCONNECT_SESSION
putExtra("session_id", s.id)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
val disconnectPending = PendingIntent.getActivity(
this, nid, disconnectIntent, PendingIntent.FLAG_IMMUTABLE
)
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this, chanId)
} else {
Notification.Builder(this)
}
val noti = builder
.setContentTitle(s.title)
.setContentText("${s.subtitle} · ${s.status}")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(s.startWhen)
.setUsesChronometer(true)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setGroup(GROUP_KEY)
.addAction(android.R.drawable.ic_media_pause, "Disconnect", disconnectPending)
.build()
nm.notify(nid, noti)
}
// Cancel stale ones
val toCancel = postedIds - currentIds
toCancel.forEach { nm.cancel(it) } toCancel.forEach { nm.cancel(it) }
// Clean up id mappings for canceled notifications to prevent growth
if (toCancel.isNotEmpty()) {
val keysToRemove = notificationIdMap.filterValues { it in toCancel }.keys
keysToRemove.forEach { notificationIdMap.remove(it) }
}
postedIds.clear() postedIds.clear()
postedIds.addAll(currentIds) notificationIdMap.clear()
// Post/update summary and ensure foreground // Create merged notification content
val maxSummaryLines = 5 val summaryLines = sessions.map { "${it.title}: ${it.status}" }
val truncated = summaryLines.size > maxSummaryLines val mergedNotification = createMergedNotification(sessions.size, summaryLines, sessions)
val displaySummaryLines = if (truncated) { ensureForeground(mergedNotification)
summaryLines.take(maxSummaryLines) + "...and ${summaryLines.size - maxSummaryLines} more"
} else {
summaryLines
}
val summary = createSummaryNotification(sessions.size, displaySummaryLines)
ensureForeground(summary)
} }
private fun clearAll() { private fun clearAll() {
val nm = getSystemService(NotificationManager::class.java) val nm = getSystemService(NotificationManager::class.java)
nm?.cancel(SUMMARY_ID) nm?.cancel(NOTIFICATION_ID)
postedIds.forEach { id -> nm?.cancel(id) } postedIds.forEach { id -> nm?.cancel(id) }
postedIds.clear() postedIds.clear()
isFgStarted = false isFgStarted = false
@@ -272,7 +301,10 @@ class ForegroundService : Service() {
private fun stopForegroundService() { private fun stopForegroundService() {
try { try {
stopForeground(true) if (isFgStarted) {
stopForeground(STOP_FOREGROUND_REMOVE)
isFgStarted = false
}
} catch (e: Exception) { } catch (e: Exception) {
logError("Error stopping foreground", e) logError("Error stopping foreground", e)
} }

View File

@@ -4,6 +4,9 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.Manifest import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.IntentFilter
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.android.FlutterFragmentActivity
@@ -16,6 +19,8 @@ class MainActivity: FlutterFragmentActivity() {
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS" private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION" private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
private val ACTION_STOP_ALL_CONNECTIONS = "tech.lolli.toolbox.STOP_ALL_CONNECTIONS"
private var stopAllReceiver: BroadcastReceiver? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
@@ -92,24 +97,32 @@ class MainActivity: FlutterFragmentActivity() {
// Handle intent if launched via notification action // Handle intent if launched via notification action
handleActionIntent(intent) handleActionIntent(intent)
// Register broadcast receiver for stop all connections
setupStopAllReceiver()
} }
private fun reqPerm() { private fun reqPerm() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
// Check if we already have the permission to avoid unnecessary prompts try {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) // Check if we already have the permission to avoid unnecessary prompts
!= PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
try { != PackageManager.PERMISSION_GRANTED) {
// Check if we should show rationale
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
android.util.Log.i("MainActivity", "User previously denied notification permission")
}
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
this, this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS), arrayOf(Manifest.permission.POST_NOTIFICATIONS),
123, 123,
) )
} catch (e: Exception) {
// Log error but don't crash
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
} }
} catch (e: Exception) {
// Log error but don't crash
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
} }
} }
@@ -141,4 +154,52 @@ class MainActivity: FlutterFragmentActivity() {
} }
} }
} }
private fun setupStopAllReceiver() {
stopAllReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == ACTION_STOP_ALL_CONNECTIONS && ::channel.isInitialized) {
try {
channel.invokeMethod("stopAllConnections", null)
} catch (e: Exception) {
android.util.Log.e("MainActivity", "Failed to invoke stopAllConnections: ${e.message}")
}
}
}
}
val filter = IntentFilter(ACTION_STOP_ALL_CONNECTIONS)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.registerReceiver(this, stopAllReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(stopAllReceiver, filter)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 123) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
android.util.Log.i("MainActivity", "Notification permission granted")
} else {
android.util.Log.w("MainActivity", "Notification permission denied")
// Optionally inform user about the limitation
}
}
}
override fun onDestroy() {
super.onDestroy()
stopAllReceiver?.let {
try {
unregisterReceiver(it)
} catch (e: Exception) {
android.util.Log.e("MainActivity", "Failed to unregister receiver: ${e.message}")
}
stopAllReceiver = null
}
}
} }

View File

@@ -0,0 +1,7 @@
Проект на базе Flutter, предоставляющий диаграммы состояний серверов под Linux, Unix и Windows и инструменты для управления ими.
Особая благодарность dartssh2 и xterm.dart.
* Диаграмма состояния (ЦП, датчики, видеокарта…), SSH Term, SFTP, Docker, пакеты, процессы…
* Платформозависимые: биометрическая аутентификация, push-уведомления, виджет, приложение для watchOS…
* Многоязычная поддержка: English, 简体中文; Deutsch, 繁體中文, Indonesian, Français, Dutch; Español, Русский язык, Português, 日本語

View File

@@ -0,0 +1 @@
Приложение для мониторинга серверов и набор инструментов управления ими

Submodule flutter_server_box.wiki deleted from f440010313

View File

@@ -748,7 +748,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -758,7 +758,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -884,7 +884,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -894,7 +894,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -912,7 +912,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -922,7 +922,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -943,7 +943,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -956,7 +956,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -982,7 +982,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -995,7 +995,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1018,7 +1018,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1031,7 +1031,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1054,7 +1054,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1066,7 +1066,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -1095,7 +1095,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1107,7 +1107,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;
@@ -1133,7 +1133,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1246; CURRENT_PROJECT_VERSION = 1270;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1145,7 +1145,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1246; MARKETING_VERSION = 1.0.1270;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;

View File

@@ -3,6 +3,7 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:fl_lib/generated/l10n/lib_l10n.dart'; import 'package:fl_lib/generated/l10n/lib_l10n.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/core/app_navigator.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
@@ -11,9 +12,16 @@ import 'package:server_box/view/page/home.dart';
part 'intro.dart'; part 'intro.dart';
class MyApp extends StatelessWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final Future<List<IntroPageBuilder>> _introFuture = _IntroPage.builders;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_setup(context); _setup(context);
@@ -57,8 +65,10 @@ class MyApp extends StatelessWidget {
final darkTheme = ThemeData(useMaterial3: true, brightness: Brightness.dark, colorScheme: dark); final darkTheme = ThemeData(useMaterial3: true, brightness: Brightness.dark, colorScheme: dark);
if (context.isDark && dark != null) { if (context.isDark && dark != null) {
UIs.primaryColor = dark.primary; UIs.primaryColor = dark.primary;
UIs.colorSeed = dark.primary;
} else if (!context.isDark && light != null) { } else if (!context.isDark && light != null) {
UIs.primaryColor = light.primary; UIs.primaryColor = light.primary;
UIs.colorSeed = light.primary;
} }
return _buildApp(context, light: lightTheme, dark: darkTheme); return _buildApp(context, light: lightTheme, dark: darkTheme);
@@ -78,6 +88,7 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
key: ValueKey(locale), key: ValueKey(locale),
navigatorKey: AppNavigator.key,
builder: ResponsivePoints.builder, builder: ResponsivePoints.builder,
locale: locale, locale: locale,
localizationsDelegates: const [LibLocalizations.delegate, ...AppLocalizations.localizationsDelegates], localizationsDelegates: const [LibLocalizations.delegate, ...AppLocalizations.localizationsDelegates],
@@ -89,7 +100,7 @@ class MyApp extends StatelessWidget {
theme: light.fixWindowsFont, theme: light.fixWindowsFont,
darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont, darkTheme: (tMode < 3 ? dark : dark.toAmoled).fixWindowsFont,
home: FutureBuilder<List<IntroPageBuilder>>( home: FutureBuilder<List<IntroPageBuilder>>(
future: _IntroPage.builders, future: _introFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
context.setLibL10n(); context.setLibL10n();
final appL10n = AppLocalizations.of(context); final appL10n = AppLocalizations.of(context);

View File

@@ -0,0 +1,8 @@
import 'package:flutter/widgets.dart';
/// Global navigator access used for cross-cutting flows (e.g. dialogs).
abstract final class AppNavigator {
static final key = GlobalKey<NavigatorState>();
static BuildContext? get context => key.currentContext;
}

View File

@@ -77,8 +77,10 @@ abstract final class MethodChans {
} }
/// Register a handler for native -> Flutter callbacks. /// Register a handler for native -> Flutter callbacks.
/// Currently handles: `disconnectSession` with argument map {id: string} /// Currently handles:
static void registerHandler(Future<void> Function(String id) onDisconnect) { /// - `disconnectSession` with argument map {id: string}
/// - `stopAllConnections` with no arguments
static void registerHandler(Future<void> Function(String id) onDisconnect, [VoidCallback? onStopAll]) {
_channel.setMethodCallHandler((call) async { _channel.setMethodCallHandler((call) async {
switch (call.method) { switch (call.method) {
case 'disconnectSession': case 'disconnectSession':
@@ -88,6 +90,9 @@ abstract final class MethodChans {
await onDisconnect(id); await onDisconnect(id);
} }
return; return;
case 'stopAllConnections':
onStopAll?.call();
return;
default: default:
return; return;
} }

View File

@@ -0,0 +1,408 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/data/model/server/discovery_result.dart';
class SshDiscoveryService {
static const _sshPort = 22;
static Future<SshDiscoveryReport> discover([SshDiscoveryConfig config = const SshDiscoveryConfig()]) async {
final t0 = DateTime.now();
final candidates = <InternetAddress>{};
// 1) Get neighbors from ARP/NDP tables
candidates.addAll(await _neighborsIPv4());
candidates.addAll(await _neighborsIPv6());
// 2) Enumerate small subnets from local interfaces (IPv4 only)
final cidrs = await _localIPv4Cidrs();
for (final c in cidrs) {
if (c.prefix >= 24 && c.prefix <= 30) {
candidates.addAll(c.enumerateHosts(limit: config.hostEnumerationLimit));
}
}
// 3) Optional: mDNS/Bonjour SSH services
if (config.enableMdns) {
candidates.addAll(await _mdnsSshCandidates());
}
// Filter out unwanted addresses: loopback, link-local, 0.0.0.0, broadcast, multicast
candidates.removeWhere(
(a) => a.isLoopback || a.isLinkLocal || a.address == '0.0.0.0' || _isBroadcastOrMulticast(a),
);
// 4) Concurrent SSH port scanning
final scanner = _Scanner(
timeout: Duration(milliseconds: config.timeoutMs),
maxConcurrency: config.maxConcurrency,
);
final results = await scanner.scan(candidates.toList(growable: false));
results.sort((a, b) => a.addr.address.compareTo(b.addr.address));
final discoveryResults = results
.map((r) => SshDiscoveryResult(ip: r.addr.address, port: _sshPort, banner: r.banner?.trim()))
.toList();
return SshDiscoveryReport(
generatedAt: DateTime.now().toIso8601String(),
durationMs: DateTime.now().difference(t0).inMilliseconds,
count: discoveryResults.length,
items: discoveryResults,
);
}
static Future<String?> _run(String exe, List<String> args, {Duration? timeout}) async {
try {
final p = await Process.start(exe, args, runInShell: false);
final out = await p.stdout
.transform(utf8.decoder)
.join()
.timeout(
timeout ?? const Duration(seconds: 5),
onTimeout: () {
p.kill();
return '';
},
);
final code = await p.exitCode;
if (code == 0) return out;
// Some tools return non-zero but still have useful output
if (out.trim().isNotEmpty) return out;
return null;
} catch (_) {
return null;
}
}
static bool get _isLinux => Platform.isLinux;
static bool get _isMac => Platform.isMacOS;
static Future<Set<InternetAddress>> _neighborsIPv4() async {
final set = <InternetAddress>{};
if (_isLinux) {
final s = await _run('ip', ['neigh']);
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final tok = line.split(RegExp(r'\s+'));
if (tok.isNotEmpty) {
final ip = tok[0];
if (InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv4) {
set.add(InternetAddress(ip));
}
}
}
}
} else if (_isMac) {
final s = await _run('/usr/sbin/arp', ['-an']);
if (s != null) {
int matchCount = 0;
for (final line in const LineSplitter().convert(s)) {
final m = RegExp(r'\((\d+\.\d+\.\d+\.\d+)\)').firstMatch(line);
if (m != null) {
set.add(InternetAddress(m.group(1)!));
matchCount++;
}
}
if (matchCount == 0) {
lprint(
'[ssh_discovery] Warning: No ARP entries parsed on macOS. Output may be unexpected or localized. Output sample: ${s.length > 100 ? '${s.substring(0, 100)}...' : s}',
);
}
}
}
return set;
}
static Future<Set<InternetAddress>> _neighborsIPv6() async {
final set = <InternetAddress>{};
if (_isLinux) {
final s = await _run('ip', ['-6', 'neigh']);
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final ip = line.split(RegExp(r'\s+')).firstOrNull;
if (ip != null && InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
set.add(InternetAddress(ip));
}
}
}
} else if (_isMac) {
final s = await _run('/usr/sbin/ndp', ['-a']);
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final ip = line.trim().split(RegExp(r'\s+')).firstOrNull;
if (ip != null && InternetAddress.tryParse(ip)?.type == InternetAddressType.IPv6) {
set.add(InternetAddress(ip));
}
}
}
}
return set;
}
static Future<List<_Cidr>> _localIPv4Cidrs() async {
final res = <_Cidr>[];
if (_isLinux) {
final s = await _run('ip', ['-o', '-4', 'addr', 'show', 'scope', 'global']);
if (s != null) {
for (final line in const LineSplitter().convert(s)) {
final m = RegExp(r'inet\s+(\d+\.\d+\.\d+\.\d+)\/(\d+)').firstMatch(line);
if (m != null) {
final ip = InternetAddress(m.group(1)!);
final prefix = int.parse(m.group(2)!);
final mask = _prefixToMask(prefix);
final net = _networkAddress(ip, mask);
final brd = _broadcastAddress(ip, mask);
res.add(_Cidr(ip, prefix, mask, net, brd));
}
}
}
} else if (_isMac) {
final s = await _run('/sbin/ifconfig', []);
if (s != null) {
for (final raw in const LineSplitter().convert(s)) {
final line = raw.trimRight();
final ifMatch = RegExp(r'^([a-z0-9]+):').firstMatch(line);
if (ifMatch != null) {
continue;
}
if (line.contains('inet ') && !line.contains('127.0.0.1')) {
try {
final ipm = RegExp(
r'inet\s+(\d+\.\d+\.\d+\.\d+)\s+netmask\s+0x([0-9a-fA-F]+)(?:\s+broadcast\s+(\d+\.\d+\.\d+\.\d+))?',
).firstMatch(line);
if (ipm == null) {
// Log unexpected format but continue processing other lines
lprint('[ssh_discovery] Warning: Unexpected ifconfig line format: $line');
continue;
}
final ip = InternetAddress(ipm.group(1)!);
final hexMask = int.parse(ipm.group(2)!, radix: 16);
final dotted =
'${(hexMask >> 24) & 0xff}.${(hexMask >> 16) & 0xff}.${(hexMask >> 8) & 0xff}.${hexMask & 0xff}';
final mask = InternetAddress(dotted);
final prefix = _maskToPrefix(mask.address);
final net = _networkAddress(ip, mask);
final brd = InternetAddress(ipm.group(3) ?? _broadcastAddress(ip, mask).address);
res.add(_Cidr(ip, prefix, mask, net, brd));
} catch (e) {
lprint('[ssh_discovery] Error parsing ifconfig output: $e, line: $line');
continue;
}
}
}
}
}
return res;
}
static bool _isBroadcastOrMulticast(InternetAddress a) {
// IPv4 broadcast: ends with .255 or is 255.255.255.255
if (a.type == InternetAddressType.IPv4) {
if (a.address == '255.255.255.255') return true;
if (a.address.split('.').last == '255') return true;
// Multicast: 224.0.0.0 - 239.255.255.255
final firstOctet = int.tryParse(a.address.split('.').first) ?? 0;
if (firstOctet >= 224 && firstOctet <= 239) return true;
} else if (a.type == InternetAddressType.IPv6) {
// IPv6 multicast: starts with ff
if (a.address.toLowerCase().startsWith('ff')) return true;
}
return false;
}
static Future<Set<InternetAddress>> _mdnsSshCandidates() async {
final set = <InternetAddress>{};
if (_isMac) {
try {
final proc = await Process.start('/usr/bin/dns-sd', ['-B', '_ssh._tcp']);
final lines = <String>[];
final subscription = proc.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(lines.add);
await Future<void>.delayed(const Duration(seconds: 2));
proc.kill();
await subscription.cancel();
for (final l in lines) {
final m = RegExp(r'Add\s+\d+\s+(\S+)\.\s+_ssh\._tcp\.').firstMatch(l);
if (m != null) {
final name = m.group(1)!;
final det = await _run('/usr/bin/dns-sd', [
'-L',
name,
'_ssh._tcp',
'local.',
], timeout: const Duration(seconds: 3));
if (det != null) {
for (final ip in RegExp(
r'Address\s*=\s*([0-9a-fA-F:\.]+)',
).allMatches(det).map((e) => e.group(1)!)) {
final parsed = InternetAddress.tryParse(ip);
if (parsed != null) set.add(parsed);
}
}
}
}
} catch (_) {}
} else if (_isLinux) {
final s = await _run('/usr/bin/avahi-browse', ['-rat', '_ssh._tcp']);
if (s != null) {
for (final ip in RegExp(
r'address = \[(.*?)\]',
).allMatches(s).map((m) => m.group(1)!).where((e) => e.isNotEmpty)) {
final parsed = InternetAddress.tryParse(ip);
if (parsed != null) set.add(parsed);
}
}
}
return set;
}
}
class _Cidr {
final InternetAddress ip;
final int prefix;
final InternetAddress netmask;
final InternetAddress network;
final InternetAddress broadcast;
_Cidr(this.ip, this.prefix, this.netmask, this.network, this.broadcast);
Iterable<InternetAddress> enumerateHosts({int? limit}) sync* {
final n = _ipv4ToInt(network.address);
final b = _ipv4ToInt(broadcast.address);
int emitted = 0;
for (int v = n + 1; v <= b - 1; v++) {
if (limit != null && emitted >= limit) break;
emitted++;
yield InternetAddress(_intToIPv4(v));
}
}
@override
String toString() => '${network.address}/$prefix';
}
class _ScanResult {
final InternetAddress addr;
final String? banner;
_ScanResult(this.addr, this.banner);
}
class _Scanner {
final Duration timeout;
final int maxConcurrency;
_Scanner({required this.timeout, required this.maxConcurrency});
Future<List<_ScanResult>> scan(List<InternetAddress> addrs) async {
final sem = _Semaphore(maxConcurrency);
final futures = <Future<_ScanResult?>>[];
for (final a in addrs) {
futures.add(_guarded(sem, () => _probeSsh(a)));
}
final out = await Future.wait(futures);
return out.whereType<_ScanResult>().toList();
}
Future<_ScanResult?> _probeSsh(InternetAddress ip) async {
Socket? socket;
StreamSubscription? sub;
try {
socket = await Socket.connect(ip, SshDiscoveryService._sshPort, timeout: timeout);
socket.timeout(timeout);
final c = Completer<String?>();
sub = socket.listen(
(data) {
final s = utf8.decode(data, allowMalformed: true);
final line = s.split('\n').firstWhere((_) => true, orElse: () => s);
if (!c.isCompleted) {
c.complete(line.trim());
sub?.cancel();
}
},
onDone: () {
if (!c.isCompleted) c.complete(null);
},
onError: (_) {
if (!c.isCompleted) c.complete(null);
},
);
final banner = await c.future.timeout(timeout, onTimeout: () => null);
return _ScanResult(ip, banner);
} catch (_) {
return null;
} finally {
sub?.cancel();
socket?.destroy();
}
}
}
class _Semaphore {
int _permits;
final Queue<Completer<void>> _q = Queue();
_Semaphore(this._permits);
Future<T> withPermit<T>(Future<T> Function() fn) async {
if (_permits > 0) {
_permits--;
try {
return await fn();
} finally {
_permits++;
if (_q.isNotEmpty) _q.removeFirst().complete();
}
} else {
final c = Completer<void>();
_q.add(c);
await c.future;
return withPermit(fn);
}
}
}
Future<T> _guarded<T>(_Semaphore sem, Future<T> Function() fn) => sem.withPermit(fn);
// IPv4 utilities
int _ipv4ToInt(String ip) {
final p = ip.split('.').map(int.parse).toList();
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
String _intToIPv4(int v) => '${(v >> 24) & 0xff}.${(v >> 16) & 0xff}.${(v >> 8) & 0xff}.${v & 0xff}';
InternetAddress _prefixToMask(int prefix) {
final mask = prefix == 0 ? 0 : 0xffffffff << (32 - prefix);
return InternetAddress(_intToIPv4(mask & 0xffffffff));
}
int _maskToPrefix(String mask) {
final v = _ipv4ToInt(mask);
int c = 0;
for (int i = 31; i >= 0; i--) {
if ((v & (1 << i)) != 0) {
c++;
} else {
break;
}
}
return c;
}
InternetAddress _networkAddress(InternetAddress ip, InternetAddress mask) {
final v = _ipv4ToInt(ip.address) & _ipv4ToInt(mask.address);
return InternetAddress(_intToIPv4(v));
}
InternetAddress _broadcastAddress(InternetAddress ip, InternetAddress mask) {
final n = _ipv4ToInt(ip.address) & _ipv4ToInt(mask.address);
final b = n | (~_ipv4ToInt(mask.address) & 0xffffffff);
return InternetAddress(_intToIPv4(b));
}

View File

@@ -0,0 +1,303 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:fl_lib/fl_lib.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:server_box/data/store/setting.dart';
/// Exception thrown when executable management fails
class ExecutableException implements Exception {
final String message;
ExecutableException(this.message);
@override
String toString() => 'ExecutableException: $message';
}
/// Information about an executable
class ExecutableInfo {
final String name;
final String? spokenName;
final String? version;
ExecutableInfo({required this.name, this.version, this.spokenName});
}
/// Generic executable manager for downloading and managing external tools
abstract final class ExecutableManager {
static const String _executablesDirName = 'executables';
static const int _posixExecuteBitsMask = 0x49; // Equivalent to POSIX octal 0o111
static late final Directory _executablesDir;
static final Map<String, ExecutableInfo> _customExecutables = {};
static bool _customExecutablesLoaded = false;
static Future<void> initialize() async {
final appDir = await getApplicationSupportDirectory();
_executablesDir = Directory(path.join(appDir.path, _executablesDirName));
if (!await _executablesDir.exists()) {
await _executablesDir.create(recursive: true);
}
_ensureCustomExecutablesLoaded();
}
/// Predefined executables
static final Map<String, ExecutableInfo> _predefinedExecutables = {
'cloudflared': ExecutableInfo(name: 'cloudflared'),
'ssh': ExecutableInfo(name: 'ssh'),
'nc': ExecutableInfo(name: 'nc'),
'socat': ExecutableInfo(name: 'socat'),
};
static void _ensureCustomExecutablesLoaded() {
if (_customExecutablesLoaded) return;
final List<dynamic> stored = SettingStore.instance.proxyCmdCustomExecs.get();
for (final raw in stored) {
final info = _parseExecutableInfo(raw);
if (info == null) continue;
_customExecutables[info.name] = info;
_predefinedExecutables[info.name] = info;
}
_customExecutablesLoaded = true;
}
static void _persistCustomExecutables() {
final values = _customExecutables.values
.map((info) => {
'name': info.name,
if (info.spokenName != null) 'spokenName': info.spokenName,
if (info.version != null) 'version': info.version,
})
.toList();
SettingStore.instance.proxyCmdCustomExecs.set(values);
}
static ExecutableInfo? _parseExecutableInfo(dynamic raw) {
if (raw is String) {
try {
return _parseExecutableInfo(jsonDecode(raw));
} catch (e) {
Loggers.app.warning('Failed to decode custom executable entry: $e');
return null;
}
}
if (raw is! Map) return null;
final name = raw['name']?.toString();
if (name == null || name.isEmpty) return null;
return ExecutableInfo(
name: name,
spokenName: raw['spokenName']?.toString(),
version: raw['version']?.toString(),
);
}
/// Check if an executable exists in PATH or local directory
static Future<bool> isExecutableAvailable(String name) async {
// First check if it's in PATH
final pathExecutable = await _lookupExecutableInSystemPath(name);
if (pathExecutable != null) {
return true;
}
// Check local executables directory
final localExecutable = _getLocalExecutablePath(name);
if (await localExecutable.exists()) {
return true;
}
return false;
}
/// Get the path to an executable (either in PATH or local)
static Future<String> getExecutablePath(String name) async {
// First check if it's in PATH
final pathExecutable = await _lookupExecutableInSystemPath(name);
if (pathExecutable != null) {
return pathExecutable;
}
// Check local executables directory
final localExecutable = _getLocalExecutablePath(name);
if (await localExecutable.exists()) {
return localExecutable.path;
}
throw ExecutableException('Executable $name not found in PATH or local directory');
}
/// Download an executable if it's not available
static Future<String> ensureExecutable(String name) async {
if (await isExecutableAvailable(name)) {
return await getExecutablePath(name);
}
throw ExecutableException('Executable "$name" not found and automatic installation is not implemented');
}
/// Remove a local executable
static Future<void> removeExecutable(String name) async {
final localExecutable = _getLocalExecutablePath(name);
if (await localExecutable.exists()) {
await localExecutable.delete();
Loggers.app.info('Removed local executable: $name');
}
}
/// List all locally downloaded executables
static Future<List<String>> listLocalExecutables() async {
if (!await _executablesDir.exists()) {
return [];
}
final executables = <String>[];
await for (final entity in _executablesDir.list()) {
if (entity is File && _isExecutable(entity)) {
executables.add(path.basenameWithoutExtension(entity.path));
}
}
return executables;
}
/// Get the size of a local executable
static Future<int> getExecutableSize(String name) async {
final localExecutable = _getLocalExecutablePath(name);
if (await localExecutable.exists()) {
return await localExecutable.length();
}
return 0;
}
/// Get the version of an executable
static Future<String?> getExecutableVersion(String name) async {
try {
final executablePath = await getExecutablePath(name);
// Try common version flags
final versionFlags = ['--version', '-v', '-V', 'version'];
for (final flag in versionFlags) {
try {
final result = await Process.run(executablePath, [flag]);
if (result.exitCode == 0) {
final output = result.stdout.toString().trim();
if (output.isNotEmpty) {
return output.split('\n').first; // Return first line only
}
}
} catch (e) {
// Try next flag
}
}
} catch (e) {
Loggers.app.warning('Error getting version for $name: $e');
}
return null;
}
/// Validate an executable by trying to run it with a help flag
static Future<bool> validateExecutable(String name) async {
try {
final executablePath = await getExecutablePath(name);
// Try to run the executable with a help flag
final helpFlags = ['--help', '-h', '-help'];
for (final flag in helpFlags) {
try {
final result = await Process.run(executablePath, [flag]);
if (result.exitCode == 0 || result.exitCode == 1) {
// Help often returns 1
return true;
}
} catch (e) {
// Try next flag
}
}
} catch (e) {
Loggers.app.warning('Error validating $name: $e');
return false;
}
return false;
}
static Future<String?> _lookupExecutableInSystemPath(String name) async {
final command = Platform.isWindows ? 'where' : 'which';
try {
final result = await Process.run(command, [name]);
if (result.exitCode != 0) {
return null;
}
final stdoutString = result.stdout.toString().trim();
if (stdoutString.isEmpty) {
return null;
}
final candidate = stdoutString
.split('\n')
.map((line) => line.trim())
.firstWhere((line) => line.isNotEmpty, orElse: () => '');
if (candidate.isEmpty) {
return null;
}
return candidate;
} catch (e) {
Loggers.app.warning('Error checking PATH for $name: $e');
return null;
}
}
/// Get the local path for an executable
static File _getLocalExecutablePath(String name) {
final extension = Platform.isWindows ? '.exe' : '';
return File(path.join(_executablesDir.path, '$name$extension'));
}
/// Check if a file is executable
static bool _isExecutable(File file) {
if (Platform.isWindows) {
return file.path.endsWith('.exe');
} else {
// Check file permissions
final stat = file.statSync();
return (stat.mode & _posixExecuteBitsMask) != 0; // Check execute bits
}
}
/// Get predefined executable info
static ExecutableInfo? getExecutableInfo(String name) {
_ensureCustomExecutablesLoaded();
return _predefinedExecutables[name];
}
/// Add a custom executable definition
static void addCustomExecutable(String name, ExecutableInfo info) {
_ensureCustomExecutablesLoaded();
_customExecutables[name] = info;
_predefinedExecutables[name] = info;
_persistCustomExecutables();
Loggers.app.info('Adding custom executable: $name');
}
/// Remove a custom executable definition
static void removeCustomExecutable(String name) {
_ensureCustomExecutablesLoaded();
final removed = _customExecutables.remove(name);
if (removed != null) {
_predefinedExecutables.remove(name);
_persistCustomExecutables();
Loggers.app.info('Removing custom executable: $name');
}
}
}

View File

@@ -0,0 +1,26 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart';
import 'package:server_box/core/utils/server.dart';
import 'package:server_box/core/utils/ssh_auth.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/res/store.dart';
Future<bool> ensureHostKeyAcceptedForSftp(BuildContext context, Spi spi) async {
final known = Stores.setting.sshKnownHostFingerprints.get();
final hostId = spi.id.isNotEmpty ? spi.id : spi.oldId;
final prefix = '$hostId::';
if (known.keys.any((key) => key.startsWith(prefix))) {
return true;
}
final (result, error) = await context.showLoadingDialog<bool>(
fn: () async {
await ensureKnownHostKey(
spi,
onKeyboardInteractive: (_) => KeybordInteractive.defaultHandle(spi, ctx: context),
);
return true;
},
);
return error == null && result == true;
}

View File

@@ -0,0 +1,300 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart';
import 'package:server_box/core/utils/executable_manager.dart';
import 'package:server_box/core/utils/proxy_socket.dart';
import 'package:server_box/data/model/server/proxy_command_config.dart';
import 'package:server_box/data/store/setting.dart';
/// Exception thrown when proxy command execution fails
class ProxyCommandException implements Exception {
final String message;
final int? exitCode;
final String? stdout;
final String? stderr;
ProxyCommandException({required this.message, this.exitCode, this.stdout, this.stderr});
@override
String toString() {
return 'ProxyCommandException: $message'
'${exitCode != null ? ' (exit code: $exitCode)' : ''}'
'${stderr != null ? '\nStderr: $stderr' : ''}';
}
}
/// Generic proxy command executor that handles SSH ProxyCommand functionality
abstract final class ProxyCommandExecutor {
static final Map<String, ProxyCommandConfig> _customPresets = {};
static bool _customPresetsLoaded = false;
static void _ensureCustomPresetsLoaded() {
if (_customPresetsLoaded) return;
final List<dynamic> stored = SettingStore.instance.proxyCmdCustomPresets.get();
for (final raw in stored) {
final preset = _parsePreset(raw);
if (preset == null) continue;
_customPresets[preset.key] = preset.value;
}
_customPresetsLoaded = true;
}
static void _persistCustomPresets() {
final list = _customPresets.entries
.map((entry) => {
'name': entry.key,
'config': entry.value.toJson(),
})
.toList();
SettingStore.instance.proxyCmdCustomPresets.set(list);
}
static MapEntry<String, ProxyCommandConfig>? _parsePreset(dynamic raw) {
dynamic payload = raw;
if (payload is String) {
try {
payload = jsonDecode(payload);
} catch (e) {
Loggers.app.warning('Failed to decode custom proxy preset entry: $e');
return null;
}
}
if (payload is! Map) return null;
final name = payload['name']?.toString();
final configRaw = payload['config'];
if (name == null || name.isEmpty || configRaw is! Map) return null;
try {
final config = ProxyCommandConfig.fromJson(Map<String, dynamic>.from(configRaw));
return MapEntry(name, config);
} catch (e) {
Loggers.app.warning('Failed to parse custom proxy preset "$name": $e');
return null;
}
}
/// Execute a proxy command and return a socket connected through the proxy
static Future<SSHSocket> executeProxyCommand(
ProxyCommandConfig config, {
required String hostname,
required int port,
required String user,
}) async {
if (Platform.isIOS) {
throw ProxyCommandException(message: 'ProxyCommand is not supported on iOS');
}
final finalCommand = config.getFinalCommand(hostname: hostname, port: port, user: user);
final tokens = _tokenizeCommand(finalCommand);
if (tokens.isEmpty) {
throw ProxyCommandException(message: 'ProxyCommand resolved to an empty command');
}
final executableToken = tokens.first;
Loggers.app.info('Executing proxy command: $finalCommand');
// Ensure executable is available if required
String executablePath;
if (config.requiresExecutable && config.executableName != null) {
executablePath = await ExecutableManager.ensureExecutable(config.executableName!);
} else {
executablePath = await ExecutableManager.getExecutablePath(executableToken);
}
// Parse command and arguments
final args = tokens.skip(1).toList();
// Set up environment
final environment = {...Platform.environment, ...?config.environment};
// Start the process
Process process;
try {
process = await Process.start(
executablePath,
args,
workingDirectory: config.workingDirectory,
environment: environment,
);
} catch (e) {
throw ProxyCommandException(message: 'Failed to start proxy command: $e', exitCode: -1);
}
// Set up timeout handling
var timedOut = false;
final timeoutTimer = Timer(config.timeout, () {
timedOut = true;
process.kill();
});
try {
// For ProxyCommand, we create a ProxySocket that wraps the process
final socket = ProxySocket(process);
// Monitor the process for immediate failures
unawaited(
process.exitCode.then((code) {
if (code != 0 && !socket.closed && !timedOut) {
socket.close();
}
}),
);
return socket;
} catch (e) {
process.kill();
rethrow;
} finally {
timeoutTimer.cancel();
}
}
/// Validate proxy command configuration
static Future<String?> validateConfig(ProxyCommandConfig config) async {
if (Platform.isIOS) {
return 'ProxyCommand is not supported on iOS';
}
final testCommand = config.getFinalCommand(hostname: 'test.example.com', port: 22, user: 'testuser');
late final List<String> tokens;
try {
tokens = _tokenizeCommand(testCommand);
} on ProxyCommandException catch (e) {
return e.message;
}
if (tokens.isEmpty) {
return 'Proxy command must not be empty';
}
// Check if required placeholders are present
if (!config.command.contains('%h')) {
return 'Proxy command must contain %h (hostname) placeholder';
}
String executablePath;
// If executable is required, check if it exists and reuse resolved path
if (config.requiresExecutable && config.executableName != null) {
try {
executablePath = await ExecutableManager.ensureExecutable(config.executableName!);
} catch (e) {
return e.toString();
}
} else {
try {
executablePath = await ExecutableManager.getExecutablePath(tokens.first);
} catch (e) {
return e.toString();
}
}
// Try to validate command syntax (dry run)
try {
await Process.run(executablePath, ['--help']);
} catch (e) {
return 'Command validation failed: $e';
}
return null; // No error
}
/// Get available proxy command presets
static Map<String, ProxyCommandConfig> getPresets() {
_ensureCustomPresetsLoaded();
return {
...proxyCommandPresets,
..._customPresets,
};
}
/// Add a custom preset
static Future<void> addCustomPreset(String name, ProxyCommandConfig config) async {
_ensureCustomPresetsLoaded();
_customPresets[name] = config;
_persistCustomPresets();
Loggers.app.info('Adding custom proxy preset: $name');
}
/// Remove a custom preset
static Future<void> removeCustomPreset(String name) async {
_ensureCustomPresetsLoaded();
final removed = _customPresets.remove(name);
if (removed != null) {
_persistCustomPresets();
Loggers.app.info('Removing custom proxy preset: $name');
}
}
static List<String> tokenizeCommand(String command) => _tokenizeCommand(command);
static List<String> _tokenizeCommand(String command) {
final tokens = <String>[];
final buffer = StringBuffer();
String? quote;
var escaped = false;
void flush() {
if (buffer.isEmpty) return;
tokens.add(buffer.toString());
buffer.clear();
}
for (final rune in command.runes) {
final char = String.fromCharCode(rune);
if (escaped) {
buffer.write(char);
escaped = false;
continue;
}
if (quote != null) {
if (char == '\\' && quote == '"') {
escaped = true;
continue;
}
if (char == quote) {
quote = null;
continue;
}
buffer.write(char);
continue;
}
if (char == '\\') {
escaped = true;
continue;
}
if (char == '"' || char == "'") {
quote = char;
continue;
}
if (char.trim().isEmpty) {
flush();
continue;
}
buffer.write(char);
}
if (quote != null) {
throw ProxyCommandException(message: 'ProxyCommand has unmatched quote');
}
if (escaped) {
throw ProxyCommandException(message: 'ProxyCommand ends with an incomplete escape sequence');
}
flush();
return tokens;
}
}

View File

@@ -0,0 +1,125 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart';
/// Socket implementation that communicates through a Process stdin/stdout
/// This is used for ProxyCommand functionality where the SSH connection
/// is proxied through an external command
class ProxySocket implements SSHSocket {
final Process _process;
final StreamController<Uint8List> _incomingController =
StreamController<Uint8List>();
final StreamController<List<int>> _outgoingController =
StreamController<List<int>>();
final Completer<void> _doneCompleter = Completer<void>();
bool _closed = false;
late StreamSubscription<Uint8List> _stdoutSubscription;
late StreamSubscription<Uint8List> _stderrSubscription;
ProxySocket(this._process) {
// Set up stdout reading
_stdoutSubscription = _process.stdout
.transform(Uint8ListStreamTransformer())
.listen(_onIncomingData,
onError: _onError,
onDone: _onProcessDone,
cancelOnError: true);
// Set up stderr reading (for logging)
_stderrSubscription = _process.stderr
.transform(Uint8ListStreamTransformer())
.listen((data) {
Loggers.app.warning('Proxy stderr: ${String.fromCharCodes(data)}');
});
// Set up outgoing data
_outgoingController.stream.listen(_onOutgoingData);
// Handle process exit
_process.exitCode.then((code) {
if (!_closed && code != 0) {
_onError('Proxy process exited with code: $code');
}
});
}
@override
Stream<Uint8List> get stream => _incomingController.stream;
@override
StreamSink<List<int>> get sink => _outgoingController.sink;
@override
Future<void> get done => _doneCompleter.future;
/// Check if the socket is closed
bool get closed => _closed;
@override
Future<void> close() async {
if (_closed) return;
_closed = true;
await _stdoutSubscription.cancel();
await _stderrSubscription.cancel();
await _outgoingController.close();
await _incomingController.close();
if (!_doneCompleter.isCompleted) {
_doneCompleter.complete();
}
// Kill the process if it's still running
try {
_process.kill();
} catch (e) {
Loggers.app.warning('Error killing proxy process: $e');
}
}
@override
void destroy() {
close();
}
void _onIncomingData(Uint8List data) {
if (!_closed) {
_incomingController.add(data);
}
}
void _onOutgoingData(List<int> data) {
if (!_closed) {
_process.stdin.add(data);
}
}
void _onError(dynamic error, [StackTrace? stackTrace]) {
if (!_closed) {
_incomingController.addError(error, stackTrace);
close();
}
}
void _onProcessDone() {
if (!_closed) {
_incomingController.close();
close();
}
}
}
/// Transformer to convert `Stream<List<int>>` to `Stream<Uint8List>`
class Uint8ListStreamTransformer
extends StreamTransformerBase<List<int>, Uint8List> {
const Uint8ListStreamTransformer();
@override
Stream<Uint8List> bind(Stream<List<int>> stream) {
return stream.map((data) => Uint8List.fromList(data));
}
}

View File

@@ -1,8 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:server_box/core/app_navigator.dart';
import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/utils/proxy_command_executor.dart';
import 'package:server_box/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
@@ -29,7 +35,7 @@ enum GenSSHClientStatus { socket, key, pwd }
String getPrivateKey(String id) { String getPrivateKey(String id) {
final pki = Stores.key.fetchOne(id); final pki = Stores.key.fetchOne(id);
if (pki == null) { if (pki == null) {
throw SSHErr(type: SSHErrType.noPrivateKey, message: 'key [$id] not found'); throw SSHErr(type: SSHErrType.noPrivateKey, message: l10n.privateKeyNotFoundFmt(id));
} }
return pki.key; return pki.key;
} }
@@ -52,13 +58,19 @@ Future<SSHClient> genClient(
/// Handle keyboard-interactive authentication /// Handle keyboard-interactive authentication
SSHUserInfoRequestHandler? onKeyboardInteractive, SSHUserInfoRequestHandler? onKeyboardInteractive,
Map<String, String>? knownHostFingerprints,
void Function(String storageKey, String fingerprintHex)? onHostKeyAccepted,
Future<bool> Function(HostKeyPromptInfo info)? onHostKeyPrompt,
}) async { }) async {
onStatus?.call(GenSSHClientStatus.socket); onStatus?.call(GenSSHClientStatus.socket);
final hostKeyCache = Map<String, String>.from(knownHostFingerprints ?? _loadKnownHostFingerprints());
final hostKeyPersist = onHostKeyAccepted ?? _persistHostKeyFingerprint;
final hostKeyPrompt = onHostKeyPrompt ?? _defaultHostKeyPrompt;
String? alterUser; String? alterUser;
final socket = await () async { // Check for Jump Server first - this needs special handling
// Proxy
final jumpSpi_ = () { final jumpSpi_ = () {
// Multi-thread or key login // Multi-thread or key login
if (jumpSpi != null) return jumpSpi; if (jumpSpi != null) return jumpSpi;
@@ -66,27 +78,102 @@ Future<SSHClient> genClient(
if (spi.jumpId != null) return Stores.server.box.get(spi.jumpId); if (spi.jumpId != null) return Stores.server.box.get(spi.jumpId);
}(); }();
if (jumpSpi_ != null) { if (jumpSpi_ != null) {
final jumpClient = await genClient(jumpSpi_, privateKey: jumpPrivateKey, timeout: timeout); // For jump server, we establish connection through the jump client
final jumpClient = await genClient(
jumpSpi_,
privateKey: jumpPrivateKey,
timeout: timeout,
knownHostFingerprints: hostKeyCache,
onHostKeyAccepted: hostKeyPersist,
onHostKeyPrompt: onHostKeyPrompt,
);
return await jumpClient.forwardLocal(spi.ip, spi.port); final forwardChannel = await jumpClient.forwardLocal(spi.ip, spi.port);
}
// Direct final hostKeyVerifier = _HostKeyVerifier(
try { spi: spi,
return await SSHSocket.connect(spi.ip, spi.port, timeout: timeout); cache: hostKeyCache,
} catch (e) { persistCallback: hostKeyPersist,
Loggers.app.warning('genClient', e); prompt: hostKeyPrompt,
if (spi.alterUrl == null) rethrow; );
try {
final res = spi.parseAlterUrl(); final keyId = spi.keyId;
alterUser = res.$2; if (keyId == null) {
return await SSHSocket.connect(res.$1, res.$3, timeout: timeout); onStatus?.call(GenSSHClientStatus.pwd);
} catch (e) { return SSHClient(
Loggers.app.warning('genClient alterUrl', e); forwardChannel,
rethrow; username: spi.user,
onPasswordRequest: () => spi.pwd,
onUserInfoRequest: onKeyboardInteractive,
onVerifyHostKey: hostKeyVerifier.call,
);
} }
privateKey ??= getPrivateKey(keyId);
onStatus?.call(GenSSHClientStatus.key);
return SSHClient(
forwardChannel,
username: spi.user,
identities: await compute(loadIndentity, privateKey),
onUserInfoRequest: onKeyboardInteractive,
onVerifyHostKey: hostKeyVerifier.call,
);
} }
}();
// For ProxyCommand and direct connections, get SSHSocket
SSHSocket? socket;
try {
final proxyCommand = spi.proxyCommand;
// ProxyCommand support - Check for ProxyCommand configuration first
if (proxyCommand != null && !Platform.isIOS) {
try {
Loggers.app.info('Connecting via ProxyCommand: ${proxyCommand.command}');
socket = await ProxyCommandExecutor.executeProxyCommand(
proxyCommand,
hostname: spi.ip,
port: spi.port,
user: spi.user,
);
} catch (e) {
Loggers.app.warning('ProxyCommand failed', e);
if (!proxyCommand.retryOnFailure) {
rethrow;
}
// If retry is enabled, fall through to direct connection
Loggers.app.info('ProxyCommand failed, falling back to direct connection');
}
} else if (proxyCommand != null && Platform.isIOS) {
Loggers.app.info('ProxyCommand configuration is ignored on iOS');
}
// Direct connection (or fallback)
socket ??= await () async {
try {
return await SSHSocket.connect(spi.ip, spi.port, timeout: timeout);
} catch (e) {
Loggers.app.warning('genClient', e);
if (spi.alterUrl == null) rethrow;
try {
final res = spi.parseAlterUrl();
alterUser = res.$2;
return await SSHSocket.connect(res.$1, res.$3, timeout: timeout);
} catch (e) {
Loggers.app.warning('genClient alterUrl', e);
rethrow;
}
}
}();
} catch (e) {
Loggers.app.warning('Failed to establish connection', e);
rethrow;
}
final hostKeyVerifier = _HostKeyVerifier(
spi: spi,
cache: hostKeyCache,
persistCallback: hostKeyPersist,
prompt: hostKeyPrompt,
);
final keyId = spi.keyId; final keyId = spi.keyId;
if (keyId == null) { if (keyId == null) {
@@ -96,8 +183,7 @@ Future<SSHClient> genClient(
username: alterUser ?? spi.user, username: alterUser ?? spi.user,
onPasswordRequest: () => spi.pwd, onPasswordRequest: () => spi.pwd,
onUserInfoRequest: onKeyboardInteractive, onUserInfoRequest: onKeyboardInteractive,
// printDebug: debugPrint, onVerifyHostKey: hostKeyVerifier.call,
// printTrace: debugPrint,
); );
} }
privateKey ??= getPrivateKey(keyId); privateKey ??= getPrivateKey(keyId);
@@ -106,10 +192,220 @@ Future<SSHClient> genClient(
return SSHClient( return SSHClient(
socket, socket,
username: spi.user, username: spi.user,
// Must use [compute] here, instead of [Computer.shared.start]
identities: await compute(loadIndentity, privateKey), identities: await compute(loadIndentity, privateKey),
onUserInfoRequest: onKeyboardInteractive, onUserInfoRequest: onKeyboardInteractive,
// printDebug: debugPrint, onVerifyHostKey: hostKeyVerifier.call,
// printTrace: debugPrint,
); );
} }
typedef _HostKeyPersistCallback = void Function(String storageKey, String fingerprintHex);
class HostKeyPromptInfo {
HostKeyPromptInfo({
required this.spi,
required this.keyType,
required this.fingerprintHex,
required this.fingerprintBase64,
required this.isMismatch,
this.previousFingerprintHex,
});
final Spi spi;
final String keyType;
final String fingerprintHex;
final String fingerprintBase64;
final bool isMismatch;
final String? previousFingerprintHex;
}
class _HostKeyVerifier {
_HostKeyVerifier({
required this.spi,
required Map<String, String> cache,
required this.prompt,
this.persistCallback,
}) : _cache = cache;
final Spi spi;
final Map<String, String> _cache;
final _HostKeyPersistCallback? persistCallback;
final Future<bool> Function(HostKeyPromptInfo info) prompt;
Future<bool> call(String keyType, Uint8List fingerprintBytes) async {
final storageKey = _hostKeyStorageKey(spi, keyType);
final fingerprintHex = _fingerprintToHex(fingerprintBytes);
final fingerprintBase64 = _fingerprintToBase64(fingerprintBytes);
final existing = _cache[storageKey];
if (existing == null) {
final accepted = await prompt(
HostKeyPromptInfo(
spi: spi,
keyType: keyType,
fingerprintHex: fingerprintHex,
fingerprintBase64: fingerprintBase64,
isMismatch: false,
),
);
if (!accepted) {
Loggers.app.warning('User rejected new SSH host key for ${spi.name} ($keyType).');
return false;
}
_cache[storageKey] = fingerprintHex;
persistCallback?.call(storageKey, fingerprintHex);
Loggers.app.info('Trusted SSH host key for ${spi.name} ($keyType).');
return true;
}
if (existing == fingerprintHex) {
return true;
}
final accepted = await prompt(
HostKeyPromptInfo(
spi: spi,
keyType: keyType,
fingerprintHex: fingerprintHex,
fingerprintBase64: fingerprintBase64,
isMismatch: true,
previousFingerprintHex: existing,
),
);
if (!accepted) {
Loggers.app.warning(
'SSH host key mismatch for ${spi.name}',
'expected $existing but received $fingerprintHex ($keyType)',
);
return false;
}
_cache[storageKey] = fingerprintHex;
persistCallback?.call(storageKey, fingerprintHex);
Loggers.app.warning('Updated stored SSH host key for ${spi.name} ($keyType) after user confirmation.');
return true;
}
}
Map<String, String> _loadKnownHostFingerprints() {
try {
final prop = Stores.setting.sshKnownHostFingerprints;
return Map<String, String>.from(prop.get());
} catch (e, stack) {
Loggers.app.warning('Load SSH host key fingerprints failed', e, stack);
return <String, String>{};
}
}
void _persistHostKeyFingerprint(String storageKey, String fingerprintHex) {
try {
final prop = Stores.setting.sshKnownHostFingerprints;
final updated = Map<String, String>.from(prop.get());
if (updated[storageKey] == fingerprintHex) {
return;
}
updated[storageKey] = fingerprintHex;
prop.put(updated);
Loggers.app.info('Stored SSH host key fingerprint for $storageKey');
} catch (e, stack) {
Loggers.app.warning('Persist SSH host key fingerprint failed', e, stack);
}
}
Future<bool> _defaultHostKeyPrompt(HostKeyPromptInfo info) async {
final ctx = AppNavigator.context;
if (ctx == null) {
Loggers.app.warning('Host key prompt skipped: navigator context unavailable.');
return false;
}
final hostLine = '${info.spi.user}@${info.spi.ip}:${info.spi.port}';
final description = info.isMismatch
? l10n.sshHostKeyChangedDesc(info.spi.name)
: l10n.sshHostKeyNewDesc(info.spi.name);
final result = await ctx.showRoundDialog<bool>(
title: libL10n.attention,
barrierDismiss: false,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(description),
const SizedBox(height: 12),
SelectableText('${l10n.server}: ${info.spi.name}'),
SelectableText('${libL10n.addr}: $hostLine'),
SelectableText('${l10n.sshHostKeyType}: ${info.keyType}'),
SelectableText(l10n.sshHostKeyFingerprintMd5Hex(info.fingerprintHex)),
SelectableText(l10n.sshHostKeyFingerprintMd5Base64(info.fingerprintBase64)),
if (info.previousFingerprintHex != null) ...[
const SizedBox(height: 12),
SelectableText(l10n.sshHostKeyStoredFingerprint(info.previousFingerprintHex!)),
],
],
),
actions: [
TextButton(onPressed: () => ctx.pop(false), child: Text(libL10n.cancel)),
TextButton(onPressed: () => ctx.pop(true), child: Text(libL10n.ok)),
],
);
return result ?? false;
}
Future<void> ensureKnownHostKey(
Spi spi, {
Duration timeout = const Duration(seconds: 5),
SSHUserInfoRequestHandler? onKeyboardInteractive,
}) async {
final cache = _loadKnownHostFingerprints();
if (_hasKnownHostFingerprintForSpi(spi, cache)) {
return;
}
final jumpSpi = spi.jumpId != null ? Stores.server.box.get(spi.jumpId) : null;
if (jumpSpi != null && !_hasKnownHostFingerprintForSpi(jumpSpi, cache)) {
await ensureKnownHostKey(
jumpSpi,
timeout: timeout,
onKeyboardInteractive: onKeyboardInteractive,
);
cache.addAll(_loadKnownHostFingerprints());
if (_hasKnownHostFingerprintForSpi(spi, cache)) return;
}
final client = await genClient(
spi,
timeout: timeout,
onKeyboardInteractive: onKeyboardInteractive,
knownHostFingerprints: cache,
);
try {
await client.authenticated;
} finally {
client.close();
}
}
bool _hasKnownHostFingerprintForSpi(Spi spi, Map<String, String> cache) {
final prefix = '${_hostIdentifier(spi)}::';
return cache.keys.any((key) => key.startsWith(prefix));
}
String _hostKeyStorageKey(Spi spi, String keyType) {
final base = _hostIdentifier(spi);
return '$base::$keyType';
}
String _hostIdentifier(Spi spi) => spi.id.isNotEmpty ? spi.id : spi.oldId;
String _fingerprintToHex(Uint8List fingerprint) {
final buffer = StringBuffer();
for (var i = 0; i < fingerprint.length; i++) {
if (i > 0) buffer.write(':');
buffer.write(fingerprint[i].toRadixString(16).padLeft(2, '0'));
}
return buffer.toString();
}
String _fingerprintToBase64(Uint8List fingerprint) => base64.encode(fingerprint);

View File

@@ -149,11 +149,28 @@ abstract final class SSHConfig {
/// Extract jump host from ProxyJump or ProxyCommand /// Extract jump host from ProxyJump or ProxyCommand
static String? _extractJumpHost(String value) { static String? _extractJumpHost(String value) {
// For ProxyJump, the format is usually: user@host:port // Normalize whitespace
// For ProxyCommand, it's more complex and might need custom parsing final parts = value.trim().split(RegExp(r'\s+'));
if (value.contains('@')) {
return value.split(' ').first; // Try to find a token that looks like a user@host[:port]
// This covers common patterns like:
// - ProxyJump user@host
// - ProxyCommand ssh -W %h:%p user@host
for (final token in parts) {
if (token.contains('@')) {
// Strip any surrounding quotes just in case
var cleaned = token;
if ((cleaned.startsWith("'") && cleaned.endsWith("'")) ||
(cleaned.startsWith('"') && cleaned.endsWith('"'))) {
cleaned = cleaned.substring(1, cleaned.length - 1);
}
return cleaned;
}
} }
// ProxyJump may also be provided as just a hostname (no user@)
// In that case we don't have enough information to build an oldId-style reference,
// so we ignore it here and let the user configure a jump server manually.
return null; return null;
} }

View File

@@ -9,8 +9,8 @@ class SystemDetector {
/// ///
/// First checks if a custom system type is configured in [spi]. /// First checks if a custom system type is configured in [spi].
/// If not, attempts to detect the system by running commands: /// If not, attempts to detect the system by running commands:
/// 1. 'ver' command to detect Windows /// 1. 'uname -a' command to detect Linux/BSD/Darwin
/// 2. 'uname -a' command to detect Linux/BSD/Darwin /// 2. 'ver' command to detect Windows (if uname fails)
/// ///
/// Returns [SystemType.linux] as default if detection fails. /// Returns [SystemType.linux] as default if detection fails.
static Future<SystemType> detect(SSHClient client, Spi spi) async { static Future<SystemType> detect(SSHClient client, Spi spi) async {
@@ -22,17 +22,8 @@ class SystemDetector {
} }
try { try {
// Try to detect Windows systems first (more reliable detection) // Try to detect Unix/Linux/BSD systems first (more reliable and doesn't create files)
final powershellResult = await client.run('ver 2>nul').string; final unixResult = await client.run('uname -a 2>/dev/null').string;
if (powershellResult.isNotEmpty &&
(powershellResult.contains('Windows') || powershellResult.contains('NT'))) {
detectedSystemType = SystemType.windows;
dprint('Detected Windows system type for ${spi.oldId}');
return detectedSystemType;
}
// Try to detect Unix/Linux/BSD systems
final unixResult = await client.run('uname -a').string;
if (unixResult.contains('Linux')) { if (unixResult.contains('Linux')) {
detectedSystemType = SystemType.linux; detectedSystemType = SystemType.linux;
dprint('Detected Linux system type for ${spi.oldId}'); dprint('Detected Linux system type for ${spi.oldId}');
@@ -42,6 +33,15 @@ class SystemDetector {
dprint('Detected BSD system type for ${spi.oldId}'); dprint('Detected BSD system type for ${spi.oldId}');
return detectedSystemType; return detectedSystemType;
} }
// If uname fails, try to detect Windows systems
final powershellResult = await client.run('ver 2>nul').string;
if (powershellResult.isNotEmpty &&
(powershellResult.contains('Windows') || powershellResult.contains('NT'))) {
detectedSystemType = SystemType.windows;
dprint('Detected Windows system type for ${spi.oldId}');
return detectedSystemType;
}
} catch (e) { } catch (e) {
Loggers.app.warning('System detection failed for ${spi.oldId}: $e'); Loggers.app.warning('System detection failed for ${spi.oldId}: $e');
} }

View File

@@ -0,0 +1,74 @@
import 'package:meta/meta.dart';
/// Chat message exchanged with the Ask AI service.
enum AskAiMessageRole { user, assistant }
@immutable
class AskAiMessage {
const AskAiMessage({
required this.role,
required this.content,
});
final AskAiMessageRole role;
final String content;
String get apiRole {
switch (role) {
case AskAiMessageRole.user:
return 'user';
case AskAiMessageRole.assistant:
return 'assistant';
}
}
}
/// Recommended command returned by the AI tool call.
@immutable
class AskAiCommand {
const AskAiCommand({
required this.command,
this.description = '',
this.toolName,
});
final String command;
final String description;
final String? toolName;
}
@immutable
sealed class AskAiEvent {
const AskAiEvent();
}
/// Incremental text delta emitted while streaming the AI response.
class AskAiContentDelta extends AskAiEvent {
const AskAiContentDelta(this.delta);
final String delta;
}
/// Emits when a tool call returns a runnable command suggestion.
class AskAiToolSuggestion extends AskAiEvent {
const AskAiToolSuggestion(this.command);
final AskAiCommand command;
}
/// Signals that the stream finished successfully.
class AskAiCompleted extends AskAiEvent {
const AskAiCompleted({
required this.fullText,
required this.commands,
});
final String fullText;
final List<AskAiCommand> commands;
}
/// Signals that the stream terminated with an error before completion.
class AskAiStreamError extends AskAiEvent {
const AskAiStreamError(this.error, this.stackTrace);
final Object error;
final StackTrace? stackTrace;
}

View File

@@ -55,9 +55,9 @@ abstract class BackupV2 with _$BackupV2 implements Mergeable {
await Mergeable.mergeStore(backupData: history, store: Stores.history, force: force); await Mergeable.mergeStore(backupData: history, store: Stores.history, force: force);
await Mergeable.mergeStore(backupData: settings, store: Stores.setting, force: force); await Mergeable.mergeStore(backupData: settings, store: Stores.setting, force: force);
if (serverChanged) GlobalRef.gRef?.read(serversNotifierProvider.notifier).reload(); if (serverChanged) GlobalRef.gRef?.read(serversProvider.notifier).reload();
if (snippetChanged) GlobalRef.gRef?.read(snippetNotifierProvider.notifier).reload(); if (snippetChanged) GlobalRef.gRef?.read(snippetProvider.notifier).reload();
if (keyChanged) GlobalRef.gRef?.read(privateKeyNotifierProvider.notifier).reload(); if (keyChanged) GlobalRef.gRef?.read(privateKeyProvider.notifier).reload();
_loggerV2.info('Merge completed'); _loggerV2.info('Merge completed');
} }

View File

@@ -37,12 +37,12 @@ final class PodmanImg implements ContainerImg {
String toRawJson() => json.encode(toJson()); String toRawJson() => json.encode(toJson());
factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg( factory PodmanImg.fromJson(Map<String, dynamic> json) => PodmanImg(
repository: json['repository'], repository: _asString(json['repository']),
tag: json['tag'], tag: _asString(json['tag']),
id: json['Id'], id: _asString(json['Id']),
created: json['Created'], created: _asInt(json['Created']),
size: json['Size'], size: _asInt(json['Size']),
containers: json['Containers'], containers: _asInt(json['Containers']),
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@@ -119,3 +119,16 @@ final class DockerImg implements ContainerImg {
'Tag': tag, 'Tag': tag,
}; };
} }
String? _asString(dynamic val) {
if (val == null) return null;
if (val is String) return val;
return val.toString();
}
int? _asInt(dynamic val) {
if (val == null) return null;
if (val is int) return val;
if (val is double) return val.toInt();
return int.tryParse(val.toString());
}

View File

@@ -20,11 +20,11 @@ ServerCustom _$ServerCustomFromJson(Map<String, dynamic> json) => ServerCustom(
Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) => Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) =>
<String, dynamic>{ <String, dynamic>{
if (instance.pveAddr case final value?) 'pveAddr': value, 'pveAddr': ?instance.pveAddr,
'pveIgnoreCert': instance.pveIgnoreCert, 'pveIgnoreCert': instance.pveIgnoreCert,
if (instance.cmds case final value?) 'cmds': value, 'cmds': ?instance.cmds,
if (instance.preferTempDev case final value?) 'preferTempDev': value, 'preferTempDev': ?instance.preferTempDev,
if (instance.logoUrl case final value?) 'logoUrl': value, 'logoUrl': ?instance.logoUrl,
if (instance.netDev case final value?) 'netDev': value, 'netDev': ?instance.netDev,
if (instance.scriptDir case final value?) 'scriptDir': value, 'scriptDir': ?instance.scriptDir,
}; };

View File

@@ -0,0 +1,49 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'discovery_result.freezed.dart';
part 'discovery_result.g.dart';
@freezed
abstract class SshDiscoveryResult with _$SshDiscoveryResult {
const factory SshDiscoveryResult({
required String ip,
required int port,
String? banner,
@Default(false) bool isSelected,
}) = _SshDiscoveryResult;
factory SshDiscoveryResult.fromJson(Map<String, dynamic> json) => _$SshDiscoveryResultFromJson(json);
}
@freezed
abstract class SshDiscoveryReport with _$SshDiscoveryReport {
const factory SshDiscoveryReport({
required String generatedAt,
required int durationMs,
required int count,
required List<SshDiscoveryResult> items,
}) = _SshDiscoveryReport;
factory SshDiscoveryReport.fromJson(Map<String, dynamic> json) => _$SshDiscoveryReportFromJson(json);
}
@freezed
abstract class SshDiscoveryConfig with _$SshDiscoveryConfig {
const factory SshDiscoveryConfig({
@Default(700) int timeoutMs,
@Default(128) int maxConcurrency,
@Default(false) bool enableMdns,
@Default(4096) int hostEnumerationLimit,
}) = _SshDiscoveryConfig;
}
extension SshDiscoveryConfigX on SshDiscoveryConfig {
List<String> toArgs() {
final args = <String>[];
args.add('--timeout-ms=$timeoutMs');
args.add('--max-concurrency=$maxConcurrency');
args.add('--host-enumeration-limit=$hostEnumerationLimit');
if (enableMdns) args.add('--enable-mdns');
return args;
}
}

View File

@@ -0,0 +1,830 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'discovery_result.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SshDiscoveryResult {
String get ip; int get port; String? get banner; bool get isSelected;
/// Create a copy of SshDiscoveryResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SshDiscoveryResultCopyWith<SshDiscoveryResult> get copyWith => _$SshDiscoveryResultCopyWithImpl<SshDiscoveryResult>(this as SshDiscoveryResult, _$identity);
/// Serializes this SshDiscoveryResult to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SshDiscoveryResult&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.banner, banner) || other.banner == banner)&&(identical(other.isSelected, isSelected) || other.isSelected == isSelected));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,ip,port,banner,isSelected);
@override
String toString() {
return 'SshDiscoveryResult(ip: $ip, port: $port, banner: $banner, isSelected: $isSelected)';
}
}
/// @nodoc
abstract mixin class $SshDiscoveryResultCopyWith<$Res> {
factory $SshDiscoveryResultCopyWith(SshDiscoveryResult value, $Res Function(SshDiscoveryResult) _then) = _$SshDiscoveryResultCopyWithImpl;
@useResult
$Res call({
String ip, int port, String? banner, bool isSelected
});
}
/// @nodoc
class _$SshDiscoveryResultCopyWithImpl<$Res>
implements $SshDiscoveryResultCopyWith<$Res> {
_$SshDiscoveryResultCopyWithImpl(this._self, this._then);
final SshDiscoveryResult _self;
final $Res Function(SshDiscoveryResult) _then;
/// Create a copy of SshDiscoveryResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? ip = null,Object? port = null,Object? banner = freezed,Object? isSelected = null,}) {
return _then(_self.copyWith(
ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
as String,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
as int,banner: freezed == banner ? _self.banner : banner // ignore: cast_nullable_to_non_nullable
as String?,isSelected: null == isSelected ? _self.isSelected : isSelected // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [SshDiscoveryResult].
extension SshDiscoveryResultPatterns on SshDiscoveryResult {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SshDiscoveryResult value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SshDiscoveryResult() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SshDiscoveryResult value) $default,){
final _that = this;
switch (_that) {
case _SshDiscoveryResult():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SshDiscoveryResult value)? $default,){
final _that = this;
switch (_that) {
case _SshDiscoveryResult() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String ip, int port, String? banner, bool isSelected)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SshDiscoveryResult() when $default != null:
return $default(_that.ip,_that.port,_that.banner,_that.isSelected);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String ip, int port, String? banner, bool isSelected) $default,) {final _that = this;
switch (_that) {
case _SshDiscoveryResult():
return $default(_that.ip,_that.port,_that.banner,_that.isSelected);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String ip, int port, String? banner, bool isSelected)? $default,) {final _that = this;
switch (_that) {
case _SshDiscoveryResult() when $default != null:
return $default(_that.ip,_that.port,_that.banner,_that.isSelected);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SshDiscoveryResult implements SshDiscoveryResult {
const _SshDiscoveryResult({required this.ip, required this.port, this.banner, this.isSelected = false});
factory _SshDiscoveryResult.fromJson(Map<String, dynamic> json) => _$SshDiscoveryResultFromJson(json);
@override final String ip;
@override final int port;
@override final String? banner;
@override@JsonKey() final bool isSelected;
/// Create a copy of SshDiscoveryResult
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SshDiscoveryResultCopyWith<_SshDiscoveryResult> get copyWith => __$SshDiscoveryResultCopyWithImpl<_SshDiscoveryResult>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SshDiscoveryResultToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SshDiscoveryResult&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.banner, banner) || other.banner == banner)&&(identical(other.isSelected, isSelected) || other.isSelected == isSelected));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,ip,port,banner,isSelected);
@override
String toString() {
return 'SshDiscoveryResult(ip: $ip, port: $port, banner: $banner, isSelected: $isSelected)';
}
}
/// @nodoc
abstract mixin class _$SshDiscoveryResultCopyWith<$Res> implements $SshDiscoveryResultCopyWith<$Res> {
factory _$SshDiscoveryResultCopyWith(_SshDiscoveryResult value, $Res Function(_SshDiscoveryResult) _then) = __$SshDiscoveryResultCopyWithImpl;
@override @useResult
$Res call({
String ip, int port, String? banner, bool isSelected
});
}
/// @nodoc
class __$SshDiscoveryResultCopyWithImpl<$Res>
implements _$SshDiscoveryResultCopyWith<$Res> {
__$SshDiscoveryResultCopyWithImpl(this._self, this._then);
final _SshDiscoveryResult _self;
final $Res Function(_SshDiscoveryResult) _then;
/// Create a copy of SshDiscoveryResult
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? ip = null,Object? port = null,Object? banner = freezed,Object? isSelected = null,}) {
return _then(_SshDiscoveryResult(
ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
as String,port: null == port ? _self.port : port // ignore: cast_nullable_to_non_nullable
as int,banner: freezed == banner ? _self.banner : banner // ignore: cast_nullable_to_non_nullable
as String?,isSelected: null == isSelected ? _self.isSelected : isSelected // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
mixin _$SshDiscoveryReport {
String get generatedAt; int get durationMs; int get count; List<SshDiscoveryResult> get items;
/// Create a copy of SshDiscoveryReport
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SshDiscoveryReportCopyWith<SshDiscoveryReport> get copyWith => _$SshDiscoveryReportCopyWithImpl<SshDiscoveryReport>(this as SshDiscoveryReport, _$identity);
/// Serializes this SshDiscoveryReport to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SshDiscoveryReport&&(identical(other.generatedAt, generatedAt) || other.generatedAt == generatedAt)&&(identical(other.durationMs, durationMs) || other.durationMs == durationMs)&&(identical(other.count, count) || other.count == count)&&const DeepCollectionEquality().equals(other.items, items));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,generatedAt,durationMs,count,const DeepCollectionEquality().hash(items));
@override
String toString() {
return 'SshDiscoveryReport(generatedAt: $generatedAt, durationMs: $durationMs, count: $count, items: $items)';
}
}
/// @nodoc
abstract mixin class $SshDiscoveryReportCopyWith<$Res> {
factory $SshDiscoveryReportCopyWith(SshDiscoveryReport value, $Res Function(SshDiscoveryReport) _then) = _$SshDiscoveryReportCopyWithImpl;
@useResult
$Res call({
String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items
});
}
/// @nodoc
class _$SshDiscoveryReportCopyWithImpl<$Res>
implements $SshDiscoveryReportCopyWith<$Res> {
_$SshDiscoveryReportCopyWithImpl(this._self, this._then);
final SshDiscoveryReport _self;
final $Res Function(SshDiscoveryReport) _then;
/// Create a copy of SshDiscoveryReport
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? generatedAt = null,Object? durationMs = null,Object? count = null,Object? items = null,}) {
return _then(_self.copyWith(
generatedAt: null == generatedAt ? _self.generatedAt : generatedAt // ignore: cast_nullable_to_non_nullable
as String,durationMs: null == durationMs ? _self.durationMs : durationMs // ignore: cast_nullable_to_non_nullable
as int,count: null == count ? _self.count : count // ignore: cast_nullable_to_non_nullable
as int,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
as List<SshDiscoveryResult>,
));
}
}
/// Adds pattern-matching-related methods to [SshDiscoveryReport].
extension SshDiscoveryReportPatterns on SshDiscoveryReport {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SshDiscoveryReport value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SshDiscoveryReport() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SshDiscoveryReport value) $default,){
final _that = this;
switch (_that) {
case _SshDiscoveryReport():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SshDiscoveryReport value)? $default,){
final _that = this;
switch (_that) {
case _SshDiscoveryReport() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SshDiscoveryReport() when $default != null:
return $default(_that.generatedAt,_that.durationMs,_that.count,_that.items);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items) $default,) {final _that = this;
switch (_that) {
case _SshDiscoveryReport():
return $default(_that.generatedAt,_that.durationMs,_that.count,_that.items);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items)? $default,) {final _that = this;
switch (_that) {
case _SshDiscoveryReport() when $default != null:
return $default(_that.generatedAt,_that.durationMs,_that.count,_that.items);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SshDiscoveryReport implements SshDiscoveryReport {
const _SshDiscoveryReport({required this.generatedAt, required this.durationMs, required this.count, required final List<SshDiscoveryResult> items}): _items = items;
factory _SshDiscoveryReport.fromJson(Map<String, dynamic> json) => _$SshDiscoveryReportFromJson(json);
@override final String generatedAt;
@override final int durationMs;
@override final int count;
final List<SshDiscoveryResult> _items;
@override List<SshDiscoveryResult> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
/// Create a copy of SshDiscoveryReport
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SshDiscoveryReportCopyWith<_SshDiscoveryReport> get copyWith => __$SshDiscoveryReportCopyWithImpl<_SshDiscoveryReport>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SshDiscoveryReportToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SshDiscoveryReport&&(identical(other.generatedAt, generatedAt) || other.generatedAt == generatedAt)&&(identical(other.durationMs, durationMs) || other.durationMs == durationMs)&&(identical(other.count, count) || other.count == count)&&const DeepCollectionEquality().equals(other._items, _items));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,generatedAt,durationMs,count,const DeepCollectionEquality().hash(_items));
@override
String toString() {
return 'SshDiscoveryReport(generatedAt: $generatedAt, durationMs: $durationMs, count: $count, items: $items)';
}
}
/// @nodoc
abstract mixin class _$SshDiscoveryReportCopyWith<$Res> implements $SshDiscoveryReportCopyWith<$Res> {
factory _$SshDiscoveryReportCopyWith(_SshDiscoveryReport value, $Res Function(_SshDiscoveryReport) _then) = __$SshDiscoveryReportCopyWithImpl;
@override @useResult
$Res call({
String generatedAt, int durationMs, int count, List<SshDiscoveryResult> items
});
}
/// @nodoc
class __$SshDiscoveryReportCopyWithImpl<$Res>
implements _$SshDiscoveryReportCopyWith<$Res> {
__$SshDiscoveryReportCopyWithImpl(this._self, this._then);
final _SshDiscoveryReport _self;
final $Res Function(_SshDiscoveryReport) _then;
/// Create a copy of SshDiscoveryReport
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? generatedAt = null,Object? durationMs = null,Object? count = null,Object? items = null,}) {
return _then(_SshDiscoveryReport(
generatedAt: null == generatedAt ? _self.generatedAt : generatedAt // ignore: cast_nullable_to_non_nullable
as String,durationMs: null == durationMs ? _self.durationMs : durationMs // ignore: cast_nullable_to_non_nullable
as int,count: null == count ? _self.count : count // ignore: cast_nullable_to_non_nullable
as int,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<SshDiscoveryResult>,
));
}
}
/// @nodoc
mixin _$SshDiscoveryConfig {
int get timeoutMs; int get maxConcurrency; bool get enableMdns; int get hostEnumerationLimit;
/// Create a copy of SshDiscoveryConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SshDiscoveryConfigCopyWith<SshDiscoveryConfig> get copyWith => _$SshDiscoveryConfigCopyWithImpl<SshDiscoveryConfig>(this as SshDiscoveryConfig, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SshDiscoveryConfig&&(identical(other.timeoutMs, timeoutMs) || other.timeoutMs == timeoutMs)&&(identical(other.maxConcurrency, maxConcurrency) || other.maxConcurrency == maxConcurrency)&&(identical(other.enableMdns, enableMdns) || other.enableMdns == enableMdns)&&(identical(other.hostEnumerationLimit, hostEnumerationLimit) || other.hostEnumerationLimit == hostEnumerationLimit));
}
@override
int get hashCode => Object.hash(runtimeType,timeoutMs,maxConcurrency,enableMdns,hostEnumerationLimit);
@override
String toString() {
return 'SshDiscoveryConfig(timeoutMs: $timeoutMs, maxConcurrency: $maxConcurrency, enableMdns: $enableMdns, hostEnumerationLimit: $hostEnumerationLimit)';
}
}
/// @nodoc
abstract mixin class $SshDiscoveryConfigCopyWith<$Res> {
factory $SshDiscoveryConfigCopyWith(SshDiscoveryConfig value, $Res Function(SshDiscoveryConfig) _then) = _$SshDiscoveryConfigCopyWithImpl;
@useResult
$Res call({
int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit
});
}
/// @nodoc
class _$SshDiscoveryConfigCopyWithImpl<$Res>
implements $SshDiscoveryConfigCopyWith<$Res> {
_$SshDiscoveryConfigCopyWithImpl(this._self, this._then);
final SshDiscoveryConfig _self;
final $Res Function(SshDiscoveryConfig) _then;
/// Create a copy of SshDiscoveryConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? timeoutMs = null,Object? maxConcurrency = null,Object? enableMdns = null,Object? hostEnumerationLimit = null,}) {
return _then(_self.copyWith(
timeoutMs: null == timeoutMs ? _self.timeoutMs : timeoutMs // ignore: cast_nullable_to_non_nullable
as int,maxConcurrency: null == maxConcurrency ? _self.maxConcurrency : maxConcurrency // ignore: cast_nullable_to_non_nullable
as int,enableMdns: null == enableMdns ? _self.enableMdns : enableMdns // ignore: cast_nullable_to_non_nullable
as bool,hostEnumerationLimit: null == hostEnumerationLimit ? _self.hostEnumerationLimit : hostEnumerationLimit // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [SshDiscoveryConfig].
extension SshDiscoveryConfigPatterns on SshDiscoveryConfig {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SshDiscoveryConfig value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SshDiscoveryConfig() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SshDiscoveryConfig value) $default,){
final _that = this;
switch (_that) {
case _SshDiscoveryConfig():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SshDiscoveryConfig value)? $default,){
final _that = this;
switch (_that) {
case _SshDiscoveryConfig() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SshDiscoveryConfig() when $default != null:
return $default(_that.timeoutMs,_that.maxConcurrency,_that.enableMdns,_that.hostEnumerationLimit);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit) $default,) {final _that = this;
switch (_that) {
case _SshDiscoveryConfig():
return $default(_that.timeoutMs,_that.maxConcurrency,_that.enableMdns,_that.hostEnumerationLimit);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit)? $default,) {final _that = this;
switch (_that) {
case _SshDiscoveryConfig() when $default != null:
return $default(_that.timeoutMs,_that.maxConcurrency,_that.enableMdns,_that.hostEnumerationLimit);case _:
return null;
}
}
}
/// @nodoc
class _SshDiscoveryConfig implements SshDiscoveryConfig {
const _SshDiscoveryConfig({this.timeoutMs = 700, this.maxConcurrency = 128, this.enableMdns = false, this.hostEnumerationLimit = 4096});
@override@JsonKey() final int timeoutMs;
@override@JsonKey() final int maxConcurrency;
@override@JsonKey() final bool enableMdns;
@override@JsonKey() final int hostEnumerationLimit;
/// Create a copy of SshDiscoveryConfig
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SshDiscoveryConfigCopyWith<_SshDiscoveryConfig> get copyWith => __$SshDiscoveryConfigCopyWithImpl<_SshDiscoveryConfig>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SshDiscoveryConfig&&(identical(other.timeoutMs, timeoutMs) || other.timeoutMs == timeoutMs)&&(identical(other.maxConcurrency, maxConcurrency) || other.maxConcurrency == maxConcurrency)&&(identical(other.enableMdns, enableMdns) || other.enableMdns == enableMdns)&&(identical(other.hostEnumerationLimit, hostEnumerationLimit) || other.hostEnumerationLimit == hostEnumerationLimit));
}
@override
int get hashCode => Object.hash(runtimeType,timeoutMs,maxConcurrency,enableMdns,hostEnumerationLimit);
@override
String toString() {
return 'SshDiscoveryConfig(timeoutMs: $timeoutMs, maxConcurrency: $maxConcurrency, enableMdns: $enableMdns, hostEnumerationLimit: $hostEnumerationLimit)';
}
}
/// @nodoc
abstract mixin class _$SshDiscoveryConfigCopyWith<$Res> implements $SshDiscoveryConfigCopyWith<$Res> {
factory _$SshDiscoveryConfigCopyWith(_SshDiscoveryConfig value, $Res Function(_SshDiscoveryConfig) _then) = __$SshDiscoveryConfigCopyWithImpl;
@override @useResult
$Res call({
int timeoutMs, int maxConcurrency, bool enableMdns, int hostEnumerationLimit
});
}
/// @nodoc
class __$SshDiscoveryConfigCopyWithImpl<$Res>
implements _$SshDiscoveryConfigCopyWith<$Res> {
__$SshDiscoveryConfigCopyWithImpl(this._self, this._then);
final _SshDiscoveryConfig _self;
final $Res Function(_SshDiscoveryConfig) _then;
/// Create a copy of SshDiscoveryConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? timeoutMs = null,Object? maxConcurrency = null,Object? enableMdns = null,Object? hostEnumerationLimit = null,}) {
return _then(_SshDiscoveryConfig(
timeoutMs: null == timeoutMs ? _self.timeoutMs : timeoutMs // ignore: cast_nullable_to_non_nullable
as int,maxConcurrency: null == maxConcurrency ? _self.maxConcurrency : maxConcurrency // ignore: cast_nullable_to_non_nullable
as int,enableMdns: null == enableMdns ? _self.enableMdns : enableMdns // ignore: cast_nullable_to_non_nullable
as bool,hostEnumerationLimit: null == hostEnumerationLimit ? _self.hostEnumerationLimit : hostEnumerationLimit // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View File

@@ -0,0 +1,41 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'discovery_result.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_SshDiscoveryResult _$SshDiscoveryResultFromJson(Map<String, dynamic> json) =>
_SshDiscoveryResult(
ip: json['ip'] as String,
port: (json['port'] as num).toInt(),
banner: json['banner'] as String?,
isSelected: json['isSelected'] as bool? ?? false,
);
Map<String, dynamic> _$SshDiscoveryResultToJson(_SshDiscoveryResult instance) =>
<String, dynamic>{
'ip': instance.ip,
'port': instance.port,
'banner': instance.banner,
'isSelected': instance.isSelected,
};
_SshDiscoveryReport _$SshDiscoveryReportFromJson(Map<String, dynamic> json) =>
_SshDiscoveryReport(
generatedAt: json['generatedAt'] as String,
durationMs: (json['durationMs'] as num).toInt(),
count: (json['count'] as num).toInt(),
items: (json['items'] as List<dynamic>)
.map((e) => SshDiscoveryResult.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$SshDiscoveryReportToJson(_SshDiscoveryReport instance) =>
<String, dynamic>{
'generatedAt': instance.generatedAt,
'durationMs': instance.durationMs,
'count': instance.count,
'items': instance.items,
};

View File

@@ -0,0 +1,81 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:server_box/core/utils/proxy_command_executor.dart' show ProxyCommandException;
part 'proxy_command_config.freezed.dart';
part 'proxy_command_config.g.dart';
/// ProxyCommand configuration for SSH connections
@freezed
abstract class ProxyCommandConfig with _$ProxyCommandConfig {
const factory ProxyCommandConfig({
/// Command template with placeholders
/// Available placeholders: %h (hostname), %p (port), %r (user)
required String command,
/// Command arguments (optional, can be included in command)
List<String>? args,
/// Working directory for the command
String? workingDirectory,
/// Environment variables for the command
Map<String, String>? environment,
/// Timeout for command execution
@Default(Duration(seconds: 30)) Duration timeout,
/// Whether to retry on connection failure
@Default(false) bool retryOnFailure,
/// Maximum retry attempts
@Default(3) int maxRetries,
/// Whether the proxy command requires executable download
@Default(false) bool requiresExecutable,
/// Executable name for download management
String? executableName,
/// Executable download URL
String? executableDownloadUrl,
}) = _ProxyCommandConfig;
factory ProxyCommandConfig.fromJson(Map<String, dynamic> json) => _$ProxyCommandConfigFromJson(json);
}
/// Common proxy command presets
const Map<String, ProxyCommandConfig> proxyCommandPresets = {
'cloudflare_access': ProxyCommandConfig(
command: 'cloudflared access ssh --hostname %h',
requiresExecutable: true,
executableName: 'cloudflared',
timeout: Duration(seconds: 15),
),
'ssh_via_bastion': ProxyCommandConfig(
command: 'ssh -W %h:%p bastion.example.com',
timeout: Duration(seconds: 10),
),
'nc_netcat': ProxyCommandConfig(command: 'nc %h %p', timeout: Duration(seconds: 10)),
'socat': ProxyCommandConfig(
command: 'socat - PROXY:%h:%p,proxyport=8080',
timeout: Duration(seconds: 10),
),
};
/// Extension for ProxyCommandConfig to add utility methods
extension ProxyCommandConfigExtension on ProxyCommandConfig {
/// Get the final command with placeholders replaced
String getFinalCommand({required String hostname, required int port, required String user}) {
if (!command.contains('%h') && !command.contains('%p') && !command.contains('%r')) {
throw ProxyCommandException(
message: 'Proxy command "$command" must include at least one placeholder (%h, %p, %r)',
);
}
var finalCommand = command;
finalCommand = finalCommand.replaceAll('%h', hostname);
finalCommand = finalCommand.replaceAll('%p', port.toString());
finalCommand = finalCommand.replaceAll('%r', user);
return finalCommand;
}
}

View File

@@ -0,0 +1,344 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'proxy_command_config.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ProxyCommandConfig {
/// Command template with placeholders
/// Available placeholders: %h (hostname), %p (port), %r (user)
String get command;/// Command arguments (optional, can be included in command)
List<String>? get args;/// Working directory for the command
String? get workingDirectory;/// Environment variables for the command
Map<String, String>? get environment;/// Timeout for command execution
Duration get timeout;/// Whether to retry on connection failure
bool get retryOnFailure;/// Maximum retry attempts
int get maxRetries;/// Whether the proxy command requires executable download
bool get requiresExecutable;/// Executable name for download management
String? get executableName;/// Executable download URL
String? get executableDownloadUrl;
/// Create a copy of ProxyCommandConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ProxyCommandConfigCopyWith<ProxyCommandConfig> get copyWith => _$ProxyCommandConfigCopyWithImpl<ProxyCommandConfig>(this as ProxyCommandConfig, _$identity);
/// Serializes this ProxyCommandConfig to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxyCommandConfig&&(identical(other.command, command) || other.command == command)&&const DeepCollectionEquality().equals(other.args, args)&&(identical(other.workingDirectory, workingDirectory) || other.workingDirectory == workingDirectory)&&const DeepCollectionEquality().equals(other.environment, environment)&&(identical(other.timeout, timeout) || other.timeout == timeout)&&(identical(other.retryOnFailure, retryOnFailure) || other.retryOnFailure == retryOnFailure)&&(identical(other.maxRetries, maxRetries) || other.maxRetries == maxRetries)&&(identical(other.requiresExecutable, requiresExecutable) || other.requiresExecutable == requiresExecutable)&&(identical(other.executableName, executableName) || other.executableName == executableName)&&(identical(other.executableDownloadUrl, executableDownloadUrl) || other.executableDownloadUrl == executableDownloadUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,command,const DeepCollectionEquality().hash(args),workingDirectory,const DeepCollectionEquality().hash(environment),timeout,retryOnFailure,maxRetries,requiresExecutable,executableName,executableDownloadUrl);
@override
String toString() {
return 'ProxyCommandConfig(command: $command, args: $args, workingDirectory: $workingDirectory, environment: $environment, timeout: $timeout, retryOnFailure: $retryOnFailure, maxRetries: $maxRetries, requiresExecutable: $requiresExecutable, executableName: $executableName, executableDownloadUrl: $executableDownloadUrl)';
}
}
/// @nodoc
abstract mixin class $ProxyCommandConfigCopyWith<$Res> {
factory $ProxyCommandConfigCopyWith(ProxyCommandConfig value, $Res Function(ProxyCommandConfig) _then) = _$ProxyCommandConfigCopyWithImpl;
@useResult
$Res call({
String command, List<String>? args, String? workingDirectory, Map<String, String>? environment, Duration timeout, bool retryOnFailure, int maxRetries, bool requiresExecutable, String? executableName, String? executableDownloadUrl
});
}
/// @nodoc
class _$ProxyCommandConfigCopyWithImpl<$Res>
implements $ProxyCommandConfigCopyWith<$Res> {
_$ProxyCommandConfigCopyWithImpl(this._self, this._then);
final ProxyCommandConfig _self;
final $Res Function(ProxyCommandConfig) _then;
/// Create a copy of ProxyCommandConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? command = null,Object? args = freezed,Object? workingDirectory = freezed,Object? environment = freezed,Object? timeout = null,Object? retryOnFailure = null,Object? maxRetries = null,Object? requiresExecutable = null,Object? executableName = freezed,Object? executableDownloadUrl = freezed,}) {
return _then(_self.copyWith(
command: null == command ? _self.command : command // ignore: cast_nullable_to_non_nullable
as String,args: freezed == args ? _self.args : args // ignore: cast_nullable_to_non_nullable
as List<String>?,workingDirectory: freezed == workingDirectory ? _self.workingDirectory : workingDirectory // ignore: cast_nullable_to_non_nullable
as String?,environment: freezed == environment ? _self.environment : environment // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,timeout: null == timeout ? _self.timeout : timeout // ignore: cast_nullable_to_non_nullable
as Duration,retryOnFailure: null == retryOnFailure ? _self.retryOnFailure : retryOnFailure // ignore: cast_nullable_to_non_nullable
as bool,maxRetries: null == maxRetries ? _self.maxRetries : maxRetries // ignore: cast_nullable_to_non_nullable
as int,requiresExecutable: null == requiresExecutable ? _self.requiresExecutable : requiresExecutable // ignore: cast_nullable_to_non_nullable
as bool,executableName: freezed == executableName ? _self.executableName : executableName // ignore: cast_nullable_to_non_nullable
as String?,executableDownloadUrl: freezed == executableDownloadUrl ? _self.executableDownloadUrl : executableDownloadUrl // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [ProxyCommandConfig].
extension ProxyCommandConfigPatterns on ProxyCommandConfig {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ProxyCommandConfig value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ProxyCommandConfig() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ProxyCommandConfig value) $default,){
final _that = this;
switch (_that) {
case _ProxyCommandConfig():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ProxyCommandConfig value)? $default,){
final _that = this;
switch (_that) {
case _ProxyCommandConfig() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String command, List<String>? args, String? workingDirectory, Map<String, String>? environment, Duration timeout, bool retryOnFailure, int maxRetries, bool requiresExecutable, String? executableName, String? executableDownloadUrl)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxyCommandConfig() when $default != null:
return $default(_that.command,_that.args,_that.workingDirectory,_that.environment,_that.timeout,_that.retryOnFailure,_that.maxRetries,_that.requiresExecutable,_that.executableName,_that.executableDownloadUrl);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String command, List<String>? args, String? workingDirectory, Map<String, String>? environment, Duration timeout, bool retryOnFailure, int maxRetries, bool requiresExecutable, String? executableName, String? executableDownloadUrl) $default,) {final _that = this;
switch (_that) {
case _ProxyCommandConfig():
return $default(_that.command,_that.args,_that.workingDirectory,_that.environment,_that.timeout,_that.retryOnFailure,_that.maxRetries,_that.requiresExecutable,_that.executableName,_that.executableDownloadUrl);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String command, List<String>? args, String? workingDirectory, Map<String, String>? environment, Duration timeout, bool retryOnFailure, int maxRetries, bool requiresExecutable, String? executableName, String? executableDownloadUrl)? $default,) {final _that = this;
switch (_that) {
case _ProxyCommandConfig() when $default != null:
return $default(_that.command,_that.args,_that.workingDirectory,_that.environment,_that.timeout,_that.retryOnFailure,_that.maxRetries,_that.requiresExecutable,_that.executableName,_that.executableDownloadUrl);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ProxyCommandConfig implements ProxyCommandConfig {
const _ProxyCommandConfig({required this.command, final List<String>? args, this.workingDirectory, final Map<String, String>? environment, this.timeout = const Duration(seconds: 30), this.retryOnFailure = false, this.maxRetries = 3, this.requiresExecutable = false, this.executableName, this.executableDownloadUrl}): _args = args,_environment = environment;
factory _ProxyCommandConfig.fromJson(Map<String, dynamic> json) => _$ProxyCommandConfigFromJson(json);
/// Command template with placeholders
/// Available placeholders: %h (hostname), %p (port), %r (user)
@override final String command;
/// Command arguments (optional, can be included in command)
final List<String>? _args;
/// Command arguments (optional, can be included in command)
@override List<String>? get args {
final value = _args;
if (value == null) return null;
if (_args is EqualUnmodifiableListView) return _args;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
/// Working directory for the command
@override final String? workingDirectory;
/// Environment variables for the command
final Map<String, String>? _environment;
/// Environment variables for the command
@override Map<String, String>? get environment {
final value = _environment;
if (value == null) return null;
if (_environment is EqualUnmodifiableMapView) return _environment;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
/// Timeout for command execution
@override@JsonKey() final Duration timeout;
/// Whether to retry on connection failure
@override@JsonKey() final bool retryOnFailure;
/// Maximum retry attempts
@override@JsonKey() final int maxRetries;
/// Whether the proxy command requires executable download
@override@JsonKey() final bool requiresExecutable;
/// Executable name for download management
@override final String? executableName;
/// Executable download URL
@override final String? executableDownloadUrl;
/// Create a copy of ProxyCommandConfig
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ProxyCommandConfigCopyWith<_ProxyCommandConfig> get copyWith => __$ProxyCommandConfigCopyWithImpl<_ProxyCommandConfig>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ProxyCommandConfigToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxyCommandConfig&&(identical(other.command, command) || other.command == command)&&const DeepCollectionEquality().equals(other._args, _args)&&(identical(other.workingDirectory, workingDirectory) || other.workingDirectory == workingDirectory)&&const DeepCollectionEquality().equals(other._environment, _environment)&&(identical(other.timeout, timeout) || other.timeout == timeout)&&(identical(other.retryOnFailure, retryOnFailure) || other.retryOnFailure == retryOnFailure)&&(identical(other.maxRetries, maxRetries) || other.maxRetries == maxRetries)&&(identical(other.requiresExecutable, requiresExecutable) || other.requiresExecutable == requiresExecutable)&&(identical(other.executableName, executableName) || other.executableName == executableName)&&(identical(other.executableDownloadUrl, executableDownloadUrl) || other.executableDownloadUrl == executableDownloadUrl));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,command,const DeepCollectionEquality().hash(_args),workingDirectory,const DeepCollectionEquality().hash(_environment),timeout,retryOnFailure,maxRetries,requiresExecutable,executableName,executableDownloadUrl);
@override
String toString() {
return 'ProxyCommandConfig(command: $command, args: $args, workingDirectory: $workingDirectory, environment: $environment, timeout: $timeout, retryOnFailure: $retryOnFailure, maxRetries: $maxRetries, requiresExecutable: $requiresExecutable, executableName: $executableName, executableDownloadUrl: $executableDownloadUrl)';
}
}
/// @nodoc
abstract mixin class _$ProxyCommandConfigCopyWith<$Res> implements $ProxyCommandConfigCopyWith<$Res> {
factory _$ProxyCommandConfigCopyWith(_ProxyCommandConfig value, $Res Function(_ProxyCommandConfig) _then) = __$ProxyCommandConfigCopyWithImpl;
@override @useResult
$Res call({
String command, List<String>? args, String? workingDirectory, Map<String, String>? environment, Duration timeout, bool retryOnFailure, int maxRetries, bool requiresExecutable, String? executableName, String? executableDownloadUrl
});
}
/// @nodoc
class __$ProxyCommandConfigCopyWithImpl<$Res>
implements _$ProxyCommandConfigCopyWith<$Res> {
__$ProxyCommandConfigCopyWithImpl(this._self, this._then);
final _ProxyCommandConfig _self;
final $Res Function(_ProxyCommandConfig) _then;
/// Create a copy of ProxyCommandConfig
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? command = null,Object? args = freezed,Object? workingDirectory = freezed,Object? environment = freezed,Object? timeout = null,Object? retryOnFailure = null,Object? maxRetries = null,Object? requiresExecutable = null,Object? executableName = freezed,Object? executableDownloadUrl = freezed,}) {
return _then(_ProxyCommandConfig(
command: null == command ? _self.command : command // ignore: cast_nullable_to_non_nullable
as String,args: freezed == args ? _self._args : args // ignore: cast_nullable_to_non_nullable
as List<String>?,workingDirectory: freezed == workingDirectory ? _self.workingDirectory : workingDirectory // ignore: cast_nullable_to_non_nullable
as String?,environment: freezed == environment ? _self._environment : environment // ignore: cast_nullable_to_non_nullable
as Map<String, String>?,timeout: null == timeout ? _self.timeout : timeout // ignore: cast_nullable_to_non_nullable
as Duration,retryOnFailure: null == retryOnFailure ? _self.retryOnFailure : retryOnFailure // ignore: cast_nullable_to_non_nullable
as bool,maxRetries: null == maxRetries ? _self.maxRetries : maxRetries // ignore: cast_nullable_to_non_nullable
as int,requiresExecutable: null == requiresExecutable ? _self.requiresExecutable : requiresExecutable // ignore: cast_nullable_to_non_nullable
as bool,executableName: freezed == executableName ? _self.executableName : executableName // ignore: cast_nullable_to_non_nullable
as String?,executableDownloadUrl: freezed == executableDownloadUrl ? _self.executableDownloadUrl : executableDownloadUrl // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -0,0 +1,39 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'proxy_command_config.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ProxyCommandConfig _$ProxyCommandConfigFromJson(Map<String, dynamic> json) =>
_ProxyCommandConfig(
command: json['command'] as String,
args: (json['args'] as List<dynamic>?)?.map((e) => e as String).toList(),
workingDirectory: json['workingDirectory'] as String?,
environment: (json['environment'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String),
),
timeout: json['timeout'] == null
? const Duration(seconds: 30)
: Duration(microseconds: (json['timeout'] as num).toInt()),
retryOnFailure: json['retryOnFailure'] as bool? ?? false,
maxRetries: (json['maxRetries'] as num?)?.toInt() ?? 3,
requiresExecutable: json['requiresExecutable'] as bool? ?? false,
executableName: json['executableName'] as String?,
executableDownloadUrl: json['executableDownloadUrl'] as String?,
);
Map<String, dynamic> _$ProxyCommandConfigToJson(_ProxyCommandConfig instance) =>
<String, dynamic>{
'command': instance.command,
'args': instance.args,
'workingDirectory': instance.workingDirectory,
'environment': instance.environment,
'timeout': instance.timeout.inMicroseconds,
'retryOnFailure': instance.retryOnFailure,
'maxRetries': instance.maxRetries,
'requiresExecutable': instance.requiresExecutable,
'executableName': instance.executableName,
'executableDownloadUrl': instance.executableDownloadUrl,
};

View File

@@ -4,6 +4,7 @@ import 'package:fl_lib/fl_lib.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:server_box/data/model/app/error.dart'; import 'package:server_box/data/model/app/error.dart';
import 'package:server_box/data/model/server/custom.dart'; import 'package:server_box/data/model/server/custom.dart';
import 'package:server_box/data/model/server/proxy_command_config.dart';
import 'package:server_box/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/model/server/wol_cfg.dart'; import 'package:server_box/data/model/server/wol_cfg.dart';
import 'package:server_box/data/store/server.dart'; import 'package:server_box/data/store/server.dart';
@@ -21,7 +22,7 @@ part 'server_private_info.g.dart';
abstract class Spi with _$Spi { abstract class Spi with _$Spi {
const Spi._(); const Spi._();
@JsonSerializable(includeIfNull: false) @JsonSerializable(includeIfNull: false, explicitToJson: true)
const factory Spi({ const factory Spi({
required String name, required String name,
required String ip, required String ip,
@@ -45,14 +46,18 @@ abstract class Spi with _$Spi {
@Default('') @JsonKey(fromJson: Spi.parseId) String id, @Default('') @JsonKey(fromJson: Spi.parseId) String id,
/// Custom system type (unix or windows). If set, skip auto-detection. /// Custom system type (unix or windows). If set, skip auto-detection.
@JsonKey(includeIfNull: false) SystemType? customSystemType, SystemType? customSystemType,
/// Disabled command types for this server /// Disabled command types for this server
@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes, List<String>? disabledCmdTypes,
/// ProxyCommand configuration for SSH connections
ProxyCommandConfig? proxyCommand,
}) = _Spi; }) = _Spi;
factory Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json); factory Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
@override @override
String toString() => 'Spi<$oldId>'; String toString() => 'Spi<$oldId>';

View File

@@ -19,8 +19,9 @@ mixin _$Spi {
@JsonKey(name: 'pubKeyId') String? get keyId; List<String>? get tags; String? get alterUrl; bool get autoConnect;/// [id] of the jump server @JsonKey(name: 'pubKeyId') String? get keyId; List<String>? get tags; String? get alterUrl; bool get autoConnect;/// [id] of the jump server
String? get jumpId; ServerCustom? get custom; WakeOnLanCfg? get wolCfg;/// It only applies to SSH terminal. String? get jumpId; ServerCustom? get custom; WakeOnLanCfg? get wolCfg;/// It only applies to SSH terminal.
Map<String, String>? get envs;@JsonKey(fromJson: Spi.parseId) String get id;/// Custom system type (unix or windows). If set, skip auto-detection. Map<String, String>? get envs;@JsonKey(fromJson: Spi.parseId) String get id;/// Custom system type (unix or windows). If set, skip auto-detection.
@JsonKey(includeIfNull: false) SystemType? get customSystemType;/// Disabled command types for this server SystemType? get customSystemType;/// Disabled command types for this server
@JsonKey(includeIfNull: false) List<String>? get disabledCmdTypes; List<String>? get disabledCmdTypes;/// ProxyCommand configuration for SSH connections
ProxyCommandConfig? get proxyCommand;
/// Create a copy of Spi /// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -33,12 +34,12 @@ $SpiCopyWith<Spi> get copyWith => _$SpiCopyWithImpl<Spi>(this as Spi, _$identity
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other.envs, envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other.disabledCmdTypes, disabledCmdTypes)); return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other.envs, envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other.disabledCmdTypes, disabledCmdTypes)&&(identical(other.proxyCommand, proxyCommand) || other.proxyCommand == proxyCommand));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(envs),id,customSystemType,const DeepCollectionEquality().hash(disabledCmdTypes)); int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(envs),id,customSystemType,const DeepCollectionEquality().hash(disabledCmdTypes),proxyCommand);
@@ -49,11 +50,11 @@ abstract mixin class $SpiCopyWith<$Res> {
factory $SpiCopyWith(Spi value, $Res Function(Spi) _then) = _$SpiCopyWithImpl; factory $SpiCopyWith(Spi value, $Res Function(Spi) _then) = _$SpiCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id, SystemType? customSystemType, List<String>? disabledCmdTypes, ProxyCommandConfig? proxyCommand
}); });
$ProxyCommandConfigCopyWith<$Res>? get proxyCommand;
} }
/// @nodoc /// @nodoc
@@ -66,7 +67,7 @@ class _$SpiCopyWithImpl<$Res>
/// Create a copy of Spi /// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,Object? proxyCommand = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
@@ -84,10 +85,23 @@ as WakeOnLanCfg?,envs: freezed == envs ? _self.envs : envs // ignore: cast_nulla
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,customSystemType: freezed == customSystemType ? _self.customSystemType : customSystemType // ignore: cast_nullable_to_non_nullable as String,customSystemType: freezed == customSystemType ? _self.customSystemType : customSystemType // ignore: cast_nullable_to_non_nullable
as SystemType?,disabledCmdTypes: freezed == disabledCmdTypes ? _self.disabledCmdTypes : disabledCmdTypes // ignore: cast_nullable_to_non_nullable as SystemType?,disabledCmdTypes: freezed == disabledCmdTypes ? _self.disabledCmdTypes : disabledCmdTypes // ignore: cast_nullable_to_non_nullable
as List<String>?, as List<String>?,proxyCommand: freezed == proxyCommand ? _self.proxyCommand : proxyCommand // ignore: cast_nullable_to_non_nullable
as ProxyCommandConfig?,
)); ));
} }
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ProxyCommandConfigCopyWith<$Res>? get proxyCommand {
if (_self.proxyCommand == null) {
return null;
}
return $ProxyCommandConfigCopyWith<$Res>(_self.proxyCommand!, (value) {
return _then(_self.copyWith(proxyCommand: value));
});
}
} }
@@ -169,10 +183,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, SystemType? customSystemType, List<String>? disabledCmdTypes, ProxyCommandConfig? proxyCommand)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _Spi() when $default != null: case _Spi() when $default != null:
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _: return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes,_that.proxyCommand);case _:
return orElse(); return orElse();
} }
@@ -190,10 +204,10 @@ return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, SystemType? customSystemType, List<String>? disabledCmdTypes, ProxyCommandConfig? proxyCommand) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _Spi(): case _Spi():
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _: return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes,_that.proxyCommand);case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@@ -210,10 +224,10 @@ return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, @JsonKey(includeIfNull: false) SystemType? customSystemType, @JsonKey(includeIfNull: false) List<String>? disabledCmdTypes)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String ip, int port, String user, String? pwd, @JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) String id, SystemType? customSystemType, List<String>? disabledCmdTypes, ProxyCommandConfig? proxyCommand)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _Spi() when $default != null: case _Spi() when $default != null:
return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes);case _: return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,_that.tags,_that.alterUrl,_that.autoConnect,_that.jumpId,_that.custom,_that.wolCfg,_that.envs,_that.id,_that.customSystemType,_that.disabledCmdTypes,_that.proxyCommand);case _:
return null; return null;
} }
@@ -223,9 +237,9 @@ return $default(_that.name,_that.ip,_that.port,_that.user,_that.pwd,_that.keyId,
/// @nodoc /// @nodoc
@JsonSerializable(includeIfNull: false) @JsonSerializable(includeIfNull: false, explicitToJson: true)
class _Spi extends Spi { class _Spi extends Spi {
const _Spi({required this.name, required this.ip, required this.port, required this.user, this.pwd, @JsonKey(name: 'pubKeyId') this.keyId, final List<String>? tags, this.alterUrl, this.autoConnect = true, this.jumpId, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = '', @JsonKey(includeIfNull: false) this.customSystemType, @JsonKey(includeIfNull: false) final List<String>? disabledCmdTypes}): _tags = tags,_envs = envs,_disabledCmdTypes = disabledCmdTypes,super._(); const _Spi({required this.name, required this.ip, required this.port, required this.user, this.pwd, @JsonKey(name: 'pubKeyId') this.keyId, final List<String>? tags, this.alterUrl, this.autoConnect = true, this.jumpId, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = '', this.customSystemType, final List<String>? disabledCmdTypes, this.proxyCommand}): _tags = tags,_envs = envs,_disabledCmdTypes = disabledCmdTypes,super._();
factory _Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json); factory _Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
@override final String name; @override final String name;
@@ -263,11 +277,11 @@ class _Spi extends Spi {
@override@JsonKey(fromJson: Spi.parseId) final String id; @override@JsonKey(fromJson: Spi.parseId) final String id;
/// Custom system type (unix or windows). If set, skip auto-detection. /// Custom system type (unix or windows). If set, skip auto-detection.
@override@JsonKey(includeIfNull: false) final SystemType? customSystemType; @override final SystemType? customSystemType;
/// Disabled command types for this server /// Disabled command types for this server
final List<String>? _disabledCmdTypes; final List<String>? _disabledCmdTypes;
/// Disabled command types for this server /// Disabled command types for this server
@override@JsonKey(includeIfNull: false) List<String>? get disabledCmdTypes { @override List<String>? get disabledCmdTypes {
final value = _disabledCmdTypes; final value = _disabledCmdTypes;
if (value == null) return null; if (value == null) return null;
if (_disabledCmdTypes is EqualUnmodifiableListView) return _disabledCmdTypes; if (_disabledCmdTypes is EqualUnmodifiableListView) return _disabledCmdTypes;
@@ -275,6 +289,8 @@ class _Spi extends Spi {
return EqualUnmodifiableListView(value); return EqualUnmodifiableListView(value);
} }
/// ProxyCommand configuration for SSH connections
@override final ProxyCommandConfig? proxyCommand;
/// Create a copy of Spi /// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -289,12 +305,12 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other._envs, _envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other._disabledCmdTypes, _disabledCmdTypes)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other._envs, _envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other._disabledCmdTypes, _disabledCmdTypes)&&(identical(other.proxyCommand, proxyCommand) || other.proxyCommand == proxyCommand));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(_tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(_envs),id,customSystemType,const DeepCollectionEquality().hash(_disabledCmdTypes)); int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(_tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(_envs),id,customSystemType,const DeepCollectionEquality().hash(_disabledCmdTypes),proxyCommand);
@@ -305,11 +321,11 @@ abstract mixin class _$SpiCopyWith<$Res> implements $SpiCopyWith<$Res> {
factory _$SpiCopyWith(_Spi value, $Res Function(_Spi) _then) = __$SpiCopyWithImpl; factory _$SpiCopyWith(_Spi value, $Res Function(_Spi) _then) = __$SpiCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id, SystemType? customSystemType, List<String>? disabledCmdTypes, ProxyCommandConfig? proxyCommand
}); });
@override $ProxyCommandConfigCopyWith<$Res>? get proxyCommand;
} }
/// @nodoc /// @nodoc
@@ -322,7 +338,7 @@ class __$SpiCopyWithImpl<$Res>
/// Create a copy of Spi /// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,Object? proxyCommand = freezed,}) {
return _then(_Spi( return _then(_Spi(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
@@ -340,11 +356,24 @@ as WakeOnLanCfg?,envs: freezed == envs ? _self._envs : envs // ignore: cast_null
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,customSystemType: freezed == customSystemType ? _self.customSystemType : customSystemType // ignore: cast_nullable_to_non_nullable as String,customSystemType: freezed == customSystemType ? _self.customSystemType : customSystemType // ignore: cast_nullable_to_non_nullable
as SystemType?,disabledCmdTypes: freezed == disabledCmdTypes ? _self._disabledCmdTypes : disabledCmdTypes // ignore: cast_nullable_to_non_nullable as SystemType?,disabledCmdTypes: freezed == disabledCmdTypes ? _self._disabledCmdTypes : disabledCmdTypes // ignore: cast_nullable_to_non_nullable
as List<String>?, as List<String>?,proxyCommand: freezed == proxyCommand ? _self.proxyCommand : proxyCommand // ignore: cast_nullable_to_non_nullable
as ProxyCommandConfig?,
)); ));
} }
/// Create a copy of Spi
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ProxyCommandConfigCopyWith<$Res>? get proxyCommand {
if (_self.proxyCommand == null) {
return null;
}
return $ProxyCommandConfigCopyWith<$Res>(_self.proxyCommand!, (value) {
return _then(_self.copyWith(proxyCommand: value));
});
}
} }
// dart format on // dart format on

View File

@@ -34,6 +34,11 @@ _Spi _$SpiFromJson(Map<String, dynamic> json) => _Spi(
disabledCmdTypes: (json['disabledCmdTypes'] as List<dynamic>?) disabledCmdTypes: (json['disabledCmdTypes'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)
.toList(), .toList(),
proxyCommand: json['proxyCommand'] == null
? null
: ProxyCommandConfig.fromJson(
json['proxyCommand'] as Map<String, dynamic>,
),
); );
Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{ Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{
@@ -41,19 +46,19 @@ Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{
'ip': instance.ip, 'ip': instance.ip,
'port': instance.port, 'port': instance.port,
'user': instance.user, 'user': instance.user,
if (instance.pwd case final value?) 'pwd': value, 'pwd': ?instance.pwd,
if (instance.keyId case final value?) 'pubKeyId': value, 'pubKeyId': ?instance.keyId,
if (instance.tags case final value?) 'tags': value, 'tags': ?instance.tags,
if (instance.alterUrl case final value?) 'alterUrl': value, 'alterUrl': ?instance.alterUrl,
'autoConnect': instance.autoConnect, 'autoConnect': instance.autoConnect,
if (instance.jumpId case final value?) 'jumpId': value, 'jumpId': ?instance.jumpId,
if (instance.custom case final value?) 'custom': value, 'custom': ?instance.custom?.toJson(),
if (instance.wolCfg case final value?) 'wolCfg': value, 'wolCfg': ?instance.wolCfg?.toJson(),
if (instance.envs case final value?) 'envs': value, 'envs': ?instance.envs,
'id': instance.id, 'id': instance.id,
if (_$SystemTypeEnumMap[instance.customSystemType] case final value?) 'customSystemType': ?_$SystemTypeEnumMap[instance.customSystemType],
'customSystemType': value, 'disabledCmdTypes': ?instance.disabledCmdTypes,
if (instance.disabledCmdTypes case final value?) 'disabledCmdTypes': value, 'proxyCommand': ?instance.proxyCommand?.toJson(),
}; };
const _$SystemTypeEnumMap = { const _$SystemTypeEnumMap = {

View File

@@ -16,5 +16,5 @@ Map<String, dynamic> _$WakeOnLanCfgToJson(WakeOnLanCfg instance) =>
<String, dynamic>{ <String, dynamic>{
'mac': instance.mac, 'mac': instance.mac,
'ip': instance.ip, 'ip': instance.ip,
if (instance.pwd case final value?) 'pwd': value, 'pwd': ?instance.pwd,
}; };

View File

@@ -0,0 +1,343 @@
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:riverpod/riverpod.dart';
import 'package:server_box/data/model/ai/ask_ai_models.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/store/setting.dart';
final askAiRepositoryProvider = Provider<AskAiRepository>((ref) {
return AskAiRepository();
});
class AskAiRepository {
AskAiRepository({Dio? dio}) : _dio = dio ?? Dio();
final Dio _dio;
SettingStore get _settings => Stores.setting;
/// Streams the AI response using the configured endpoint.
Stream<AskAiEvent> ask({
required String selection,
String? localeHint,
List<AskAiMessage> conversation = const [],
}) async* {
final baseUrl = _settings.askAiBaseUrl.fetch().trim();
final apiKey = _settings.askAiApiKey.fetch().trim();
final model = _settings.askAiModel.fetch().trim();
final missing = <AskAiConfigField>[];
if (baseUrl.isEmpty) missing.add(AskAiConfigField.baseUrl);
if (apiKey.isEmpty) missing.add(AskAiConfigField.apiKey);
if (model.isEmpty) missing.add(AskAiConfigField.model);
if (missing.isNotEmpty) {
throw AskAiConfigException(missingFields: missing);
}
final parsedBaseUri = Uri.tryParse(baseUrl);
final hasScheme = parsedBaseUri?.hasScheme ?? false;
final hasHost = (parsedBaseUri?.host ?? '').isNotEmpty;
if (!hasScheme || !hasHost) {
throw AskAiConfigException(invalidBaseUrl: baseUrl);
}
final uri = _composeUri(baseUrl, '/v1/chat/completions');
final authHeader = apiKey.startsWith('Bearer ') ? apiKey : 'Bearer $apiKey';
final headers = <String, String>{
Headers.acceptHeader: 'text/event-stream',
Headers.contentTypeHeader: Headers.jsonContentType,
'Authorization': authHeader,
};
final requestBody = _buildRequestBody(
model: model,
selection: selection,
localeHint: localeHint,
conversation: conversation,
);
Response<ResponseBody> response;
try {
response = await _dio.postUri<ResponseBody>(
uri,
data: jsonEncode(requestBody),
options: Options(
responseType: ResponseType.stream,
headers: headers,
sendTimeout: const Duration(seconds: 20),
receiveTimeout: const Duration(minutes: 2),
),
);
} on DioException catch (e) {
throw AskAiNetworkException(message: e.message ?? 'Request failed', cause: e);
}
final body = response.data;
if (body == null) {
throw AskAiNetworkException(message: 'Empty response body');
}
final contentBuffer = StringBuffer();
final commands = <AskAiCommand>[];
final toolBuilders = <int, _ToolCallBuilder>{};
final utf8Stream = body.stream.cast<List<int>>().transform(utf8.decoder);
final carry = StringBuffer();
try {
await for (final chunk in utf8Stream) {
carry.write(chunk);
final segments = carry.toString().split('\n\n');
carry
..clear()
..write(segments.removeLast());
for (final segment in segments) {
final lines = segment.split('\n');
for (final rawLine in lines) {
final line = rawLine.trim();
if (line.isEmpty || !line.startsWith('data:')) {
continue;
}
final payload = line.substring(5).trim();
if (payload.isEmpty) {
continue;
}
if (payload == '[DONE]') {
yield AskAiCompleted(
fullText: contentBuffer.toString(),
commands: List.unmodifiable(commands),
);
return;
}
Map<String, dynamic> json;
try {
json = jsonDecode(payload) as Map<String, dynamic>;
} catch (e, s) {
yield AskAiStreamError(e, s);
continue;
}
final choices = json['choices'];
if (choices is! List || choices.isEmpty) {
continue;
}
for (final choice in choices) {
if (choice is! Map<String, dynamic>) {
continue;
}
final delta = choice['delta'];
if (delta is Map<String, dynamic>) {
final content = delta['content'];
if (content is String && content.isNotEmpty) {
contentBuffer.write(content);
yield AskAiContentDelta(content);
} else if (content is List) {
for (final item in content) {
if (item is Map<String, dynamic>) {
final text = item['text'] as String?;
if (text != null && text.isNotEmpty) {
contentBuffer.write(text);
yield AskAiContentDelta(text);
}
}
}
}
final toolCalls = delta['tool_calls'];
if (toolCalls is List) {
for (final toolCall in toolCalls) {
if (toolCall is! Map<String, dynamic>) continue;
final index = toolCall['index'] as int? ?? 0;
final builder = toolBuilders.putIfAbsent(index, _ToolCallBuilder.new);
final function = toolCall['function'];
if (function is Map<String, dynamic>) {
builder.name ??= function['name'] as String?;
final args = function['arguments'] as String?;
if (args != null && args.isNotEmpty) {
builder.arguments.write(args);
final command = builder.tryBuild();
if (command != null) {
commands.add(command);
yield AskAiToolSuggestion(command);
}
}
}
}
}
}
final finishReason = choice['finish_reason'];
if (finishReason == 'tool_calls') {
for (final builder in toolBuilders.values) {
final command = builder.tryBuild(force: true);
if (command != null) {
commands.add(command);
yield AskAiToolSuggestion(command);
}
}
toolBuilders.clear();
}
}
}
}
}
// Flush remaining buffer if [DONE] not received.
if (contentBuffer.isNotEmpty || commands.isNotEmpty) {
yield AskAiCompleted(
fullText: contentBuffer.toString(),
commands: List.unmodifiable(commands),
);
}
} catch (e, s) {
yield AskAiStreamError(e, s);
return;
}
}
Map<String, dynamic> _buildRequestBody({
required String model,
required String selection,
required List<AskAiMessage> conversation,
String? localeHint,
}) {
final promptBuffer = StringBuffer()
..writeln('你是一个 SSH 终端助手。')
..writeln('用户会提供一段终端输出或命令,请结合上下文给出解释。')
..writeln('当需要给出可执行命令时,调用 `recommend_shell` 工具,并提供简短描述。')
..writeln('仅在非常确定命令安全时才给出建议。');
if (localeHint != null && localeHint.isNotEmpty) {
promptBuffer
.writeln('请优先使用用户的语言输出:$localeHint');
}
final messages = <Map<String, String>>[
{
'role': 'system',
'content': promptBuffer.toString(),
},
...conversation.map((message) => {
'role': message.apiRole,
'content': message.content,
}),
{
'role': 'user',
'content': '以下是终端选中的内容:\n$selection',
},
];
return {
'model': model,
'stream': true,
'messages': messages,
'tools': [
{
'type': 'function',
'function': {
'name': 'recommend_shell',
'description': '返回一个用户可以直接复制执行的终端命令。',
'parameters': {
'type': 'object',
'required': ['command'],
'properties': {
'command': {
'type': 'string',
'description': '完整的终端命令,确保可以被粘贴后直接执行。',
},
'description': {
'type': 'string',
'description': '简述该命令的作用或注意事项。',
},
},
},
},
},
],
};
}
Uri _composeUri(String base, String path) {
final sanitizedBase = base.replaceAll(RegExp(r'/+$'), '');
final sanitizedPath = path.replaceFirst(RegExp(r'^/+'), '');
return Uri.parse('$sanitizedBase/$sanitizedPath');
}
}
class _ToolCallBuilder {
_ToolCallBuilder();
final StringBuffer arguments = StringBuffer();
String? name;
bool _emitted = false;
AskAiCommand? tryBuild({bool force = false}) {
if (_emitted && !force) return null;
final raw = arguments.toString();
try {
final decoded = jsonDecode(raw) as Map<String, dynamic>;
final command = decoded['command'] as String?;
if (command == null || command.trim().isEmpty) {
if (force) {
_emitted = true;
}
return null;
}
final description = decoded['description'] as String? ?? decoded['explanation'] as String? ?? '';
_emitted = true;
return AskAiCommand(
command: command.trim(),
description: description.trim(),
toolName: name,
);
} on FormatException {
if (force) {
_emitted = true;
}
return null;
}
}
}
@immutable
enum AskAiConfigField { baseUrl, apiKey, model }
class AskAiConfigException implements Exception {
const AskAiConfigException({this.missingFields = const [], this.invalidBaseUrl});
final List<AskAiConfigField> missingFields;
final String? invalidBaseUrl;
bool get hasInvalidBaseUrl => (invalidBaseUrl ?? '').isNotEmpty;
@override
String toString() {
final parts = <String>[];
if (missingFields.isNotEmpty) {
parts.add('missing: ${missingFields.map((e) => e.name).join(', ')}');
}
if (hasInvalidBaseUrl) {
parts.add('invalidBaseUrl: $invalidBaseUrl');
}
if (parts.isEmpty) {
return 'AskAiConfigException()';
}
return 'AskAiConfigException(${parts.join('; ')})';
}
}
@immutable
class AskAiNetworkException implements Exception {
const AskAiNetworkException({required this.message, this.cause});
final String message;
final Object? cause;
@override
String toString() => 'AskAiNetworkException(message: $message)';
}

View File

@@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'app.g.dart';
part 'app.freezed.dart';
@freezed
abstract class AppState with _$AppState {
const factory AppState() = _AppState;
}
@Riverpod(keepAlive: true)
class AppStates extends _$AppStates {
static BuildContext? ctx;
@override
AppState build() {
return const AppState();
}
}

View File

@@ -1,206 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'app.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AppState {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'AppState()';
}
}
/// @nodoc
class $AppStateCopyWith<$Res> {
$AppStateCopyWith(AppState _, $Res Function(AppState) __);
}
/// Adds pattern-matching-related methods to [AppState].
extension AppStatePatterns on AppState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _AppState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _AppState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _AppState value) $default,){
final _that = this;
switch (_that) {
case _AppState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _AppState value)? $default,){
final _that = this;
switch (_that) {
case _AppState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function()? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AppState() when $default != null:
return $default();case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function() $default,) {final _that = this;
switch (_that) {
case _AppState():
return $default();case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function()? $default,) {final _that = this;
switch (_that) {
case _AppState() when $default != null:
return $default();case _:
return null;
}
}
}
/// @nodoc
class _AppState implements AppState {
const _AppState();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState);
}
@override
int get hashCode => runtimeType.hashCode;
@override
String toString() {
return 'AppState()';
}
}
// dart format on

View File

@@ -1,25 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$appStatesHash() => r'ef96f10f6fff0f3dd6d3128ebf070ad79cbc8bc9';
/// See also [AppStates].
@ProviderFor(AppStates)
final appStatesProvider = NotifierProvider<AppStates, AppState>.internal(
AppStates.new,
name: r'appStatesProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appStatesHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppStates = Notifier<AppState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -48,14 +48,7 @@ class ContainerNotifier extends _$ContainerNotifier {
} }
Future<void> setType(ContainerType type) async { Future<void> setType(ContainerType type) async {
state = state.copyWith( state = state.copyWith(type: type, error: null, runLog: null, items: null, images: null, version: null);
type: type,
error: null,
runLog: null,
items: null,
images: null,
version: null,
);
Stores.container.setType(type, hostId); Stores.container.setType(type, hostId);
sudoCompleter = Completer<bool>(); sudoCompleter = Completer<bool>();
await refresh(); await refresh();
@@ -180,9 +173,13 @@ class ContainerNotifier extends _$ContainerNotifier {
try { try {
final statsLines = statsRaw.split('\n'); final statsLines = statsRaw.split('\n');
statsLines.removeWhere((element) => element.isEmpty); statsLines.removeWhere((element) => element.isEmpty);
for (var item in state.items!) { final items = state.items;
if (items == null) return;
for (var item in items) {
final id = item.id; final id = item.id;
if (id == null) continue; if (id == null) continue;
if (id.length < 5) continue;
final statsLine = statsLines.firstWhereOrNull( final statsLine = statsLines.firstWhereOrNull(
/// Use 5 characters to match the container id, possibility of mismatch /// Use 5 characters to match the container id, possibility of mismatch
/// is very low. /// is very low.
@@ -267,7 +264,6 @@ class ContainerNotifier extends _$ContainerNotifier {
} }
} }
const _jsonFmt = '--format "{{json .}}"'; const _jsonFmt = '--format "{{json .}}"';
enum ContainerCmdType { enum ContainerCmdType {

View File

@@ -6,35 +6,98 @@ part of 'container.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$containerNotifierHash() => r'fea65e66499234b0a59bffff8d69c4ab8c93b2fd'; // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Copied from Dart SDK @ProviderFor(ContainerNotifier)
class _SystemHash { const containerProvider = ContainerNotifierFamily._();
_SystemHash._();
static int combine(int hash, int value) { final class ContainerNotifierProvider
// ignore: parameter_assignments extends $NotifierProvider<ContainerNotifier, ContainerState> {
hash = 0x1fffffff & (hash + value); const ContainerNotifierProvider._({
// ignore: parameter_assignments required ContainerNotifierFamily super.from,
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); required (SSHClient?, String, String, BuildContext) super.argument,
return hash ^ (hash >> 6); }) : super(
retry: null,
name: r'containerProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$containerNotifierHash();
@override
String toString() {
return r'containerProvider'
''
'$argument';
} }
static int finish(int hash) { @$internal
// ignore: parameter_assignments @override
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); ContainerNotifier create() => ContainerNotifier();
// ignore: parameter_assignments
hash = hash ^ (hash >> 11); /// {@macro riverpod.override_with_value}
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); Override overrideWithValue(ContainerState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ContainerState>(value),
);
}
@override
bool operator ==(Object other) {
return other is ContainerNotifierProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
} }
} }
abstract class _$ContainerNotifier String _$containerNotifierHash() => r'e6ced8a914631253daabe0de452e0338078cd1d9';
extends BuildlessAutoDisposeNotifier<ContainerState> {
late final SSHClient? client; final class ContainerNotifierFamily extends $Family
late final String userName; with
late final String hostId; $ClassFamilyOverride<
late final BuildContext context; ContainerNotifier,
ContainerState,
ContainerState,
ContainerState,
(SSHClient?, String, String, BuildContext)
> {
const ContainerNotifierFamily._()
: super(
retry: null,
name: r'containerProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
ContainerNotifierProvider call(
SSHClient? client,
String userName,
String hostId,
BuildContext context,
) => ContainerNotifierProvider._(
argument: (client, userName, hostId, context),
from: this,
);
@override
String toString() => r'containerProvider';
}
abstract class _$ContainerNotifier extends $Notifier<ContainerState> {
late final _$args = ref.$arg as (SSHClient?, String, String, BuildContext);
SSHClient? get client => _$args.$1;
String get userName => _$args.$2;
String get hostId => _$args.$3;
BuildContext get context => _$args.$4;
ContainerState build( ContainerState build(
SSHClient? client, SSHClient? client,
@@ -42,187 +105,19 @@ abstract class _$ContainerNotifier
String hostId, String hostId,
BuildContext context, BuildContext context,
); );
} @$mustCallSuper
/// See also [ContainerNotifier].
@ProviderFor(ContainerNotifier)
const containerNotifierProvider = ContainerNotifierFamily();
/// See also [ContainerNotifier].
class ContainerNotifierFamily extends Family<ContainerState> {
/// See also [ContainerNotifier].
const ContainerNotifierFamily();
/// See also [ContainerNotifier].
ContainerNotifierProvider call(
SSHClient? client,
String userName,
String hostId,
BuildContext context,
) {
return ContainerNotifierProvider(client, userName, hostId, context);
}
@override @override
ContainerNotifierProvider getProviderOverride( void runBuild() {
covariant ContainerNotifierProvider provider, final created = build(_$args.$1, _$args.$2, _$args.$3, _$args.$4);
) { final ref = this.ref as $Ref<ContainerState, ContainerState>;
return call( final element =
provider.client, ref.element
provider.userName, as $ClassProviderElement<
provider.hostId, AnyNotifier<ContainerState, ContainerState>,
provider.context, ContainerState,
); Object?,
} Object?
>;
static const Iterable<ProviderOrFamily>? _dependencies = null; element.handleValue(ref, created);
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'containerNotifierProvider';
}
/// See also [ContainerNotifier].
class ContainerNotifierProvider
extends AutoDisposeNotifierProviderImpl<ContainerNotifier, ContainerState> {
/// See also [ContainerNotifier].
ContainerNotifierProvider(
SSHClient? client,
String userName,
String hostId,
BuildContext context,
) : this._internal(
() => ContainerNotifier()
..client = client
..userName = userName
..hostId = hostId
..context = context,
from: containerNotifierProvider,
name: r'containerNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$containerNotifierHash,
dependencies: ContainerNotifierFamily._dependencies,
allTransitiveDependencies:
ContainerNotifierFamily._allTransitiveDependencies,
client: client,
userName: userName,
hostId: hostId,
context: context,
);
ContainerNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.client,
required this.userName,
required this.hostId,
required this.context,
}) : super.internal();
final SSHClient? client;
final String userName;
final String hostId;
final BuildContext context;
@override
ContainerState runNotifierBuild(covariant ContainerNotifier notifier) {
return notifier.build(client, userName, hostId, context);
}
@override
Override overrideWith(ContainerNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ContainerNotifierProvider._internal(
() => create()
..client = client
..userName = userName
..hostId = hostId
..context = context,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
client: client,
userName: userName,
hostId: hostId,
context: context,
),
);
}
@override
AutoDisposeNotifierProviderElement<ContainerNotifier, ContainerState>
createElement() {
return _ContainerNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ContainerNotifierProvider &&
other.client == client &&
other.userName == userName &&
other.hostId == hostId &&
other.context == context;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, client.hashCode);
hash = _SystemHash.combine(hash, userName.hashCode);
hash = _SystemHash.combine(hash, hostId.hashCode);
hash = _SystemHash.combine(hash, context.hashCode);
return _SystemHash.finish(hash);
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ContainerNotifierRef on AutoDisposeNotifierProviderRef<ContainerState> {
/// The parameter `client` of this provider.
SSHClient? get client;
/// The parameter `userName` of this provider.
String get userName;
/// The parameter `hostId` of this provider.
String get hostId;
/// The parameter `context` of this provider.
BuildContext get context;
}
class _ContainerNotifierProviderElement
extends
AutoDisposeNotifierProviderElement<ContainerNotifier, ContainerState>
with ContainerNotifierRef {
_ContainerNotifierProviderElement(super.provider);
@override
SSHClient? get client => (origin as ContainerNotifierProvider).client;
@override
String get userName => (origin as ContainerNotifierProvider).userName;
@override
String get hostId => (origin as ContainerNotifierProvider).hostId;
@override
BuildContext get context => (origin as ContainerNotifierProvider).context;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -6,22 +6,59 @@ part of 'private_key.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(PrivateKeyNotifier)
const privateKeyProvider = PrivateKeyNotifierProvider._();
final class PrivateKeyNotifierProvider
extends $NotifierProvider<PrivateKeyNotifier, PrivateKeyState> {
const PrivateKeyNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'privateKeyProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$privateKeyNotifierHash();
@$internal
@override
PrivateKeyNotifier create() => PrivateKeyNotifier();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(PrivateKeyState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<PrivateKeyState>(value),
);
}
}
String _$privateKeyNotifierHash() => String _$privateKeyNotifierHash() =>
r'12edd05dca29d1cbc9e2a3e047c3d417d22f7bb7'; r'12edd05dca29d1cbc9e2a3e047c3d417d22f7bb7';
/// See also [PrivateKeyNotifier]. abstract class _$PrivateKeyNotifier extends $Notifier<PrivateKeyState> {
@ProviderFor(PrivateKeyNotifier) PrivateKeyState build();
final privateKeyNotifierProvider = @$mustCallSuper
NotifierProvider<PrivateKeyNotifier, PrivateKeyState>.internal( @override
PrivateKeyNotifier.new, void runBuild() {
name: r'privateKeyNotifierProvider', final created = build();
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') final ref = this.ref as $Ref<PrivateKeyState, PrivateKeyState>;
? null final element =
: _$privateKeyNotifierHash, ref.element
dependencies: null, as $ClassProviderElement<
allTransitiveDependencies: null, AnyNotifier<PrivateKeyState, PrivateKeyState>,
); PrivateKeyState,
Object?,
typedef _$PrivateKeyNotifier = Notifier<PrivateKeyState>; Object?
// ignore_for_file: type=lint >;
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package element.handleValue(ref, created);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/misc.dart';
import 'package:server_box/data/provider/app.dart';
import 'package:server_box/data/provider/private_key.dart'; import 'package:server_box/data/provider/private_key.dart';
import 'package:server_box/data/provider/server/all.dart'; import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/provider/sftp.dart'; import 'package:server_box/data/provider/sftp.dart';
@@ -10,7 +11,7 @@ import 'package:server_box/data/provider/snippet.dart';
/// ref.useNotifier, ref.readProvider, ref.watchProvider /// ref.useNotifier, ref.readProvider, ref.watchProvider
/// ///
/// Usage: /// Usage:
/// - `providers.read.server` -> `ref.read(serversNotifierProvider)` /// - `providers.read.server` -> `ref.read(serversProvider)`
/// - `providers.use.snippet` -> `ref.read(snippetsNotifierProvider.notifier)` /// - `providers.use.snippet` -> `ref.read(snippetsNotifierProvider.notifier)`
extension RiverpodNotifiers on ConsumerState { extension RiverpodNotifiers on ConsumerState {
@@ -45,11 +46,11 @@ final class ReadMyProvider {
T call<T>(ProviderBase<T> provider) => ref.read(provider); T call<T>(ProviderBase<T> provider) => ref.read(provider);
// Specific provider getters // Specific provider getters
ServersState get server => ref.read(serversNotifierProvider); ServersState get server => ref.read(serversProvider);
SnippetState get snippet => ref.read(snippetNotifierProvider); SnippetState get snippet => ref.read(snippetProvider);
AppState get app => ref.read(appStatesProvider); AppState get app => ref.read(appStatesProvider);
PrivateKeyState get privateKey => ref.read(privateKeyNotifierProvider); PrivateKeyState get privateKey => ref.read(privateKeyProvider);
SftpState get sftp => ref.read(sftpNotifierProvider); SftpState get sftp => ref.read(sftpProvider);
} }
final class WatchMyProvider { final class WatchMyProvider {
@@ -59,11 +60,11 @@ final class WatchMyProvider {
T call<T>(ProviderBase<T> provider) => ref.watch(provider); T call<T>(ProviderBase<T> provider) => ref.watch(provider);
// Specific provider getters // Specific provider getters
ServersState get server => ref.watch(serversNotifierProvider); ServersState get server => ref.watch(serversProvider);
SnippetState get snippet => ref.watch(snippetNotifierProvider); SnippetState get snippet => ref.watch(snippetProvider);
AppState get app => ref.watch(appStatesProvider); AppState get app => ref.watch(appStatesProvider);
PrivateKeyState get privateKey => ref.watch(privateKeyNotifierProvider); PrivateKeyState get privateKey => ref.watch(privateKeyProvider);
SftpState get sftp => ref.watch(sftpNotifierProvider); SftpState get sftp => ref.watch(sftpProvider);
} }
final class UseNotifierMyProvider { final class UseNotifierMyProvider {
@@ -74,9 +75,9 @@ final class UseNotifierMyProvider {
ref.read(provider.notifier); ref.read(provider.notifier);
// Specific provider notifier getters // Specific provider notifier getters
ServersNotifier get server => ref.read(serversNotifierProvider.notifier); ServersNotifier get server => ref.read(serversProvider.notifier);
SnippetNotifier get snippet => ref.read(snippetNotifierProvider.notifier); SnippetNotifier get snippet => ref.read(snippetProvider.notifier);
AppStates get app => ref.read(appStatesProvider.notifier); AppStates get app => ref.read(appStatesProvider.notifier);
PrivateKeyNotifier get privateKey => ref.read(privateKeyNotifierProvider.notifier); PrivateKeyNotifier get privateKey => ref.read(privateKeyProvider.notifier);
SftpNotifier get sftp => ref.read(sftpNotifierProvider.notifier); SftpNotifier get sftp => ref.read(sftpProvider.notifier);
} }

View File

@@ -45,7 +45,7 @@ class PveNotifier extends _$PveNotifier {
@override @override
PveState build(Spi spiParam) { PveState build(Spi spiParam) {
spi = spiParam; spi = spiParam;
final serverState = ref.watch(serverNotifierProvider(spi.id)); final serverState = ref.watch(serverProvider(spi.id));
final client = serverState.client; final client = serverState.client;
if (client == null) { if (client == null) {
return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null')); return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null'));

View File

@@ -6,155 +6,96 @@ part of 'pve.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$pveNotifierHash() => r'b5da7240db1b9ee7d61f238cebca45821b7a3445'; // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$PveNotifier extends BuildlessAutoDisposeNotifier<PveState> {
late final Spi spiParam;
PveState build(Spi spiParam);
}
/// See also [PveNotifier].
@ProviderFor(PveNotifier) @ProviderFor(PveNotifier)
const pveNotifierProvider = PveNotifierFamily(); const pveProvider = PveNotifierFamily._();
/// See also [PveNotifier]. final class PveNotifierProvider
class PveNotifierFamily extends Family<PveState> { extends $NotifierProvider<PveNotifier, PveState> {
/// See also [PveNotifier]. const PveNotifierProvider._({
const PveNotifierFamily(); required PveNotifierFamily super.from,
required Spi super.argument,
}) : super(
retry: null,
name: r'pveProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
/// See also [PveNotifier]. @override
PveNotifierProvider call(Spi spiParam) { String debugGetCreateSourceHash() => _$pveNotifierHash();
return PveNotifierProvider(spiParam);
@override
String toString() {
return r'pveProvider'
''
'($argument)';
} }
@$internal
@override @override
PveNotifierProvider getProviderOverride( PveNotifier create() => PveNotifier();
covariant PveNotifierProvider provider,
) {
return call(provider.spiParam);
}
static const Iterable<ProviderOrFamily>? _dependencies = null; /// {@macro riverpod.override_with_value}
Override overrideWithValue(PveState value) {
@override return $ProviderOverride(
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'pveNotifierProvider';
}
/// See also [PveNotifier].
class PveNotifierProvider
extends AutoDisposeNotifierProviderImpl<PveNotifier, PveState> {
/// See also [PveNotifier].
PveNotifierProvider(Spi spiParam)
: this._internal(
() => PveNotifier()..spiParam = spiParam,
from: pveNotifierProvider,
name: r'pveNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$pveNotifierHash,
dependencies: PveNotifierFamily._dependencies,
allTransitiveDependencies: PveNotifierFamily._allTransitiveDependencies,
spiParam: spiParam,
);
PveNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.spiParam,
}) : super.internal();
final Spi spiParam;
@override
PveState runNotifierBuild(covariant PveNotifier notifier) {
return notifier.build(spiParam);
}
@override
Override overrideWith(PveNotifier Function() create) {
return ProviderOverride(
origin: this, origin: this,
override: PveNotifierProvider._internal( providerOverride: $SyncValueProvider<PveState>(value),
() => create()..spiParam = spiParam,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
spiParam: spiParam,
),
); );
} }
@override
AutoDisposeNotifierProviderElement<PveNotifier, PveState> createElement() {
return _PveNotifierProviderElement(this);
}
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is PveNotifierProvider && other.spiParam == spiParam; return other is PveNotifierProvider && other.argument == argument;
} }
@override @override
int get hashCode { int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode); return argument.hashCode;
hash = _SystemHash.combine(hash, spiParam.hashCode);
return _SystemHash.finish(hash);
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead') String _$pveNotifierHash() => r'ba5f2d6cb47c33735f7cc09b771b4a86501b86c6';
// ignore: unused_element
mixin PveNotifierRef on AutoDisposeNotifierProviderRef<PveState> {
/// The parameter `spiParam` of this provider.
Spi get spiParam;
}
class _PveNotifierProviderElement final class PveNotifierFamily extends $Family
extends AutoDisposeNotifierProviderElement<PveNotifier, PveState> with $ClassFamilyOverride<PveNotifier, PveState, PveState, PveState, Spi> {
with PveNotifierRef { const PveNotifierFamily._()
_PveNotifierProviderElement(super.provider); : super(
retry: null,
name: r'pveProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
PveNotifierProvider call(Spi spiParam) =>
PveNotifierProvider._(argument: spiParam, from: this);
@override @override
Spi get spiParam => (origin as PveNotifierProvider).spiParam; String toString() => r'pveProvider';
} }
// ignore_for_file: type=lint abstract class _$PveNotifier extends $Notifier<PveState> {
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package late final _$args = ref.$arg as Spi;
Spi get spiParam => _$args;
PveState build(Spi spiParam);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref = this.ref as $Ref<PveState, PveState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<PveState, PveState>,
PveState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -98,7 +98,7 @@ class ServersNotifier extends _$ServersNotifier {
if (spi != null) { if (spi != null) {
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(spi.id); final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(spi.id);
state = state.copyWith(manualDisconnectedIds: newManualDisconnected); state = state.copyWith(manualDisconnectedIds: newManualDisconnected);
final serverNotifier = ref.read(serverNotifierProvider(spi.id).notifier); final serverNotifier = ref.read(serverProvider(spi.id).notifier);
await serverNotifier.refresh(); await serverNotifier.refresh();
return; return;
} }
@@ -109,19 +109,19 @@ class ServersNotifier extends _$ServersNotifier {
final spi = entry.value; final spi = entry.value;
if (onlyFailed) { if (onlyFailed) {
final serverState = ref.read(serverNotifierProvider(serverId)); final serverState = ref.read(serverProvider(serverId));
if (serverState.conn != ServerConn.failed) return; if (serverState.conn != ServerConn.failed) return;
TryLimiter.reset(serverId); TryLimiter.reset(serverId);
} }
if (state.manualDisconnectedIds.contains(serverId)) return; if (state.manualDisconnectedIds.contains(serverId)) return;
final serverState = ref.read(serverNotifierProvider(serverId)); final serverState = ref.read(serverProvider(serverId));
if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) { if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) {
return; return;
} }
final serverNotifier = ref.read(serverNotifierProvider(serverId).notifier); final serverNotifier = ref.read(serverProvider(serverId).notifier);
await serverNotifier.refresh(); await serverNotifier.refresh();
}), }),
); );
@@ -153,7 +153,7 @@ class ServersNotifier extends _$ServersNotifier {
void setDisconnected() { void setDisconnected() {
for (final serverId in state.servers.keys) { for (final serverId in state.servers.keys) {
final serverNotifier = ref.read(serverNotifierProvider(serverId).notifier); final serverNotifier = ref.read(serverProvider(serverId).notifier);
serverNotifier.updateConnection(ServerConn.disconnected); serverNotifier.updateConnection(ServerConn.disconnected);
// Update SSH session status to disconnected // Update SSH session status to disconnected
@@ -180,7 +180,7 @@ class ServersNotifier extends _$ServersNotifier {
return; return;
} }
final serverNotifier = ref.read(serverNotifierProvider(id).notifier); final serverNotifier = ref.read(serverProvider(id).notifier);
serverNotifier.closeConnection(); serverNotifier.closeConnection();
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..add(id); final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..add(id);
@@ -239,6 +239,50 @@ class ServersNotifier extends _$ServersNotifier {
bakSync.sync(milliDelay: 1000); bakSync.sync(milliDelay: 1000);
} }
void updateServerOrder(List<String> order) {
final seen = <String>{};
final newOrder = <String>[];
for (final id in order) {
if (!state.servers.containsKey(id)) {
continue;
}
if (!seen.add(id)) {
continue;
}
newOrder.add(id);
}
for (final id in state.servers.keys) {
if (seen.add(id)) {
newOrder.add(id);
}
}
if (_isSameOrder(newOrder, state.serverOrder)) {
return;
}
state = state.copyWith(serverOrder: newOrder);
Stores.setting.serverOrder.put(newOrder);
bakSync.sync(milliDelay: 1000);
}
bool _isSameOrder(List<String> a, List<String> b) {
if (identical(a, b)) {
return true;
}
if (a.length != b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
Future<void> updateServer(Spi old, Spi newSpi) async { Future<void> updateServer(Spi old, Spi newSpi) async {
if (old != newSpi) { if (old != newSpi) {
Stores.server.update(old, newSpi); Stores.server.update(old, newSpi);
@@ -259,7 +303,7 @@ class ServersNotifier extends _$ServersNotifier {
} else { } else {
newServers[old.id] = newSpi; newServers[old.id] = newSpi;
// Update SPI in the corresponding IndividualServerNotifier // Update SPI in the corresponding IndividualServerNotifier
final serverNotifier = ref.read(serverNotifierProvider(old.id).notifier); final serverNotifier = ref.read(serverProvider(old.id).notifier);
serverNotifier.updateSpi(newSpi); serverNotifier.updateSpi(newSpi);
} }

View File

@@ -6,21 +6,58 @@ part of 'all.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$serversNotifierHash() => r'2b29ad3027a203c7a20bfd0142d384a503cbbcaa'; // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// See also [ServersNotifier].
@ProviderFor(ServersNotifier) @ProviderFor(ServersNotifier)
final serversNotifierProvider = const serversProvider = ServersNotifierProvider._();
NotifierProvider<ServersNotifier, ServersState>.internal(
ServersNotifier.new,
name: r'serversNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$serversNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ServersNotifier = Notifier<ServersState>; final class ServersNotifierProvider
// ignore_for_file: type=lint extends $NotifierProvider<ServersNotifier, ServersState> {
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package const ServersNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'serversProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$serversNotifierHash();
@$internal
@override
ServersNotifier create() => ServersNotifier();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ServersState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ServersState>(value),
);
}
}
String _$serversNotifierHash() => r'3292bdce7d602ff64687b05ff81d120e71761ec2';
abstract class _$ServersNotifier extends $Notifier<ServersState> {
ServersState build();
@$mustCallSuper
@override
void runBuild() {
final created = build();
final ref = this.ref as $Ref<ServersState, ServersState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<ServersState, ServersState>,
ServersState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -45,7 +45,7 @@ abstract class ServerState with _$ServerState {
class ServerNotifier extends _$ServerNotifier { class ServerNotifier extends _$ServerNotifier {
@override @override
ServerState build(String serverId) { ServerState build(String serverId) {
final serverNotifier = ref.read(serversNotifierProvider); final serverNotifier = ref.read(serversProvider);
final spi = serverNotifier.servers[serverId]; final spi = serverNotifier.servers[serverId];
if (spi == null) { if (spi == null) {
throw StateError('Server $serverId not found'); throw StateError('Server $serverId not found');
@@ -160,7 +160,7 @@ class ServerNotifier extends _$ServerNotifier {
id: sessionId, id: sessionId,
spi: spi, spi: spi,
startTimeMs: time1.millisecondsSinceEpoch, startTimeMs: time1.millisecondsSinceEpoch,
disconnect: () => ref.read(serversNotifierProvider.notifier).closeOneServer(spi.id), disconnect: () => ref.read(serversProvider.notifier).closeOneServer(spi.id),
status: TermSessionStatus.connecting, status: TermSessionStatus.connecting,
); );
TermSessionManager.setActive(sessionId, hasTerminal: false); TermSessionManager.setActive(sessionId, hasTerminal: false);

View File

@@ -6,158 +6,103 @@ part of 'single.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$serverNotifierHash() => r'524647748cc3810c17e5c1cd29e360f3936f5014'; // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ServerNotifier
extends BuildlessAutoDisposeNotifier<ServerState> {
late final String serverId;
ServerState build(String serverId);
}
/// See also [ServerNotifier].
@ProviderFor(ServerNotifier) @ProviderFor(ServerNotifier)
const serverNotifierProvider = ServerNotifierFamily(); const serverProvider = ServerNotifierFamily._();
/// See also [ServerNotifier]. final class ServerNotifierProvider
class ServerNotifierFamily extends Family<ServerState> { extends $NotifierProvider<ServerNotifier, ServerState> {
/// See also [ServerNotifier]. const ServerNotifierProvider._({
const ServerNotifierFamily(); required ServerNotifierFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'serverProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
/// See also [ServerNotifier]. @override
ServerNotifierProvider call(String serverId) { String debugGetCreateSourceHash() => _$serverNotifierHash();
return ServerNotifierProvider(serverId);
@override
String toString() {
return r'serverProvider'
''
'($argument)';
} }
@$internal
@override @override
ServerNotifierProvider getProviderOverride( ServerNotifier create() => ServerNotifier();
covariant ServerNotifierProvider provider,
) {
return call(provider.serverId);
}
static const Iterable<ProviderOrFamily>? _dependencies = null; /// {@macro riverpod.override_with_value}
Override overrideWithValue(ServerState value) {
@override return $ProviderOverride(
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'serverNotifierProvider';
}
/// See also [ServerNotifier].
class ServerNotifierProvider
extends AutoDisposeNotifierProviderImpl<ServerNotifier, ServerState> {
/// See also [ServerNotifier].
ServerNotifierProvider(String serverId)
: this._internal(
() => ServerNotifier()..serverId = serverId,
from: serverNotifierProvider,
name: r'serverNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$serverNotifierHash,
dependencies: ServerNotifierFamily._dependencies,
allTransitiveDependencies:
ServerNotifierFamily._allTransitiveDependencies,
serverId: serverId,
);
ServerNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.serverId,
}) : super.internal();
final String serverId;
@override
ServerState runNotifierBuild(covariant ServerNotifier notifier) {
return notifier.build(serverId);
}
@override
Override overrideWith(ServerNotifier Function() create) {
return ProviderOverride(
origin: this, origin: this,
override: ServerNotifierProvider._internal( providerOverride: $SyncValueProvider<ServerState>(value),
() => create()..serverId = serverId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
serverId: serverId,
),
); );
} }
@override
AutoDisposeNotifierProviderElement<ServerNotifier, ServerState>
createElement() {
return _ServerNotifierProviderElement(this);
}
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is ServerNotifierProvider && other.serverId == serverId; return other is ServerNotifierProvider && other.argument == argument;
} }
@override @override
int get hashCode { int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode); return argument.hashCode;
hash = _SystemHash.combine(hash, serverId.hashCode);
return _SystemHash.finish(hash);
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead') String _$serverNotifierHash() => r'185c6b4546c3bc526f5b2ca79d16aed665818863';
// ignore: unused_element
mixin ServerNotifierRef on AutoDisposeNotifierProviderRef<ServerState> {
/// The parameter `serverId` of this provider.
String get serverId;
}
class _ServerNotifierProviderElement final class ServerNotifierFamily extends $Family
extends AutoDisposeNotifierProviderElement<ServerNotifier, ServerState> with
with ServerNotifierRef { $ClassFamilyOverride<
_ServerNotifierProviderElement(super.provider); ServerNotifier,
ServerState,
ServerState,
ServerState,
String
> {
const ServerNotifierFamily._()
: super(
retry: null,
name: r'serverProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: false,
);
ServerNotifierProvider call(String serverId) =>
ServerNotifierProvider._(argument: serverId, from: this);
@override @override
String get serverId => (origin as ServerNotifierProvider).serverId; String toString() => r'serverProvider';
} }
// ignore_for_file: type=lint abstract class _$ServerNotifier extends $Notifier<ServerState> {
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package late final _$args = ref.$arg as String;
String get serverId => _$args;
ServerState build(String serverId);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref = this.ref as $Ref<ServerState, ServerState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<ServerState, ServerState>,
ServerState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -6,20 +6,58 @@ part of 'sftp.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(SftpNotifier)
const sftpProvider = SftpNotifierProvider._();
final class SftpNotifierProvider
extends $NotifierProvider<SftpNotifier, SftpState> {
const SftpNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'sftpProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$sftpNotifierHash();
@$internal
@override
SftpNotifier create() => SftpNotifier();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(SftpState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<SftpState>(value),
);
}
}
String _$sftpNotifierHash() => r'f8412a4bd1f2bc5919ec31a3eba1c27e9a578f41'; String _$sftpNotifierHash() => r'f8412a4bd1f2bc5919ec31a3eba1c27e9a578f41';
/// See also [SftpNotifier]. abstract class _$SftpNotifier extends $Notifier<SftpState> {
@ProviderFor(SftpNotifier) SftpState build();
final sftpNotifierProvider = NotifierProvider<SftpNotifier, SftpState>.internal( @$mustCallSuper
SftpNotifier.new, @override
name: r'sftpNotifierProvider', void runBuild() {
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') final created = build();
? null final ref = this.ref as $Ref<SftpState, SftpState>;
: _$sftpNotifierHash, final element =
dependencies: null, ref.element
allTransitiveDependencies: null, as $ClassProviderElement<
); AnyNotifier<SftpState, SftpState>,
SftpState,
typedef _$SftpNotifier = Notifier<SftpState>; Object?,
// ignore_for_file: type=lint Object?
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package >;
element.handleValue(ref, created);
}
}

View File

@@ -6,21 +6,58 @@ part of 'snippet.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(SnippetNotifier)
const snippetProvider = SnippetNotifierProvider._();
final class SnippetNotifierProvider
extends $NotifierProvider<SnippetNotifier, SnippetState> {
const SnippetNotifierProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'snippetProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$snippetNotifierHash();
@$internal
@override
SnippetNotifier create() => SnippetNotifier();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(SnippetState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<SnippetState>(value),
);
}
}
String _$snippetNotifierHash() => r'8285c7edf905a4aaa41cd8b65b0a6755c8b97fc9'; String _$snippetNotifierHash() => r'8285c7edf905a4aaa41cd8b65b0a6755c8b97fc9';
/// See also [SnippetNotifier]. abstract class _$SnippetNotifier extends $Notifier<SnippetState> {
@ProviderFor(SnippetNotifier) SnippetState build();
final snippetNotifierProvider = @$mustCallSuper
NotifierProvider<SnippetNotifier, SnippetState>.internal( @override
SnippetNotifier.new, void runBuild() {
name: r'snippetNotifierProvider', final created = build();
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') final ref = this.ref as $Ref<SnippetState, SnippetState>;
? null final element =
: _$snippetNotifierHash, ref.element
dependencies: null, as $ClassProviderElement<
allTransitiveDependencies: null, AnyNotifier<SnippetState, SnippetState>,
); SnippetState,
Object?,
typedef _$SnippetNotifier = Notifier<SnippetState>; Object?
// ignore_for_file: type=lint >;
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package element.handleValue(ref, created);
}
}

View File

@@ -25,7 +25,7 @@ class SystemdNotifier extends _$SystemdNotifier {
@override @override
SystemdState build(Spi spi) { SystemdState build(Spi spi) {
final si = ref.read(serverNotifierProvider(spi.id)); final si = ref.read(serverProvider(spi.id));
_si = si; _si = si;
// Async initialization // Async initialization
Future.microtask(() => getUnits()); Future.microtask(() => getUnits());

View File

@@ -6,158 +6,103 @@ part of 'systemd.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$systemdNotifierHash() => r'98466bd176518545be49cae52f8dbe12af3a88a6'; // GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$SystemdNotifier
extends BuildlessAutoDisposeNotifier<SystemdState> {
late final Spi spi;
SystemdState build(Spi spi);
}
/// See also [SystemdNotifier].
@ProviderFor(SystemdNotifier) @ProviderFor(SystemdNotifier)
const systemdNotifierProvider = SystemdNotifierFamily(); const systemdProvider = SystemdNotifierFamily._();
/// See also [SystemdNotifier]. final class SystemdNotifierProvider
class SystemdNotifierFamily extends Family<SystemdState> { extends $NotifierProvider<SystemdNotifier, SystemdState> {
/// See also [SystemdNotifier]. const SystemdNotifierProvider._({
const SystemdNotifierFamily(); required SystemdNotifierFamily super.from,
required Spi super.argument,
}) : super(
retry: null,
name: r'systemdProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
/// See also [SystemdNotifier]. @override
SystemdNotifierProvider call(Spi spi) { String debugGetCreateSourceHash() => _$systemdNotifierHash();
return SystemdNotifierProvider(spi);
@override
String toString() {
return r'systemdProvider'
''
'($argument)';
} }
@$internal
@override @override
SystemdNotifierProvider getProviderOverride( SystemdNotifier create() => SystemdNotifier();
covariant SystemdNotifierProvider provider,
) {
return call(provider.spi);
}
static const Iterable<ProviderOrFamily>? _dependencies = null; /// {@macro riverpod.override_with_value}
Override overrideWithValue(SystemdState value) {
@override return $ProviderOverride(
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'systemdNotifierProvider';
}
/// See also [SystemdNotifier].
class SystemdNotifierProvider
extends AutoDisposeNotifierProviderImpl<SystemdNotifier, SystemdState> {
/// See also [SystemdNotifier].
SystemdNotifierProvider(Spi spi)
: this._internal(
() => SystemdNotifier()..spi = spi,
from: systemdNotifierProvider,
name: r'systemdNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$systemdNotifierHash,
dependencies: SystemdNotifierFamily._dependencies,
allTransitiveDependencies:
SystemdNotifierFamily._allTransitiveDependencies,
spi: spi,
);
SystemdNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.spi,
}) : super.internal();
final Spi spi;
@override
SystemdState runNotifierBuild(covariant SystemdNotifier notifier) {
return notifier.build(spi);
}
@override
Override overrideWith(SystemdNotifier Function() create) {
return ProviderOverride(
origin: this, origin: this,
override: SystemdNotifierProvider._internal( providerOverride: $SyncValueProvider<SystemdState>(value),
() => create()..spi = spi,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
spi: spi,
),
); );
} }
@override
AutoDisposeNotifierProviderElement<SystemdNotifier, SystemdState>
createElement() {
return _SystemdNotifierProviderElement(this);
}
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is SystemdNotifierProvider && other.spi == spi; return other is SystemdNotifierProvider && other.argument == argument;
} }
@override @override
int get hashCode { int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode); return argument.hashCode;
hash = _SystemHash.combine(hash, spi.hashCode);
return _SystemHash.finish(hash);
} }
} }
@Deprecated('Will be removed in 3.0. Use Ref instead') String _$systemdNotifierHash() => r'030d556efc3d897419cd3462d37cb705813e24c7';
// ignore: unused_element
mixin SystemdNotifierRef on AutoDisposeNotifierProviderRef<SystemdState> {
/// The parameter `spi` of this provider.
Spi get spi;
}
class _SystemdNotifierProviderElement final class SystemdNotifierFamily extends $Family
extends AutoDisposeNotifierProviderElement<SystemdNotifier, SystemdState> with
with SystemdNotifierRef { $ClassFamilyOverride<
_SystemdNotifierProviderElement(super.provider); SystemdNotifier,
SystemdState,
SystemdState,
SystemdState,
Spi
> {
const SystemdNotifierFamily._()
: super(
retry: null,
name: r'systemdProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
SystemdNotifierProvider call(Spi spi) =>
SystemdNotifierProvider._(argument: spi, from: this);
@override @override
Spi get spi => (origin as SystemdNotifierProvider).spi; String toString() => r'systemdProvider';
} }
// ignore_for_file: type=lint abstract class _$SystemdNotifier extends $Notifier<SystemdState> {
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package late final _$args = ref.$arg as Spi;
Spi get spi => _$args;
SystemdState build(Spi spi);
@$mustCallSuper
@override
void runBuild() {
final created = build(_$args);
final ref = this.ref as $Ref<SystemdState, SystemdState>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<SystemdState, SystemdState>,
SystemdState,
Object?,
Object?
>;
element.handleValue(ref, created);
}
}

View File

@@ -6,21 +6,58 @@ part of 'virtual_keyboard.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(VirtKeyboard)
const virtKeyboardProvider = VirtKeyboardProvider._();
final class VirtKeyboardProvider
extends $NotifierProvider<VirtKeyboard, VirtKeyState> {
const VirtKeyboardProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'virtKeyboardProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$virtKeyboardHash();
@$internal
@override
VirtKeyboard create() => VirtKeyboard();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(VirtKeyState value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<VirtKeyState>(value),
);
}
}
String _$virtKeyboardHash() => r'1327d412bfb0dd261f3b555f353a8852b4f753e5'; String _$virtKeyboardHash() => r'1327d412bfb0dd261f3b555f353a8852b4f753e5';
/// See also [VirtKeyboard]. abstract class _$VirtKeyboard extends $Notifier<VirtKeyState> {
@ProviderFor(VirtKeyboard) VirtKeyState build();
final virtKeyboardProvider = @$mustCallSuper
AutoDisposeNotifierProvider<VirtKeyboard, VirtKeyState>.internal( @override
VirtKeyboard.new, void runBuild() {
name: r'virtKeyboardProvider', final created = build();
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') final ref = this.ref as $Ref<VirtKeyState, VirtKeyState>;
? null final element =
: _$virtKeyboardHash, ref.element
dependencies: null, as $ClassProviderElement<
allTransitiveDependencies: null, AnyNotifier<VirtKeyState, VirtKeyState>,
); VirtKeyState,
Object?,
typedef _$VirtKeyboard = AutoDisposeNotifier<VirtKeyState>; Object?
// ignore_for_file: type=lint >;
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package element.handleValue(ref, created);
}
}

View File

@@ -3,6 +3,6 @@
abstract class BuildData { abstract class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 1246; static const int build = 1270;
static const int script = 69; static const int script = 69;
} }

View File

@@ -126,6 +126,10 @@ abstract final class GithubIds {
'cnen2018', 'cnen2018',
'xiaomeng9597', 'xiaomeng9597',
'mingzhao2019', 'mingzhao2019',
'HHXXYY123',
'Lancerys',
'yaziku',
'yeluosln',
}; };
} }

View File

@@ -51,12 +51,31 @@ abstract final class TermSessionManager {
static void init() { static void init() {
if (isAndroid) { if (isAndroid) {
MethodChans.registerHandler((id) async { MethodChans.registerHandler(
_entries[id]?.disconnect?.call(); (id) async {
}); _entries[id]?.disconnect?.call();
},
() {
// Stop all connections when notification "Stop All" is pressed
stopAllConnections();
},
);
} }
} }
/// Called when Android notification "Stop All" button is pressed
static void stopAllConnections() {
// Disconnect all sessions
final disconnectCallbacks = _entries.values.map((e) => e.disconnect).where((cb) => cb != null).toList();
for (final disconnect in disconnectCallbacks) {
disconnect!();
}
// Clear all entries
_entries.clear();
_activeId = null;
_sync();
}
/// Add a session record and push update to Android. /// Add a session record and push update to Android.
static void add({ static void add({
required String id, required String id,

View File

@@ -72,6 +72,18 @@ class SettingStore extends HiveStore {
late final editorFontSize = propertyDefault('editorFontSize', 12.5); late final editorFontSize = propertyDefault('editorFontSize', 12.5);
/// Trusted SSH host key fingerprints keyed by `serverId::keyType`.
late final sshKnownHostFingerprints = propertyDefault<Map<String, String>>(
'sshKnownHostFingerprints',
const {},
fromObj: (raw) {
if (raw is Map) {
return raw.map((key, value) => MapEntry(key.toString(), value.toString()));
}
return <String, String>{};
},
);
// Editor theme // Editor theme
late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme); late final editorTheme = propertyDefault('editorTheme', Defaults.editorTheme);
@@ -142,6 +154,11 @@ class SettingStore extends HiveStore {
/// Whether collapse UI items by default /// Whether collapse UI items by default
late final collapseUIDefault = propertyDefault('collapseUIDefault', true); late final collapseUIDefault = propertyDefault('collapseUIDefault', true);
/// Terminal AI helper configuration
late final askAiBaseUrl = propertyDefault('askAiBaseUrl', 'https://api.openai.com');
late final askAiApiKey = propertyDefault('askAiApiKey', '');
late final askAiModel = propertyDefault('askAiModel', 'gpt-4o-mini');
late final serverFuncBtns = listProperty('serverBtns', defaultValue: ServerFuncBtn.defaultIdxs); late final serverFuncBtns = listProperty('serverBtns', defaultValue: ServerFuncBtn.defaultIdxs);
/// Docker is more popular than podman, set to `false` to use docker /// Docker is more popular than podman, set to `false` to use docker
@@ -211,6 +228,10 @@ class SettingStore extends HiveStore {
late final betaTest = propertyDefault('betaTest', false); late final betaTest = propertyDefault('betaTest', false);
late final proxyCmdCustomExecs = listProperty('proxyCmdCustomExecs');
late final proxyCmdCustomPresets = listProperty('proxyCmdCustomPresets');
/// For desktop only. /// For desktop only.
/// Record the position and size of the window. /// Record the position and size of the window.
late final windowState = property<WindowState>( late final windowState = property<WindowState>(

View File

@@ -155,6 +155,108 @@ abstract class AppLocalizations {
/// **'Already in last directory.'** /// **'Already in last directory.'**
String get alreadyLastDir; String get alreadyLastDir;
/// No description provided for @askAi.
///
/// In en, this message translates to:
/// **'Ask AI'**
String get askAi;
/// No description provided for @askAiApiKey.
///
/// In en, this message translates to:
/// **'API Key'**
String get askAiApiKey;
/// No description provided for @askAiAwaitingResponse.
///
/// In en, this message translates to:
/// **'Waiting for AI response...'**
String get askAiAwaitingResponse;
/// No description provided for @askAiBaseUrl.
///
/// In en, this message translates to:
/// **'Base URL'**
String get askAiBaseUrl;
/// No description provided for @askAiCommandInserted.
///
/// In en, this message translates to:
/// **'Command inserted into terminal'**
String get askAiCommandInserted;
/// No description provided for @askAiConfigMissing.
///
/// In en, this message translates to:
/// **'Please configure {fields} in Settings.'**
String askAiConfigMissing(Object fields);
/// No description provided for @askAiConfirmExecute.
///
/// In en, this message translates to:
/// **'Confirm before executing'**
String get askAiConfirmExecute;
/// No description provided for @askAiConversation.
///
/// In en, this message translates to:
/// **'AI conversation'**
String get askAiConversation;
/// No description provided for @askAiDisclaimer.
///
/// In en, this message translates to:
/// **'AI may be incorrect. Review carefully before applying.'**
String get askAiDisclaimer;
/// No description provided for @askAiFollowUpHint.
///
/// In en, this message translates to:
/// **'Ask a follow-up...'**
String get askAiFollowUpHint;
/// No description provided for @askAiInsertTerminal.
///
/// In en, this message translates to:
/// **'Insert into terminal'**
String get askAiInsertTerminal;
/// No description provided for @askAiModel.
///
/// In en, this message translates to:
/// **'Model'**
String get askAiModel;
/// No description provided for @askAiNoResponse.
///
/// In en, this message translates to:
/// **'No response'**
String get askAiNoResponse;
/// No description provided for @askAiRecommendedCommand.
///
/// In en, this message translates to:
/// **'AI suggested command'**
String get askAiRecommendedCommand;
/// No description provided for @askAiSelectedContent.
///
/// In en, this message translates to:
/// **'Selected content'**
String get askAiSelectedContent;
/// No description provided for @askAiUsageHint.
///
/// In en, this message translates to:
/// **'Used in SSH Terminal'**
String get askAiUsageHint;
/// No description provided for @atLeastOneTab.
///
/// In en, this message translates to:
/// **'At least one tab must be selected'**
String get atLeastOneTab;
/// No description provided for @authFailTip. /// No description provided for @authFailTip.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -185,6 +287,12 @@ abstract class AppLocalizations {
/// **'Automatic home widget update'** /// **'Automatic home widget update'**
String get autoUpdateHomeWidget; String get autoUpdateHomeWidget;
/// No description provided for @availableTabs.
///
/// In en, this message translates to:
/// **'Available Tabs'**
String get availableTabs;
/// No description provided for @backupEncrypted. /// No description provided for @backupEncrypted.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -257,6 +365,36 @@ abstract class AppLocalizations {
/// **'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".'** /// **'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".'**
String get bgRunTip; String get bgRunTip;
/// No description provided for @clearAllStatsContent.
///
/// In en, this message translates to:
/// **'Are you sure you want to clear all server connection statistics? This action cannot be undone.'**
String get clearAllStatsContent;
/// No description provided for @clearAllStatsTitle.
///
/// In en, this message translates to:
/// **'Clear All Statistics'**
String get clearAllStatsTitle;
/// No description provided for @clearServerStatsContent.
///
/// In en, this message translates to:
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
String clearServerStatsContent(Object serverName);
/// No description provided for @clearServerStatsTitle.
///
/// In en, this message translates to:
/// **'Clear {serverName} Statistics'**
String clearServerStatsTitle(Object serverName);
/// No description provided for @clearThisServerStats.
///
/// In en, this message translates to:
/// **'Clear This Server Statistics'**
String get clearThisServerStats;
/// No description provided for @closeAfterSave. /// No description provided for @closeAfterSave.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -281,6 +419,24 @@ abstract class AppLocalizations {
/// **'Connection'** /// **'Connection'**
String get conn; String get conn;
/// No description provided for @connectionDetails.
///
/// In en, this message translates to:
/// **'Connection Details'**
String get connectionDetails;
/// No description provided for @connectionStats.
///
/// In en, this message translates to:
/// **'Connection Statistics'**
String get connectionStats;
/// No description provided for @connectionStatsDesc.
///
/// In en, this message translates to:
/// **'View server connection success rate and history'**
String get connectionStatsDesc;
/// No description provided for @container. /// No description provided for @container.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -371,6 +527,30 @@ abstract class AppLocalizations {
/// **'Disconnected'** /// **'Disconnected'**
String get disconnected; String get disconnected;
/// No description provided for @discoverSshServers.
///
/// In en, this message translates to:
/// **'Discover SSH Servers'**
String get discoverSshServers;
/// No description provided for @discoveryFailed.
///
/// In en, this message translates to:
/// **'Discovery failed'**
String get discoveryFailed;
/// No description provided for @discoverySettings.
///
/// In en, this message translates to:
/// **'Discovery Settings'**
String get discoverySettings;
/// No description provided for @discoverySummary.
///
/// In en, this message translates to:
/// **'Discovery Summary'**
String get discoverySummary;
/// No description provided for @disk. /// No description provided for @disk.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -452,12 +632,6 @@ abstract class AppLocalizations {
/// **'Edit virtual keys'** /// **'Edit virtual keys'**
String get editVirtKeys; String get editVirtKeys;
/// No description provided for @editor.
///
/// In en, this message translates to:
/// **'Editor'**
String get editor;
/// No description provided for @editorHighlightTip. /// No description provided for @editorHighlightTip.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -470,6 +644,18 @@ abstract class AppLocalizations {
/// **'Emulator'** /// **'Emulator'**
String get emulator; String get emulator;
/// No description provided for @enableMdns.
///
/// In en, this message translates to:
/// **'Enable mDNS'**
String get enableMdns;
/// No description provided for @enableMdnsDesc.
///
/// In en, this message translates to:
/// **'Use mDNS/Bonjour to discover SSH services'**
String get enableMdnsDesc;
/// No description provided for @encode. /// No description provided for @encode.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -524,18 +710,18 @@ abstract class AppLocalizations {
/// **'File \'{file}\' too large {size}, max {sizeMax}'** /// **'File \'{file}\' too large {size}, max {sizeMax}'**
String fileTooLarge(Object file, Object size, Object sizeMax); String fileTooLarge(Object file, Object size, Object sizeMax);
/// No description provided for @finishedAt.
///
/// In en, this message translates to:
/// **'Finished at'**
String get finishedAt;
/// No description provided for @followSystem. /// No description provided for @followSystem.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Follow system'** /// **'Follow system'**
String get followSystem; String get followSystem;
/// No description provided for @font.
///
/// In en, this message translates to:
/// **'Font'**
String get font;
/// No description provided for @fontSize. /// No description provided for @fontSize.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -596,6 +782,18 @@ abstract class AppLocalizations {
/// **'Code highlighting'** /// **'Code highlighting'**
String get highlight; String get highlight;
/// No description provided for @homeTabs.
///
/// In en, this message translates to:
/// **'Home Tabs'**
String get homeTabs;
/// No description provided for @homeTabsCustomizeDesc.
///
/// In en, this message translates to:
/// **'Customize which tabs appear on the home page and their order'**
String get homeTabsCustomizeDesc;
/// No description provided for @homeWidgetUrlConfig. /// No description provided for @homeWidgetUrlConfig.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -632,12 +830,6 @@ abstract class AppLocalizations {
/// **'Images list'** /// **'Images list'**
String get imagesList; String get imagesList;
/// No description provided for @init.
///
/// In en, this message translates to:
/// **'Initialize'**
String get init;
/// No description provided for @inner. /// No description provided for @inner.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -692,6 +884,18 @@ abstract class AppLocalizations {
/// **'Key Auth'** /// **'Key Auth'**
String get keyAuth; String get keyAuth;
/// No description provided for @lastFailure.
///
/// In en, this message translates to:
/// **'Last Failure'**
String get lastFailure;
/// No description provided for @lastSuccess.
///
/// In en, this message translates to:
/// **'Last Success'**
String get lastSuccess;
/// No description provided for @letterCache. /// No description provided for @letterCache.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -704,12 +908,6 @@ abstract class AppLocalizations {
/// **'Recommended to disable, but after disabling, it will be impossible to input CJK characters.'** /// **'Recommended to disable, but after disabling, it will be impossible to input CJK characters.'**
String get letterCacheTip; String get letterCacheTip;
/// No description provided for @license.
///
/// In en, this message translates to:
/// **'License'**
String get license;
/// No description provided for @location. /// No description provided for @location.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -728,18 +926,18 @@ abstract class AppLocalizations {
/// **'Made with ❤️ by {myGithub}'** /// **'Made with ❤️ by {myGithub}'**
String madeWithLove(Object myGithub); String madeWithLove(Object myGithub);
/// No description provided for @manual.
///
/// In en, this message translates to:
/// **'Manual'**
String get manual;
/// No description provided for @max. /// No description provided for @max.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'max'** /// **'max'**
String get max; String get max;
/// No description provided for @maxConcurrency.
///
/// In en, this message translates to:
/// **'Max Concurrency'**
String get maxConcurrency;
/// No description provided for @maxRetryCount. /// No description provided for @maxRetryCount.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -812,6 +1010,12 @@ abstract class AppLocalizations {
/// **'New container'** /// **'New container'**
String get newContainer; String get newContainer;
/// No description provided for @noConnectionStatsData.
///
/// In en, this message translates to:
/// **'No connection statistics data'**
String get noConnectionStatsData;
/// No description provided for @noLineChart. /// No description provided for @noLineChart.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -938,18 +1142,18 @@ abstract class AppLocalizations {
/// **'Prioritize displaying disk capacity'** /// **'Prioritize displaying disk capacity'**
String get preferDiskAmount; String get preferDiskAmount;
/// No description provided for @preview.
///
/// In en, this message translates to:
/// **'Preview'**
String get preview;
/// No description provided for @privateKey. /// No description provided for @privateKey.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Private Key'** /// **'Private Key'**
String get privateKey; String get privateKey;
/// No description provided for @privateKeyNotFoundFmt.
///
/// In en, this message translates to:
/// **'Private key [{keyId}] not found.'**
String privateKeyNotFoundFmt(Object keyId);
/// No description provided for @process. /// No description provided for @process.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -998,6 +1202,12 @@ abstract class AppLocalizations {
/// **'Reboot'** /// **'Reboot'**
String get reboot; String get reboot;
/// No description provided for @recentConnections.
///
/// In en, this message translates to:
/// **'Recent Connections'**
String get recentConnections;
/// No description provided for @rememberPwdInMem. /// No description provided for @rememberPwdInMem.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1118,6 +1328,18 @@ abstract class AppLocalizations {
/// **'Server order'** /// **'Server order'**
String get serverOrder; String get serverOrder;
/// No description provided for @serverTabRequired.
///
/// In en, this message translates to:
/// **'Server tab cannot be removed'**
String get serverTabRequired;
/// No description provided for @servers.
///
/// In en, this message translates to:
/// **'servers'**
String get servers;
/// No description provided for @sftpDlPrepare. /// No description provided for @sftpDlPrepare.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1256,6 +1478,42 @@ abstract class AppLocalizations {
/// **'Imported {count} servers from SSH config'** /// **'Imported {count} servers from SSH config'**
String sshConfigImported(Object count); String sshConfigImported(Object count);
/// No description provided for @sshHostKeyChangedDesc.
///
/// In en, this message translates to:
/// **'The SSH host key changed for {serverName}. Only continue if you trust this server.'**
String sshHostKeyChangedDesc(Object serverName);
/// No description provided for @sshHostKeyFingerprintMd5Base64.
///
/// In en, this message translates to:
/// **'Fingerprint (MD5 base64): {fingerprint}'**
String sshHostKeyFingerprintMd5Base64(Object fingerprint);
/// No description provided for @sshHostKeyFingerprintMd5Hex.
///
/// In en, this message translates to:
/// **'Fingerprint (MD5 hex): {fingerprint}'**
String sshHostKeyFingerprintMd5Hex(Object fingerprint);
/// Label for the SSH host key type displayed in the host key verification dialog.
///
/// In en, this message translates to:
/// **'SSH host key type'**
String get sshHostKeyType;
/// No description provided for @sshHostKeyNewDesc.
///
/// In en, this message translates to:
/// **'A new SSH host key was received from {serverName}. Review the fingerprint before trusting.'**
String sshHostKeyNewDesc(Object serverName);
/// No description provided for @sshHostKeyStoredFingerprint.
///
/// In en, this message translates to:
/// **'Stored fingerprint: {fingerprint}'**
String sshHostKeyStoredFingerprint(Object fingerprint);
/// No description provided for @sshConfigManualSelect. /// No description provided for @sshConfigManualSelect.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1358,12 +1616,6 @@ abstract class AppLocalizations {
/// **'Switch to {val}'** /// **'Switch to {val}'**
String switchTo(Object val); String switchTo(Object val);
/// No description provided for @sync.
///
/// In en, this message translates to:
/// **'Sync'**
String get sync;
/// No description provided for @syncTip. /// No description provided for @syncTip.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1382,6 +1634,12 @@ abstract class AppLocalizations {
/// **'Tags'** /// **'Tags'**
String get tag; String get tag;
/// No description provided for @tapToStartDiscovery.
///
/// In en, this message translates to:
/// **'Tap the search button to discover SSH servers on your network'**
String get tapToStartDiscovery;
/// No description provided for @temperature. /// No description provided for @temperature.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1442,6 +1700,12 @@ abstract class AppLocalizations {
/// **'Total'** /// **'Total'**
String get total; String get total;
/// No description provided for @totalAttempts.
///
/// In en, this message translates to:
/// **'Total'**
String get totalAttempts;
/// No description provided for @traffic. /// No description provided for @traffic.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1490,12 +1754,6 @@ abstract class AppLocalizations {
/// **'Server status update interval'** /// **'Server status update interval'**
String get updateServerStatusInterval; String get updateServerStatusInterval;
/// No description provided for @upload.
///
/// In en, this message translates to:
/// **'Upload'**
String get upload;
/// No description provided for @upsideDown. /// No description provided for @upsideDown.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1544,6 +1802,12 @@ abstract class AppLocalizations {
/// **'View'** /// **'View'**
String get view; String get view;
/// No description provided for @viewDetails.
///
/// In en, this message translates to:
/// **'View Details'**
String get viewDetails;
/// No description provided for @viewErr. /// No description provided for @viewErr.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1621,126 +1885,6 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.'** /// **'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.'**
String get writeScriptTip; String get writeScriptTip;
/// No description provided for @connectionStats.
///
/// In en, this message translates to:
/// **'Connection Statistics'**
String get connectionStats;
/// No description provided for @connectionStatsDesc.
///
/// In en, this message translates to:
/// **'View server connection success rate and history'**
String get connectionStatsDesc;
/// No description provided for @noConnectionStatsData.
///
/// In en, this message translates to:
/// **'No connection statistics data'**
String get noConnectionStatsData;
/// No description provided for @totalAttempts.
///
/// In en, this message translates to:
/// **'Total'**
String get totalAttempts;
/// No description provided for @lastSuccess.
///
/// In en, this message translates to:
/// **'Last Success'**
String get lastSuccess;
/// No description provided for @lastFailure.
///
/// In en, this message translates to:
/// **'Last Failure'**
String get lastFailure;
/// No description provided for @recentConnections.
///
/// In en, this message translates to:
/// **'Recent Connections'**
String get recentConnections;
/// No description provided for @viewDetails.
///
/// In en, this message translates to:
/// **'View Details'**
String get viewDetails;
/// No description provided for @connectionDetails.
///
/// In en, this message translates to:
/// **'Connection Details'**
String get connectionDetails;
/// No description provided for @clearThisServerStats.
///
/// In en, this message translates to:
/// **'Clear This Server Statistics'**
String get clearThisServerStats;
/// No description provided for @clearAllStatsTitle.
///
/// In en, this message translates to:
/// **'Clear All Statistics'**
String get clearAllStatsTitle;
/// No description provided for @clearAllStatsContent.
///
/// In en, this message translates to:
/// **'Are you sure you want to clear all server connection statistics? This action cannot be undone.'**
String get clearAllStatsContent;
/// No description provided for @clearServerStatsTitle.
///
/// In en, this message translates to:
/// **'Clear {serverName} Statistics'**
String clearServerStatsTitle(String serverName);
/// No description provided for @clearServerStatsContent.
///
/// In en, this message translates to:
/// **'Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.'**
String clearServerStatsContent(String serverName);
/// No description provided for @homeTabs.
///
/// In en, this message translates to:
/// **'Home Tabs'**
String get homeTabs;
/// No description provided for @homeTabsCustomizeDesc.
///
/// In en, this message translates to:
/// **'Customize which tabs appear on the home page and their order'**
String get homeTabsCustomizeDesc;
/// No description provided for @reset.
///
/// In en, this message translates to:
/// **'Reset'**
String get reset;
/// No description provided for @availableTabs.
///
/// In en, this message translates to:
/// **'Available Tabs'**
String get availableTabs;
/// No description provided for @atLeastOneTab.
///
/// In en, this message translates to:
/// **'At least one tab must be selected'**
String get atLeastOneTab;
/// No description provided for @serverTabRequired.
///
/// In en, this message translates to:
/// **'Server tab cannot be removed'**
String get serverTabRequired;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@@ -28,6 +28,60 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Bereits im letzten Verzeichnis.'; String get alreadyLastDir => 'Bereits im letzten Verzeichnis.';
@override
String get askAi => 'KI fragen';
@override
String get askAiApiKey => 'API-Schlüssel';
@override
String get askAiAwaitingResponse => 'Warte auf KI-Antwort...';
@override
String get askAiBaseUrl => 'Basis-URL';
@override
String get askAiCommandInserted => 'Befehl ins Terminal eingefügt';
@override
String askAiConfigMissing(Object fields) {
return 'Bitte konfigurieren Sie $fields in den Einstellungen.';
}
@override
String get askAiConfirmExecute => 'Vor Ausführung bestätigen';
@override
String get askAiConversation => 'KI-Unterhaltung';
@override
String get askAiDisclaimer =>
'KI kann Fehler machen. Bitte vorsichtig verwenden.';
@override
String get askAiFollowUpHint => 'Weitere Frage stellen...';
@override
String get askAiInsertTerminal => 'In Terminal einfügen';
@override
String get askAiModel => 'Modell';
@override
String get askAiNoResponse => 'Keine Antwort';
@override
String get askAiRecommendedCommand => 'KI-empfohlener Befehl';
@override
String get askAiSelectedContent => 'Ausgewählter Inhalt';
@override
String get askAiUsageHint => 'Verwendet im SSH-Terminal';
@override
String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein';
@override @override
String get authFailTip => String get authFailTip =>
'Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.'; 'Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.';
@@ -45,6 +99,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => 'Home-Widget automatisch aktualisieren'; String get autoUpdateHomeWidget => 'Home-Widget automatisch aktualisieren';
@override
String get availableTabs => 'Verfügbare Tabs';
@override @override
String get backupEncrypted => 'Backup ist verschlüsselt'; String get backupEncrypted => 'Backup ist verschlüsselt';
@@ -85,6 +142,26 @@ class AppLocalizationsDe extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".'; 'Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".';
@override
String get clearAllStatsContent =>
'Sind Sie sicher, dass Sie alle Server-Verbindungsstatistiken löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
@override
String get clearAllStatsTitle => 'Alle Statistiken löschen';
@override
String clearServerStatsContent(Object serverName) {
return 'Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"$serverName\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
}
@override
String clearServerStatsTitle(Object serverName) {
return '$serverName Statistiken löschen';
}
@override
String get clearThisServerStats => 'Statistiken dieses Servers löschen';
@override @override
String get closeAfterSave => 'Speichern und schließen'; String get closeAfterSave => 'Speichern und schließen';
@@ -98,6 +175,16 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get conn => 'Verbindung'; String get conn => 'Verbindung';
@override
String get connectionDetails => 'Verbindungsdetails';
@override
String get connectionStats => 'Verbindungsstatistiken';
@override
String get connectionStatsDesc =>
'Server-Verbindungserfolgsrate und Verlauf anzeigen';
@override @override
String get container => 'Container'; String get container => 'Container';
@@ -147,6 +234,18 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get disconnected => 'Disconnected'; String get disconnected => 'Disconnected';
@override
String get discoverSshServers => 'SSH-Server entdecken';
@override
String get discoveryFailed => 'Entdeckung fehlgeschlagen';
@override
String get discoverySettings => 'Entdeckungseinstellungen';
@override
String get discoverySummary => 'Entdeckungs-Zusammenfassung';
@override @override
String get disk => 'Festplatte'; String get disk => 'Festplatte';
@@ -199,9 +298,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get editVirtKeys => 'Virtuelle Tasten bearbeiten'; String get editVirtKeys => 'Virtuelle Tasten bearbeiten';
@override
String get editor => 'Editor';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.'; 'Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.';
@@ -209,6 +305,13 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get emulator => 'Emulator'; String get emulator => 'Emulator';
@override
String get enableMdns => 'mDNS aktivieren';
@override
String get enableMdnsDesc =>
'mDNS/Bonjour verwenden, um SSH-Dienste zu entdecken';
@override @override
String get encode => 'Encode'; String get encode => 'Encode';
@@ -241,10 +344,10 @@ class AppLocalizationsDe extends AppLocalizations {
} }
@override @override
String get followSystem => 'System verfolgen'; String get finishedAt => 'Beendet um';
@override @override
String get font => 'Schriftarten'; String get followSystem => 'System verfolgen';
@override @override
String get fontSize => 'Schriftgröße'; String get fontSize => 'Schriftgröße';
@@ -277,6 +380,13 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get highlight => 'Code highlight'; String get highlight => 'Code highlight';
@override
String get homeTabs => 'Home-Tabs';
@override
String get homeTabsCustomizeDesc =>
'Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge';
@override @override
String get homeWidgetUrlConfig => 'Home-Widget-Link konfigurieren'; String get homeWidgetUrlConfig => 'Home-Widget-Link konfigurieren';
@@ -297,9 +407,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get imagesList => 'Images'; String get imagesList => 'Images';
@override
String get init => 'Initialisieren';
@override @override
String get inner => 'Eingebaut'; String get inner => 'Eingebaut';
@@ -329,6 +436,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get keyAuth => 'Schlüsselauthentifzierung'; String get keyAuth => 'Schlüsselauthentifzierung';
@override
String get lastFailure => 'Letzter Fehler';
@override
String get lastSuccess => 'Letzter Erfolg';
@override @override
String get letterCache => 'Buchstaben-Caching'; String get letterCache => 'Buchstaben-Caching';
@@ -336,9 +449,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.'; 'Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.';
@override
String get license => 'Lizenzen';
@override @override
String get location => 'Standort'; String get location => 'Standort';
@@ -351,10 +461,10 @@ class AppLocalizationsDe extends AppLocalizations {
} }
@override @override
String get manual => 'Handbuch'; String get max => 'max';
@override @override
String get max => 'max'; String get maxConcurrency => 'Maximale Gleichzeitigkeit';
@override @override
String get maxRetryCount => 'Anzahl an Verbindungsversuchen'; String get maxRetryCount => 'Anzahl an Verbindungsversuchen';
@@ -395,6 +505,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get newContainer => 'Neuer Container'; String get newContainer => 'Neuer Container';
@override
String get noConnectionStatsData => 'Keine Verbindungsstatistikdaten';
@override @override
String get noLineChart => 'Verwenden Sie keine Liniendiagramme'; String get noLineChart => 'Verwenden Sie keine Liniendiagramme';
@@ -466,10 +579,12 @@ class AppLocalizationsDe extends AppLocalizations {
String get preferDiskAmount => 'Festplattenkapazität vorrangig anzeigen'; String get preferDiskAmount => 'Festplattenkapazität vorrangig anzeigen';
@override @override
String get preview => 'Vorschau'; String get privateKey => 'Private Key';
@override @override
String get privateKey => 'Private Key'; String privateKeyNotFoundFmt(Object keyId) {
return 'Privater Schlüssel [$keyId] wurde nicht gefunden.';
}
@override @override
String get process => 'Prozess'; String get process => 'Prozess';
@@ -498,6 +613,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get reboot => 'Neustart'; String get reboot => 'Neustart';
@override
String get recentConnections => 'Kürzliche Verbindungen';
@override @override
String get rememberPwdInMem => 'Passwort im Speicher behalten'; String get rememberPwdInMem => 'Passwort im Speicher behalten';
@@ -559,6 +677,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get serverOrder => 'Server-Bestellung'; String get serverOrder => 'Server-Bestellung';
@override
String get serverTabRequired => 'Server-Tab kann nicht entfernt werden';
@override
String get servers => 'Server';
@override @override
String get sftpDlPrepare => 'Verbindung vorbereiten...'; String get sftpDlPrepare => 'Verbindung vorbereiten...';
@@ -645,6 +769,34 @@ class AppLocalizationsDe extends AppLocalizations {
return '$count Server aus SSH-Konfiguration importiert'; return '$count Server aus SSH-Konfiguration importiert';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'Der SSH-Hostschlüssel für $serverName hat sich geändert. Fahren Sie nur fort, wenn Sie diesem Server vertrauen.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Fingerabdruck (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Fingerabdruck (MD5 Hex): $fingerprint';
}
@override
String get sshHostKeyType => 'SSH-Hostschlüsseltyp';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Ein neuer SSH-Hostschlüssel wurde von $serverName empfangen. Prüfen Sie den Fingerabdruck, bevor Sie vertrauen.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Gespeicherter Fingerabdruck: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Möchten Sie die SSH-Konfigurationsdatei manuell auswählen?'; 'Möchten Sie die SSH-Konfigurationsdatei manuell auswählen?';
@@ -709,9 +861,6 @@ class AppLocalizationsDe extends AppLocalizations {
return 'Wechseln zu $val'; return 'Wechseln zu $val';
} }
@override
String get sync => 'Sync';
@override @override
String get syncTip => String get syncTip =>
'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.'; 'Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.';
@@ -722,6 +871,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get tag => 'Tags'; String get tag => 'Tags';
@override
String get tapToStartDiscovery =>
'Tippen Sie auf die Suche-Schaltfläche, um SSH-Server in Ihrem Netzwerk zu entdecken';
@override @override
String get temperature => 'Temperatur'; String get temperature => 'Temperatur';
@@ -754,6 +907,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get total => 'Total'; String get total => 'Total';
@override
String get totalAttempts => 'Gesamt';
@override @override
String get traffic => 'Durchflussmenge'; String get traffic => 'Durchflussmenge';
@@ -780,9 +936,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get updateServerStatusInterval => String get updateServerStatusInterval =>
'Aktualisierungsintervall des Serverstatus'; 'Aktualisierungsintervall des Serverstatus';
@override
String get upload => 'Hochladen';
@override @override
String get upsideDown => 'Upside Down'; String get upsideDown => 'Upside Down';
@@ -808,6 +961,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get view => 'Ansicht'; String get view => 'Ansicht';
@override
String get viewDetails => 'Details anzeigen';
@override @override
String get viewErr => 'Fehler anzeigen'; String get viewErr => 'Fehler anzeigen';
@@ -851,71 +1007,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.'; 'Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.';
@override
String get connectionStats => 'Verbindungsstatistiken';
@override
String get connectionStatsDesc =>
'Server-Verbindungserfolgsrate und Verlauf anzeigen';
@override
String get noConnectionStatsData => 'Keine Verbindungsstatistikdaten';
@override
String get totalAttempts => 'Gesamt';
@override
String get lastSuccess => 'Letzter Erfolg';
@override
String get lastFailure => 'Letzter Fehler';
@override
String get recentConnections => 'Kürzliche Verbindungen';
@override
String get viewDetails => 'Details anzeigen';
@override
String get connectionDetails => 'Verbindungsdetails';
@override
String get clearThisServerStats => 'Statistiken dieses Servers löschen';
@override
String get clearAllStatsTitle => 'Alle Statistiken löschen';
@override
String get clearAllStatsContent =>
'Sind Sie sicher, dass Sie alle Server-Verbindungsstatistiken löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
@override
String clearServerStatsTitle(String serverName) {
return '$serverName Statistiken löschen';
}
@override
String clearServerStatsContent(String serverName) {
return 'Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"$serverName\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.';
}
@override
String get homeTabs => 'Home-Tabs';
@override
String get homeTabsCustomizeDesc =>
'Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge';
@override
String get reset => 'Zurücksetzen';
@override
String get availableTabs => 'Verfügbare Tabs';
@override
String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein';
@override
String get serverTabRequired => 'Server-Tab kann nicht entfernt werden';
} }

View File

@@ -28,6 +28,60 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Already in last directory.'; String get alreadyLastDir => 'Already in last directory.';
@override
String get askAi => 'Ask AI';
@override
String get askAiApiKey => 'API Key';
@override
String get askAiAwaitingResponse => 'Waiting for AI response...';
@override
String get askAiBaseUrl => 'Base URL';
@override
String get askAiCommandInserted => 'Command inserted into terminal';
@override
String askAiConfigMissing(Object fields) {
return 'Please configure $fields in Settings.';
}
@override
String get askAiConfirmExecute => 'Confirm before executing';
@override
String get askAiConversation => 'AI conversation';
@override
String get askAiDisclaimer =>
'AI may be incorrect. Review carefully before applying.';
@override
String get askAiFollowUpHint => 'Ask a follow-up...';
@override
String get askAiInsertTerminal => 'Insert into terminal';
@override
String get askAiModel => 'Model';
@override
String get askAiNoResponse => 'No response';
@override
String get askAiRecommendedCommand => 'AI suggested command';
@override
String get askAiSelectedContent => 'Selected content';
@override
String get askAiUsageHint => 'Used in SSH Terminal';
@override
String get atLeastOneTab => 'At least one tab must be selected';
@override @override
String get authFailTip => String get authFailTip =>
'Authentication failed, please check whether credentials are correct'; 'Authentication failed, please check whether credentials are correct';
@@ -45,6 +99,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => 'Automatic home widget update'; String get autoUpdateHomeWidget => 'Automatic home widget update';
@override
String get availableTabs => 'Available Tabs';
@override @override
String get backupEncrypted => 'Backup is encrypted'; String get backupEncrypted => 'Backup is encrypted';
@@ -84,6 +141,26 @@ class AppLocalizationsEn extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".'; 'This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".';
@override
String get clearAllStatsContent =>
'Are you sure you want to clear all server connection statistics? This action cannot be undone.';
@override
String get clearAllStatsTitle => 'Clear All Statistics';
@override
String clearServerStatsContent(Object serverName) {
return 'Are you sure you want to clear connection statistics for server \"$serverName\"? This action cannot be undone.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Clear $serverName Statistics';
}
@override
String get clearThisServerStats => 'Clear This Server Statistics';
@override @override
String get closeAfterSave => 'Save and close'; String get closeAfterSave => 'Save and close';
@@ -97,6 +174,16 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get conn => 'Connection'; String get conn => 'Connection';
@override
String get connectionDetails => 'Connection Details';
@override
String get connectionStats => 'Connection Statistics';
@override
String get connectionStatsDesc =>
'View server connection success rate and history';
@override @override
String get container => 'Container'; String get container => 'Container';
@@ -146,6 +233,18 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get disconnected => 'Disconnected'; String get disconnected => 'Disconnected';
@override
String get discoverSshServers => 'Discover SSH Servers';
@override
String get discoveryFailed => 'Discovery failed';
@override
String get discoverySettings => 'Discovery Settings';
@override
String get discoverySummary => 'Discovery Summary';
@override @override
String get disk => 'Disk'; String get disk => 'Disk';
@@ -198,9 +297,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get editVirtKeys => 'Edit virtual keys'; String get editVirtKeys => 'Edit virtual keys';
@override
String get editor => 'Editor';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'The current code highlighting performance is not ideal and can be optionally turned off to improve.'; 'The current code highlighting performance is not ideal and can be optionally turned off to improve.';
@@ -208,6 +304,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get emulator => 'Emulator'; String get emulator => 'Emulator';
@override
String get enableMdns => 'Enable mDNS';
@override
String get enableMdnsDesc => 'Use mDNS/Bonjour to discover SSH services';
@override @override
String get encode => 'Encode'; String get encode => 'Encode';
@@ -240,10 +342,10 @@ class AppLocalizationsEn extends AppLocalizations {
} }
@override @override
String get followSystem => 'Follow system'; String get finishedAt => 'Finished at';
@override @override
String get font => 'Font'; String get followSystem => 'Follow system';
@override @override
String get fontSize => 'Font size'; String get fontSize => 'Font size';
@@ -276,6 +378,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get highlight => 'Code highlighting'; String get highlight => 'Code highlighting';
@override
String get homeTabs => 'Home Tabs';
@override
String get homeTabsCustomizeDesc =>
'Customize which tabs appear on the home page and their order';
@override @override
String get homeWidgetUrlConfig => 'Config home widget url'; String get homeWidgetUrlConfig => 'Config home widget url';
@@ -296,9 +405,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get imagesList => 'Images list'; String get imagesList => 'Images list';
@override
String get init => 'Initialize';
@override @override
String get inner => 'Inner'; String get inner => 'Inner';
@@ -328,6 +434,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get keyAuth => 'Key Auth'; String get keyAuth => 'Key Auth';
@override
String get lastFailure => 'Last Failure';
@override
String get lastSuccess => 'Last Success';
@override @override
String get letterCache => 'Letter caching'; String get letterCache => 'Letter caching';
@@ -335,9 +447,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Recommended to disable, but after disabling, it will be impossible to input CJK characters.'; 'Recommended to disable, but after disabling, it will be impossible to input CJK characters.';
@override
String get license => 'License';
@override @override
String get location => 'Location'; String get location => 'Location';
@@ -350,10 +459,10 @@ class AppLocalizationsEn extends AppLocalizations {
} }
@override @override
String get manual => 'Manual'; String get max => 'max';
@override @override
String get max => 'max'; String get maxConcurrency => 'Max Concurrency';
@override @override
String get maxRetryCount => 'Number of server reconnections'; String get maxRetryCount => 'Number of server reconnections';
@@ -393,6 +502,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get newContainer => 'New container'; String get newContainer => 'New container';
@override
String get noConnectionStatsData => 'No connection statistics data';
@override @override
String get noLineChart => 'Do not use line charts'; String get noLineChart => 'Do not use line charts';
@@ -464,10 +576,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get preferDiskAmount => 'Prioritize displaying disk capacity'; String get preferDiskAmount => 'Prioritize displaying disk capacity';
@override @override
String get preview => 'Preview'; String get privateKey => 'Private Key';
@override @override
String get privateKey => 'Private Key'; String privateKeyNotFoundFmt(Object keyId) {
return 'Private key [$keyId] not found.';
}
@override @override
String get process => 'Process'; String get process => 'Process';
@@ -496,6 +610,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get reboot => 'Reboot'; String get reboot => 'Reboot';
@override
String get recentConnections => 'Recent Connections';
@override @override
String get rememberPwdInMem => 'Remember password in memory'; String get rememberPwdInMem => 'Remember password in memory';
@@ -556,6 +673,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get serverOrder => 'Server order'; String get serverOrder => 'Server order';
@override
String get serverTabRequired => 'Server tab cannot be removed';
@override
String get servers => 'servers';
@override @override
String get sftpDlPrepare => 'Preparing to connect...'; String get sftpDlPrepare => 'Preparing to connect...';
@@ -640,6 +763,34 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Imported $count servers from SSH config'; return 'Imported $count servers from SSH config';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'The SSH host key changed for $serverName. Only continue if you trust this server.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Fingerprint (MD5 base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Fingerprint (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'SSH host key type';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'A new SSH host key was received from $serverName. Review the fingerprint before trusting.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Stored fingerprint: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Would you like to select the SSH config file manually?'; 'Would you like to select the SSH config file manually?';
@@ -702,9 +853,6 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Switch to $val'; return 'Switch to $val';
} }
@override
String get sync => 'Sync';
@override @override
String get syncTip => String get syncTip =>
'A restart may be required for some changes to take effect.'; 'A restart may be required for some changes to take effect.';
@@ -715,6 +863,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get tag => 'Tags'; String get tag => 'Tags';
@override
String get tapToStartDiscovery =>
'Tap the search button to discover SSH servers on your network';
@override @override
String get temperature => 'Temperature'; String get temperature => 'Temperature';
@@ -747,6 +899,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get total => 'Total'; String get total => 'Total';
@override
String get totalAttempts => 'Total';
@override @override
String get traffic => 'Traffic'; String get traffic => 'Traffic';
@@ -772,9 +927,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get updateServerStatusInterval => 'Server status update interval'; String get updateServerStatusInterval => 'Server status update interval';
@override
String get upload => 'Upload';
@override @override
String get upsideDown => 'Upside Down'; String get upsideDown => 'Upside Down';
@@ -800,6 +952,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get view => 'View'; String get view => 'View';
@override
String get viewDetails => 'View Details';
@override @override
String get viewErr => 'See error'; String get viewErr => 'See error';
@@ -843,71 +998,4 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.'; 'After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.';
@override
String get connectionStats => 'Connection Statistics';
@override
String get connectionStatsDesc =>
'View server connection success rate and history';
@override
String get noConnectionStatsData => 'No connection statistics data';
@override
String get totalAttempts => 'Total';
@override
String get lastSuccess => 'Last Success';
@override
String get lastFailure => 'Last Failure';
@override
String get recentConnections => 'Recent Connections';
@override
String get viewDetails => 'View Details';
@override
String get connectionDetails => 'Connection Details';
@override
String get clearThisServerStats => 'Clear This Server Statistics';
@override
String get clearAllStatsTitle => 'Clear All Statistics';
@override
String get clearAllStatsContent =>
'Are you sure you want to clear all server connection statistics? This action cannot be undone.';
@override
String clearServerStatsTitle(String serverName) {
return 'Clear $serverName Statistics';
}
@override
String clearServerStatsContent(String serverName) {
return 'Are you sure you want to clear connection statistics for server \"$serverName\"? This action cannot be undone.';
}
@override
String get homeTabs => 'Home Tabs';
@override
String get homeTabsCustomizeDesc =>
'Customize which tabs appear on the home page and their order';
@override
String get reset => 'Reset';
@override
String get availableTabs => 'Available Tabs';
@override
String get atLeastOneTab => 'At least one tab must be selected';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,60 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Ya estás en el directorio superior'; String get alreadyLastDir => 'Ya estás en el directorio superior';
@override
String get askAi => 'Preguntar a la IA';
@override
String get askAiApiKey => 'Clave API';
@override
String get askAiAwaitingResponse => 'Esperando la respuesta de la IA...';
@override
String get askAiBaseUrl => 'URL base';
@override
String get askAiCommandInserted => 'Comando insertado en el terminal';
@override
String askAiConfigMissing(Object fields) {
return 'Configura $fields en Ajustes.';
}
@override
String get askAiConfirmExecute => 'Confirmar antes de ejecutar';
@override
String get askAiConversation => 'Conversación con la IA';
@override
String get askAiDisclaimer =>
'La IA puede equivocarse. Úsala con precaución.';
@override
String get askAiFollowUpHint => 'Haz una pregunta adicional...';
@override
String get askAiInsertTerminal => 'Insertar en el terminal';
@override
String get askAiModel => 'Modelo';
@override
String get askAiNoResponse => 'Sin respuesta';
@override
String get askAiRecommendedCommand => 'Comando sugerido por la IA';
@override
String get askAiSelectedContent => 'Contenido seleccionado';
@override
String get askAiUsageHint => 'Usado en el terminal SSH';
@override
String get atLeastOneTab => 'Al menos una pestaña debe estar seleccionada';
@override @override
String get authFailTip => String get authFailTip =>
'La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.'; 'La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.';
@@ -45,6 +99,9 @@ class AppLocalizationsEs extends AppLocalizations {
String get autoUpdateHomeWidget => String get autoUpdateHomeWidget =>
'Actualizar automáticamente el widget del escritorio'; 'Actualizar automáticamente el widget del escritorio';
@override
String get availableTabs => 'Pestañas disponibles';
@override @override
String get backupEncrypted => 'El respaldo está encriptado'; String get backupEncrypted => 'El respaldo está encriptado';
@@ -85,6 +142,26 @@ class AppLocalizationsEs extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.'; 'Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.';
@override
String get clearAllStatsContent =>
'¿Estás seguro de que quieres limpiar todas las estadísticas de conexión del servidor? Esta acción no se puede deshacer.';
@override
String get clearAllStatsTitle => 'Limpiar todas las estadísticas';
@override
String clearServerStatsContent(Object serverName) {
return '¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"$serverName\"? Esta acción no se puede deshacer.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Limpiar estadísticas de $serverName';
}
@override
String get clearThisServerStats => 'Limpiar estadísticas de este servidor';
@override @override
String get closeAfterSave => 'Guardar y cerrar'; String get closeAfterSave => 'Guardar y cerrar';
@@ -98,6 +175,16 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get conn => 'Conectar'; String get conn => 'Conectar';
@override
String get connectionDetails => 'Detalles de conexión';
@override
String get connectionStats => 'Estadísticas de conexión';
@override
String get connectionStatsDesc =>
'Ver la tasa de éxito de conexión del servidor e historial';
@override @override
String get container => 'Contenedor'; String get container => 'Contenedor';
@@ -147,6 +234,18 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get disconnected => 'Desconectado'; String get disconnected => 'Desconectado';
@override
String get discoverSshServers => 'Descubrir servidores SSH';
@override
String get discoveryFailed => 'Falló el descubrimiento';
@override
String get discoverySettings => 'Configuración de descubrimiento';
@override
String get discoverySummary => 'Resumen del descubrimiento';
@override @override
String get disk => 'Disco'; String get disk => 'Disco';
@@ -199,9 +298,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get editVirtKeys => 'Editar teclas virtuales'; String get editVirtKeys => 'Editar teclas virtuales';
@override
String get editor => 'Editor';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.'; 'El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.';
@@ -209,6 +305,12 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get emulator => 'Emulador'; String get emulator => 'Emulador';
@override
String get enableMdns => 'Habilitar mDNS';
@override
String get enableMdnsDesc => 'Usar mDNS/Bonjour para descubrir servicios SSH';
@override @override
String get encode => 'Codificar'; String get encode => 'Codificar';
@@ -241,10 +343,10 @@ class AppLocalizationsEs extends AppLocalizations {
} }
@override @override
String get followSystem => 'Seguir al sistema'; String get finishedAt => 'Terminado en';
@override @override
String get font => 'Fuente'; String get followSystem => 'Seguir al sistema';
@override @override
String get fontSize => 'Tamaño de fuente'; String get fontSize => 'Tamaño de fuente';
@@ -277,6 +379,13 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get highlight => 'Resaltar código'; String get highlight => 'Resaltar código';
@override
String get homeTabs => 'Pestañas de inicio';
@override
String get homeTabsCustomizeDesc =>
'Personaliza qué pestañas aparecen en la página de inicio y su orden';
@override @override
String get homeWidgetUrlConfig => 'Configuración de URL del widget de inicio'; String get homeWidgetUrlConfig => 'Configuración de URL del widget de inicio';
@@ -297,9 +406,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get imagesList => 'Lista de imágenes'; String get imagesList => 'Lista de imágenes';
@override
String get init => 'Inicializar';
@override @override
String get inner => 'Interno'; String get inner => 'Interno';
@@ -329,6 +435,12 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get keyAuth => 'Autenticación con llave'; String get keyAuth => 'Autenticación con llave';
@override
String get lastFailure => 'Último fallo';
@override
String get lastSuccess => 'Último éxito';
@override @override
String get letterCache => 'Caché de letras'; String get letterCache => 'Caché de letras';
@@ -336,9 +448,6 @@ class AppLocalizationsEs extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.'; 'Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.';
@override
String get license => 'Licencia de código abierto';
@override @override
String get location => 'Ubicación'; String get location => 'Ubicación';
@@ -351,10 +460,10 @@ class AppLocalizationsEs extends AppLocalizations {
} }
@override @override
String get manual => 'Manual'; String get max => 'Máximo';
@override @override
String get max => 'Máximo'; String get maxConcurrency => 'Concurrencia máxima';
@override @override
String get maxRetryCount => String get maxRetryCount =>
@@ -395,6 +504,10 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get newContainer => 'Crear contenedor nuevo'; String get newContainer => 'Crear contenedor nuevo';
@override
String get noConnectionStatsData =>
'No hay datos de estadísticas de conexión';
@override @override
String get noLineChart => 'No utilice gráficos de líneas'; String get noLineChart => 'No utilice gráficos de líneas';
@@ -468,10 +581,12 @@ class AppLocalizationsEs extends AppLocalizations {
'Priorizar la visualización de la capacidad del disco'; 'Priorizar la visualización de la capacidad del disco';
@override @override
String get preview => 'Vista previa'; String get privateKey => 'Llave privada';
@override @override
String get privateKey => 'Llave privada'; String privateKeyNotFoundFmt(Object keyId) {
return 'No se encontró la clave privada [$keyId].';
}
@override @override
String get process => 'Proceso'; String get process => 'Proceso';
@@ -500,6 +615,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get reboot => 'Reiniciar'; String get reboot => 'Reiniciar';
@override
String get recentConnections => 'Conexiones recientes';
@override @override
String get rememberPwdInMem => 'Recordar contraseña en la memoria'; String get rememberPwdInMem => 'Recordar contraseña en la memoria';
@@ -562,6 +680,13 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get serverOrder => 'Orden del servidor'; String get serverOrder => 'Orden del servidor';
@override
String get serverTabRequired =>
'La pestaña del servidor no se puede eliminar';
@override
String get servers => 'servidores';
@override @override
String get sftpDlPrepare => 'Preparando para conectar al servidor...'; String get sftpDlPrepare => 'Preparando para conectar al servidor...';
@@ -647,6 +772,34 @@ class AppLocalizationsEs extends AppLocalizations {
return 'Se importaron $count servidores desde la configuración SSH'; return 'Se importaron $count servidores desde la configuración SSH';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'La clave de host SSH de $serverName ha cambiado. Continúa solo si confías en este servidor.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Huella (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Huella (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'Tipo de clave de host SSH';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Se recibió una nueva clave de host SSH de $serverName. Revisa la huella antes de confiar.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Huella almacenada: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'¿Te gustaría seleccionar manualmente el archivo de configuración SSH?'; '¿Te gustaría seleccionar manualmente el archivo de configuración SSH?';
@@ -710,9 +863,6 @@ class AppLocalizationsEs extends AppLocalizations {
return 'Cambiar a $val'; return 'Cambiar a $val';
} }
@override
String get sync => 'Sincronizar';
@override @override
String get syncTip => String get syncTip =>
'Puede que necesites reiniciar para que algunos cambios tengan efecto.'; 'Puede que necesites reiniciar para que algunos cambios tengan efecto.';
@@ -723,6 +873,10 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get tag => 'Etiqueta'; String get tag => 'Etiqueta';
@override
String get tapToStartDiscovery =>
'Toca el botón de búsqueda para descubrir servidores SSH en tu red';
@override @override
String get temperature => 'Temperatura'; String get temperature => 'Temperatura';
@@ -755,6 +909,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get total => 'Total'; String get total => 'Total';
@override
String get totalAttempts => 'Total';
@override @override
String get traffic => 'Tráfico'; String get traffic => 'Tráfico';
@@ -781,9 +938,6 @@ class AppLocalizationsEs extends AppLocalizations {
String get updateServerStatusInterval => String get updateServerStatusInterval =>
'Intervalo de actualización del estado del servidor'; 'Intervalo de actualización del estado del servidor';
@override
String get upload => 'Subir';
@override @override
String get upsideDown => 'Invertir arriba por abajo'; String get upsideDown => 'Invertir arriba por abajo';
@@ -809,6 +963,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get view => 'Vista'; String get view => 'Vista';
@override
String get viewDetails => 'Ver detalles';
@override @override
String get viewErr => 'Ver error'; String get viewErr => 'Ver error';
@@ -852,72 +1009,4 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.'; 'Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.';
@override
String get connectionStats => 'Estadísticas de conexión';
@override
String get connectionStatsDesc =>
'Ver la tasa de éxito de conexión del servidor e historial';
@override
String get noConnectionStatsData =>
'No hay datos de estadísticas de conexión';
@override
String get totalAttempts => 'Total';
@override
String get lastSuccess => 'Último éxito';
@override
String get lastFailure => 'Último fallo';
@override
String get recentConnections => 'Conexiones recientes';
@override
String get viewDetails => 'Ver detalles';
@override
String get connectionDetails => 'Detalles de conexión';
@override
String get clearThisServerStats => 'Limpiar estadísticas de este servidor';
@override
String get clearAllStatsTitle => 'Limpiar todas las estadísticas';
@override
String get clearAllStatsContent =>
'¿Estás seguro de que quieres limpiar todas las estadísticas de conexión del servidor? Esta acción no se puede deshacer.';
@override
String clearServerStatsTitle(String serverName) {
return 'Limpiar estadísticas de $serverName';
}
@override
String clearServerStatsContent(String serverName) {
return '¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"$serverName\"? Esta acción no se puede deshacer.';
}
@override
String get homeTabs => 'Pestañas de inicio';
@override
String get homeTabsCustomizeDesc =>
'Personaliza qué pestañas aparecen en la página de inicio y su orden';
@override
String get reset => 'Restablecer';
@override
String get availableTabs => 'Pestañas disponibles';
@override
String get atLeastOneTab => 'Al menos una pestaña debe estar seleccionada';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,60 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Déjà dans le dernier répertoire.'; String get alreadyLastDir => 'Déjà dans le dernier répertoire.';
@override
String get askAi => 'Demander à l\'IA';
@override
String get askAiApiKey => 'Clé API';
@override
String get askAiAwaitingResponse => 'En attente de la réponse de l\'IA...';
@override
String get askAiBaseUrl => 'URL de base';
@override
String get askAiCommandInserted => 'Commande insérée dans le terminal';
@override
String askAiConfigMissing(Object fields) {
return 'Veuillez configurer $fields dans les paramètres.';
}
@override
String get askAiConfirmExecute => 'Confirmer avant d\'exécuter';
@override
String get askAiConversation => 'Conversation avec l\'IA';
@override
String get askAiDisclaimer =>
'L\'IA peut se tromper. Utilisez-la avec prudence.';
@override
String get askAiFollowUpHint => 'Poser une question supplémentaire...';
@override
String get askAiInsertTerminal => 'Insérer dans le terminal';
@override
String get askAiModel => 'Modèle';
@override
String get askAiNoResponse => 'Aucune réponse';
@override
String get askAiRecommendedCommand => 'Commande suggérée par l\'IA';
@override
String get askAiSelectedContent => 'Contenu sélectionné';
@override
String get askAiUsageHint => 'Utilisé dans le terminal SSH';
@override
String get atLeastOneTab => 'Au moins un onglet doit être sélectionné';
@override @override
String get authFailTip => String get authFailTip =>
'Échec de l\'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.'; 'Échec de l\'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.';
@@ -45,6 +99,9 @@ class AppLocalizationsFr extends AppLocalizations {
String get autoUpdateHomeWidget => String get autoUpdateHomeWidget =>
'Mise à jour automatique du widget d\'accueil'; 'Mise à jour automatique du widget d\'accueil';
@override
String get availableTabs => 'Onglets disponibles';
@override @override
String get backupEncrypted => 'La sauvegarde est chiffrée'; String get backupEncrypted => 'La sauvegarde est chiffrée';
@@ -85,6 +142,26 @@ class AppLocalizationsFr extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Cette option signifie seulement que le programme essaiera de s\'exécuter en arrière-plan, que cela soit possible dépend de l\'autorisation activée ou non. Pour Android natif, veuillez désactiver l\'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d\'économie d\'énergie en « Illimité ».'; 'Cette option signifie seulement que le programme essaiera de s\'exécuter en arrière-plan, que cela soit possible dépend de l\'autorisation activée ou non. Pour Android natif, veuillez désactiver l\'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d\'économie d\'énergie en « Illimité ».';
@override
String get clearAllStatsContent =>
'Êtes-vous sûr de vouloir effacer toutes les statistiques de connexion des serveurs ? Cette action ne peut pas être annulée.';
@override
String get clearAllStatsTitle => 'Effacer toutes les statistiques';
@override
String clearServerStatsContent(Object serverName) {
return 'Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"$serverName\" ? Cette action ne peut pas être annulée.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Effacer les statistiques de $serverName';
}
@override
String get clearThisServerStats => 'Effacer les statistiques de ce serveur';
@override @override
String get closeAfterSave => 'Enregistrer et fermer'; String get closeAfterSave => 'Enregistrer et fermer';
@@ -98,6 +175,16 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get conn => 'Connexion'; String get conn => 'Connexion';
@override
String get connectionDetails => 'Détails de connexion';
@override
String get connectionStats => 'Statistiques de connexion';
@override
String get connectionStatsDesc =>
'Voir le taux de réussite de connexion du serveur et l\'historique';
@override @override
String get container => 'Conteneur'; String get container => 'Conteneur';
@@ -147,6 +234,18 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get disconnected => 'Déconnecté'; String get disconnected => 'Déconnecté';
@override
String get discoverSshServers => 'Découvrir les serveurs SSH';
@override
String get discoveryFailed => 'Échec de la découverte';
@override
String get discoverySettings => 'Paramètres de découverte';
@override
String get discoverySummary => 'Résumé de la découverte';
@override @override
String get disk => 'Disque'; String get disk => 'Disque';
@@ -199,9 +298,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get editVirtKeys => 'Modifier les touches virtuelles'; String get editVirtKeys => 'Modifier les touches virtuelles';
@override
String get editor => 'Éditeur';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s\'améliorer.'; 'La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s\'améliorer.';
@@ -209,6 +305,13 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get emulator => 'Émulateur'; String get emulator => 'Émulateur';
@override
String get enableMdns => 'Activer mDNS';
@override
String get enableMdnsDesc =>
'Utiliser mDNS/Bonjour pour découvrir les services SSH';
@override @override
String get encode => 'Encoder'; String get encode => 'Encoder';
@@ -241,10 +344,10 @@ class AppLocalizationsFr extends AppLocalizations {
} }
@override @override
String get followSystem => 'Suivre le système'; String get finishedAt => 'Terminé à';
@override @override
String get font => 'Police'; String get followSystem => 'Suivre le système';
@override @override
String get fontSize => 'Taille de la police'; String get fontSize => 'Taille de la police';
@@ -277,6 +380,13 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get highlight => 'Mise en surbrillance du code'; String get highlight => 'Mise en surbrillance du code';
@override
String get homeTabs => 'Onglets d\'accueil';
@override
String get homeTabsCustomizeDesc =>
'Personnalisez les onglets qui apparaissent sur la page d\'accueil et leur ordre';
@override @override
String get homeWidgetUrlConfig => 'Configurer l\'URL du widget d\'accueil'; String get homeWidgetUrlConfig => 'Configurer l\'URL du widget d\'accueil';
@@ -297,9 +407,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get imagesList => 'Liste des images'; String get imagesList => 'Liste des images';
@override
String get init => 'Initialiser';
@override @override
String get inner => 'Interne'; String get inner => 'Interne';
@@ -329,6 +436,12 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get keyAuth => 'Authentification par clé'; String get keyAuth => 'Authentification par clé';
@override
String get lastFailure => 'Dernier échec';
@override
String get lastSuccess => 'Dernier succès';
@override @override
String get letterCache => 'Mise en cache des lettres'; String get letterCache => 'Mise en cache des lettres';
@@ -336,9 +449,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.'; 'Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.';
@override
String get license => 'Licence';
@override @override
String get location => 'Emplacement'; String get location => 'Emplacement';
@@ -351,10 +461,10 @@ class AppLocalizationsFr extends AppLocalizations {
} }
@override @override
String get manual => 'Manuel'; String get max => 'max';
@override @override
String get max => 'max'; String get maxConcurrency => 'Concurrence maximale';
@override @override
String get maxRetryCount => 'Nombre de reconnexions au serveur'; String get maxRetryCount => 'Nombre de reconnexions au serveur';
@@ -394,6 +504,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get newContainer => 'Nouveau conteneur'; String get newContainer => 'Nouveau conteneur';
@override
String get noConnectionStatsData =>
'Aucune donnée de statistiques de connexion';
@override @override
String get noLineChart => 'Ne pas utiliser de graphiques linéaires'; String get noLineChart => 'Ne pas utiliser de graphiques linéaires';
@@ -469,10 +583,12 @@ class AppLocalizationsFr extends AppLocalizations {
'Prioriser laffichage de la capacité du disque'; 'Prioriser laffichage de la capacité du disque';
@override @override
String get preview => 'Aperçu'; String get privateKey => 'Clé privée';
@override @override
String get privateKey => 'Clé privée'; String privateKeyNotFoundFmt(Object keyId) {
return 'Clé privée [$keyId] introuvable.';
}
@override @override
String get process => 'Processus'; String get process => 'Processus';
@@ -501,6 +617,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get reboot => 'Redémarrer'; String get reboot => 'Redémarrer';
@override
String get recentConnections => 'Connexions récentes';
@override @override
String get rememberPwdInMem => 'Mémoriser le mot de passe en mémoire'; String get rememberPwdInMem => 'Mémoriser le mot de passe en mémoire';
@@ -563,6 +682,12 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get serverOrder => 'Ordre du serveur'; String get serverOrder => 'Ordre du serveur';
@override
String get serverTabRequired => 'L\'onglet serveur ne peut pas être supprimé';
@override
String get servers => 'serveurs';
@override @override
String get sftpDlPrepare => 'Préparation de la connexion...'; String get sftpDlPrepare => 'Préparation de la connexion...';
@@ -649,6 +774,34 @@ class AppLocalizationsFr extends AppLocalizations {
return '$count serveurs importés depuis la configuration SSH'; return '$count serveurs importés depuis la configuration SSH';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'La clé d\'hôte SSH de $serverName a changé. Ne continuez que si vous faites confiance à ce serveur.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Empreinte (MD5 Base64) : $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Empreinte (MD5 hex) : $fingerprint';
}
@override
String get sshHostKeyType => 'Type de clé d\'hôte SSH';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Une nouvelle clé d\'hôte SSH a été reçue de $serverName. Vérifiez l\'empreinte avant de faire confiance.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Empreinte enregistrée : $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Souhaitez-vous sélectionner manuellement le fichier de configuration SSH ?'; 'Souhaitez-vous sélectionner manuellement le fichier de configuration SSH ?';
@@ -713,9 +866,6 @@ class AppLocalizationsFr extends AppLocalizations {
return 'Passer à $val'; return 'Passer à $val';
} }
@override
String get sync => 'Sync';
@override @override
String get syncTip => String get syncTip =>
'Un redémarrage peut être nécessaire pour que certains changements prennent effet.'; 'Un redémarrage peut être nécessaire pour que certains changements prennent effet.';
@@ -726,6 +876,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get tag => 'Étiquettes'; String get tag => 'Étiquettes';
@override
String get tapToStartDiscovery =>
'Appuyez sur le bouton de recherche pour découvrir les serveurs SSH sur votre réseau';
@override @override
String get temperature => 'Température'; String get temperature => 'Température';
@@ -758,6 +912,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get total => 'Total'; String get total => 'Total';
@override
String get totalAttempts => 'Total';
@override @override
String get traffic => 'Trafic'; String get traffic => 'Trafic';
@@ -784,9 +941,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get updateServerStatusInterval => String get updateServerStatusInterval =>
'Intervalle de mise à jour de l\'état du serveur'; 'Intervalle de mise à jour de l\'état du serveur';
@override
String get upload => 'Télécharger';
@override @override
String get upsideDown => 'À l\'envers'; String get upsideDown => 'À l\'envers';
@@ -812,6 +966,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get view => 'Vue'; String get view => 'Vue';
@override
String get viewDetails => 'Voir les détails';
@override @override
String get viewErr => 'Voir erreur'; String get viewErr => 'Voir erreur';
@@ -855,72 +1012,4 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l\'état du système. Vous pouvez examiner le contenu du script.'; 'Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l\'état du système. Vous pouvez examiner le contenu du script.';
@override
String get connectionStats => 'Statistiques de connexion';
@override
String get connectionStatsDesc =>
'Voir le taux de réussite de connexion du serveur et l\'historique';
@override
String get noConnectionStatsData =>
'Aucune donnée de statistiques de connexion';
@override
String get totalAttempts => 'Total';
@override
String get lastSuccess => 'Dernier succès';
@override
String get lastFailure => 'Dernier échec';
@override
String get recentConnections => 'Connexions récentes';
@override
String get viewDetails => 'Voir les détails';
@override
String get connectionDetails => 'Détails de connexion';
@override
String get clearThisServerStats => 'Effacer les statistiques de ce serveur';
@override
String get clearAllStatsTitle => 'Effacer toutes les statistiques';
@override
String get clearAllStatsContent =>
'Êtes-vous sûr de vouloir effacer toutes les statistiques de connexion des serveurs ? Cette action ne peut pas être annulée.';
@override
String clearServerStatsTitle(String serverName) {
return 'Effacer les statistiques de $serverName';
}
@override
String clearServerStatsContent(String serverName) {
return 'Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"$serverName\" ? Cette action ne peut pas être annulée.';
}
@override
String get homeTabs => 'Onglets d\'accueil';
@override
String get homeTabsCustomizeDesc =>
'Personnalisez les onglets qui apparaissent sur la page d\'accueil et leur ordre';
@override
String get reset => 'Réinitialiser';
@override
String get availableTabs => 'Onglets disponibles';
@override
String get atLeastOneTab => 'Au moins un onglet doit être sélectionné';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -28,6 +28,59 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Sudah di direktori terakhir.'; String get alreadyLastDir => 'Sudah di direktori terakhir.';
@override
String get askAi => 'Tanya AI';
@override
String get askAiApiKey => 'Kunci API';
@override
String get askAiAwaitingResponse => 'Menunggu respons AI...';
@override
String get askAiBaseUrl => 'URL dasar';
@override
String get askAiCommandInserted => 'Perintah dimasukkan ke terminal';
@override
String askAiConfigMissing(Object fields) {
return 'Harap konfigurasikan $fields di Pengaturan.';
}
@override
String get askAiConfirmExecute => 'Konfirmasi sebelum menjalankan';
@override
String get askAiConversation => 'Percakapan AI';
@override
String get askAiDisclaimer => 'AI bisa saja salah. Gunakan dengan hati-hati.';
@override
String get askAiFollowUpHint => 'Ajukan pertanyaan lanjutan...';
@override
String get askAiInsertTerminal => 'Masukkan ke terminal';
@override
String get askAiModel => 'Model';
@override
String get askAiNoResponse => 'Tidak ada respons';
@override
String get askAiRecommendedCommand => 'Perintah yang disarankan AI';
@override
String get askAiSelectedContent => 'Konten yang dipilih';
@override
String get askAiUsageHint => 'Digunakan di Terminal SSH';
@override
String get atLeastOneTab => 'Setidaknya satu tab harus dipilih';
@override @override
String get authFailTip => String get authFailTip =>
'Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.'; 'Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.';
@@ -45,6 +98,9 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => 'Widget Rumah Pembaruan Otomatis'; String get autoUpdateHomeWidget => 'Widget Rumah Pembaruan Otomatis';
@override
String get availableTabs => 'Tab Tersedia';
@override @override
String get backupEncrypted => 'Cadangan telah dienkripsi'; String get backupEncrypted => 'Cadangan telah dienkripsi';
@@ -84,6 +140,26 @@ class AppLocalizationsId extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".'; 'Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".';
@override
String get clearAllStatsContent =>
'Apakah Anda yakin ingin menghapus semua statistik koneksi server? Tindakan ini tidak dapat dibatalkan.';
@override
String get clearAllStatsTitle => 'Hapus Semua Statistik';
@override
String clearServerStatsContent(Object serverName) {
return 'Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"$serverName\"? Tindakan ini tidak dapat dibatalkan.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Hapus Statistik $serverName';
}
@override
String get clearThisServerStats => 'Hapus Statistik Server Ini';
@override @override
String get closeAfterSave => 'Simpan dan tutup'; String get closeAfterSave => 'Simpan dan tutup';
@@ -97,6 +173,16 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get conn => 'Koneksi'; String get conn => 'Koneksi';
@override
String get connectionDetails => 'Detail Koneksi';
@override
String get connectionStats => 'Statistik Koneksi';
@override
String get connectionStatsDesc =>
'Lihat tingkat keberhasilan koneksi server dan riwayat';
@override @override
String get container => 'Wadah'; String get container => 'Wadah';
@@ -146,6 +232,18 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get disconnected => 'Terputus'; String get disconnected => 'Terputus';
@override
String get discoverSshServers => 'Temukan Server SSH';
@override
String get discoveryFailed => 'Penemuan gagal';
@override
String get discoverySettings => 'Pengaturan Penemuan';
@override
String get discoverySummary => 'Ringkasan Penemuan';
@override @override
String get disk => 'Disk'; String get disk => 'Disk';
@@ -198,9 +296,6 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get editVirtKeys => 'Edit kunci virtual'; String get editVirtKeys => 'Edit kunci virtual';
@override
String get editor => 'Editor';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.'; 'Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.';
@@ -208,6 +303,13 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get emulator => 'Emulator'; String get emulator => 'Emulator';
@override
String get enableMdns => 'Aktifkan mDNS';
@override
String get enableMdnsDesc =>
'Gunakan mDNS/Bonjour untuk menemukan layanan SSH';
@override @override
String get encode => 'Menyandi'; String get encode => 'Menyandi';
@@ -240,10 +342,10 @@ class AppLocalizationsId extends AppLocalizations {
} }
@override @override
String get followSystem => 'Ikuti sistem'; String get finishedAt => 'Selesai pada';
@override @override
String get font => 'Font'; String get followSystem => 'Ikuti sistem';
@override @override
String get fontSize => 'Ukuran huruf'; String get fontSize => 'Ukuran huruf';
@@ -276,6 +378,13 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get highlight => 'Sorotan kode'; String get highlight => 'Sorotan kode';
@override
String get homeTabs => 'Tab Beranda';
@override
String get homeTabsCustomizeDesc =>
'Sesuaikan tab mana yang muncul di halaman beranda dan urutannya';
@override @override
String get homeWidgetUrlConfig => 'Konfigurasi URL Widget Rumah'; String get homeWidgetUrlConfig => 'Konfigurasi URL Widget Rumah';
@@ -296,9 +405,6 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get imagesList => 'Daftar gambar'; String get imagesList => 'Daftar gambar';
@override
String get init => 'Menginisialisasi';
@override @override
String get inner => 'Batin'; String get inner => 'Batin';
@@ -328,6 +434,12 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get keyAuth => 'Auth kunci'; String get keyAuth => 'Auth kunci';
@override
String get lastFailure => 'Gagal Terakhir';
@override
String get lastSuccess => 'Sukses Terakhir';
@override @override
String get letterCache => 'Caching huruf'; String get letterCache => 'Caching huruf';
@@ -335,9 +447,6 @@ class AppLocalizationsId extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.'; 'Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.';
@override
String get license => 'Lisensi';
@override @override
String get location => 'Lokasi'; String get location => 'Lokasi';
@@ -350,10 +459,10 @@ class AppLocalizationsId extends AppLocalizations {
} }
@override @override
String get manual => 'Manual'; String get max => 'Max';
@override @override
String get max => 'Max'; String get maxConcurrency => 'Konkurensi Maksimum';
@override @override
String get maxRetryCount => 'Jumlah penyambungan kembali server'; String get maxRetryCount => 'Jumlah penyambungan kembali server';
@@ -393,6 +502,9 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get newContainer => 'Wadah baru'; String get newContainer => 'Wadah baru';
@override
String get noConnectionStatsData => 'Tidak ada data statistik koneksi';
@override @override
String get noLineChart => 'Jangan gunakan grafik garis'; String get noLineChart => 'Jangan gunakan grafik garis';
@@ -464,10 +576,12 @@ class AppLocalizationsId extends AppLocalizations {
String get preferDiskAmount => 'Prioritaskan tampilan kapasitas disk'; String get preferDiskAmount => 'Prioritaskan tampilan kapasitas disk';
@override @override
String get preview => 'Pratinjau'; String get privateKey => 'Kunci Pribadi';
@override @override
String get privateKey => 'Kunci Pribadi'; String privateKeyNotFoundFmt(Object keyId) {
return 'Kunci privat [$keyId] tidak ditemukan.';
}
@override @override
String get process => 'Proses'; String get process => 'Proses';
@@ -496,6 +610,9 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get reboot => 'Reboot'; String get reboot => 'Reboot';
@override
String get recentConnections => 'Koneksi Terkini';
@override @override
String get rememberPwdInMem => 'Ingat kata sandi di dalam memori'; String get rememberPwdInMem => 'Ingat kata sandi di dalam memori';
@@ -557,6 +674,12 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get serverOrder => 'Pesanan server'; String get serverOrder => 'Pesanan server';
@override
String get serverTabRequired => 'Tab server tidak dapat dihapus';
@override
String get servers => 'server';
@override @override
String get sftpDlPrepare => 'Bersiap untuk terhubung ...'; String get sftpDlPrepare => 'Bersiap untuk terhubung ...';
@@ -641,6 +764,34 @@ class AppLocalizationsId extends AppLocalizations {
return 'Berhasil mengimpor $count server dari konfigurasi SSH'; return 'Berhasil mengimpor $count server dari konfigurasi SSH';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'Kunci host SSH untuk $serverName telah berubah. Lanjutkan hanya jika Anda mempercayai server ini.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Sidik jari (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Sidik jari (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'Jenis kunci host SSH';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Kunci host SSH baru diterima dari $serverName. Periksa sidik jarinya sebelum mempercayai.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Sidik jari tersimpan: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Apakah Anda ingin memilih file konfigurasi SSH secara manual?'; 'Apakah Anda ingin memilih file konfigurasi SSH secara manual?';
@@ -703,9 +854,6 @@ class AppLocalizationsId extends AppLocalizations {
return 'Beralih ke $val'; return 'Beralih ke $val';
} }
@override
String get sync => 'Sinkronisasi';
@override @override
String get syncTip => String get syncTip =>
'Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.'; 'Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.';
@@ -716,6 +864,10 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get tag => 'Tag'; String get tag => 'Tag';
@override
String get tapToStartDiscovery =>
'Tekan tombol pencarian untuk menemukan server SSH di jaringan Anda';
@override @override
String get temperature => 'Suhu'; String get temperature => 'Suhu';
@@ -748,6 +900,9 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get total => 'Total'; String get total => 'Total';
@override
String get totalAttempts => 'Total';
@override @override
String get traffic => 'Lalu lintas'; String get traffic => 'Lalu lintas';
@@ -773,9 +928,6 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get updateServerStatusInterval => 'Interval Pembaruan Status Server'; String get updateServerStatusInterval => 'Interval Pembaruan Status Server';
@override
String get upload => 'Mengunggah';
@override @override
String get upsideDown => 'Terbalik'; String get upsideDown => 'Terbalik';
@@ -801,6 +953,9 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get view => 'Tampilan'; String get view => 'Tampilan';
@override
String get viewDetails => 'Lihat Detail';
@override @override
String get viewErr => 'Lihat kesalahan'; String get viewErr => 'Lihat kesalahan';
@@ -843,71 +998,4 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.'; 'Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.';
@override
String get connectionStats => 'Statistik Koneksi';
@override
String get connectionStatsDesc =>
'Lihat tingkat keberhasilan koneksi server dan riwayat';
@override
String get noConnectionStatsData => 'Tidak ada data statistik koneksi';
@override
String get totalAttempts => 'Total';
@override
String get lastSuccess => 'Sukses Terakhir';
@override
String get lastFailure => 'Gagal Terakhir';
@override
String get recentConnections => 'Koneksi Terkini';
@override
String get viewDetails => 'Lihat Detail';
@override
String get connectionDetails => 'Detail Koneksi';
@override
String get clearThisServerStats => 'Hapus Statistik Server Ini';
@override
String get clearAllStatsTitle => 'Hapus Semua Statistik';
@override
String get clearAllStatsContent =>
'Apakah Anda yakin ingin menghapus semua statistik koneksi server? Tindakan ini tidak dapat dibatalkan.';
@override
String clearServerStatsTitle(String serverName) {
return 'Hapus Statistik $serverName';
}
@override
String clearServerStatsContent(String serverName) {
return 'Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"$serverName\"? Tindakan ini tidak dapat dibatalkan.';
}
@override
String get homeTabs => 'Tab Beranda';
@override
String get homeTabsCustomizeDesc =>
'Sesuaikan tab mana yang muncul di halaman beranda dan urutannya';
@override
String get reset => 'Reset';
@override
String get availableTabs => 'Tab Tersedia';
@override
String get atLeastOneTab => 'Setidaknya satu tab harus dipilih';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,59 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get alreadyLastDir => 'すでに最上位のディレクトリです'; String get alreadyLastDir => 'すでに最上位のディレクトリです';
@override
String get askAi => 'AI に質問';
@override
String get askAiApiKey => 'API キー';
@override
String get askAiAwaitingResponse => 'AI の応答を待機中...';
@override
String get askAiBaseUrl => 'ベース URL';
@override
String get askAiCommandInserted => 'コマンドをターミナルに挿入しました';
@override
String askAiConfigMissing(Object fields) {
return '設定で $fields を構成してください。';
}
@override
String get askAiConfirmExecute => '実行前に確認';
@override
String get askAiConversation => 'AI 会話';
@override
String get askAiDisclaimer => 'AI が誤る可能性があります。注意してご利用ください。';
@override
String get askAiFollowUpHint => '追質問をする...';
@override
String get askAiInsertTerminal => 'ターミナルに挿入';
@override
String get askAiModel => 'モデル';
@override
String get askAiNoResponse => '応答なし';
@override
String get askAiRecommendedCommand => 'AI 推奨コマンド';
@override
String get askAiSelectedContent => '選択した内容';
@override
String get askAiUsageHint => 'SSH ターミナルで使用';
@override
String get atLeastOneTab => '少なくとも1つのタブを選択する必要があります';
@override @override
String get authFailTip => '認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。'; String get authFailTip => '認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。';
@@ -42,6 +95,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => 'ホームウィジェットを自動更新'; String get autoUpdateHomeWidget => 'ホームウィジェットを自動更新';
@override
String get availableTabs => '利用可能なタブ';
@override @override
String get backupEncrypted => 'バックアップは暗号化されています'; String get backupEncrypted => 'バックアップは暗号化されています';
@@ -80,6 +136,25 @@ class AppLocalizationsJa extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。'; 'このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。';
@override
String get clearAllStatsContent => 'すべてのサーバー接続統計を削除してもよろしいですか?この操作は元に戻せません。';
@override
String get clearAllStatsTitle => 'すべての統計をクリア';
@override
String clearServerStatsContent(Object serverName) {
return 'サーバー\"$serverName\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。';
}
@override
String clearServerStatsTitle(Object serverName) {
return '$serverNameの統計をクリア';
}
@override
String get clearThisServerStats => 'このサーバーの統計をクリア';
@override @override
String get closeAfterSave => '保存して閉じる'; String get closeAfterSave => '保存して閉じる';
@@ -92,6 +167,15 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get conn => '接続'; String get conn => '接続';
@override
String get connectionDetails => '接続の詳細';
@override
String get connectionStats => '接続統計';
@override
String get connectionStatsDesc => 'サーバー接続成功率と履歴を表示';
@override @override
String get container => 'コンテナ'; String get container => 'コンテナ';
@@ -139,6 +223,18 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get disconnected => '接続が切断されました'; String get disconnected => '接続が切断されました';
@override
String get discoverSshServers => 'SSHサーバーの発見';
@override
String get discoveryFailed => '発見に失敗';
@override
String get discoverySettings => '発見設定';
@override
String get discoverySummary => '発見の概要';
@override @override
String get disk => 'ディスク'; String get disk => 'ディスク';
@@ -191,9 +287,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get editVirtKeys => '仮想キーを編集'; String get editVirtKeys => '仮想キーを編集';
@override
String get editor => 'エディター';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。'; '現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。';
@@ -201,6 +294,12 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get emulator => 'エミュレーター'; String get emulator => 'エミュレーター';
@override
String get enableMdns => 'mDNSを有効化';
@override
String get enableMdnsDesc => 'mDNS/BonjourでSSHサービスを発見';
@override @override
String get encode => 'エンコード'; String get encode => 'エンコード';
@@ -233,10 +332,10 @@ class AppLocalizationsJa extends AppLocalizations {
} }
@override @override
String get followSystem => 'システムに従う'; String get finishedAt => '完了時刻';
@override @override
String get font => 'フォント'; String get followSystem => 'システムに従う';
@override @override
String get fontSize => 'フォントサイズ'; String get fontSize => 'フォントサイズ';
@@ -269,6 +368,12 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get highlight => 'コードハイライト'; String get highlight => 'コードハイライト';
@override
String get homeTabs => 'ホームタブ';
@override
String get homeTabsCustomizeDesc => 'ホームページに表示するタブとその順序をカスタマイズします';
@override @override
String get homeWidgetUrlConfig => 'ホームウィジェットURL設定'; String get homeWidgetUrlConfig => 'ホームウィジェットURL設定';
@@ -289,9 +394,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get imagesList => 'イメージリスト'; String get imagesList => 'イメージリスト';
@override
String get init => '初期化する';
@override @override
String get inner => '内蔵'; String get inner => '内蔵';
@@ -320,15 +422,18 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get keyAuth => 'キー認証'; String get keyAuth => 'キー認証';
@override
String get lastFailure => '最後の失敗';
@override
String get lastSuccess => '最後の成功';
@override @override
String get letterCache => '文字キャッシング'; String get letterCache => '文字キャッシング';
@override @override
String get letterCacheTip => '無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。'; String get letterCacheTip => '無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。';
@override
String get license => 'オープンソースライセンス';
@override @override
String get location => '場所'; String get location => '場所';
@@ -341,10 +446,10 @@ class AppLocalizationsJa extends AppLocalizations {
} }
@override @override
String get manual => 'マニュアル'; String get max => '最大';
@override @override
String get max => '最大'; String get maxConcurrency => '最大同時実行数';
@override @override
String get maxRetryCount => 'サーバーの再接続試行回数'; String get maxRetryCount => 'サーバーの再接続試行回数';
@@ -384,6 +489,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get newContainer => '新しいコンテナを作成'; String get newContainer => '新しいコンテナを作成';
@override
String get noConnectionStatsData => '接続統計データがありません';
@override @override
String get noLineChart => '折れ線グラフを使用しない'; String get noLineChart => '折れ線グラフを使用しない';
@@ -450,10 +558,12 @@ class AppLocalizationsJa extends AppLocalizations {
String get preferDiskAmount => 'ディスク容量を優先的に表示'; String get preferDiskAmount => 'ディスク容量を優先的に表示';
@override @override
String get preview => 'プレビュー'; String get privateKey => '秘密鍵';
@override @override
String get privateKey => '秘密鍵'; String privateKeyNotFoundFmt(Object keyId) {
return '秘密鍵 [$keyId] が見つかりません。';
}
@override @override
String get process => 'プロセス'; String get process => 'プロセス';
@@ -481,6 +591,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get reboot => '再起動'; String get reboot => '再起動';
@override
String get recentConnections => '最近の接続';
@override @override
String get rememberPwdInMem => 'メモリにパスワードを記憶する'; String get rememberPwdInMem => 'メモリにパスワードを記憶する';
@@ -541,6 +654,12 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get serverOrder => 'サーバー順序'; String get serverOrder => 'サーバー順序';
@override
String get serverTabRequired => 'サーバータブは削除できません';
@override
String get servers => 'サーバー';
@override @override
String get sftpDlPrepare => 'サーバーへの接続を準備中...'; String get sftpDlPrepare => 'サーバーへの接続を準備中...';
@@ -623,6 +742,34 @@ class AppLocalizationsJa extends AppLocalizations {
return 'SSH設定から$count個のサーバーをインポートしました'; return 'SSH設定から$count個のサーバーをインポートしました';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return '$serverName の SSH ホスト鍵が変更されました。このサーバーを信頼できる場合のみ続行してください。';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'フィンガープリント (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'フィンガープリント (MD5 16進): $fingerprint';
}
@override
String get sshHostKeyType => 'SSH ホストキーの種類';
@override
String sshHostKeyNewDesc(Object serverName) {
return '$serverName から新しい SSH ホスト鍵を受信しました。信頼する前にフィンガープリントを確認してください。';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return '保存済みフィンガープリント: $fingerprint';
}
@override @override
String get sshConfigManualSelect => 'SSH設定ファイルを手動で選択しますか'; String get sshConfigManualSelect => 'SSH設定ファイルを手動で選択しますか';
@@ -681,9 +828,6 @@ class AppLocalizationsJa extends AppLocalizations {
return '$valに切り替える'; return '$valに切り替える';
} }
@override
String get sync => '同期する';
@override @override
String get syncTip => '再起動が必要な場合があります。一部の変更はその後に有効になります。'; String get syncTip => '再起動が必要な場合があります。一部の変更はその後に有効になります。';
@@ -693,6 +837,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get tag => 'タグ'; String get tag => 'タグ';
@override
String get tapToStartDiscovery => '検索ボタンをタップしてネットワーク上のSSHサーバーを発見';
@override @override
String get temperature => '温度'; String get temperature => '温度';
@@ -725,6 +872,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get total => '合計'; String get total => '合計';
@override
String get totalAttempts => '総計';
@override @override
String get traffic => 'トラフィック'; String get traffic => 'トラフィック';
@@ -750,9 +900,6 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get updateServerStatusInterval => 'サーバー状態の更新間隔'; String get updateServerStatusInterval => 'サーバー状態の更新間隔';
@override
String get upload => 'アップロード';
@override @override
String get upsideDown => '上下逆転'; String get upsideDown => '上下逆転';
@@ -777,6 +924,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get view => 'ビュー'; String get view => 'ビュー';
@override
String get viewDetails => '詳細を表示';
@override @override
String get viewErr => 'エラーを表示'; String get viewErr => 'エラーを表示';
@@ -818,68 +968,4 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。'; 'サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。';
@override
String get connectionStats => '接続統計';
@override
String get connectionStatsDesc => 'サーバー接続成功率と履歴を表示';
@override
String get noConnectionStatsData => '接続統計データがありません';
@override
String get totalAttempts => '総計';
@override
String get lastSuccess => '最後の成功';
@override
String get lastFailure => '最後の失敗';
@override
String get recentConnections => '最近の接続';
@override
String get viewDetails => '詳細を表示';
@override
String get connectionDetails => '接続の詳細';
@override
String get clearThisServerStats => 'このサーバーの統計をクリア';
@override
String get clearAllStatsTitle => 'すべての統計をクリア';
@override
String get clearAllStatsContent => 'すべてのサーバー接続統計を削除してもよろしいですか?この操作は元に戻せません。';
@override
String clearServerStatsTitle(String serverName) {
return '$serverNameの統計をクリア';
}
@override
String clearServerStatsContent(String serverName) {
return 'サーバー\"$serverName\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。';
}
@override
String get homeTabs => 'ホームタブ';
@override
String get homeTabsCustomizeDesc => 'ホームページに表示するタブとその順序をカスタマイズします';
@override
String get reset => 'リセット';
@override
String get availableTabs => '利用可能なタブ';
@override
String get atLeastOneTab => '少なくとも1つのタブを選択する必要があります';
@override
String get serverTabRequired => 'サーバータブは削除できません';
} }

View File

@@ -28,6 +28,60 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Al in de laatst gebruikte map.'; String get alreadyLastDir => 'Al in de laatst gebruikte map.';
@override
String get askAi => 'AI vragen';
@override
String get askAiApiKey => 'API-sleutel';
@override
String get askAiAwaitingResponse => 'Wachten op AI-reactie...';
@override
String get askAiBaseUrl => 'Basis-URL';
@override
String get askAiCommandInserted => 'Commando in terminal ingevoegd';
@override
String askAiConfigMissing(Object fields) {
return 'Configureer $fields in de instellingen.';
}
@override
String get askAiConfirmExecute => 'Bevestigen voor uitvoeren';
@override
String get askAiConversation => 'AI-gesprek';
@override
String get askAiDisclaimer => 'AI kan fouten maken. Gebruik het zorgvuldig.';
@override
String get askAiFollowUpHint => 'Stel een vervolgvraag...';
@override
String get askAiInsertTerminal => 'In terminal invoegen';
@override
String get askAiModel => 'Model';
@override
String get askAiNoResponse => 'Geen reactie';
@override
String get askAiRecommendedCommand => 'Door AI voorgestelde opdracht';
@override
String get askAiSelectedContent => 'Geselecteerde inhoud';
@override
String get askAiUsageHint => 'Gebruikt in de SSH-terminal';
@override
String get atLeastOneTab =>
'Er moet minimaal één tabblad worden geselecteerd';
@override @override
String get authFailTip => String get authFailTip =>
'Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.'; 'Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.';
@@ -45,6 +99,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => 'Automatische update van home-widget'; String get autoUpdateHomeWidget => 'Automatische update van home-widget';
@override
String get availableTabs => 'Beschikbare tabbladen';
@override @override
String get backupEncrypted => 'Back-up is versleuteld'; String get backupEncrypted => 'Back-up is versleuteld';
@@ -84,6 +141,26 @@ class AppLocalizationsNl extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".'; 'Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".';
@override
String get clearAllStatsContent =>
'Weet u zeker dat u alle serververbindingsstatistieken wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
@override
String get clearAllStatsTitle => 'Alle statistieken wissen';
@override
String clearServerStatsContent(Object serverName) {
return 'Weet u zeker dat u de verbindingsstatistieken voor server \"$serverName\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Statistieken van $serverName wissen';
}
@override
String get clearThisServerStats => 'Statistieken van deze server wissen';
@override @override
String get closeAfterSave => 'Opslaan en sluiten'; String get closeAfterSave => 'Opslaan en sluiten';
@@ -97,6 +174,16 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get conn => 'Verbinding'; String get conn => 'Verbinding';
@override
String get connectionDetails => 'Verbindingsdetails';
@override
String get connectionStats => 'Verbindingsstatistieken';
@override
String get connectionStatsDesc =>
'Bekijk server verbindingssucces ratio en geschiedenis';
@override @override
String get container => 'Container'; String get container => 'Container';
@@ -146,6 +233,18 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get disconnected => 'Verbroken'; String get disconnected => 'Verbroken';
@override
String get discoverSshServers => 'SSH-servers ontdekken';
@override
String get discoveryFailed => 'Ontdekking mislukt';
@override
String get discoverySettings => 'Ontdekkingsinstellingen';
@override
String get discoverySummary => 'Ontdekkingssamenvatting';
@override @override
String get disk => 'Schijf'; String get disk => 'Schijf';
@@ -198,9 +297,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get editVirtKeys => 'Virtuele toetsen bewerken'; String get editVirtKeys => 'Virtuele toetsen bewerken';
@override
String get editor => 'Editor';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.'; 'De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.';
@@ -208,6 +304,13 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get emulator => 'Emulator'; String get emulator => 'Emulator';
@override
String get enableMdns => 'mDNS inschakelen';
@override
String get enableMdnsDesc =>
'Gebruik mDNS/Bonjour om SSH-services te ontdekken';
@override @override
String get encode => 'Coderen'; String get encode => 'Coderen';
@@ -240,10 +343,10 @@ class AppLocalizationsNl extends AppLocalizations {
} }
@override @override
String get followSystem => 'Volg systeem'; String get finishedAt => 'Voltooid om';
@override @override
String get font => 'Lettertype'; String get followSystem => 'Volg systeem';
@override @override
String get fontSize => 'Lettergrootte'; String get fontSize => 'Lettergrootte';
@@ -276,6 +379,13 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get highlight => 'Code-highlight'; String get highlight => 'Code-highlight';
@override
String get homeTabs => 'Home-tabbladen';
@override
String get homeTabsCustomizeDesc =>
'Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde';
@override @override
String get homeWidgetUrlConfig => 'Home-widget-url configureren'; String get homeWidgetUrlConfig => 'Home-widget-url configureren';
@@ -296,9 +406,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get imagesList => 'Lijst met afbeeldingen'; String get imagesList => 'Lijst met afbeeldingen';
@override
String get init => 'Initialiseren';
@override @override
String get inner => 'Intern'; String get inner => 'Intern';
@@ -328,6 +435,12 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get keyAuth => 'Sleutelauthenticatie'; String get keyAuth => 'Sleutelauthenticatie';
@override
String get lastFailure => 'Laatst gefaald';
@override
String get lastSuccess => 'Laatst succesvol';
@override @override
String get letterCache => 'Lettercaching'; String get letterCache => 'Lettercaching';
@@ -335,9 +448,6 @@ class AppLocalizationsNl extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.'; 'Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.';
@override
String get license => 'Licentie';
@override @override
String get location => 'Locatie'; String get location => 'Locatie';
@@ -350,10 +460,10 @@ class AppLocalizationsNl extends AppLocalizations {
} }
@override @override
String get manual => 'Handleiding'; String get max => 'max';
@override @override
String get max => 'max'; String get maxConcurrency => 'Maximale gelijktijdigheid';
@override @override
String get maxRetryCount => 'Aantal serverherverbindingen'; String get maxRetryCount => 'Aantal serverherverbindingen';
@@ -393,6 +503,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get newContainer => 'Nieuwe container'; String get newContainer => 'Nieuwe container';
@override
String get noConnectionStatsData => 'Geen verbindingsstatistiekgegevens';
@override @override
String get noLineChart => 'lijndiagrammen gebruiken'; String get noLineChart => 'lijndiagrammen gebruiken';
@@ -465,10 +578,12 @@ class AppLocalizationsNl extends AppLocalizations {
'Geef de schijfcapaciteit prioriteit bij weergave'; 'Geef de schijfcapaciteit prioriteit bij weergave';
@override @override
String get preview => 'Voorbeeld'; String get privateKey => 'Privésleutel';
@override @override
String get privateKey => 'Privésleutel'; String privateKeyNotFoundFmt(Object keyId) {
return 'Privésleutel [$keyId] niet gevonden.';
}
@override @override
String get process => 'Proces'; String get process => 'Proces';
@@ -497,6 +612,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get reboot => 'Herstart'; String get reboot => 'Herstart';
@override
String get recentConnections => 'Recente verbindingen';
@override @override
String get rememberPwdInMem => 'Wachtwoord onthouden in geheugen'; String get rememberPwdInMem => 'Wachtwoord onthouden in geheugen';
@@ -558,6 +676,12 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get serverOrder => 'Servervolgorde'; String get serverOrder => 'Servervolgorde';
@override
String get serverTabRequired => 'Servertabblad kan niet worden verwijderd';
@override
String get servers => 'servers';
@override @override
String get sftpDlPrepare => 'Voorbereiden om verbinding te maken...'; String get sftpDlPrepare => 'Voorbereiden om verbinding te maken...';
@@ -644,6 +768,34 @@ class AppLocalizationsNl extends AppLocalizations {
return '$count servers geïmporteerd uit SSH-configuratie'; return '$count servers geïmporteerd uit SSH-configuratie';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'De SSH-hostsleutel voor $serverName is gewijzigd. Ga alleen verder als u deze server vertrouwt.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Vingerafdruk (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Vingerafdruk (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'Type SSH-hostsleutel';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Er is een nieuwe SSH-hostsleutel ontvangen van $serverName. Controleer de vingerafdruk voordat u vertrouwt.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Opgeslagen vingerafdruk: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Wilt u het SSH-configuratiebestand handmatig selecteren?'; 'Wilt u het SSH-configuratiebestand handmatig selecteren?';
@@ -707,9 +859,6 @@ class AppLocalizationsNl extends AppLocalizations {
return 'Overschakelen naar $val'; return 'Overschakelen naar $val';
} }
@override
String get sync => 'Sync';
@override @override
String get syncTip => String get syncTip =>
'Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.'; 'Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.';
@@ -720,6 +869,10 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get tag => 'Labels'; String get tag => 'Labels';
@override
String get tapToStartDiscovery =>
'Tik op de zoekknop om SSH-servers op uw netwerk te ontdekken';
@override @override
String get temperature => 'Temperatuur'; String get temperature => 'Temperatuur';
@@ -752,6 +905,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get total => 'Totaal'; String get total => 'Totaal';
@override
String get totalAttempts => 'Totaal';
@override @override
String get traffic => 'Verkeer'; String get traffic => 'Verkeer';
@@ -778,9 +934,6 @@ class AppLocalizationsNl extends AppLocalizations {
String get updateServerStatusInterval => String get updateServerStatusInterval =>
'Interne server status bijwerking interval'; 'Interne server status bijwerking interval';
@override
String get upload => 'Upload';
@override @override
String get upsideDown => 'Ondersteboven'; String get upsideDown => 'Ondersteboven';
@@ -806,6 +959,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get view => 'Weergave'; String get view => 'Weergave';
@override
String get viewDetails => 'Details bekijken';
@override @override
String get viewErr => 'Zie foutmelding'; String get viewErr => 'Zie foutmelding';
@@ -849,72 +1005,4 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.'; 'Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.';
@override
String get connectionStats => 'Verbindingsstatistieken';
@override
String get connectionStatsDesc =>
'Bekijk server verbindingssucces ratio en geschiedenis';
@override
String get noConnectionStatsData => 'Geen verbindingsstatistiekgegevens';
@override
String get totalAttempts => 'Totaal';
@override
String get lastSuccess => 'Laatst succesvol';
@override
String get lastFailure => 'Laatst gefaald';
@override
String get recentConnections => 'Recente verbindingen';
@override
String get viewDetails => 'Details bekijken';
@override
String get connectionDetails => 'Verbindingsdetails';
@override
String get clearThisServerStats => 'Statistieken van deze server wissen';
@override
String get clearAllStatsTitle => 'Alle statistieken wissen';
@override
String get clearAllStatsContent =>
'Weet u zeker dat u alle serververbindingsstatistieken wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
@override
String clearServerStatsTitle(String serverName) {
return 'Statistieken van $serverName wissen';
}
@override
String clearServerStatsContent(String serverName) {
return 'Weet u zeker dat u de verbindingsstatistieken voor server \"$serverName\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.';
}
@override
String get homeTabs => 'Home-tabbladen';
@override
String get homeTabsCustomizeDesc =>
'Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde';
@override
String get reset => 'Resetten';
@override
String get availableTabs => 'Beschikbare tabbladen';
@override
String get atLeastOneTab =>
'Er moet minimaal één tabblad worden geselecteerd';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,59 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Já é o diretório mais alto'; String get alreadyLastDir => 'Já é o diretório mais alto';
@override
String get askAi => 'Perguntar à IA';
@override
String get askAiApiKey => 'Chave de API';
@override
String get askAiAwaitingResponse => 'Aguardando resposta da IA...';
@override
String get askAiBaseUrl => 'URL base';
@override
String get askAiCommandInserted => 'Comando inserido no terminal';
@override
String askAiConfigMissing(Object fields) {
return 'Configure $fields nas configurações.';
}
@override
String get askAiConfirmExecute => 'Confirmar antes de executar';
@override
String get askAiConversation => 'Conversa com a IA';
@override
String get askAiDisclaimer => 'A IA pode errar. Use com cautela.';
@override
String get askAiFollowUpHint => 'Faça uma pergunta adicional...';
@override
String get askAiInsertTerminal => 'Inserir no terminal';
@override
String get askAiModel => 'Modelo';
@override
String get askAiNoResponse => 'Sem resposta';
@override
String get askAiRecommendedCommand => 'Comando sugerido pela IA';
@override
String get askAiSelectedContent => 'Conteúdo selecionado';
@override
String get askAiUsageHint => 'Usado no terminal SSH';
@override
String get atLeastOneTab => 'Pelo menos uma aba deve ser selecionada';
@override @override
String get authFailTip => String get authFailTip =>
'Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.'; 'Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.';
@@ -45,6 +98,9 @@ class AppLocalizationsPt extends AppLocalizations {
String get autoUpdateHomeWidget => String get autoUpdateHomeWidget =>
'Atualização automática do widget da tela inicial'; 'Atualização automática do widget da tela inicial';
@override
String get availableTabs => 'Abas disponíveis';
@override @override
String get backupEncrypted => 'Backup está criptografado'; String get backupEncrypted => 'Backup está criptografado';
@@ -85,6 +141,26 @@ class AppLocalizationsPt extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a \'Otimização de bateria\' para este app, no MIUI, altere a estratégia de economia de energia para \'Sem restrições\'.'; 'Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a \'Otimização de bateria\' para este app, no MIUI, altere a estratégia de economia de energia para \'Sem restrições\'.';
@override
String get clearAllStatsContent =>
'Tem certeza de que deseja limpar todas as estatísticas de conexão do servidor? Esta ação não pode ser desfeita.';
@override
String get clearAllStatsTitle => 'Limpar todas as estatísticas';
@override
String clearServerStatsContent(Object serverName) {
return 'Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"$serverName\"? Esta ação não pode ser desfeita.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Limpar estatísticas de $serverName';
}
@override
String get clearThisServerStats => 'Limpar estatísticas deste servidor';
@override @override
String get closeAfterSave => 'Salvar e fechar'; String get closeAfterSave => 'Salvar e fechar';
@@ -97,6 +173,16 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get conn => 'Conectar'; String get conn => 'Conectar';
@override
String get connectionDetails => 'Detalhes da conexão';
@override
String get connectionStats => 'Estatísticas de conexão';
@override
String get connectionStatsDesc =>
'Ver taxa de sucesso de conexão do servidor e histórico';
@override @override
String get container => 'Contêiner'; String get container => 'Contêiner';
@@ -146,6 +232,18 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get disconnected => 'Desconectado'; String get disconnected => 'Desconectado';
@override
String get discoverSshServers => 'Descobrir servidores SSH';
@override
String get discoveryFailed => 'Descoberta falhou';
@override
String get discoverySettings => 'Configurações de descoberta';
@override
String get discoverySummary => 'Resumo da descoberta';
@override @override
String get disk => 'Disco'; String get disk => 'Disco';
@@ -198,9 +296,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get editVirtKeys => 'Editar teclas virtuais'; String get editVirtKeys => 'Editar teclas virtuais';
@override
String get editor => 'Editor';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.'; 'O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.';
@@ -208,6 +303,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get emulator => 'Emulador'; String get emulator => 'Emulador';
@override
String get enableMdns => 'Ativar mDNS';
@override
String get enableMdnsDesc => 'Usar mDNS/Bonjour para descobrir serviços SSH';
@override @override
String get encode => 'Codificar'; String get encode => 'Codificar';
@@ -240,10 +341,10 @@ class AppLocalizationsPt extends AppLocalizations {
} }
@override @override
String get followSystem => 'Seguir sistema'; String get finishedAt => 'Terminado em';
@override @override
String get font => 'Fonte'; String get followSystem => 'Seguir sistema';
@override @override
String get fontSize => 'Tamanho da fonte'; String get fontSize => 'Tamanho da fonte';
@@ -276,6 +377,13 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get highlight => 'Destaque de código'; String get highlight => 'Destaque de código';
@override
String get homeTabs => 'Abas iniciais';
@override
String get homeTabsCustomizeDesc =>
'Personalize quais abas aparecem na página inicial e sua ordem';
@override @override
String get homeWidgetUrlConfig => String get homeWidgetUrlConfig =>
'Configuração de URL do widget da tela inicial'; 'Configuração de URL do widget da tela inicial';
@@ -297,9 +405,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get imagesList => 'Lista de imagens'; String get imagesList => 'Lista de imagens';
@override
String get init => 'Inicializar';
@override @override
String get inner => 'Interno'; String get inner => 'Interno';
@@ -328,6 +433,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get keyAuth => 'Autenticação por chave'; String get keyAuth => 'Autenticação por chave';
@override
String get lastFailure => 'Última falha';
@override
String get lastSuccess => 'Último sucesso';
@override @override
String get letterCache => 'Cache de letras'; String get letterCache => 'Cache de letras';
@@ -335,9 +446,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.'; 'Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.';
@override
String get license => 'Licença de código aberto';
@override @override
String get location => 'Localização'; String get location => 'Localização';
@@ -350,10 +458,10 @@ class AppLocalizationsPt extends AppLocalizations {
} }
@override @override
String get manual => 'Manual'; String get max => 'Máximo';
@override @override
String get max => 'Máximo'; String get maxConcurrency => 'Concorrência máxima';
@override @override
String get maxRetryCount => String get maxRetryCount =>
@@ -394,6 +502,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get newContainer => 'Novo contêiner'; String get newContainer => 'Novo contêiner';
@override
String get noConnectionStatsData => 'Não há dados de estatísticas de conexão';
@override @override
String get noLineChart => 'Não usar gráficos de linha'; String get noLineChart => 'Não usar gráficos de linha';
@@ -465,10 +576,12 @@ class AppLocalizationsPt extends AppLocalizations {
String get preferDiskAmount => 'Priorizar a exibição da capacidade do disco'; String get preferDiskAmount => 'Priorizar a exibição da capacidade do disco';
@override @override
String get preview => 'Pré-visualização'; String get privateKey => 'Chave privada';
@override @override
String get privateKey => 'Chave privada'; String privateKeyNotFoundFmt(Object keyId) {
return 'Chave privada [$keyId] não encontrada.';
}
@override @override
String get process => 'Processo'; String get process => 'Processo';
@@ -497,6 +610,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get reboot => 'Reiniciar'; String get reboot => 'Reiniciar';
@override
String get recentConnections => 'Conexões recentes';
@override @override
String get rememberPwdInMem => 'Lembrar senha na memória'; String get rememberPwdInMem => 'Lembrar senha na memória';
@@ -558,6 +674,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get serverOrder => 'Ordem do servidor'; String get serverOrder => 'Ordem do servidor';
@override
String get serverTabRequired => 'A aba do servidor não pode ser removida';
@override
String get servers => 'servidores';
@override @override
String get sftpDlPrepare => 'Preparando para conectar ao servidor...'; String get sftpDlPrepare => 'Preparando para conectar ao servidor...';
@@ -642,6 +764,34 @@ class AppLocalizationsPt extends AppLocalizations {
return 'Importados $count servidores da configuração SSH'; return 'Importados $count servidores da configuração SSH';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'A chave de host SSH de $serverName foi alterada. Continue apenas se confiar neste servidor.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Impressão digital (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Impressão digital (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'Tipo de chave de host SSH';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Uma nova chave de host SSH foi recebida de $serverName. Verifique a impressão digital antes de confiar.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Impressão digital armazenada: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Gostaria de selecionar manualmente o arquivo de configuração SSH?'; 'Gostaria de selecionar manualmente o arquivo de configuração SSH?';
@@ -705,9 +855,6 @@ class AppLocalizationsPt extends AppLocalizations {
return 'Mudar para $val'; return 'Mudar para $val';
} }
@override
String get sync => 'Sincronizar';
@override @override
String get syncTip => String get syncTip =>
'Pode ser necessário reiniciar para algumas mudanças surtirem efeito.'; 'Pode ser necessário reiniciar para algumas mudanças surtirem efeito.';
@@ -718,6 +865,10 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get tag => 'Tag'; String get tag => 'Tag';
@override
String get tapToStartDiscovery =>
'Toque no botão de pesquisa para descobrir servidores SSH na sua rede';
@override @override
String get temperature => 'Temperatura'; String get temperature => 'Temperatura';
@@ -750,6 +901,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get total => 'Total'; String get total => 'Total';
@override
String get totalAttempts => 'Total';
@override @override
String get traffic => 'Tráfego'; String get traffic => 'Tráfego';
@@ -776,9 +930,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get updateServerStatusInterval => String get updateServerStatusInterval =>
'Intervalo de atualização do estado do servidor'; 'Intervalo de atualização do estado do servidor';
@override
String get upload => 'Upload';
@override @override
String get upsideDown => 'Inverter verticalmente'; String get upsideDown => 'Inverter verticalmente';
@@ -804,6 +955,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get view => 'Visualização'; String get view => 'Visualização';
@override
String get viewDetails => 'Ver detalhes';
@override @override
String get viewErr => 'Ver erro'; String get viewErr => 'Ver erro';
@@ -846,71 +1000,4 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.'; 'Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.';
@override
String get connectionStats => 'Estatísticas de conexão';
@override
String get connectionStatsDesc =>
'Ver taxa de sucesso de conexão do servidor e histórico';
@override
String get noConnectionStatsData => 'Não há dados de estatísticas de conexão';
@override
String get totalAttempts => 'Total';
@override
String get lastSuccess => 'Último sucesso';
@override
String get lastFailure => 'Última falha';
@override
String get recentConnections => 'Conexões recentes';
@override
String get viewDetails => 'Ver detalhes';
@override
String get connectionDetails => 'Detalhes da conexão';
@override
String get clearThisServerStats => 'Limpar estatísticas deste servidor';
@override
String get clearAllStatsTitle => 'Limpar todas as estatísticas';
@override
String get clearAllStatsContent =>
'Tem certeza de que deseja limpar todas as estatísticas de conexão do servidor? Esta ação não pode ser desfeita.';
@override
String clearServerStatsTitle(String serverName) {
return 'Limpar estatísticas de $serverName';
}
@override
String clearServerStatsContent(String serverName) {
return 'Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"$serverName\"? Esta ação não pode ser desfeita.';
}
@override
String get homeTabs => 'Abas iniciais';
@override
String get homeTabsCustomizeDesc =>
'Personalize quais abas aparecem na página inicial e sua ordem';
@override
String get reset => 'Redefinir';
@override
String get availableTabs => 'Abas disponíveis';
@override
String get atLeastOneTab => 'Pelo menos uma aba deve ser selecionada';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,60 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Уже в корневом каталоге'; String get alreadyLastDir => 'Уже в корневом каталоге';
@override
String get askAi => 'Спросить ИИ';
@override
String get askAiApiKey => 'Ключ API';
@override
String get askAiAwaitingResponse => 'Ожидание ответа ИИ...';
@override
String get askAiBaseUrl => 'Базовый URL';
@override
String get askAiCommandInserted => 'Команда вставлена в терминал';
@override
String askAiConfigMissing(Object fields) {
return 'Настройте $fields в настройках.';
}
@override
String get askAiConfirmExecute => 'Подтвердите перед выполнением';
@override
String get askAiConversation => 'Разговор с ИИ';
@override
String get askAiDisclaimer =>
'ИИ может ошибаться. Используйте с осторожностью.';
@override
String get askAiFollowUpHint => 'Задайте дополнительный вопрос...';
@override
String get askAiInsertTerminal => 'Вставить в терминал';
@override
String get askAiModel => 'Модель';
@override
String get askAiNoResponse => 'Нет ответа';
@override
String get askAiRecommendedCommand => 'Команда, предложенная ИИ';
@override
String get askAiSelectedContent => 'Выбранное содержимое';
@override
String get askAiUsageHint => 'Используется в SSH-терминале';
@override
String get atLeastOneTab => 'Должна быть выбрана хотя бы одна вкладка';
@override @override
String get authFailTip => String get authFailTip =>
'Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.'; 'Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.';
@@ -45,6 +99,9 @@ class AppLocalizationsRu extends AppLocalizations {
String get autoUpdateHomeWidget => String get autoUpdateHomeWidget =>
'Автоматическое обновление виджета на главном экране'; 'Автоматическое обновление виджета на главном экране';
@override
String get availableTabs => 'Доступные вкладки';
@override @override
String get backupEncrypted => 'Резервная копия зашифрована'; String get backupEncrypted => 'Резервная копия зашифрована';
@@ -85,6 +142,26 @@ class AppLocalizationsRu extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».'; 'Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».';
@override
String get clearAllStatsContent =>
'Вы уверены, что хотите очистить всю статистику соединений сервера? Это действие не может быть отменено.';
@override
String get clearAllStatsTitle => 'Очистить всю статистику';
@override
String clearServerStatsContent(Object serverName) {
return 'Вы уверены, что хотите очистить статистику соединений для сервера \"$serverName\"? Это действие не может быть отменено.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Очистить статистику $serverName';
}
@override
String get clearThisServerStats => 'Очистить статистику этого сервера';
@override @override
String get closeAfterSave => 'Сохранить и закрыть'; String get closeAfterSave => 'Сохранить и закрыть';
@@ -97,6 +174,16 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get conn => 'Подключение'; String get conn => 'Подключение';
@override
String get connectionDetails => 'Детали соединения';
@override
String get connectionStats => 'Статистика соединений';
@override
String get connectionStatsDesc =>
'Просмотр коэффициента успешности подключения к серверу и истории';
@override @override
String get container => 'Контейнер'; String get container => 'Контейнер';
@@ -146,6 +233,18 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get disconnected => 'Отключено'; String get disconnected => 'Отключено';
@override
String get discoverSshServers => 'Обнаружить SSH серверы';
@override
String get discoveryFailed => 'Обнаружение не удалось';
@override
String get discoverySettings => 'Настройки обнаружения';
@override
String get discoverySummary => 'Сводка обнаружения';
@override @override
String get disk => 'Диск'; String get disk => 'Диск';
@@ -198,9 +297,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get editVirtKeys => 'Редактировать виртуальные клавиши'; String get editVirtKeys => 'Редактировать виртуальные клавиши';
@override
String get editor => 'Редактор';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.'; 'Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.';
@@ -208,6 +304,13 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get emulator => 'Эмулятор'; String get emulator => 'Эмулятор';
@override
String get enableMdns => 'Включить mDNS';
@override
String get enableMdnsDesc =>
'Использовать mDNS/Bonjour для обнаружения SSH служб';
@override @override
String get encode => 'Кодировать'; String get encode => 'Кодировать';
@@ -240,10 +343,10 @@ class AppLocalizationsRu extends AppLocalizations {
} }
@override @override
String get followSystem => 'Следовать за системой'; String get finishedAt => 'Завершено в';
@override @override
String get font => 'Шрифт'; String get followSystem => 'Следовать за системой';
@override @override
String get fontSize => 'Размер шрифта'; String get fontSize => 'Размер шрифта';
@@ -276,6 +379,13 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get highlight => 'Подсветка кода'; String get highlight => 'Подсветка кода';
@override
String get homeTabs => 'Вкладки дома';
@override
String get homeTabsCustomizeDesc =>
'Настройте, какие вкладки появляются на главной странице и их порядок';
@override @override
String get homeWidgetUrlConfig => 'Конфигурация URL виджета домашнего экрана'; String get homeWidgetUrlConfig => 'Конфигурация URL виджета домашнего экрана';
@@ -296,9 +406,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get imagesList => 'Список образов'; String get imagesList => 'Список образов';
@override
String get init => 'Инициализировать';
@override @override
String get inner => 'Встроенный'; String get inner => 'Встроенный';
@@ -328,6 +435,12 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get keyAuth => 'Аутентификация по ключу'; String get keyAuth => 'Аутентификация по ключу';
@override
String get lastFailure => 'Последний сбой';
@override
String get lastSuccess => 'Последний успех';
@override @override
String get letterCache => 'Кэширование букв'; String get letterCache => 'Кэширование букв';
@@ -335,9 +448,6 @@ class AppLocalizationsRu extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.'; 'Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.';
@override
String get license => 'Лицензия';
@override @override
String get location => 'Местоположение'; String get location => 'Местоположение';
@@ -350,10 +460,10 @@ class AppLocalizationsRu extends AppLocalizations {
} }
@override @override
String get manual => 'Вручную'; String get max => 'максимум';
@override @override
String get max => 'максимум'; String get maxConcurrency => 'Максимальная параллельность';
@override @override
String get maxRetryCount => String get maxRetryCount =>
@@ -395,6 +505,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get newContainer => 'Создать контейнер'; String get newContainer => 'Создать контейнер';
@override
String get noConnectionStatsData => 'Нет данных статистики соединений';
@override @override
String get noLineChart => 'Не использовать линейные графики'; String get noLineChart => 'Не использовать линейные графики';
@@ -466,10 +579,12 @@ class AppLocalizationsRu extends AppLocalizations {
String get preferDiskAmount => 'Приоритетное отображение объёма диска'; String get preferDiskAmount => 'Приоритетное отображение объёма диска';
@override @override
String get preview => 'Предпросмотр'; String get privateKey => 'Приватный ключ';
@override @override
String get privateKey => 'Приватный ключ'; String privateKeyNotFoundFmt(Object keyId) {
return 'Закрытый ключ [$keyId] не найден.';
}
@override @override
String get process => 'Процесс'; String get process => 'Процесс';
@@ -498,6 +613,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get reboot => 'Перезагрузка'; String get reboot => 'Перезагрузка';
@override
String get recentConnections => 'Недавние соединения';
@override @override
String get rememberPwdInMem => 'Запомнить пароль в памяти'; String get rememberPwdInMem => 'Запомнить пароль в памяти';
@@ -560,6 +678,12 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get serverOrder => 'Порядок серверов'; String get serverOrder => 'Порядок серверов';
@override
String get serverTabRequired => 'Вкладку сервера нельзя удалить';
@override
String get servers => 'серверов';
@override @override
String get sftpDlPrepare => 'Подготовка подключения...'; String get sftpDlPrepare => 'Подготовка подключения...';
@@ -645,6 +769,34 @@ class AppLocalizationsRu extends AppLocalizations {
return 'Импортировано $count серверов из SSH-конфигурации'; return 'Импортировано $count серверов из SSH-конфигурации';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'SSH-ключ хоста для $serverName изменился. Продолжайте только если доверяете этому серверу.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Отпечаток (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Отпечаток (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'Тип ключа хоста SSH';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Получен новый SSH-ключ хоста от $serverName. Проверьте отпечаток перед продолжением.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Сохранённый отпечаток: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Хотели бы вы вручную выбрать файл конфигурации SSH?'; 'Хотели бы вы вручную выбрать файл конфигурации SSH?';
@@ -707,9 +859,6 @@ class AppLocalizationsRu extends AppLocalizations {
return 'Переключиться на $val'; return 'Переключиться на $val';
} }
@override
String get sync => 'Синхронизировать';
@override @override
String get syncTip => String get syncTip =>
'Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.'; 'Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.';
@@ -720,6 +869,10 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get tag => 'Теги'; String get tag => 'Теги';
@override
String get tapToStartDiscovery =>
'Нажмите кнопку поиска, чтобы обнаружить SSH серверы в вашей сети';
@override @override
String get temperature => 'Температура'; String get temperature => 'Температура';
@@ -752,6 +905,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get total => 'Всего'; String get total => 'Всего';
@override
String get totalAttempts => 'Общее';
@override @override
String get traffic => 'Трафик'; String get traffic => 'Трафик';
@@ -778,9 +934,6 @@ class AppLocalizationsRu extends AppLocalizations {
String get updateServerStatusInterval => String get updateServerStatusInterval =>
'Интервал обновления статуса сервера'; 'Интервал обновления статуса сервера';
@override
String get upload => 'Загрузить';
@override @override
String get upsideDown => 'Перевернуть'; String get upsideDown => 'Перевернуть';
@@ -806,6 +959,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get view => 'Вид'; String get view => 'Вид';
@override
String get viewDetails => 'Просмотр деталей';
@override @override
String get viewErr => 'Просмотр ошибок'; String get viewErr => 'Просмотр ошибок';
@@ -848,71 +1004,4 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.'; 'После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.';
@override
String get connectionStats => 'Статистика соединений';
@override
String get connectionStatsDesc =>
'Просмотр коэффициента успешности подключения к серверу и истории';
@override
String get noConnectionStatsData => 'Нет данных статистики соединений';
@override
String get totalAttempts => 'Общее';
@override
String get lastSuccess => 'Последний успех';
@override
String get lastFailure => 'Последний сбой';
@override
String get recentConnections => 'Недавние соединения';
@override
String get viewDetails => 'Просмотр деталей';
@override
String get connectionDetails => 'Детали соединения';
@override
String get clearThisServerStats => 'Очистить статистику этого сервера';
@override
String get clearAllStatsTitle => 'Очистить всю статистику';
@override
String get clearAllStatsContent =>
'Вы уверены, что хотите очистить всю статистику соединений сервера? Это действие не может быть отменено.';
@override
String clearServerStatsTitle(String serverName) {
return 'Очистить статистику $serverName';
}
@override
String clearServerStatsContent(String serverName) {
return 'Вы уверены, что хотите очистить статистику соединений для сервера \"$serverName\"? Это действие не может быть отменено.';
}
@override
String get homeTabs => 'Вкладки дома';
@override
String get homeTabsCustomizeDesc =>
'Настройте, какие вкладки появляются на главной странице и их порядок';
@override
String get reset => 'Сброс';
@override
String get availableTabs => 'Доступные вкладки';
@override
String get atLeastOneTab => 'Должна быть выбрана хотя бы одна вкладка';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,60 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Zaten son dizindesiniz.'; String get alreadyLastDir => 'Zaten son dizindesiniz.';
@override
String get askAi => 'Yapay zekaya sor';
@override
String get askAiApiKey => 'API anahtarı';
@override
String get askAiAwaitingResponse => 'Yapay zekâ yanıtı bekleniyor...';
@override
String get askAiBaseUrl => 'Temel URL';
@override
String get askAiCommandInserted => 'Komut terminale eklendi';
@override
String askAiConfigMissing(Object fields) {
return 'Lütfen Ayarlar\'da $fields öğesini yapılandırın.';
}
@override
String get askAiConfirmExecute => 'Çalıştırmadan önce onayla';
@override
String get askAiConversation => 'YZ sohbeti';
@override
String get askAiDisclaimer =>
'Yapay zeka hata yapabilir. Lütfen dikkatli kullanın.';
@override
String get askAiFollowUpHint => 'Yeni bir soru sor...';
@override
String get askAiInsertTerminal => 'Terminale ekle';
@override
String get askAiModel => 'Model';
@override
String get askAiNoResponse => 'Yanıt yok';
@override
String get askAiRecommendedCommand => 'YZ önerilen komut';
@override
String get askAiSelectedContent => 'Seçilen içerik';
@override
String get askAiUsageHint => 'SSH Terminalinde kullanılır';
@override
String get atLeastOneTab => 'En az bir sekme seçilmelidir';
@override @override
String get authFailTip => String get authFailTip =>
'Kimlik doğrulama başarısız oldu, lütfen kimlik bilgilerinin doğru olup olmadığını kontrol edin'; 'Kimlik doğrulama başarısız oldu, lütfen kimlik bilgilerinin doğru olup olmadığını kontrol edin';
@@ -44,6 +98,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => 'Ana ekran bileşenini otomatik güncelle'; String get autoUpdateHomeWidget => 'Ana ekran bileşenini otomatik güncelle';
@override
String get availableTabs => 'Mevcut Sekmeler';
@override @override
String get backupEncrypted => 'Yedekleme şifrelenmiş'; String get backupEncrypted => 'Yedekleme şifrelenmiş';
@@ -83,6 +140,26 @@ class AppLocalizationsTr extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM\'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.'; 'Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM\'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.';
@override
String get clearAllStatsContent =>
'Tüm sunucu bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
@override
String get clearAllStatsTitle => 'Tüm İstatistikleri Temizle';
@override
String clearServerStatsContent(Object serverName) {
return '\"$serverName\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
}
@override
String clearServerStatsTitle(Object serverName) {
return '$serverName İstatistiklerini Temizle';
}
@override
String get clearThisServerStats => 'Bu Sunucu İstatistiklerini Temizle';
@override @override
String get closeAfterSave => 'Kaydet ve kapat'; String get closeAfterSave => 'Kaydet ve kapat';
@@ -96,6 +173,16 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get conn => 'Bağlantı'; String get conn => 'Bağlantı';
@override
String get connectionDetails => 'Bağlantı Detayları';
@override
String get connectionStats => 'Bağlantı İstatistikleri';
@override
String get connectionStatsDesc =>
'Sunucu bağlantı başarı oranını ve geçmişi görüntüle';
@override @override
String get container => 'Konteyner'; String get container => 'Konteyner';
@@ -145,6 +232,18 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get disconnected => 'Bağlantı kesildi'; String get disconnected => 'Bağlantı kesildi';
@override
String get discoverSshServers => 'SSH Sunucularını Keşfet';
@override
String get discoveryFailed => 'Keşif başarısız';
@override
String get discoverySettings => 'Keşif Ayarları';
@override
String get discoverySummary => 'Keşif Özeti';
@override @override
String get disk => 'Disk'; String get disk => 'Disk';
@@ -197,9 +296,6 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get editVirtKeys => 'Sanal tuşları düzenle'; String get editVirtKeys => 'Sanal tuşları düzenle';
@override
String get editor => 'Düzenleyici';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'Mevcut kod vurgulama performansı ideal değil ve isteğe bağlı olarak kapatılabilir.'; 'Mevcut kod vurgulama performansı ideal değil ve isteğe bağlı olarak kapatılabilir.';
@@ -207,6 +303,13 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get emulator => 'Emülatör'; String get emulator => 'Emülatör';
@override
String get enableMdns => 'mDNS\'yi Etkinleştir';
@override
String get enableMdnsDesc =>
'SSH hizmetlerini keşfetmek için mDNS/Bonjour kullan';
@override @override
String get encode => 'Kodla'; String get encode => 'Kodla';
@@ -239,10 +342,10 @@ class AppLocalizationsTr extends AppLocalizations {
} }
@override @override
String get followSystem => 'Sistemi takip et'; String get finishedAt => 'Tamamlandı:';
@override @override
String get font => 'Yazı tipi'; String get followSystem => 'Sistemi takip et';
@override @override
String get fontSize => 'Yazı tipi boyutu'; String get fontSize => 'Yazı tipi boyutu';
@@ -275,6 +378,13 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get highlight => 'Kod vurgulama'; String get highlight => 'Kod vurgulama';
@override
String get homeTabs => 'Ana Sayfa Sekmeleri';
@override
String get homeTabsCustomizeDesc =>
'Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin';
@override @override
String get homeWidgetUrlConfig => 'Ana ekran bileşeni URL\'sini yapılandır'; String get homeWidgetUrlConfig => 'Ana ekran bileşeni URL\'sini yapılandır';
@@ -295,9 +405,6 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get imagesList => 'Görüntü listesi'; String get imagesList => 'Görüntü listesi';
@override
String get init => 'Başlat';
@override @override
String get inner => 'İç'; String get inner => 'İç';
@@ -327,6 +434,12 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get keyAuth => 'Anahtar Kimlik Doğrulama'; String get keyAuth => 'Anahtar Kimlik Doğrulama';
@override
String get lastFailure => 'Son Başarısızlık';
@override
String get lastSuccess => 'Son Başarı';
@override @override
String get letterCache => 'Harf önbelleği'; String get letterCache => 'Harf önbelleği';
@@ -334,9 +447,6 @@ class AppLocalizationsTr extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Devre dışı bırakılması önerilir, ancak devre dışı bırakıldığında CJK karakterlerini girmek mümkün olmayacaktır.'; 'Devre dışı bırakılması önerilir, ancak devre dışı bırakıldığında CJK karakterlerini girmek mümkün olmayacaktır.';
@override
String get license => 'Lisans';
@override @override
String get location => 'Konum'; String get location => 'Konum';
@@ -349,10 +459,10 @@ class AppLocalizationsTr extends AppLocalizations {
} }
@override @override
String get manual => 'Manuel'; String get max => 'maks';
@override @override
String get max => 'maks'; String get maxConcurrency => 'Maksimum Eşzamanlılık';
@override @override
String get maxRetryCount => 'Sunucu yeniden bağlantı sayısı'; String get maxRetryCount => 'Sunucu yeniden bağlantı sayısı';
@@ -392,6 +502,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get newContainer => 'Yeni konteyner'; String get newContainer => 'Yeni konteyner';
@override
String get noConnectionStatsData => 'Bağlantı istatistik verisi yok';
@override @override
String get noLineChart => 'Çizgi grafikleri kullanma'; String get noLineChart => 'Çizgi grafikleri kullanma';
@@ -463,10 +576,12 @@ class AppLocalizationsTr extends AppLocalizations {
String get preferDiskAmount => 'Disk kapasitesini öncelikli olarak göster'; String get preferDiskAmount => 'Disk kapasitesini öncelikli olarak göster';
@override @override
String get preview => 'Önizleme'; String get privateKey => 'Özel Anahtar';
@override @override
String get privateKey => 'Özel Anahtar'; String privateKeyNotFoundFmt(Object keyId) {
return 'Özel anahtar [$keyId] bulunamadı.';
}
@override @override
String get process => 'İşlem'; String get process => 'İşlem';
@@ -495,6 +610,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get reboot => 'Yeniden başlat'; String get reboot => 'Yeniden başlat';
@override
String get recentConnections => 'Son Bağlantılar';
@override @override
String get rememberPwdInMem => 'Şifreyi bellekte hatırla'; String get rememberPwdInMem => 'Şifreyi bellekte hatırla';
@@ -556,6 +674,12 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get serverOrder => 'Sunucu sırası'; String get serverOrder => 'Sunucu sırası';
@override
String get serverTabRequired => 'Sunucu sekmesi kaldırılamaz';
@override
String get servers => 'sunucu';
@override @override
String get sftpDlPrepare => 'Bağlantı hazırlanıyor...'; String get sftpDlPrepare => 'Bağlantı hazırlanıyor...';
@@ -641,6 +765,34 @@ class AppLocalizationsTr extends AppLocalizations {
return 'SSH yapılandırmasından $count sunucu içe aktarıldı'; return 'SSH yapılandırmasından $count sunucu içe aktarıldı';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return '$serverName için SSH ana bilgisayar anahtarı değişti. Yalnızca bu sunucuya güveniyorsanız devam edin.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Parmak izi (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Parmak izi (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'SSH ana bilgisayar anahtarı türü';
@override
String sshHostKeyNewDesc(Object serverName) {
return '$serverName üzerinden yeni bir SSH ana bilgisayar anahtarı alındı. Güvenmeden önce parmak izini kontrol edin.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Kaydedilen parmak izi: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'SSH yapılandırma dosyasını manuel olarak seçmek ister misiniz?'; 'SSH yapılandırma dosyasını manuel olarak seçmek ister misiniz?';
@@ -703,9 +855,6 @@ class AppLocalizationsTr extends AppLocalizations {
return '$val\'a geç'; return '$val\'a geç';
} }
@override
String get sync => 'Senkronize et';
@override @override
String get syncTip => String get syncTip =>
'Bazı değişikliklerin etkili olması için yeniden başlatma gerekebilir.'; 'Bazı değişikliklerin etkili olması için yeniden başlatma gerekebilir.';
@@ -716,6 +865,10 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get tag => 'Etiketler'; String get tag => 'Etiketler';
@override
String get tapToStartDiscovery =>
'ınızdaki SSH sunucularını keşfetmek için arama düğmesine dokunun';
@override @override
String get temperature => 'Sıcaklık'; String get temperature => 'Sıcaklık';
@@ -748,6 +901,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get total => 'Toplam'; String get total => 'Toplam';
@override
String get totalAttempts => 'Toplam';
@override @override
String get traffic => 'Trafik'; String get traffic => 'Trafik';
@@ -773,9 +929,6 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get updateServerStatusInterval => 'Sunucu durumu güncelleme aralığı'; String get updateServerStatusInterval => 'Sunucu durumu güncelleme aralığı';
@override
String get upload => 'Yükle';
@override @override
String get upsideDown => 'Başaşağı'; String get upsideDown => 'Başaşağı';
@@ -801,6 +954,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get view => 'Görünüm'; String get view => 'Görünüm';
@override
String get viewDetails => 'Detayları Görüntüle';
@override @override
String get viewErr => 'Hatayı gör'; String get viewErr => 'Hatayı gör';
@@ -843,71 +999,4 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.'; 'Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.';
@override
String get connectionStats => 'Bağlantı İstatistikleri';
@override
String get connectionStatsDesc =>
'Sunucu bağlantı başarı oranını ve geçmişi görüntüle';
@override
String get noConnectionStatsData => 'Bağlantı istatistik verisi yok';
@override
String get totalAttempts => 'Toplam';
@override
String get lastSuccess => 'Son Başarı';
@override
String get lastFailure => 'Son Başarısızlık';
@override
String get recentConnections => 'Son Bağlantılar';
@override
String get viewDetails => 'Detayları Görüntüle';
@override
String get connectionDetails => 'Bağlantı Detayları';
@override
String get clearThisServerStats => 'Bu Sunucu İstatistiklerini Temizle';
@override
String get clearAllStatsTitle => 'Tüm İstatistikleri Temizle';
@override
String get clearAllStatsContent =>
'Tüm sunucu bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
@override
String clearServerStatsTitle(String serverName) {
return '$serverName İstatistiklerini Temizle';
}
@override
String clearServerStatsContent(String serverName) {
return '\"$serverName\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.';
}
@override
String get homeTabs => 'Ana Sayfa Sekmeleri';
@override
String get homeTabsCustomizeDesc =>
'Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin';
@override
String get reset => 'Sıfırla';
@override
String get availableTabs => 'Mevcut Sekmeler';
@override
String get atLeastOneTab => 'En az bir sekme seçilmelidir';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -27,6 +27,59 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get alreadyLastDir => 'Вже в останньому каталозі.'; String get alreadyLastDir => 'Вже в останньому каталозі.';
@override
String get askAi => 'Запитати ШІ';
@override
String get askAiApiKey => 'Ключ API';
@override
String get askAiAwaitingResponse => 'Очікування відповіді ШІ...';
@override
String get askAiBaseUrl => 'Базова URL';
@override
String get askAiCommandInserted => 'Команду вставлено в термінал';
@override
String askAiConfigMissing(Object fields) {
return 'Налаштуйте $fields у налаштуваннях.';
}
@override
String get askAiConfirmExecute => 'Підтвердити перед виконанням';
@override
String get askAiConversation => 'Розмова з ШІ';
@override
String get askAiDisclaimer => 'ШІ може помилятися. Користуйтеся обережно.';
@override
String get askAiFollowUpHint => 'Поставте додаткове запитання...';
@override
String get askAiInsertTerminal => 'Вставити в термінал';
@override
String get askAiModel => 'Модель';
@override
String get askAiNoResponse => 'Відповідь відсутня';
@override
String get askAiRecommendedCommand => 'Команда, запропонована ШІ';
@override
String get askAiSelectedContent => 'Вибраний вміст';
@override
String get askAiUsageHint => 'Використовується в SSH-терміналі';
@override
String get atLeastOneTab => 'Потрібно вибрати принаймні одну вкладку';
@override @override
String get authFailTip => String get authFailTip =>
'Авторизація не вдалася, будь ласка, перевірте правильність облікових даних'; 'Авторизація не вдалася, будь ласка, перевірте правильність облікових даних';
@@ -45,6 +98,9 @@ class AppLocalizationsUk extends AppLocalizations {
String get autoUpdateHomeWidget => String get autoUpdateHomeWidget =>
'Автоматичне оновлення віджетів на головному екрані'; 'Автоматичне оновлення віджетів на головному екрані';
@override
String get availableTabs => 'Доступні вкладки';
@override @override
String get backupEncrypted => 'Резервна копія зашифрована'; String get backupEncrypted => 'Резервна копія зашифрована';
@@ -85,6 +141,26 @@ class AppLocalizationsUk extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".'; 'Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".';
@override
String get clearAllStatsContent =>
'Ви впевнені, що хочете очистити всю статистику з\'єднань сервера? Цю дію не можна скасувати.';
@override
String get clearAllStatsTitle => 'Очистити всю статистику';
@override
String clearServerStatsContent(Object serverName) {
return 'Ви впевнені, що хочете очистити статистику з\'єднань для сервера \"$serverName\"? Цю дію не можна скасувати.';
}
@override
String clearServerStatsTitle(Object serverName) {
return 'Очистити статистику $serverName';
}
@override
String get clearThisServerStats => 'Очистити статистику цього сервера';
@override @override
String get closeAfterSave => 'Зберегти та закрити'; String get closeAfterSave => 'Зберегти та закрити';
@@ -98,6 +174,16 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get conn => 'З\'єднання'; String get conn => 'З\'єднання';
@override
String get connectionDetails => 'Деталі з\'єднання';
@override
String get connectionStats => 'Статистика з\'єднань';
@override
String get connectionStatsDesc =>
'Переглянути коефіцієнт успішності підключення до сервера та історію';
@override @override
String get container => 'Контейнер'; String get container => 'Контейнер';
@@ -147,6 +233,18 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get disconnected => 'Відключено'; String get disconnected => 'Відключено';
@override
String get discoverSshServers => 'Виявити SSH сервери';
@override
String get discoveryFailed => 'Виявлення не вдалось';
@override
String get discoverySettings => 'Налаштування виявлення';
@override
String get discoverySummary => 'Підсумок виявлення';
@override @override
String get disk => 'Диск'; String get disk => 'Диск';
@@ -199,9 +297,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get editVirtKeys => 'Редагувати віртуальні клавіші'; String get editVirtKeys => 'Редагувати віртуальні клавіші';
@override
String get editor => 'Редактор';
@override @override
String get editorHighlightTip => String get editorHighlightTip =>
'Поточна підсвітка коду не ідеальна і може бути вимкнена для покращення.'; 'Поточна підсвітка коду не ідеальна і може бути вимкнена для покращення.';
@@ -209,6 +304,13 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get emulator => 'Емулятор'; String get emulator => 'Емулятор';
@override
String get enableMdns => 'Увімкнути mDNS';
@override
String get enableMdnsDesc =>
'Використовувати mDNS/Bonjour для виявлення SSH сервісів';
@override @override
String get encode => 'Кодувати'; String get encode => 'Кодувати';
@@ -241,10 +343,10 @@ class AppLocalizationsUk extends AppLocalizations {
} }
@override @override
String get followSystem => 'Слідувати системі'; String get finishedAt => 'Завершено о';
@override @override
String get font => 'Шрифт'; String get followSystem => 'Слідувати системі';
@override @override
String get fontSize => 'Розмір шрифту'; String get fontSize => 'Розмір шрифту';
@@ -277,6 +379,13 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get highlight => 'Підсвітка коду'; String get highlight => 'Підсвітка коду';
@override
String get homeTabs => 'Домашні вкладки';
@override
String get homeTabsCustomizeDesc =>
'Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок';
@override @override
String get homeWidgetUrlConfig => String get homeWidgetUrlConfig =>
'Налаштувати URL віджета на головному екрані'; 'Налаштувати URL віджета на головному екрані';
@@ -298,9 +407,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get imagesList => 'Список зображень'; String get imagesList => 'Список зображень';
@override
String get init => 'Ініціалізувати';
@override @override
String get inner => 'Внутрішній'; String get inner => 'Внутрішній';
@@ -330,6 +436,12 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get keyAuth => 'Аутентифікація ключем'; String get keyAuth => 'Аутентифікація ключем';
@override
String get lastFailure => 'Остання помилка';
@override
String get lastSuccess => 'Останній успіх';
@override @override
String get letterCache => 'Кешування букв'; String get letterCache => 'Кешування букв';
@@ -337,9 +449,6 @@ class AppLocalizationsUk extends AppLocalizations {
String get letterCacheTip => String get letterCacheTip =>
'Рекомендується відключити, але після вимкнення стане неможливим введення CJK (китайських, японських, корейських) символів.'; 'Рекомендується відключити, але після вимкнення стане неможливим введення CJK (китайських, японських, корейських) символів.';
@override
String get license => 'Ліцензія';
@override @override
String get location => 'Місцезнаходження'; String get location => 'Місцезнаходження';
@@ -352,10 +461,10 @@ class AppLocalizationsUk extends AppLocalizations {
} }
@override @override
String get manual => 'Посібник'; String get max => 'макс.';
@override @override
String get max => 'макс.'; String get maxConcurrency => 'Максимальна паралельність';
@override @override
String get maxRetryCount => String get maxRetryCount =>
@@ -397,6 +506,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get newContainer => 'Новий контейнер'; String get newContainer => 'Новий контейнер';
@override
String get noConnectionStatsData => 'Немає даних статистики з\'єднань';
@override @override
String get noLineChart => 'Не використовувати лінійні діаграми'; String get noLineChart => 'Не використовувати лінійні діаграми';
@@ -468,10 +580,12 @@ class AppLocalizationsUk extends AppLocalizations {
String get preferDiskAmount => 'Пріоритетно показувати ємність диска'; String get preferDiskAmount => 'Пріоритетно показувати ємність диска';
@override @override
String get preview => 'Попередній перегляд'; String get privateKey => 'Приватний ключ';
@override @override
String get privateKey => 'Приватний ключ'; String privateKeyNotFoundFmt(Object keyId) {
return 'Приватний ключ [$keyId] не знайдено.';
}
@override @override
String get process => 'Процес'; String get process => 'Процес';
@@ -500,6 +614,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get reboot => 'Перезавантажити'; String get reboot => 'Перезавантажити';
@override
String get recentConnections => 'Останні з\'єднання';
@override @override
String get rememberPwdInMem => 'Запам\'ятати пароль у пам\'яті'; String get rememberPwdInMem => 'Запам\'ятати пароль у пам\'яті';
@@ -561,6 +678,12 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get serverOrder => 'Порядок сервера'; String get serverOrder => 'Порядок сервера';
@override
String get serverTabRequired => 'Вкладку сервера не можна видалити';
@override
String get servers => 'серверів';
@override @override
String get sftpDlPrepare => 'Підготовка до підключення...'; String get sftpDlPrepare => 'Підготовка до підключення...';
@@ -646,6 +769,34 @@ class AppLocalizationsUk extends AppLocalizations {
return 'Імпортовано $count серверів з SSH-конфігурації'; return 'Імпортовано $count серверів з SSH-конфігурації';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return 'SSH-ключ хоста для $serverName змінено. Продовжуйте лише якщо довіряєте цьому серверу.';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return 'Відбиток (MD5 Base64): $fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return 'Відбиток (MD5 hex): $fingerprint';
}
@override
String get sshHostKeyType => 'Тип ключа хоста SSH';
@override
String sshHostKeyNewDesc(Object serverName) {
return 'Отримано новий SSH-ключ хоста від $serverName. Перевірте відбиток перед тим, як довіряти.';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return 'Збережений відбиток: $fingerprint';
}
@override @override
String get sshConfigManualSelect => String get sshConfigManualSelect =>
'Чи хочете ви вручну вибрати файл конфігурації SSH?'; 'Чи хочете ви вручну вибрати файл конфігурації SSH?';
@@ -708,9 +859,6 @@ class AppLocalizationsUk extends AppLocalizations {
return 'Переключитися на $val'; return 'Переключитися на $val';
} }
@override
String get sync => 'Синхронізація';
@override @override
String get syncTip => String get syncTip =>
'Може знадобитися перезапуск, щоб деякі зміни набрали чинності.'; 'Може знадобитися перезапуск, щоб деякі зміни набрали чинності.';
@@ -721,6 +869,10 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get tag => 'Теги'; String get tag => 'Теги';
@override
String get tapToStartDiscovery =>
'Натисніть кнопку пошуку, щоб виявити SSH сервери у вашій мережі';
@override @override
String get temperature => 'Температура'; String get temperature => 'Температура';
@@ -753,6 +905,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get total => 'Всього'; String get total => 'Всього';
@override
String get totalAttempts => 'Загальна кількість';
@override @override
String get traffic => 'Трафік'; String get traffic => 'Трафік';
@@ -778,9 +933,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get updateServerStatusInterval => 'Інтервал оновлення статусу сервера'; String get updateServerStatusInterval => 'Інтервал оновлення статусу сервера';
@override
String get upload => 'Завантаження';
@override @override
String get upsideDown => 'Доверху дном'; String get upsideDown => 'Доверху дном';
@@ -806,6 +958,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get view => 'Переглянути'; String get view => 'Переглянути';
@override
String get viewDetails => 'Переглянути деталі';
@override @override
String get viewErr => 'Переглянути помилку'; String get viewErr => 'Переглянути помилку';
@@ -849,71 +1004,4 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.'; 'Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.';
@override
String get connectionStats => 'Статистика з\'єднань';
@override
String get connectionStatsDesc =>
'Переглянути коефіцієнт успішності підключення до сервера та історію';
@override
String get noConnectionStatsData => 'Немає даних статистики з\'єднань';
@override
String get totalAttempts => 'Загальна кількість';
@override
String get lastSuccess => 'Останній успіх';
@override
String get lastFailure => 'Остання помилка';
@override
String get recentConnections => 'Останні з\'єднання';
@override
String get viewDetails => 'Переглянути деталі';
@override
String get connectionDetails => 'Деталі з\'єднання';
@override
String get clearThisServerStats => 'Очистити статистику цього сервера';
@override
String get clearAllStatsTitle => 'Очистити всю статистику';
@override
String get clearAllStatsContent =>
'Ви впевнені, що хочете очистити всю статистику з\'єднань сервера? Цю дію не можна скасувати.';
@override
String clearServerStatsTitle(String serverName) {
return 'Очистити статистику $serverName';
}
@override
String clearServerStatsContent(String serverName) {
return 'Ви впевнені, що хочете очистити статистику з\'єднань для сервера \"$serverName\"? Цю дію не можна скасувати.';
}
@override
String get homeTabs => 'Домашні вкладки';
@override
String get homeTabsCustomizeDesc =>
'Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок';
@override
String get reset => 'Скинути';
@override
String get availableTabs => 'Доступні вкладки';
@override
String get atLeastOneTab => 'Потрібно вибрати принаймні одну вкладку';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -26,6 +26,59 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get alreadyLastDir => '已是顶级目录'; String get alreadyLastDir => '已是顶级目录';
@override
String get askAi => '问 AI';
@override
String get askAiApiKey => 'API 密钥';
@override
String get askAiAwaitingResponse => '等待 AI 响应...';
@override
String get askAiBaseUrl => '基础 URL';
@override
String get askAiCommandInserted => '命令已插入终端';
@override
String askAiConfigMissing(Object fields) {
return '请前往设置配置 $fields';
}
@override
String get askAiConfirmExecute => '执行前确认';
@override
String get askAiConversation => 'AI 对话';
@override
String get askAiDisclaimer => 'AI 可能会犯错,请谨慎使用。';
@override
String get askAiFollowUpHint => '继续提问...';
@override
String get askAiInsertTerminal => '插入终端';
@override
String get askAiModel => '模型';
@override
String get askAiNoResponse => '无回复内容';
@override
String get askAiRecommendedCommand => 'AI 推荐命令';
@override
String get askAiSelectedContent => '选中的内容';
@override
String get askAiUsageHint => '用于 SSH 终端';
@override
String get atLeastOneTab => '至少需要选择一个标签';
@override @override
String get authFailTip => '认证失败,请检查连接信息是否正确'; String get authFailTip => '认证失败,请检查连接信息是否正确';
@@ -41,6 +94,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get autoUpdateHomeWidget => '自动更新桌面小部件'; String get autoUpdateHomeWidget => '自动更新桌面小部件';
@override
String get availableTabs => '可用标签';
@override @override
String get backupEncrypted => '备份已加密'; String get backupEncrypted => '备份已加密';
@@ -78,6 +134,25 @@ class AppLocalizationsZh extends AppLocalizations {
String get bgRunTip => String get bgRunTip =>
'此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请将省电策略改为“无限制”。'; '此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请将省电策略改为“无限制”。';
@override
String get clearAllStatsContent => '确定要清空所有服务器的连接统计数据吗?此操作无法撤销。';
@override
String get clearAllStatsTitle => '清空所有统计';
@override
String clearServerStatsContent(Object serverName) {
return '确定要清空服务器 \"$serverName\" 的连接统计数据吗?此操作无法撤销。';
}
@override
String clearServerStatsTitle(Object serverName) {
return '清空 $serverName 统计';
}
@override
String get clearThisServerStats => '清空此服务器统计';
@override @override
String get closeAfterSave => '保存后关闭'; String get closeAfterSave => '保存后关闭';
@@ -90,6 +165,15 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get conn => '连接'; String get conn => '连接';
@override
String get connectionDetails => '连接详情';
@override
String get connectionStats => '连接统计';
@override
String get connectionStatsDesc => '查看服务器连接成功率和历史记录';
@override @override
String get container => '容器'; String get container => '容器';
@@ -137,6 +221,18 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get disconnected => '已断开连接'; String get disconnected => '已断开连接';
@override
String get discoverSshServers => '发现SSH服务器';
@override
String get discoveryFailed => '发现失败';
@override
String get discoverySettings => '发现设置';
@override
String get discoverySummary => '发现摘要';
@override @override
String get disk => '磁盘'; String get disk => '磁盘';
@@ -188,15 +284,18 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get editVirtKeys => '编辑虚拟按键'; String get editVirtKeys => '编辑虚拟按键';
@override
String get editor => '编辑器';
@override @override
String get editorHighlightTip => '代码高亮功能可能影响性能,可选择关闭。'; String get editorHighlightTip => '代码高亮功能可能影响性能,可选择关闭。';
@override @override
String get emulator => '模拟器'; String get emulator => '模拟器';
@override
String get enableMdns => '启用mDNS';
@override
String get enableMdnsDesc => '使用mDNS/Bonjour发现SSH服务';
@override @override
String get encode => '编码'; String get encode => '编码';
@@ -228,10 +327,10 @@ class AppLocalizationsZh extends AppLocalizations {
} }
@override @override
String get followSystem => '跟随系统'; String get finishedAt => '完成于';
@override @override
String get font => '字体'; String get followSystem => '跟随系统';
@override @override
String get fontSize => '字体大小'; String get fontSize => '字体大小';
@@ -263,6 +362,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get highlight => '代码高亮'; String get highlight => '代码高亮';
@override
String get homeTabs => '主页标签';
@override
String get homeTabsCustomizeDesc => '自定义主页上显示的标签及其顺序';
@override @override
String get homeWidgetUrlConfig => '桌面部件链接配置'; String get homeWidgetUrlConfig => '桌面部件链接配置';
@@ -283,9 +388,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get imagesList => '镜像列表'; String get imagesList => '镜像列表';
@override
String get init => '初始化';
@override @override
String get inner => '内置'; String get inner => '内置';
@@ -314,15 +416,18 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get keyAuth => '密钥认证'; String get keyAuth => '密钥认证';
@override
String get lastFailure => '最后失败';
@override
String get lastSuccess => '最后成功';
@override @override
String get letterCache => '输入法字符缓存'; String get letterCache => '输入法字符缓存';
@override @override
String get letterCacheTip => '推荐关闭,但是关闭后无法输入 CJK 等文字'; String get letterCacheTip => '推荐关闭,但是关闭后无法输入 CJK 等文字';
@override
String get license => '证书';
@override @override
String get location => '位置'; String get location => '位置';
@@ -335,10 +440,10 @@ class AppLocalizationsZh extends AppLocalizations {
} }
@override @override
String get manual => '手动'; String get max => '最大';
@override @override
String get max => '最大'; String get maxConcurrency => '最大并发数';
@override @override
String get maxRetryCount => '服务器尝试重连次数'; String get maxRetryCount => '服务器尝试重连次数';
@@ -378,6 +483,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get newContainer => '新建容器'; String get newContainer => '新建容器';
@override
String get noConnectionStatsData => '暂无连接统计数据';
@override @override
String get noLineChart => '不使用折线图'; String get noLineChart => '不使用折线图';
@@ -444,10 +552,12 @@ class AppLocalizationsZh extends AppLocalizations {
String get preferDiskAmount => '优先显示硬盘容量'; String get preferDiskAmount => '优先显示硬盘容量';
@override @override
String get preview => '预览'; String get privateKey => '私钥';
@override @override
String get privateKey => '私钥'; String privateKeyNotFoundFmt(Object keyId) {
return '未找到私钥 [$keyId]。';
}
@override @override
String get process => '进程'; String get process => '进程';
@@ -473,6 +583,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get reboot => '重启'; String get reboot => '重启';
@override
String get recentConnections => '最近连接记录';
@override @override
String get rememberPwdInMem => '在内存中记住密码'; String get rememberPwdInMem => '在内存中记住密码';
@@ -533,6 +646,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get serverOrder => '服务器顺序'; String get serverOrder => '服务器顺序';
@override
String get serverTabRequired => '服务器标签不能被移除';
@override
String get servers => '服务器';
@override @override
String get sftpDlPrepare => '准备连接至服务器...'; String get sftpDlPrepare => '准备连接至服务器...';
@@ -613,6 +732,34 @@ class AppLocalizationsZh extends AppLocalizations {
return '从 SSH 配置导入了 $count 个服务器'; return '从 SSH 配置导入了 $count 个服务器';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return '服务器 $serverName 的 SSH 主机密钥已更改,仅在信任该服务器时继续。';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return '指纹MD5 Base64$fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return '指纹MD5 十六进制):$fingerprint';
}
@override
String get sshHostKeyType => 'SSH 主机密钥类型';
@override
String sshHostKeyNewDesc(Object serverName) {
return '收到来自 $serverName 的新 SSH 主机密钥,在信任前请检查指纹。';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return '已存储的指纹:$fingerprint';
}
@override @override
String get sshConfigManualSelect => '是否要手动选择 SSH 配置文件?'; String get sshConfigManualSelect => '是否要手动选择 SSH 配置文件?';
@@ -671,9 +818,6 @@ class AppLocalizationsZh extends AppLocalizations {
return '切换到 $val'; return '切换到 $val';
} }
@override
String get sync => '同步';
@override @override
String get syncTip => '可能需要重新启动,某些更改才能生效。'; String get syncTip => '可能需要重新启动,某些更改才能生效。';
@@ -683,6 +827,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get tag => '标签'; String get tag => '标签';
@override
String get tapToStartDiscovery => '点击搜索按钮发现网络中的SSH服务器';
@override @override
String get temperature => '温度'; String get temperature => '温度';
@@ -713,6 +860,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get total => '总共'; String get total => '总共';
@override
String get totalAttempts => '总次数';
@override @override
String get traffic => '流量'; String get traffic => '流量';
@@ -737,9 +887,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get updateServerStatusInterval => '服务器状态刷新间隔'; String get updateServerStatusInterval => '服务器状态刷新间隔';
@override
String get upload => '上传';
@override @override
String get upsideDown => '上下交换'; String get upsideDown => '上下交换';
@@ -764,6 +911,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get view => '视图'; String get view => '视图';
@override
String get viewDetails => '查看详情';
@override @override
String get viewErr => '查看错误'; String get viewErr => '查看错误';
@@ -803,70 +953,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。'; '在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。';
@override
String get connectionStats => '连接统计';
@override
String get connectionStatsDesc => '查看服务器连接成功率和历史记录';
@override
String get noConnectionStatsData => '暂无连接统计数据';
@override
String get totalAttempts => '总次数';
@override
String get lastSuccess => '最后成功';
@override
String get lastFailure => '最后失败';
@override
String get recentConnections => '最近连接记录';
@override
String get viewDetails => '查看详情';
@override
String get connectionDetails => '连接详情';
@override
String get clearThisServerStats => '清空此服务器统计';
@override
String get clearAllStatsTitle => '清空所有统计';
@override
String get clearAllStatsContent => '确定要清空所有服务器的连接统计数据吗?此操作无法撤销。';
@override
String clearServerStatsTitle(String serverName) {
return '清空 $serverName 统计';
}
@override
String clearServerStatsContent(String serverName) {
return '确定要清空服务器 \"$serverName\" 的连接统计数据吗?此操作无法撤销。';
}
@override
String get homeTabs => '主页标签';
@override
String get homeTabsCustomizeDesc => '自定义主页上显示的标签及其顺序';
@override
String get reset => '重置';
@override
String get availableTabs => '可用标签';
@override
String get atLeastOneTab => '至少需要选择一个标签';
@override
String get serverTabRequired => '服务器标签不能被移除';
} }
/// The translations for Chinese, as used in Taiwan (`zh_TW`). /// The translations for Chinese, as used in Taiwan (`zh_TW`).
@@ -891,6 +977,59 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get alreadyLastDir => '已是頂層目錄'; String get alreadyLastDir => '已是頂層目錄';
@override
String get askAi => '詢問 AI';
@override
String get askAiApiKey => 'API 金鑰';
@override
String get askAiAwaitingResponse => '等待 AI 回應...';
@override
String get askAiBaseUrl => '基礎 URL';
@override
String get askAiCommandInserted => '指令已插入終端機';
@override
String askAiConfigMissing(Object fields) {
return '請前往設定配置 $fields';
}
@override
String get askAiConfirmExecute => '執行前確認';
@override
String get askAiConversation => 'AI 對話';
@override
String get askAiDisclaimer => 'AI 可能會犯錯,請謹慎使用。';
@override
String get askAiFollowUpHint => '繼續提問...';
@override
String get askAiInsertTerminal => '插入終端機';
@override
String get askAiModel => '模型';
@override
String get askAiNoResponse => '無回覆內容';
@override
String get askAiRecommendedCommand => 'AI 推薦指令';
@override
String get askAiSelectedContent => '選取的內容';
@override
String get askAiUsageHint => '於 SSH 終端機中使用';
@override
String get atLeastOneTab => '至少需要選擇一個標籤';
@override @override
String get authFailTip => '認證失敗,請檢查連線資訊是否正確'; String get authFailTip => '認證失敗,請檢查連線資訊是否正確';
@@ -906,6 +1045,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get autoUpdateHomeWidget => '自動更新桌面小工具'; String get autoUpdateHomeWidget => '自動更新桌面小工具';
@override
String get availableTabs => '可用標籤';
@override @override
String get backupEncrypted => '備份已加密'; String get backupEncrypted => '備份已加密';
@@ -943,6 +1085,25 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
String get bgRunTip => String get bgRunTip =>
'此開關僅代表程式會嘗試於背景執行,能否成功取決於系統權限。在原生 Android 上,請關閉本應用的「電池最佳化」;在 MIUI / HyperOS 上,請將省電策略調整為「無限制」。'; '此開關僅代表程式會嘗試於背景執行,能否成功取決於系統權限。在原生 Android 上,請關閉本應用的「電池最佳化」;在 MIUI / HyperOS 上,請將省電策略調整為「無限制」。';
@override
String get clearAllStatsContent => '確定要清空所有伺服器的連線統計資料嗎?此操作無法撤銷。';
@override
String get clearAllStatsTitle => '清空所有統計';
@override
String clearServerStatsContent(Object serverName) {
return '確定要清空伺服器 \"$serverName\" 的連線統計資料嗎?此操作無法撤銷。';
}
@override
String clearServerStatsTitle(Object serverName) {
return '清空 $serverName 統計';
}
@override
String get clearThisServerStats => '清空此伺服器統計';
@override @override
String get closeAfterSave => '儲存後關閉'; String get closeAfterSave => '儲存後關閉';
@@ -955,6 +1116,15 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get conn => '連線'; String get conn => '連線';
@override
String get connectionDetails => '連線詳情';
@override
String get connectionStats => '連線統計';
@override
String get connectionStatsDesc => '檢視伺服器連線成功率和歷史記錄';
@override @override
String get container => '容器'; String get container => '容器';
@@ -1002,6 +1172,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get disconnected => '已中斷連線'; String get disconnected => '已中斷連線';
@override
String get discoverSshServers => '發現SSH服務器';
@override
String get discoveryFailed => '發現失敗';
@override
String get discoverySettings => '發現設定';
@override
String get discoverySummary => '發現摘要';
@override @override
String get disk => '磁碟'; String get disk => '磁碟';
@@ -1053,15 +1235,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get editVirtKeys => '編輯虛擬按鍵'; String get editVirtKeys => '編輯虛擬按鍵';
@override
String get editor => '編輯器';
@override @override
String get editorHighlightTip => '程式碼高亮功能可能影響效能,可選擇性關閉。'; String get editorHighlightTip => '程式碼高亮功能可能影響效能,可選擇性關閉。';
@override @override
String get emulator => '模擬器'; String get emulator => '模擬器';
@override
String get enableMdns => '啟用mDNS';
@override
String get enableMdnsDesc => '使用mDNS/Bonjour發現SSH服務';
@override @override
String get encode => '編碼'; String get encode => '編碼';
@@ -1093,10 +1278,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
} }
@override @override
String get followSystem => '跟隨系統'; String get finishedAt => '完成於';
@override @override
String get font => '字型'; String get followSystem => '跟隨系統';
@override @override
String get fontSize => '字型大小'; String get fontSize => '字型大小';
@@ -1128,6 +1313,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get highlight => '程式碼標記'; String get highlight => '程式碼標記';
@override
String get homeTabs => '主頁標籤';
@override
String get homeTabsCustomizeDesc => '自訂主頁上顯示的標籤及其順序';
@override @override
String get homeWidgetUrlConfig => '桌面小工具連結配置'; String get homeWidgetUrlConfig => '桌面小工具連結配置';
@@ -1148,9 +1339,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get imagesList => '映像檔列表'; String get imagesList => '映像檔列表';
@override
String get init => '初始化';
@override @override
String get inner => '內建'; String get inner => '內建';
@@ -1179,15 +1367,18 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get keyAuth => '金鑰認證'; String get keyAuth => '金鑰認證';
@override
String get lastFailure => '最後失敗';
@override
String get lastSuccess => '最後成功';
@override @override
String get letterCache => '輸入法字符快取'; String get letterCache => '輸入法字符快取';
@override @override
String get letterCacheTip => '建議關閉,但關閉後將無法輸入 CJK 等文字。'; String get letterCacheTip => '建議關閉,但關閉後將無法輸入 CJK 等文字。';
@override
String get license => '憑證';
@override @override
String get location => '位置'; String get location => '位置';
@@ -1200,10 +1391,10 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
} }
@override @override
String get manual => '手動'; String get max => '最大';
@override @override
String get max => '最大'; String get maxConcurrency => '最大並發數';
@override @override
String get maxRetryCount => '伺服器嘗試重連次數'; String get maxRetryCount => '伺服器嘗試重連次數';
@@ -1243,6 +1434,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get newContainer => '新建容器'; String get newContainer => '新建容器';
@override
String get noConnectionStatsData => '暫無連線統計資料';
@override @override
String get noLineChart => '不使用折線圖'; String get noLineChart => '不使用折線圖';
@@ -1309,10 +1503,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
String get preferDiskAmount => '優先顯示硬碟容量'; String get preferDiskAmount => '優先顯示硬碟容量';
@override @override
String get preview => '預覽'; String get privateKey => '私鑰';
@override @override
String get privateKey => '私鑰'; String privateKeyNotFoundFmt(Object keyId) {
return '未找到私鑰 [$keyId]。';
}
@override @override
String get process => '處理程序'; String get process => '處理程序';
@@ -1338,6 +1534,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get reboot => '重開'; String get reboot => '重開';
@override
String get recentConnections => '最近連線記錄';
@override @override
String get rememberPwdInMem => '在記憶體中記住密碼'; String get rememberPwdInMem => '在記憶體中記住密碼';
@@ -1398,6 +1597,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get serverOrder => '伺服器順序'; String get serverOrder => '伺服器順序';
@override
String get serverTabRequired => '服務器標籤不能被移除';
@override
String get servers => '服務器';
@override @override
String get sftpDlPrepare => '準備連線至伺服器...'; String get sftpDlPrepare => '準備連線至伺服器...';
@@ -1478,6 +1683,34 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
return '已從SSH設定匯入$count個伺服器'; return '已從SSH設定匯入$count個伺服器';
} }
@override
String sshHostKeyChangedDesc(Object serverName) {
return '伺服器 $serverName 的 SSH 主機金鑰已變更,僅在信任該伺服器時繼續。';
}
@override
String sshHostKeyFingerprintMd5Base64(Object fingerprint) {
return '指紋MD5 Base64$fingerprint';
}
@override
String sshHostKeyFingerprintMd5Hex(Object fingerprint) {
return '指紋MD5 十六進位):$fingerprint';
}
@override
String get sshHostKeyType => 'SSH 主機金鑰類型';
@override
String sshHostKeyNewDesc(Object serverName) {
return '收到來自 $serverName 的新 SSH 主機金鑰,信任前請先檢查指紋。';
}
@override
String sshHostKeyStoredFingerprint(Object fingerprint) {
return '已儲存的指紋:$fingerprint';
}
@override @override
String get sshConfigManualSelect => '是否要手動選擇 SSH 設定檔案?'; String get sshConfigManualSelect => '是否要手動選擇 SSH 設定檔案?';
@@ -1536,9 +1769,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
return '切換到 $val'; return '切換到 $val';
} }
@override
String get sync => '同步';
@override @override
String get syncTip => '可能需要重新啟動,某些更改才能生效。'; String get syncTip => '可能需要重新啟動,某些更改才能生效。';
@@ -1548,6 +1778,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get tag => '標籤'; String get tag => '標籤';
@override
String get tapToStartDiscovery => '點擊搜尋按鈕發現網路中的SSH服務器';
@override @override
String get temperature => '溫度'; String get temperature => '溫度';
@@ -1578,6 +1811,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get total => '總共'; String get total => '總共';
@override
String get totalAttempts => '總次數';
@override @override
String get traffic => '流量'; String get traffic => '流量';
@@ -1602,9 +1838,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get updateServerStatusInterval => '伺服器狀態更新間隔'; String get updateServerStatusInterval => '伺服器狀態更新間隔';
@override
String get upload => '上傳';
@override @override
String get upsideDown => '上下交換'; String get upsideDown => '上下交換';
@@ -1629,6 +1862,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get view => '檢視'; String get view => '檢視';
@override
String get viewDetails => '檢視詳情';
@override @override
String get viewErr => '查看錯誤'; String get viewErr => '查看錯誤';
@@ -1668,68 +1904,4 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get writeScriptTip => String get writeScriptTip =>
'連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。'; '連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。';
@override
String get connectionStats => '連線統計';
@override
String get connectionStatsDesc => '檢視伺服器連線成功率和歷史記錄';
@override
String get noConnectionStatsData => '暫無連線統計資料';
@override
String get totalAttempts => '總次數';
@override
String get lastSuccess => '最後成功';
@override
String get lastFailure => '最後失敗';
@override
String get recentConnections => '最近連線記錄';
@override
String get viewDetails => '檢視詳情';
@override
String get connectionDetails => '連線詳情';
@override
String get clearThisServerStats => '清空此伺服器統計';
@override
String get clearAllStatsTitle => '清空所有統計';
@override
String get clearAllStatsContent => '確定要清空所有伺服器的連線統計資料嗎?此操作無法撤銷。';
@override
String clearServerStatsTitle(String serverName) {
return '清空 $serverName 統計';
}
@override
String clearServerStatsContent(String serverName) {
return '確定要清空伺服器 \"$serverName\" 的連線統計資料嗎?此操作無法撤銷。';
}
@override
String get homeTabs => '主頁標籤';
@override
String get homeTabsCustomizeDesc => '自訂主頁上顯示的標籤及其順序';
@override
String get reset => '重置';
@override
String get availableTabs => '可用標籤';
@override
String get atLeastOneTab => '至少需要選擇一個標籤';
@override
String get serverTabRequired => '服務器標籤不能被移除';
} }

View File

@@ -3,6 +3,7 @@ import 'package:server_box/data/model/app/menu/server_func.dart';
import 'package:server_box/data/model/app/net_view.dart'; import 'package:server_box/data/model/app/net_view.dart';
import 'package:server_box/data/model/server/custom.dart'; import 'package:server_box/data/model/server/custom.dart';
import 'package:server_box/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
import 'package:server_box/data/model/server/proxy_command_config.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:server_box/data/model/server/system.dart'; import 'package:server_box/data/model/server/system.dart';
@@ -19,5 +20,6 @@ import 'package:server_box/data/model/ssh/virtual_key.dart';
AdapterSpec<ServerCustom>(), AdapterSpec<ServerCustom>(),
AdapterSpec<WakeOnLanCfg>(), AdapterSpec<WakeOnLanCfg>(),
AdapterSpec<SystemType>(), AdapterSpec<SystemType>(),
AdapterSpec<ProxyCommandConfig>(),
]) ])
part 'hive_adapters.g.dart'; part 'hive_adapters.g.dart';

View File

@@ -113,13 +113,14 @@ class SpiAdapter extends TypeAdapter<Spi> {
id: fields[13] == null ? '' : fields[13] as String, id: fields[13] == null ? '' : fields[13] as String,
customSystemType: fields[14] as SystemType?, customSystemType: fields[14] as SystemType?,
disabledCmdTypes: (fields[15] as List?)?.cast<String>(), disabledCmdTypes: (fields[15] as List?)?.cast<String>(),
proxyCommand: fields[16] as ProxyCommandConfig?,
); );
} }
@override @override
void write(BinaryWriter writer, Spi obj) { void write(BinaryWriter writer, Spi obj) {
writer writer
..writeByte(16) ..writeByte(17)
..writeByte(0) ..writeByte(0)
..write(obj.name) ..write(obj.name)
..writeByte(1) ..writeByte(1)
@@ -151,7 +152,9 @@ class SpiAdapter extends TypeAdapter<Spi> {
..writeByte(14) ..writeByte(14)
..write(obj.customSystemType) ..write(obj.customSystemType)
..writeByte(15) ..writeByte(15)
..write(obj.disabledCmdTypes); ..write(obj.disabledCmdTypes)
..writeByte(16)
..write(obj.proxyCommand);
} }
@override @override
@@ -604,3 +607,66 @@ class SystemTypeAdapter extends TypeAdapter<SystemType> {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
typeId == other.typeId; typeId == other.typeId;
} }
class ProxyCommandConfigAdapter extends TypeAdapter<ProxyCommandConfig> {
@override
final typeId = 10;
@override
ProxyCommandConfig read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ProxyCommandConfig(
command: fields[0] as String,
args: (fields[1] as List?)?.cast<String>(),
workingDirectory: fields[2] as String?,
environment: (fields[3] as Map?)?.cast<String, String>(),
timeout: fields[4] == null
? const Duration(seconds: 30)
: fields[4] as Duration,
retryOnFailure: fields[5] == null ? false : fields[5] as bool,
maxRetries: fields[6] == null ? 3 : (fields[6] as num).toInt(),
requiresExecutable: fields[7] == null ? false : fields[7] as bool,
executableName: fields[8] as String?,
executableDownloadUrl: fields[9] as String?,
);
}
@override
void write(BinaryWriter writer, ProxyCommandConfig obj) {
writer
..writeByte(10)
..writeByte(0)
..write(obj.command)
..writeByte(1)
..write(obj.args)
..writeByte(2)
..write(obj.workingDirectory)
..writeByte(3)
..write(obj.environment)
..writeByte(4)
..write(obj.timeout)
..writeByte(5)
..write(obj.retryOnFailure)
..writeByte(6)
..write(obj.maxRetries)
..writeByte(7)
..write(obj.requiresExecutable)
..writeByte(8)
..write(obj.executableName)
..writeByte(9)
..write(obj.executableDownloadUrl);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ProxyCommandConfigAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -1,7 +1,7 @@
# Generated by Hive CE # Generated by Hive CE
# Manual modifications may be necessary for certain migrations # Manual modifications may be necessary for certain migrations
# Check in to version control # Check in to version control
nextTypeId: 10 nextTypeId: 11
types: types:
PrivateKeyInfo: PrivateKeyInfo:
typeId: 1 typeId: 1
@@ -27,7 +27,7 @@ types:
index: 4 index: 4
Spi: Spi:
typeId: 3 typeId: 3
nextIndex: 16 nextIndex: 17
fields: fields:
name: name:
index: 0 index: 0
@@ -61,6 +61,8 @@ types:
index: 14 index: 14
disabledCmdTypes: disabledCmdTypes:
index: 15 index: 15
proxyCommand:
index: 16
VirtKey: VirtKey:
typeId: 4 typeId: 4
nextIndex: 45 nextIndex: 45
@@ -221,3 +223,27 @@ types:
index: 1 index: 1
windows: windows:
index: 2 index: 2
ProxyCommandConfig:
typeId: 10
nextIndex: 10
fields:
command:
index: 0
args:
index: 1
workingDirectory:
index: 2
environment:
index: 3
timeout:
index: 4
retryOnFailure:
index: 5
maxRetries:
index: 6
requiresExecutable:
index: 7
executableName:
index: 8
executableDownloadUrl:
index: 9

View File

@@ -14,6 +14,7 @@ extension HiveRegistrar on HiveInterface {
registerAdapter(ConnectionStatAdapter()); registerAdapter(ConnectionStatAdapter());
registerAdapter(NetViewTypeAdapter()); registerAdapter(NetViewTypeAdapter());
registerAdapter(PrivateKeyInfoAdapter()); registerAdapter(PrivateKeyInfoAdapter());
registerAdapter(ProxyCommandConfigAdapter());
registerAdapter(ServerConnectionStatsAdapter()); registerAdapter(ServerConnectionStatsAdapter());
registerAdapter(ServerCustomAdapter()); registerAdapter(ServerCustomAdapter());
registerAdapter(ServerFuncBtnAdapter()); registerAdapter(ServerFuncBtnAdapter());
@@ -32,6 +33,7 @@ extension IsolatedHiveRegistrar on IsolatedHiveInterface {
registerAdapter(ConnectionStatAdapter()); registerAdapter(ConnectionStatAdapter());
registerAdapter(NetViewTypeAdapter()); registerAdapter(NetViewTypeAdapter());
registerAdapter(PrivateKeyInfoAdapter()); registerAdapter(PrivateKeyInfoAdapter());
registerAdapter(ProxyCommandConfigAdapter());
registerAdapter(ServerConnectionStatsAdapter()); registerAdapter(ServerConnectionStatsAdapter());
registerAdapter(ServerCustomAdapter()); registerAdapter(ServerCustomAdapter());
registerAdapter(ServerFuncBtnAdapter()); registerAdapter(ServerFuncBtnAdapter());

View File

@@ -31,45 +31,8 @@ final class _IntroPage extends StatelessWidget {
padding: _introListPad, padding: _introListPad,
children: [ children: [
SizedBox(height: padTop), SizedBox(height: padTop),
IntroPage.title(text: l10n.init, big: true), IntroPage.title(text: libL10n.init, big: true),
SizedBox(height: padTop), SizedBox(height: padTop),
// Prompt to set backup password after migration or on first launch
ListTile(
leading: const Icon(Icons.lock),
title: Text(l10n.backupPassword),
subtitle: Text(l10n.backupPasswordTip, style: UIs.textGrey),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () async {
final currentPwd = await SecureStoreProps.bakPwd.read();
final controller = TextEditingController(text: currentPwd ?? '');
final result = await ctx.showRoundDialog<bool>(
title: l10n.backupPassword,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(l10n.backupPasswordTip, style: UIs.textGrey),
UIs.height13,
Input(
label: l10n.backupPassword,
controller: controller,
obscureText: true,
onSubmitted: (_) => ctx.pop(true),
),
],
),
actions: Btnx.oks,
);
if (result == true) {
final pwd = controller.text.trim();
if (pwd.isEmpty) {
ctx.showSnackBar(libL10n.empty);
return;
}
await SecureStoreProps.bakPwd.write(pwd);
ctx.showSnackBar(l10n.backupPasswordSet);
}
},
).cardx,
ListTile( ListTile(
leading: const Icon(IonIcons.language), leading: const Icon(IonIcons.language),
title: Text(libL10n.language), title: Text(libL10n.language),

View File

@@ -6,11 +6,29 @@
"added2List": "Zur Aufgabenliste hinzugefügt", "added2List": "Zur Aufgabenliste hinzugefügt",
"addr": "Adresse", "addr": "Adresse",
"alreadyLastDir": "Bereits im letzten Verzeichnis.", "alreadyLastDir": "Bereits im letzten Verzeichnis.",
"askAi": "KI fragen",
"askAiApiKey": "API-Schlüssel",
"askAiAwaitingResponse": "Warte auf KI-Antwort...",
"askAiBaseUrl": "Basis-URL",
"askAiCommandInserted": "Befehl ins Terminal eingefügt",
"askAiConfigMissing": "Bitte konfigurieren Sie {fields} in den Einstellungen.",
"askAiConfirmExecute": "Vor Ausführung bestätigen",
"askAiConversation": "KI-Unterhaltung",
"askAiDisclaimer": "KI kann Fehler machen. Bitte vorsichtig verwenden.",
"askAiFollowUpHint": "Weitere Frage stellen...",
"askAiInsertTerminal": "In Terminal einfügen",
"askAiModel": "Modell",
"askAiNoResponse": "Keine Antwort",
"askAiRecommendedCommand": "KI-empfohlener Befehl",
"askAiSelectedContent": "Ausgewählter Inhalt",
"askAiUsageHint": "Verwendet im SSH-Terminal",
"atLeastOneTab": "Mindestens ein Tab muss ausgewählt sein",
"authFailTip": "Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.", "authFailTip": "Authentifizierung fehlgeschlagen, bitte überprüfen Sie, ob das Passwort/Schlüssel/Host/Benutzer usw. falsch sind.",
"autoBackupConflict": "Es kann nur eine automatische Sicherung gleichzeitig aktiviert werden.", "autoBackupConflict": "Es kann nur eine automatische Sicherung gleichzeitig aktiviert werden.",
"autoConnect": "Automatisch verbinden", "autoConnect": "Automatisch verbinden",
"autoRun": "Automatischer Start", "autoRun": "Automatischer Start",
"autoUpdateHomeWidget": "Home-Widget automatisch aktualisieren", "autoUpdateHomeWidget": "Home-Widget automatisch aktualisieren",
"availableTabs": "Verfügbare Tabs",
"backupEncrypted": "Backup ist verschlüsselt", "backupEncrypted": "Backup ist verschlüsselt",
"backupNotEncrypted": "Backup ist nicht verschlüsselt", "backupNotEncrypted": "Backup ist nicht verschlüsselt",
"backupPassword": "Backup-Passwort", "backupPassword": "Backup-Passwort",
@@ -23,10 +41,18 @@
"battery": "Batterie", "battery": "Batterie",
"bgRun": "Hintergrundaktualisierung", "bgRun": "Hintergrundaktualisierung",
"bgRunTip": "Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".", "bgRunTip": "Dieser Schalter bedeutet nur, dass die App versuchen wird, im Hintergrund zu laufen. Ob sie im Hintergrund laufen kann, hängt davon ab, ob die Berechtigungen aktiviert sind oder nicht. Bei nativem Android deaktivieren Sie bitte \"Batterieoptimierung\" in dieser App, und bei miui ändern Sie bitte die Energiesparrichtlinie auf \"Unbegrenzt\".",
"clearAllStatsContent": "Sind Sie sicher, dass Sie alle Server-Verbindungsstatistiken löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"clearAllStatsTitle": "Alle Statistiken löschen",
"clearServerStatsContent": "Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"{serverName}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"clearServerStatsTitle": "{serverName} Statistiken löschen",
"clearThisServerStats": "Statistiken dieses Servers löschen",
"closeAfterSave": "Speichern und schließen", "closeAfterSave": "Speichern und schließen",
"cmd": "Command", "cmd": "Command",
"collapseUITip": "Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht", "collapseUITip": "Ob lange Listen in der Benutzeroberfläche standardmäßig eingeklappt werden sollen oder nicht",
"conn": "Verbindung", "conn": "Verbindung",
"connectionDetails": "Verbindungsdetails",
"connectionStats": "Verbindungsstatistiken",
"connectionStatsDesc": "Server-Verbindungserfolgsrate und Verlauf anzeigen",
"container": "Container", "container": "Container",
"containerTrySudoTip": "Zum Beispiel: In der App ist der Benutzer auf aaa eingestellt, aber Docker ist unter dem Root-Benutzer installiert. In diesem Fall müssen Sie diese Option aktivieren", "containerTrySudoTip": "Zum Beispiel: In der App ist der Benutzer auf aaa eingestellt, aber Docker ist unter dem Root-Benutzer installiert. In diesem Fall müssen Sie diese Option aktivieren",
"convert": "Konvertieren", "convert": "Konvertieren",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Befehl zum Öffnen des Terminal-Emulators beim Starten von SSH-Sitzungen.", "desktopTerminalTip": "Befehl zum Öffnen des Terminal-Emulators beim Starten von SSH-Sitzungen.",
"dirEmpty": "Stelle sicher, dass der Ordner leer ist.", "dirEmpty": "Stelle sicher, dass der Ordner leer ist.",
"disconnected": "Disconnected", "disconnected": "Disconnected",
"discoverSshServers": "SSH-Server entdecken",
"discoveryFailed": "Entdeckung fehlgeschlagen",
"discoverySettings": "Entdeckungseinstellungen",
"discoverySummary": "Entdeckungs-Zusammenfassung",
"disk": "Festplatte", "disk": "Festplatte",
"diskHealth": "Festplattengesundheit", "diskHealth": "Festplattengesundheit",
"diskIgnorePath": "Pfad für Datenträger ignorieren", "diskIgnorePath": "Pfad für Datenträger ignorieren",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Doppelspaltiger Modus", "doubleColumnMode": "Doppelspaltiger Modus",
"doubleColumnTip": "Diese Option aktiviert nur die Funktion, ob sie tatsächlich aktiviert werden kann, hängt auch von der Breite des Geräts ab", "doubleColumnTip": "Diese Option aktiviert nur die Funktion, ob sie tatsächlich aktiviert werden kann, hängt auch von der Breite des Geräts ab",
"editVirtKeys": "Virtuelle Tasten bearbeiten", "editVirtKeys": "Virtuelle Tasten bearbeiten",
"editor": "Editor",
"editorHighlightTip": "Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.", "editorHighlightTip": "Die Leistung der aktuellen Codehervorhebung ist schlechter und kann zur Verbesserung optional ausgeschaltet werden.",
"emulator": "Emulator", "emulator": "Emulator",
"enableMdns": "mDNS aktivieren",
"enableMdnsDesc": "mDNS/Bonjour verwenden, um SSH-Dienste zu entdecken",
"encode": "Encode", "encode": "Encode",
"envVars": "Umgebungsvariable", "envVars": "Umgebungsvariable",
"experimentalFeature": "Experimentelles Feature", "experimentalFeature": "Experimentelles Feature",
@@ -67,8 +98,8 @@
"fgService": "Vordergrund-Dienst", "fgService": "Vordergrund-Dienst",
"fgServiceTip": "Nach dem Einschalten kann es bei einigen Gerätemodellen zu Abstürzen kommen. Das Ausschalten kann bei einigen Modellen dazu führen, dass SSH-Verbindungen im Hintergrund nicht aufrechterhalten werden können. Bitte erlauben Sie ServerBox in den Systemeinstellungen Benachrichtigungsrechte, Hintergrundausführung und Selbstaktivierung.", "fgServiceTip": "Nach dem Einschalten kann es bei einigen Gerätemodellen zu Abstürzen kommen. Das Ausschalten kann bei einigen Modellen dazu führen, dass SSH-Verbindungen im Hintergrund nicht aufrechterhalten werden können. Bitte erlauben Sie ServerBox in den Systemeinstellungen Benachrichtigungsrechte, Hintergrundausführung und Selbstaktivierung.",
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}", "fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
"finishedAt": "Beendet um",
"followSystem": "System verfolgen", "followSystem": "System verfolgen",
"font": "Schriftarten",
"fontSize": "Schriftgröße", "fontSize": "Schriftgröße",
"force": "freiwillig", "force": "freiwillig",
"fullScreen": "Vollbildmodus", "fullScreen": "Vollbildmodus",
@@ -79,13 +110,14 @@
"goto": "Pfad öffnen", "goto": "Pfad öffnen",
"hideTitleBar": "Titelleiste ausblenden", "hideTitleBar": "Titelleiste ausblenden",
"highlight": "Code highlight", "highlight": "Code highlight",
"homeTabs": "Home-Tabs",
"homeTabsCustomizeDesc": "Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge",
"homeWidgetUrlConfig": "Home-Widget-Link konfigurieren", "homeWidgetUrlConfig": "Home-Widget-Link konfigurieren",
"host": "Host", "host": "Host",
"httpFailedWithCode": "Anfrage fehlgeschlagen, Statuscode: {code}", "httpFailedWithCode": "Anfrage fehlgeschlagen, Statuscode: {code}",
"ignoreCert": "Zertifikat ignorieren", "ignoreCert": "Zertifikat ignorieren",
"image": "Image", "image": "Image",
"imagesList": "Images", "imagesList": "Images",
"init": "Initialisieren",
"inner": "Eingebaut", "inner": "Eingebaut",
"install": "install", "install": "install",
"installDockerWithUrl": "Bitte installiere docker zuerst. https://docs.docker.com/engine/install", "installDockerWithUrl": "Bitte installiere docker zuerst. https://docs.docker.com/engine/install",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Den letzten Serverstatus beibehalten", "keepStatusWhenErr": "Den letzten Serverstatus beibehalten",
"keepStatusWhenErrTip": "Nur im Fehlerfall während der Ausführung des Skripts", "keepStatusWhenErrTip": "Nur im Fehlerfall während der Ausführung des Skripts",
"keyAuth": "Schlüsselauthentifzierung", "keyAuth": "Schlüsselauthentifzierung",
"lastFailure": "Letzter Fehler",
"lastSuccess": "Letzter Erfolg",
"letterCache": "Buchstaben-Caching", "letterCache": "Buchstaben-Caching",
"letterCacheTip": "Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.", "letterCacheTip": "Empfohlen, zu deaktivieren, aber nach dem Deaktivieren können keine CJK-Zeichen eingegeben werden.",
"license": "Lizenzen",
"location": "Standort", "location": "Standort",
"loss": "loss", "loss": "loss",
"madeWithLove": "Erstellt mit ❤️ von {myGithub}", "madeWithLove": "Erstellt mit ❤️ von {myGithub}",
"manual": "Handbuch",
"max": "max", "max": "max",
"maxConcurrency": "Maximale Gleichzeitigkeit",
"maxRetryCount": "Anzahl an Verbindungsversuchen", "maxRetryCount": "Anzahl an Verbindungsversuchen",
"maxRetryCountEqual0": "Unbegrenzte Verbindungsversuche zum Server", "maxRetryCountEqual0": "Unbegrenzte Verbindungsversuche zum Server",
"min": "min", "min": "min",
@@ -115,6 +148,7 @@
"net": "Netzwerk", "net": "Netzwerk",
"netViewType": "Netzwerkansicht Typ", "netViewType": "Netzwerkansicht Typ",
"newContainer": "Neuer Container", "newContainer": "Neuer Container",
"noConnectionStatsData": "Keine Verbindungsstatistikdaten",
"noLineChart": "Verwenden Sie keine Liniendiagramme", "noLineChart": "Verwenden Sie keine Liniendiagramme",
"noLineChartForCpu": "Verwenden Sie keine Liniendiagramme für CPU", "noLineChartForCpu": "Verwenden Sie keine Liniendiagramme für CPU",
"noPrivateKeyTip": "Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.", "noPrivateKeyTip": "Der private Schlüssel existiert nicht, möglicherweise wurde er gelöscht oder es liegt ein Konfigurationsfehler vor.",
@@ -136,8 +170,8 @@
"plugInType": "Einfügetyp", "plugInType": "Einfügetyp",
"port": "Port", "port": "Port",
"preferDiskAmount": "Festplattenkapazität vorrangig anzeigen", "preferDiskAmount": "Festplattenkapazität vorrangig anzeigen",
"preview": "Vorschau",
"privateKey": "Private Key", "privateKey": "Private Key",
"privateKeyNotFoundFmt": "Privater Schlüssel [{keyId}] wurde nicht gefunden.",
"process": "Prozess", "process": "Prozess",
"prune": "Beschneiden", "prune": "Beschneiden",
"pushToken": "Push Token", "pushToken": "Push Token",
@@ -146,6 +180,7 @@
"pveVersionLow": "Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.", "pveVersionLow": "Diese Funktion befindet sich derzeit in der Testphase und wurde nur auf PVE 8+ getestet. Bitte verwenden Sie sie mit Vorsicht.",
"read": "Lesen", "read": "Lesen",
"reboot": "Neustart", "reboot": "Neustart",
"recentConnections": "Kürzliche Verbindungen",
"rememberPwdInMem": "Passwort im Speicher behalten", "rememberPwdInMem": "Passwort im Speicher behalten",
"rememberPwdInMemTip": "Für Container, Aufhängen usw.", "rememberPwdInMemTip": "Für Container, Aufhängen usw.",
"rememberWindowSize": "Fenstergröße merken", "rememberWindowSize": "Fenstergröße merken",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Reihenfolge der Widgets auf der Detailseite", "serverDetailOrder": "Reihenfolge der Widgets auf der Detailseite",
"serverFuncBtns": "Server-Funktionsschaltflächen", "serverFuncBtns": "Server-Funktionsschaltflächen",
"serverOrder": "Server-Bestellung", "serverOrder": "Server-Bestellung",
"serverTabRequired": "Server-Tab kann nicht entfernt werden",
"servers": "Server",
"sftpDlPrepare": "Verbindung vorbereiten...", "sftpDlPrepare": "Verbindung vorbereiten...",
"sftpEditorTip": "Wenn leer, verwenden Sie den im App integrierten Dateieditor. Wenn ein Wert vorhanden ist, wird der Editor des Remote-Servers verwendet, z.B. `vim` (es wird empfohlen, automatisch gemäß `EDITOR` zu ermitteln).", "sftpEditorTip": "Wenn leer, verwenden Sie den im App integrierten Dateieditor. Wenn ein Wert vorhanden ist, wird der Editor des Remote-Servers verwendet, z.B. `vim` (es wird empfohlen, automatisch gemäß `EDITOR` zu ermitteln).",
"sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.", "sftpRmrDirSummary": "Verwenden Sie \"rm -r\", um das Verzeichnis in SFTP zu löschen.",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Möchten Sie die Berechtigung erteilen, ~/.ssh/config zu lesen und Server-Einstellungen automatisch zu importieren?", "sshConfigImportPermission": "Möchten Sie die Berechtigung erteilen, ~/.ssh/config zu lesen und Server-Einstellungen automatisch zu importieren?",
"sshConfigImportTip": "Bei der ersten Server-Erstellung zum Lesen von ~/.ssh/config auffordern", "sshConfigImportTip": "Bei der ersten Server-Erstellung zum Lesen von ~/.ssh/config auffordern",
"sshConfigImported": "{count} Server aus SSH-Konfiguration importiert", "sshConfigImported": "{count} Server aus SSH-Konfiguration importiert",
"sshHostKeyChangedDesc": "Der SSH-Hostschlüssel für {serverName} hat sich geändert. Fahren Sie nur fort, wenn Sie diesem Server vertrauen.",
"sshHostKeyFingerprintMd5Base64": "Fingerabdruck (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Fingerabdruck (MD5 Hex): {fingerprint}",
"sshHostKeyType": "SSH-Hostschlüsseltyp",
"sshHostKeyNewDesc": "Ein neuer SSH-Hostschlüssel wurde von {serverName} empfangen. Prüfen Sie den Fingerabdruck, bevor Sie vertrauen.",
"sshHostKeyStoredFingerprint": "Gespeicherter Fingerabdruck: {fingerprint}",
"sshConfigManualSelect": "Möchten Sie die SSH-Konfigurationsdatei manuell auswählen?", "sshConfigManualSelect": "Möchten Sie die SSH-Konfigurationsdatei manuell auswählen?",
"sshConfigNoServers": "Keine Server in der SSH-Konfiguration gefunden", "sshConfigNoServers": "Keine Server in der SSH-Konfiguration gefunden",
"sshConfigPermissionDenied": "Aufgrund der macOS-Berechtigungen kann nicht auf die SSH-Konfigurationsdatei zugegriffen werden.", "sshConfigPermissionDenied": "Aufgrund der macOS-Berechtigungen kann nicht auf die SSH-Konfigurationsdatei zugegriffen werden.",
@@ -206,10 +249,10 @@
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.", "suspendTip": "Die Suspend-Funktion erfordert Root-Rechte und systemd-Unterstützung.",
"switchTo": "Wechseln zu {val}", "switchTo": "Wechseln zu {val}",
"sync": "Sync",
"syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.", "syncTip": "Damit einige Änderungen wirksam werden, kann ein Neustart erforderlich sein.",
"system": "Systeme", "system": "Systeme",
"tag": "Tags", "tag": "Tags",
"tapToStartDiscovery": "Tippen Sie auf die Suche-Schaltfläche, um SSH-Server in Ihrem Netzwerk zu entdecken",
"temperature": "Temperatur", "temperature": "Temperatur",
"termFontSizeTip": "Diese Einstellung beeinflusst die Größe des Terminals (Breite und Höhe). Sie können die Terminalseite zoomen, um die Schriftgröße der aktuellen Sitzung anzupassen.", "termFontSizeTip": "Diese Einstellung beeinflusst die Größe des Terminals (Breite und Höhe). Sie können die Terminalseite zoomen, um die Schriftgröße der aktuellen Sitzung anzupassen.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Zeit", "time": "Zeit",
"times": "x", "times": "x",
"total": "Total", "total": "Total",
"totalAttempts": "Gesamt",
"traffic": "Durchflussmenge", "traffic": "Durchflussmenge",
"trySudo": "Versuche es mit sudo", "trySudo": "Versuche es mit sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Update", "update": "Update",
"updateIntervalEqual0": "Wenn du den Wert 0 einstellst, wird nicht automatisch aktualisiert.\nDer CPU-Status kann nicht berechnet werden.", "updateIntervalEqual0": "Wenn du den Wert 0 einstellst, wird nicht automatisch aktualisiert.\nDer CPU-Status kann nicht berechnet werden.",
"updateServerStatusInterval": "Aktualisierungsintervall des Serverstatus", "updateServerStatusInterval": "Aktualisierungsintervall des Serverstatus",
"upload": "Hochladen",
"upsideDown": "Upside Down", "upsideDown": "Upside Down",
"uptime": "Betriebszeit", "uptime": "Betriebszeit",
"useCdn": "Verwenden von CDN", "useCdn": "Verwenden von CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Standardmäßige Verwendung von Podman", "usePodmanByDefault": "Standardmäßige Verwendung von Podman",
"used": "Gebraucht", "used": "Gebraucht",
"view": "Ansicht", "view": "Ansicht",
"viewDetails": "Details anzeigen",
"viewErr": "Fehler anzeigen", "viewErr": "Fehler anzeigen",
"virtKeyHelpClipboard": "In die Zwischenablage kopieren, wenn das ausgewählte Terminal nicht leer ist, andernfalls den Inhalt der Zwischenablage in das Terminal einfügen.", "virtKeyHelpClipboard": "In die Zwischenablage kopieren, wenn das ausgewählte Terminal nicht leer ist, andernfalls den Inhalt der Zwischenablage in das Terminal einfügen.",
"virtKeyHelpIME": "Tastatur ein-/ausschalten", "virtKeyHelpIME": "Tastatur ein-/ausschalten",
@@ -249,39 +293,5 @@
"wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.", "wolTip": "Nach der Konfiguration von WOL (Wake-on-LAN) wird jedes Mal, wenn der Server verbunden wird, eine WOL-Anfrage gesendet.",
"write": "Schreiben", "write": "Schreiben",
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.", "writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.",
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.", "writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen."
"connectionStats": "Verbindungsstatistiken",
"connectionStatsDesc": "Server-Verbindungserfolgsrate und Verlauf anzeigen",
"noConnectionStatsData": "Keine Verbindungsstatistikdaten",
"totalAttempts": "Gesamt",
"lastSuccess": "Letzter Erfolg",
"lastFailure": "Letzter Fehler",
"recentConnections": "Kürzliche Verbindungen",
"viewDetails": "Details anzeigen",
"connectionDetails": "Verbindungsdetails",
"clearThisServerStats": "Statistiken dieses Servers löschen",
"clearAllStatsTitle": "Alle Statistiken löschen",
"clearAllStatsContent": "Sind Sie sicher, dass Sie alle Server-Verbindungsstatistiken löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"clearServerStatsTitle": "{serverName} Statistiken löschen",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Sind Sie sicher, dass Sie die Verbindungsstatistiken für Server \"{serverName}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Home-Tabs",
"homeTabsCustomizeDesc": "Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge",
"reset": "Zurücksetzen",
"availableTabs": "Verfügbare Tabs",
"atLeastOneTab": "Mindestens ein Tab muss ausgewählt sein",
"serverTabRequired": "Server-Tab kann nicht entfernt werden"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Added to task list", "added2List": "Added to task list",
"addr": "Address", "addr": "Address",
"alreadyLastDir": "Already in last directory.", "alreadyLastDir": "Already in last directory.",
"askAi": "Ask AI",
"askAiApiKey": "API Key",
"askAiAwaitingResponse": "Waiting for AI response...",
"askAiBaseUrl": "Base URL",
"askAiCommandInserted": "Command inserted into terminal",
"askAiConfigMissing": "Please configure {fields} in Settings.",
"askAiConfirmExecute": "Confirm before executing",
"askAiConversation": "AI conversation",
"askAiDisclaimer": "AI may be incorrect. Review carefully before applying.",
"askAiFollowUpHint": "Ask a follow-up...",
"askAiInsertTerminal": "Insert into terminal",
"askAiModel": "Model",
"askAiNoResponse": "No response",
"askAiRecommendedCommand": "AI suggested command",
"askAiSelectedContent": "Selected content",
"askAiUsageHint": "Used in SSH Terminal",
"atLeastOneTab": "At least one tab must be selected",
"authFailTip": "Authentication failed, please check whether credentials are correct", "authFailTip": "Authentication failed, please check whether credentials are correct",
"autoBackupConflict": "Only one automatic backup can be turned on at the same time.", "autoBackupConflict": "Only one automatic backup can be turned on at the same time.",
"autoConnect": "Auto connect", "autoConnect": "Auto connect",
"autoRun": "Auto run", "autoRun": "Auto run",
"autoUpdateHomeWidget": "Automatic home widget update", "autoUpdateHomeWidget": "Automatic home widget update",
"availableTabs": "Available Tabs",
"backupEncrypted": "Backup is encrypted", "backupEncrypted": "Backup is encrypted",
"backupNotEncrypted": "Backup is not encrypted", "backupNotEncrypted": "Backup is not encrypted",
"backupPassword": "Backup password", "backupPassword": "Backup password",
@@ -23,10 +41,18 @@
"battery": "Battery", "battery": "Battery",
"bgRun": "Run in background", "bgRun": "Run in background",
"bgRunTip": "This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".", "bgRunTip": "This switch only means the program will try to run in the background. Whether it can run in the background depends on whether the permission is enabled or not. For AOSP-based Android ROMs, please disable \"Battery Optimization\" in this app. For MIUI / HyperOS, please change the power saving policy to \"Unlimited\".",
"clearAllStatsContent": "Are you sure you want to clear all server connection statistics? This action cannot be undone.",
"clearAllStatsTitle": "Clear All Statistics",
"clearServerStatsContent": "Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.",
"clearServerStatsTitle": "Clear {serverName} Statistics",
"clearThisServerStats": "Clear This Server Statistics",
"closeAfterSave": "Save and close", "closeAfterSave": "Save and close",
"cmd": "Command", "cmd": "Command",
"collapseUITip": "Whether to collapse long lists present in the UI by default", "collapseUITip": "Whether to collapse long lists present in the UI by default",
"conn": "Connection", "conn": "Connection",
"connectionDetails": "Connection Details",
"connectionStats": "Connection Statistics",
"connectionStatsDesc": "View server connection success rate and history",
"container": "Container", "container": "Container",
"containerTrySudoTip": "For example: In the app, the user is set to aaa, but Docker is installed under the root user. In this case, you need to enable this option.", "containerTrySudoTip": "For example: In the app, the user is set to aaa, but Docker is installed under the root user. In this case, you need to enable this option.",
"convert": "Convert", "convert": "Convert",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Command used to open the terminal emulator when launching SSH sessions.", "desktopTerminalTip": "Command used to open the terminal emulator when launching SSH sessions.",
"dirEmpty": "Make sure the folder is empty.", "dirEmpty": "Make sure the folder is empty.",
"disconnected": "Disconnected", "disconnected": "Disconnected",
"discoverSshServers": "Discover SSH Servers",
"discoveryFailed": "Discovery failed",
"discoverySettings": "Discovery Settings",
"discoverySummary": "Discovery Summary",
"disk": "Disk", "disk": "Disk",
"diskHealth": "Disk Health", "diskHealth": "Disk Health",
"diskIgnorePath": "Ignore path for disk", "diskIgnorePath": "Ignore path for disk",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Double column mode", "doubleColumnMode": "Double column mode",
"doubleColumnTip": "This option only enables the feature, whether it can actually be enabled depends on the width of the device", "doubleColumnTip": "This option only enables the feature, whether it can actually be enabled depends on the width of the device",
"editVirtKeys": "Edit virtual keys", "editVirtKeys": "Edit virtual keys",
"editor": "Editor",
"editorHighlightTip": "The current code highlighting performance is not ideal and can be optionally turned off to improve.", "editorHighlightTip": "The current code highlighting performance is not ideal and can be optionally turned off to improve.",
"emulator": "Emulator", "emulator": "Emulator",
"enableMdns": "Enable mDNS",
"enableMdnsDesc": "Use mDNS/Bonjour to discover SSH services",
"encode": "Encode", "encode": "Encode",
"envVars": "Environment variable", "envVars": "Environment variable",
"experimentalFeature": "Experimental feature", "experimentalFeature": "Experimental feature",
@@ -67,8 +98,8 @@
"fgService": "Foreground Service", "fgService": "Foreground Service",
"fgServiceTip": "After enabling, some device models may crash. Disabling it may cause some models to be unable to maintain SSH connections in the background. Please allow ServerBox notification permissions, background running, and self-wake-up in system settings.", "fgServiceTip": "After enabling, some device models may crash. Disabling it may cause some models to be unable to maintain SSH connections in the background. Please allow ServerBox notification permissions, background running, and self-wake-up in system settings.",
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}", "fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
"finishedAt": "Finished at",
"followSystem": "Follow system", "followSystem": "Follow system",
"font": "Font",
"fontSize": "Font size", "fontSize": "Font size",
"force": "Force", "force": "Force",
"fullScreen": "Full screen mode", "fullScreen": "Full screen mode",
@@ -79,13 +110,14 @@
"goto": "Go to", "goto": "Go to",
"hideTitleBar": "Hide title bar", "hideTitleBar": "Hide title bar",
"highlight": "Code highlighting", "highlight": "Code highlighting",
"homeTabs": "Home Tabs",
"homeTabsCustomizeDesc": "Customize which tabs appear on the home page and their order",
"homeWidgetUrlConfig": "Config home widget url", "homeWidgetUrlConfig": "Config home widget url",
"host": "Host", "host": "Host",
"httpFailedWithCode": "request failed, status code: {code}", "httpFailedWithCode": "request failed, status code: {code}",
"ignoreCert": "Ignore certificate", "ignoreCert": "Ignore certificate",
"image": "Image", "image": "Image",
"imagesList": "Images list", "imagesList": "Images list",
"init": "Initialize",
"inner": "Inner", "inner": "Inner",
"install": "install", "install": "install",
"installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.", "installDockerWithUrl": "Please https://docs.docker.com/engine/install docker first.",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Preserve the last server state", "keepStatusWhenErr": "Preserve the last server state",
"keepStatusWhenErrTip": "Only in the event of an error during script execution", "keepStatusWhenErrTip": "Only in the event of an error during script execution",
"keyAuth": "Key Auth", "keyAuth": "Key Auth",
"lastFailure": "Last Failure",
"lastSuccess": "Last Success",
"letterCache": "Letter caching", "letterCache": "Letter caching",
"letterCacheTip": "Recommended to disable, but after disabling, it will be impossible to input CJK characters.", "letterCacheTip": "Recommended to disable, but after disabling, it will be impossible to input CJK characters.",
"license": "License",
"location": "Location", "location": "Location",
"loss": "loss", "loss": "loss",
"madeWithLove": "Made with ❤️ by {myGithub}", "madeWithLove": "Made with ❤️ by {myGithub}",
"manual": "Manual",
"max": "max", "max": "max",
"maxConcurrency": "Max Concurrency",
"maxRetryCount": "Number of server reconnections", "maxRetryCount": "Number of server reconnections",
"maxRetryCountEqual0": "Will retry again and again.", "maxRetryCountEqual0": "Will retry again and again.",
"min": "min", "min": "min",
@@ -115,6 +148,7 @@
"net": "Network", "net": "Network",
"netViewType": "Network view type", "netViewType": "Network view type",
"newContainer": "New container", "newContainer": "New container",
"noConnectionStatsData": "No connection statistics data",
"noLineChart": "Do not use line charts", "noLineChart": "Do not use line charts",
"noLineChartForCpu": "Do not use line charts for CPU", "noLineChartForCpu": "Do not use line charts for CPU",
"noPrivateKeyTip": "The private key does not exist, it may have been deleted or there is a configuration error.", "noPrivateKeyTip": "The private key does not exist, it may have been deleted or there is a configuration error.",
@@ -136,8 +170,8 @@
"plugInType": "Insertion Type", "plugInType": "Insertion Type",
"port": "Port", "port": "Port",
"preferDiskAmount": "Prioritize displaying disk capacity", "preferDiskAmount": "Prioritize displaying disk capacity",
"preview": "Preview",
"privateKey": "Private Key", "privateKey": "Private Key",
"privateKeyNotFoundFmt": "Private key [{keyId}] not found.",
"process": "Process", "process": "Process",
"prune": "Prune", "prune": "Prune",
"pushToken": "Push token", "pushToken": "Push token",
@@ -146,6 +180,7 @@
"pveVersionLow": "This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.", "pveVersionLow": "This feature is currently in the testing phase and has only been tested on PVE 8+. Please use it with caution.",
"read": "Read", "read": "Read",
"reboot": "Reboot", "reboot": "Reboot",
"recentConnections": "Recent Connections",
"rememberPwdInMem": "Remember password in memory", "rememberPwdInMem": "Remember password in memory",
"rememberPwdInMemTip": "Used for containers, suspending, etc.", "rememberPwdInMemTip": "Used for containers, suspending, etc.",
"rememberWindowSize": "Remember window size", "rememberWindowSize": "Remember window size",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Detail page widget order", "serverDetailOrder": "Detail page widget order",
"serverFuncBtns": "Server function buttons", "serverFuncBtns": "Server function buttons",
"serverOrder": "Server order", "serverOrder": "Server order",
"serverTabRequired": "Server tab cannot be removed",
"servers": "servers",
"sftpDlPrepare": "Preparing to connect...", "sftpDlPrepare": "Preparing to connect...",
"sftpEditorTip": "If empty, use the built-in file editor of the app. If a value is present, use the remote servers editor, e.g., `vim` (recommended to automatically detect according to `EDITOR`).", "sftpEditorTip": "If empty, use the built-in file editor of the app. If a value is present, use the remote servers editor, e.g., `vim` (recommended to automatically detect according to `EDITOR`).",
"sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.", "sftpRmrDirSummary": "Use `rm -r` to delete a folder in SFTP.",
@@ -189,6 +226,15 @@
"sshConfigImportPermission": "Would you like to give permission to read ~/.ssh/config and automatically import server settings?", "sshConfigImportPermission": "Would you like to give permission to read ~/.ssh/config and automatically import server settings?",
"sshConfigImportTip": "Prompt to read ~/.ssh/config on first server creation", "sshConfigImportTip": "Prompt to read ~/.ssh/config on first server creation",
"sshConfigImported": "Imported {count} servers from SSH config", "sshConfigImported": "Imported {count} servers from SSH config",
"sshHostKeyChangedDesc": "The SSH host key changed for {serverName}. Only continue if you trust this server.",
"sshHostKeyFingerprintMd5Base64": "Fingerprint (MD5 base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Fingerprint (MD5 hex): {fingerprint}",
"sshHostKeyType": "SSH host key type",
"@sshHostKeyType": {
"description": "Label for the SSH host key type displayed in the host key verification dialog."
},
"sshHostKeyNewDesc": "A new SSH host key was received from {serverName}. Review the fingerprint before trusting.",
"sshHostKeyStoredFingerprint": "Stored fingerprint: {fingerprint}",
"sshConfigManualSelect": "Would you like to select the SSH config file manually?", "sshConfigManualSelect": "Would you like to select the SSH config file manually?",
"sshConfigNoServers": "No servers found in SSH config", "sshConfigNoServers": "No servers found in SSH config",
"sshConfigPermissionDenied": "Cannot access SSH config file due to macOS permissions.", "sshConfigPermissionDenied": "Cannot access SSH config file due to macOS permissions.",
@@ -206,10 +252,10 @@
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "The suspend function requires root permission and systemd support.", "suspendTip": "The suspend function requires root permission and systemd support.",
"switchTo": "Switch to {val}", "switchTo": "Switch to {val}",
"sync": "Sync",
"syncTip": "A restart may be required for some changes to take effect.", "syncTip": "A restart may be required for some changes to take effect.",
"system": "System", "system": "System",
"tag": "Tags", "tag": "Tags",
"tapToStartDiscovery": "Tap the search button to discover SSH servers on your network",
"temperature": "Temperature", "temperature": "Temperature",
"termFontSizeTip": "This setting will affect the terminal size (width and height). You can zoom in on the terminal page to adjust the font size of the current session.", "termFontSizeTip": "This setting will affect the terminal size (width and height). You can zoom in on the terminal page to adjust the font size of the current session.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +266,7 @@
"time": "Time", "time": "Time",
"times": "Times", "times": "Times",
"total": "Total", "total": "Total",
"totalAttempts": "Total",
"traffic": "Traffic", "traffic": "Traffic",
"trySudo": "Try using sudo", "trySudo": "Try using sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +275,6 @@
"update": "Update", "update": "Update",
"updateIntervalEqual0": "You set to 0, will not update automatically.\nCan't calculate CPU status.", "updateIntervalEqual0": "You set to 0, will not update automatically.\nCan't calculate CPU status.",
"updateServerStatusInterval": "Server status update interval", "updateServerStatusInterval": "Server status update interval",
"upload": "Upload",
"upsideDown": "Upside Down", "upsideDown": "Upside Down",
"uptime": "Uptime", "uptime": "Uptime",
"useCdn": "Using CDN", "useCdn": "Using CDN",
@@ -237,6 +283,7 @@
"usePodmanByDefault": "Use Podman by default", "usePodmanByDefault": "Use Podman by default",
"used": "Used", "used": "Used",
"view": "View", "view": "View",
"viewDetails": "View Details",
"viewErr": "See error", "viewErr": "See error",
"virtKeyHelpClipboard": "Copy to the clipboard if the selected terminal is not empty, otherwise paste the content of the clipboard to the terminal.", "virtKeyHelpClipboard": "Copy to the clipboard if the selected terminal is not empty, otherwise paste the content of the clipboard to the terminal.",
"virtKeyHelpIME": "Turn on/off the keyboard", "virtKeyHelpIME": "Turn on/off the keyboard",
@@ -249,39 +296,5 @@
"wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.", "wolTip": "After configuring WOL (Wake-on-LAN), a WOL request is sent each time the server is connected.",
"write": "Write", "write": "Write",
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.", "writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.",
"writeScriptTip": "After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.", "writeScriptTip": "After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content."
"connectionStats": "Connection Statistics",
"connectionStatsDesc": "View server connection success rate and history",
"noConnectionStatsData": "No connection statistics data",
"totalAttempts": "Total",
"lastSuccess": "Last Success",
"lastFailure": "Last Failure",
"recentConnections": "Recent Connections",
"viewDetails": "View Details",
"connectionDetails": "Connection Details",
"clearThisServerStats": "Clear This Server Statistics",
"clearAllStatsTitle": "Clear All Statistics",
"clearAllStatsContent": "Are you sure you want to clear all server connection statistics? This action cannot be undone.",
"clearServerStatsTitle": "Clear {serverName} Statistics",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Are you sure you want to clear connection statistics for server \"{serverName}\"? This action cannot be undone.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Home Tabs",
"homeTabsCustomizeDesc": "Customize which tabs appear on the home page and their order",
"reset": "Reset",
"availableTabs": "Available Tabs",
"atLeastOneTab": "At least one tab must be selected",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Añadido a la lista de tareas", "added2List": "Añadido a la lista de tareas",
"addr": "Dirección", "addr": "Dirección",
"alreadyLastDir": "Ya estás en el directorio superior", "alreadyLastDir": "Ya estás en el directorio superior",
"askAi": "Preguntar a la IA",
"askAiApiKey": "Clave API",
"askAiAwaitingResponse": "Esperando la respuesta de la IA...",
"askAiBaseUrl": "URL base",
"askAiCommandInserted": "Comando insertado en el terminal",
"askAiConfigMissing": "Configura {fields} en Ajustes.",
"askAiConfirmExecute": "Confirmar antes de ejecutar",
"askAiConversation": "Conversación con la IA",
"askAiDisclaimer": "La IA puede equivocarse. Úsala con precaución.",
"askAiFollowUpHint": "Haz una pregunta adicional...",
"askAiInsertTerminal": "Insertar en el terminal",
"askAiModel": "Modelo",
"askAiNoResponse": "Sin respuesta",
"askAiRecommendedCommand": "Comando sugerido por la IA",
"askAiSelectedContent": "Contenido seleccionado",
"askAiUsageHint": "Usado en el terminal SSH",
"atLeastOneTab": "Al menos una pestaña debe estar seleccionada",
"authFailTip": "La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.", "authFailTip": "La autenticación ha fallado, por favor verifica si la contraseña/llave/host/usuario, etc., son incorrectos.",
"autoBackupConflict": "Solo se puede activar una copia de seguridad automática a la vez", "autoBackupConflict": "Solo se puede activar una copia de seguridad automática a la vez",
"autoConnect": "Conexión automática", "autoConnect": "Conexión automática",
"autoRun": "Ejecución automática", "autoRun": "Ejecución automática",
"autoUpdateHomeWidget": "Actualizar automáticamente el widget del escritorio", "autoUpdateHomeWidget": "Actualizar automáticamente el widget del escritorio",
"availableTabs": "Pestañas disponibles",
"backupEncrypted": "El respaldo está encriptado", "backupEncrypted": "El respaldo está encriptado",
"backupNotEncrypted": "El respaldo no está encriptado", "backupNotEncrypted": "El respaldo no está encriptado",
"backupPassword": "Contraseña de respaldo", "backupPassword": "Contraseña de respaldo",
@@ -23,10 +41,18 @@
"battery": "Batería", "battery": "Batería",
"bgRun": "Ejecución en segundo plano", "bgRun": "Ejecución en segundo plano",
"bgRunTip": "Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.", "bgRunTip": "Este interruptor solo indica que la aplicación intentará correr en segundo plano, si puede hacerlo o no depende de si tiene el permiso correspondiente. En Android puro, por favor desactiva la “optimización de batería” para esta app, en MIUI por favor cambia la estrategia de ahorro de energía a “Sin restricciones”.",
"clearAllStatsContent": "¿Estás seguro de que quieres limpiar todas las estadísticas de conexión del servidor? Esta acción no se puede deshacer.",
"clearAllStatsTitle": "Limpiar todas las estadísticas",
"clearServerStatsContent": "¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"{serverName}\"? Esta acción no se puede deshacer.",
"clearServerStatsTitle": "Limpiar estadísticas de {serverName}",
"clearThisServerStats": "Limpiar estadísticas de este servidor",
"closeAfterSave": "Guardar y cerrar", "closeAfterSave": "Guardar y cerrar",
"cmd": "Comando", "cmd": "Comando",
"collapseUITip": "¿Colapsar por defecto las listas largas en la UI?", "collapseUITip": "¿Colapsar por defecto las listas largas en la UI?",
"conn": "Conectar", "conn": "Conectar",
"connectionDetails": "Detalles de conexión",
"connectionStats": "Estadísticas de conexión",
"connectionStatsDesc": "Ver la tasa de éxito de conexión del servidor e historial",
"container": "Contenedor", "container": "Contenedor",
"containerTrySudoTip": "Por ejemplo: si configuras el usuario dentro de la app como aaa, pero Docker está instalado bajo el usuario root, entonces necesitarás habilitar esta opción", "containerTrySudoTip": "Por ejemplo: si configuras el usuario dentro de la app como aaa, pero Docker está instalado bajo el usuario root, entonces necesitarás habilitar esta opción",
"convert": "Convertir", "convert": "Convertir",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Comando utilizado para abrir el emulador de terminal al iniciar sesiones SSH.", "desktopTerminalTip": "Comando utilizado para abrir el emulador de terminal al iniciar sesiones SSH.",
"dirEmpty": "Asegúrate de que el directorio esté vacío", "dirEmpty": "Asegúrate de que el directorio esté vacío",
"disconnected": "Desconectado", "disconnected": "Desconectado",
"discoverSshServers": "Descubrir servidores SSH",
"discoveryFailed": "Falló el descubrimiento",
"discoverySettings": "Configuración de descubrimiento",
"discoverySummary": "Resumen del descubrimiento",
"disk": "Disco", "disk": "Disco",
"diskHealth": "Salud del disco", "diskHealth": "Salud del disco",
"diskIgnorePath": "Rutas de disco ignoradas", "diskIgnorePath": "Rutas de disco ignoradas",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Modo de doble columna", "doubleColumnMode": "Modo de doble columna",
"doubleColumnTip": "Esta opción solo habilita la función, si se puede activar o no depende del ancho del dispositivo", "doubleColumnTip": "Esta opción solo habilita la función, si se puede activar o no depende del ancho del dispositivo",
"editVirtKeys": "Editar teclas virtuales", "editVirtKeys": "Editar teclas virtuales",
"editor": "Editor",
"editorHighlightTip": "El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.", "editorHighlightTip": "El rendimiento del resaltado de código es bastante pobre actualmente, puedes elegir desactivarlo para mejorar.",
"emulator": "Emulador", "emulator": "Emulador",
"enableMdns": "Habilitar mDNS",
"enableMdnsDesc": "Usar mDNS/Bonjour para descubrir servicios SSH",
"encode": "Codificar", "encode": "Codificar",
"envVars": "Variable de entorno", "envVars": "Variable de entorno",
"experimentalFeature": "Función experimental", "experimentalFeature": "Función experimental",
@@ -67,8 +98,8 @@
"fgService": "Servicio en primer plano", "fgService": "Servicio en primer plano",
"fgServiceTip": "Después de activarlo, algunos modelos de dispositivos pueden bloquearse. Desactivarlo puede hacer que algunos modelos no puedan mantener las conexiones SSH en segundo plano. Por favor, permita los permisos de notificación de ServerBox, la ejecución en segundo plano y el auto-despertar en la configuración del sistema.", "fgServiceTip": "Después de activarlo, algunos modelos de dispositivos pueden bloquearse. Desactivarlo puede hacer que algunos modelos no puedan mantener las conexiones SSH en segundo plano. Por favor, permita los permisos de notificación de ServerBox, la ejecución en segundo plano y el auto-despertar en la configuración del sistema.",
"fileTooLarge": "El archivo '{file}' es demasiado grande '{size}', supera el {sizeMax}", "fileTooLarge": "El archivo '{file}' es demasiado grande '{size}', supera el {sizeMax}",
"finishedAt": "Terminado en",
"followSystem": "Seguir al sistema", "followSystem": "Seguir al sistema",
"font": "Fuente",
"fontSize": "Tamaño de fuente", "fontSize": "Tamaño de fuente",
"force": "Forzar", "force": "Forzar",
"fullScreen": "Modo pantalla completa", "fullScreen": "Modo pantalla completa",
@@ -79,13 +110,14 @@
"goto": "Ir a", "goto": "Ir a",
"hideTitleBar": "Ocultar barra de título", "hideTitleBar": "Ocultar barra de título",
"highlight": "Resaltar código", "highlight": "Resaltar código",
"homeTabs": "Pestañas de inicio",
"homeTabsCustomizeDesc": "Personaliza qué pestañas aparecen en la página de inicio y su orden",
"homeWidgetUrlConfig": "Configuración de URL del widget de inicio", "homeWidgetUrlConfig": "Configuración de URL del widget de inicio",
"host": "Anfitrión", "host": "Anfitrión",
"httpFailedWithCode": "Fallo en la solicitud, código de estado: {code}", "httpFailedWithCode": "Fallo en la solicitud, código de estado: {code}",
"ignoreCert": "Ignorar certificado", "ignoreCert": "Ignorar certificado",
"image": "Imagen", "image": "Imagen",
"imagesList": "Lista de imágenes", "imagesList": "Lista de imágenes",
"init": "Inicializar",
"inner": "Interno", "inner": "Interno",
"install": "Instalar", "install": "Instalar",
"installDockerWithUrl": "Por favor instala Docker primero desde https://docs.docker.com/engine/install", "installDockerWithUrl": "Por favor instala Docker primero desde https://docs.docker.com/engine/install",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Mantener el estado anterior del servidor", "keepStatusWhenErr": "Mantener el estado anterior del servidor",
"keepStatusWhenErrTip": "Solo aplica cuando hay errores al ejecutar scripts", "keepStatusWhenErrTip": "Solo aplica cuando hay errores al ejecutar scripts",
"keyAuth": "Autenticación con llave", "keyAuth": "Autenticación con llave",
"lastFailure": "Último fallo",
"lastSuccess": "Último éxito",
"letterCache": "Caché de letras", "letterCache": "Caché de letras",
"letterCacheTip": "Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.", "letterCacheTip": "Recomendado desactivar, pero después de desactivarlo, no se podrán ingresar caracteres CJK.",
"license": "Licencia de código abierto",
"location": "Ubicación", "location": "Ubicación",
"loss": "Tasa de pérdida", "loss": "Tasa de pérdida",
"madeWithLove": "Hecho con ❤️ por {myGithub}", "madeWithLove": "Hecho con ❤️ por {myGithub}",
"manual": "Manual",
"max": "Máximo", "max": "Máximo",
"maxConcurrency": "Concurrencia máxima",
"maxRetryCount": "Número máximo de reintentos de conexión al servidor", "maxRetryCount": "Número máximo de reintentos de conexión al servidor",
"maxRetryCountEqual0": "Reintentará infinitamente", "maxRetryCountEqual0": "Reintentará infinitamente",
"min": "Mínimo", "min": "Mínimo",
@@ -115,6 +148,7 @@
"net": "Red", "net": "Red",
"netViewType": "Tipo de vista de red", "netViewType": "Tipo de vista de red",
"newContainer": "Crear contenedor nuevo", "newContainer": "Crear contenedor nuevo",
"noConnectionStatsData": "No hay datos de estadísticas de conexión",
"noLineChart": "No utilice gráficos de líneas", "noLineChart": "No utilice gráficos de líneas",
"noLineChartForCpu": "No utilice gráficos lineales para la CPU", "noLineChartForCpu": "No utilice gráficos lineales para la CPU",
"noPrivateKeyTip": "La clave privada no existe, puede haber sido eliminada o hay un error de configuración.", "noPrivateKeyTip": "La clave privada no existe, puede haber sido eliminada o hay un error de configuración.",
@@ -136,8 +170,8 @@
"plugInType": "Tipo de inserción", "plugInType": "Tipo de inserción",
"port": "Puerto", "port": "Puerto",
"preferDiskAmount": "Priorizar la visualización de la capacidad del disco", "preferDiskAmount": "Priorizar la visualización de la capacidad del disco",
"preview": "Vista previa",
"privateKey": "Llave privada", "privateKey": "Llave privada",
"privateKeyNotFoundFmt": "No se encontró la clave privada [{keyId}].",
"process": "Proceso", "process": "Proceso",
"prune": "Podar", "prune": "Podar",
"pushToken": "Token de notificaciones", "pushToken": "Token de notificaciones",
@@ -146,6 +180,7 @@
"pveVersionLow": "Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.", "pveVersionLow": "Esta función está actualmente en fase de prueba y solo se ha probado en PVE 8+. Úsela con precaución.",
"read": "Leer", "read": "Leer",
"reboot": "Reiniciar", "reboot": "Reiniciar",
"recentConnections": "Conexiones recientes",
"rememberPwdInMem": "Recordar contraseña en la memoria", "rememberPwdInMem": "Recordar contraseña en la memoria",
"rememberPwdInMemTip": "Utilizado para contenedores, suspensión, etc.", "rememberPwdInMemTip": "Utilizado para contenedores, suspensión, etc.",
"rememberWindowSize": "Recordar el tamaño de la ventana", "rememberWindowSize": "Recordar el tamaño de la ventana",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Orden de los componentes en la página de detalles del servidor", "serverDetailOrder": "Orden de los componentes en la página de detalles del servidor",
"serverFuncBtns": "Botones de función del servidor", "serverFuncBtns": "Botones de función del servidor",
"serverOrder": "Orden del servidor", "serverOrder": "Orden del servidor",
"serverTabRequired": "La pestaña del servidor no se puede eliminar",
"servers": "servidores",
"sftpDlPrepare": "Preparando para conectar al servidor...", "sftpDlPrepare": "Preparando para conectar al servidor...",
"sftpEditorTip": "Si está vacío, use el editor de archivos incorporado de la aplicación. Si hay un valor, use el editor del servidor remoto, por ejemplo, `vim` (se recomienda detectar automáticamente según `EDITOR`).", "sftpEditorTip": "Si está vacío, use el editor de archivos incorporado de la aplicación. Si hay un valor, use el editor del servidor remoto, por ejemplo, `vim` (se recomienda detectar automáticamente según `EDITOR`).",
"sftpRmrDirSummary": "Usar `rm -r` en SFTP para eliminar directorios", "sftpRmrDirSummary": "Usar `rm -r` en SFTP para eliminar directorios",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "¿Te gustaría dar permiso para leer ~/.ssh/config e importar automáticamente la configuración de servidores?", "sshConfigImportPermission": "¿Te gustaría dar permiso para leer ~/.ssh/config e importar automáticamente la configuración de servidores?",
"sshConfigImportTip": "Sugerencia para leer ~/.ssh/config al crear el primer servidor", "sshConfigImportTip": "Sugerencia para leer ~/.ssh/config al crear el primer servidor",
"sshConfigImported": "Se importaron {count} servidores desde la configuración SSH", "sshConfigImported": "Se importaron {count} servidores desde la configuración SSH",
"sshHostKeyChangedDesc": "La clave de host SSH de {serverName} ha cambiado. Continúa solo si confías en este servidor.",
"sshHostKeyFingerprintMd5Base64": "Huella (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Huella (MD5 hex): {fingerprint}",
"sshHostKeyType": "Tipo de clave de host SSH",
"sshHostKeyNewDesc": "Se recibió una nueva clave de host SSH de {serverName}. Revisa la huella antes de confiar.",
"sshHostKeyStoredFingerprint": "Huella almacenada: {fingerprint}",
"sshConfigManualSelect": "¿Te gustaría seleccionar manualmente el archivo de configuración SSH?", "sshConfigManualSelect": "¿Te gustaría seleccionar manualmente el archivo de configuración SSH?",
"sshConfigNoServers": "No se encontraron servidores en la configuración SSH", "sshConfigNoServers": "No se encontraron servidores en la configuración SSH",
"sshConfigPermissionDenied": "No se puede acceder al archivo de configuración SSH debido a los permisos de macOS.", "sshConfigPermissionDenied": "No se puede acceder al archivo de configuración SSH debido a los permisos de macOS.",
@@ -206,10 +249,10 @@
"suspend": "Suspender", "suspend": "Suspender",
"suspendTip": "La función de suspender necesita permisos de root y soporte de systemd.", "suspendTip": "La función de suspender necesita permisos de root y soporte de systemd.",
"switchTo": "Cambiar a {val}", "switchTo": "Cambiar a {val}",
"sync": "Sincronizar",
"syncTip": "Puede que necesites reiniciar para que algunos cambios tengan efecto.", "syncTip": "Puede que necesites reiniciar para que algunos cambios tengan efecto.",
"system": "Sistema", "system": "Sistema",
"tag": "Etiqueta", "tag": "Etiqueta",
"tapToStartDiscovery": "Toca el botón de búsqueda para descubrir servidores SSH en tu red",
"temperature": "Temperatura", "temperature": "Temperatura",
"termFontSizeTip": "Este ajuste afectará el tamaño del terminal (ancho y alto). Puedes hacer zoom en la página del terminal para ajustar el tamaño de fuente de la sesión actual.", "termFontSizeTip": "Este ajuste afectará el tamaño del terminal (ancho y alto). Puedes hacer zoom en la página del terminal para ajustar el tamaño de fuente de la sesión actual.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Tiempo", "time": "Tiempo",
"times": "Veces", "times": "Veces",
"total": "Total", "total": "Total",
"totalAttempts": "Total",
"traffic": "Tráfico", "traffic": "Tráfico",
"trySudo": "Intentar con sudo", "trySudo": "Intentar con sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Actualizar", "update": "Actualizar",
"updateIntervalEqual0": "Si configuras esto a 0, el estado del servidor no se refrescará automáticamente.\nY no se podrá calcular el uso de CPU.", "updateIntervalEqual0": "Si configuras esto a 0, el estado del servidor no se refrescará automáticamente.\nY no se podrá calcular el uso de CPU.",
"updateServerStatusInterval": "Intervalo de actualización del estado del servidor", "updateServerStatusInterval": "Intervalo de actualización del estado del servidor",
"upload": "Subir",
"upsideDown": "Invertir arriba por abajo", "upsideDown": "Invertir arriba por abajo",
"uptime": "Tiempo de actividad", "uptime": "Tiempo de actividad",
"useCdn": "Usando CDN", "useCdn": "Usando CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Usar Podman por defecto", "usePodmanByDefault": "Usar Podman por defecto",
"used": "Usado", "used": "Usado",
"view": "Vista", "view": "Vista",
"viewDetails": "Ver detalles",
"viewErr": "Ver error", "viewErr": "Ver error",
"virtKeyHelpClipboard": "Si el terminal tiene caracteres seleccionados, entonces copiará los caracteres seleccionados al portapapeles, de lo contrario, pegará el contenido del portapapeles al terminal.", "virtKeyHelpClipboard": "Si el terminal tiene caracteres seleccionados, entonces copiará los caracteres seleccionados al portapapeles, de lo contrario, pegará el contenido del portapapeles al terminal.",
"virtKeyHelpIME": "Encender/apagar el teclado", "virtKeyHelpIME": "Encender/apagar el teclado",
@@ -249,39 +293,5 @@
"wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.", "wolTip": "Después de configurar WOL (Wake-on-LAN), se envía una solicitud de WOL cada vez que se conecta el servidor.",
"write": "Escribir", "write": "Escribir",
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.", "writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.",
"writeScriptTip": "Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.", "writeScriptTip": "Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script."
"connectionStats": "Estadísticas de conexión",
"connectionStatsDesc": "Ver la tasa de éxito de conexión del servidor e historial",
"noConnectionStatsData": "No hay datos de estadísticas de conexión",
"totalAttempts": "Total",
"lastSuccess": "Último éxito",
"lastFailure": "Último fallo",
"recentConnections": "Conexiones recientes",
"viewDetails": "Ver detalles",
"connectionDetails": "Detalles de conexión",
"clearThisServerStats": "Limpiar estadísticas de este servidor",
"clearAllStatsTitle": "Limpiar todas las estadísticas",
"clearAllStatsContent": "¿Estás seguro de que quieres limpiar todas las estadísticas de conexión del servidor? Esta acción no se puede deshacer.",
"clearServerStatsTitle": "Limpiar estadísticas de {serverName}",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "¿Estás seguro de que quieres limpiar las estadísticas de conexión del servidor \"{serverName}\"? Esta acción no se puede deshacer.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Pestañas de inicio",
"homeTabsCustomizeDesc": "Personaliza qué pestañas aparecen en la página de inicio y su orden",
"reset": "Restablecer",
"availableTabs": "Pestañas disponibles",
"atLeastOneTab": "Al menos una pestaña debe estar seleccionada",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Ajouté à la liste des tâches", "added2List": "Ajouté à la liste des tâches",
"addr": "Adresse", "addr": "Adresse",
"alreadyLastDir": "Déjà dans le dernier répertoire.", "alreadyLastDir": "Déjà dans le dernier répertoire.",
"askAi": "Demander à l'IA",
"askAiApiKey": "Clé API",
"askAiAwaitingResponse": "En attente de la réponse de l'IA...",
"askAiBaseUrl": "URL de base",
"askAiCommandInserted": "Commande insérée dans le terminal",
"askAiConfigMissing": "Veuillez configurer {fields} dans les paramètres.",
"askAiConfirmExecute": "Confirmer avant d'exécuter",
"askAiConversation": "Conversation avec l'IA",
"askAiDisclaimer": "L'IA peut se tromper. Utilisez-la avec prudence.",
"askAiFollowUpHint": "Poser une question supplémentaire...",
"askAiInsertTerminal": "Insérer dans le terminal",
"askAiModel": "Modèle",
"askAiNoResponse": "Aucune réponse",
"askAiRecommendedCommand": "Commande suggérée par l'IA",
"askAiSelectedContent": "Contenu sélectionné",
"askAiUsageHint": "Utilisé dans le terminal SSH",
"atLeastOneTab": "Au moins un onglet doit être sélectionné",
"authFailTip": "Échec de l'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.", "authFailTip": "Échec de l'authentification. Veuillez vérifier si le mot de passe/clé/hôte/utilisateur, etc., est incorrect.",
"autoBackupConflict": "Un seul sauvegarde automatique peut être activé en même temps.", "autoBackupConflict": "Un seul sauvegarde automatique peut être activé en même temps.",
"autoConnect": "Connexion automatique", "autoConnect": "Connexion automatique",
"autoRun": "Exécution automatique", "autoRun": "Exécution automatique",
"autoUpdateHomeWidget": "Mise à jour automatique du widget d'accueil", "autoUpdateHomeWidget": "Mise à jour automatique du widget d'accueil",
"availableTabs": "Onglets disponibles",
"backupEncrypted": "La sauvegarde est chiffrée", "backupEncrypted": "La sauvegarde est chiffrée",
"backupNotEncrypted": "La sauvegarde n'est pas chiffrée", "backupNotEncrypted": "La sauvegarde n'est pas chiffrée",
"backupPassword": "Mot de passe de sauvegarde", "backupPassword": "Mot de passe de sauvegarde",
@@ -23,10 +41,18 @@
"battery": "Batterie", "battery": "Batterie",
"bgRun": "Exécution en arrière-plan", "bgRun": "Exécution en arrière-plan",
"bgRunTip": "Cette option signifie seulement que le programme essaiera de s'exécuter en arrière-plan, que cela soit possible dépend de l'autorisation activée ou non. Pour Android natif, veuillez désactiver l'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d'économie d'énergie en « Illimité ».", "bgRunTip": "Cette option signifie seulement que le programme essaiera de s'exécuter en arrière-plan, que cela soit possible dépend de l'autorisation activée ou non. Pour Android natif, veuillez désactiver l'« Optimisation de la batterie » dans cette application, et pour MIUI, veuillez changer la politique d'économie d'énergie en « Illimité ».",
"clearAllStatsContent": "Êtes-vous sûr de vouloir effacer toutes les statistiques de connexion des serveurs ? Cette action ne peut pas être annulée.",
"clearAllStatsTitle": "Effacer toutes les statistiques",
"clearServerStatsContent": "Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"{serverName}\" ? Cette action ne peut pas être annulée.",
"clearServerStatsTitle": "Effacer les statistiques de {serverName}",
"clearThisServerStats": "Effacer les statistiques de ce serveur",
"closeAfterSave": "Enregistrer et fermer", "closeAfterSave": "Enregistrer et fermer",
"cmd": "Commande", "cmd": "Commande",
"collapseUITip": "Indique si les longues listes présentées dans l'interface utilisateur doivent être réduites par défaut.", "collapseUITip": "Indique si les longues listes présentées dans l'interface utilisateur doivent être réduites par défaut.",
"conn": "Connexion", "conn": "Connexion",
"connectionDetails": "Détails de connexion",
"connectionStats": "Statistiques de connexion",
"connectionStatsDesc": "Voir le taux de réussite de connexion du serveur et l'historique",
"container": "Conteneur", "container": "Conteneur",
"containerTrySudoTip": "Par exemple : Dans l'application, l'utilisateur est défini comme aaa, mais Docker est installé sous l'utilisateur root. Dans ce cas, vous devez activer cette option.", "containerTrySudoTip": "Par exemple : Dans l'application, l'utilisateur est défini comme aaa, mais Docker est installé sous l'utilisateur root. Dans ce cas, vous devez activer cette option.",
"convert": "Convertir", "convert": "Convertir",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Commande utilisée pour ouvrir lémulateur de terminal lors du lancement de sessions SSH.", "desktopTerminalTip": "Commande utilisée pour ouvrir lémulateur de terminal lors du lancement de sessions SSH.",
"dirEmpty": "Assurez-vous que le répertoire est vide.", "dirEmpty": "Assurez-vous que le répertoire est vide.",
"disconnected": "Déconnecté", "disconnected": "Déconnecté",
"discoverSshServers": "Découvrir les serveurs SSH",
"discoveryFailed": "Échec de la découverte",
"discoverySettings": "Paramètres de découverte",
"discoverySummary": "Résumé de la découverte",
"disk": "Disque", "disk": "Disque",
"diskHealth": "Santé du disque", "diskHealth": "Santé du disque",
"diskIgnorePath": "Chemin à ignorer pour le disque", "diskIgnorePath": "Chemin à ignorer pour le disque",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Mode double colonne", "doubleColumnMode": "Mode double colonne",
"doubleColumnTip": "Cette option n'active que la fonctionnalité, qu'elle puisse être activée dépend de la largeur de l'appareil.", "doubleColumnTip": "Cette option n'active que la fonctionnalité, qu'elle puisse être activée dépend de la largeur de l'appareil.",
"editVirtKeys": "Modifier les touches virtuelles", "editVirtKeys": "Modifier les touches virtuelles",
"editor": "Éditeur",
"editorHighlightTip": "La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s'améliorer.", "editorHighlightTip": "La performance actuelle de mise en surbrillance du code est pire et peut être désactivée en option pour s'améliorer.",
"emulator": "Émulateur", "emulator": "Émulateur",
"enableMdns": "Activer mDNS",
"enableMdnsDesc": "Utiliser mDNS/Bonjour pour découvrir les services SSH",
"encode": "Encoder", "encode": "Encoder",
"envVars": "Variable denvironnement", "envVars": "Variable denvironnement",
"experimentalFeature": "Fonctionnalité expérimentale", "experimentalFeature": "Fonctionnalité expérimentale",
@@ -67,8 +98,8 @@
"fgService": "Service de premier plan", "fgService": "Service de premier plan",
"fgServiceTip": "Après l'activation, certains modèles d'appareils peuvent planter. La désactivation peut empêcher certains modèles de maintenir les connexions SSH en arrière-plan. Veuillez autoriser les permissions de notification ServerBox, l'exécution en arrière-plan et l'auto-réveil dans les paramètres système.", "fgServiceTip": "Après l'activation, certains modèles d'appareils peuvent planter. La désactivation peut empêcher certains modèles de maintenir les connexions SSH en arrière-plan. Veuillez autoriser les permissions de notification ServerBox, l'exécution en arrière-plan et l'auto-réveil dans les paramètres système.",
"fileTooLarge": "Fichier '{file}' trop volumineux {size}, max {sizeMax}", "fileTooLarge": "Fichier '{file}' trop volumineux {size}, max {sizeMax}",
"finishedAt": "Terminé à",
"followSystem": "Suivre le système", "followSystem": "Suivre le système",
"font": "Police",
"fontSize": "Taille de la police", "fontSize": "Taille de la police",
"force": "Forcer", "force": "Forcer",
"fullScreen": "Mode plein écran", "fullScreen": "Mode plein écran",
@@ -79,13 +110,14 @@
"goto": "Aller à", "goto": "Aller à",
"hideTitleBar": "Masquer la barre de titre", "hideTitleBar": "Masquer la barre de titre",
"highlight": "Mise en surbrillance du code", "highlight": "Mise en surbrillance du code",
"homeTabs": "Onglets d'accueil",
"homeTabsCustomizeDesc": "Personnalisez les onglets qui apparaissent sur la page d'accueil et leur ordre",
"homeWidgetUrlConfig": "Configurer l'URL du widget d'accueil", "homeWidgetUrlConfig": "Configurer l'URL du widget d'accueil",
"host": "Hôte", "host": "Hôte",
"httpFailedWithCode": "Échec de la requête, code d'état : {code}", "httpFailedWithCode": "Échec de la requête, code d'état : {code}",
"ignoreCert": "Ignorer le certificat", "ignoreCert": "Ignorer le certificat",
"image": "Image", "image": "Image",
"imagesList": "Liste des images", "imagesList": "Liste des images",
"init": "Initialiser",
"inner": "Interne", "inner": "Interne",
"install": "Installer", "install": "Installer",
"installDockerWithUrl": "Veuillez d'abord installer docker depuis https://docs.docker.com/engine/install.", "installDockerWithUrl": "Veuillez d'abord installer docker depuis https://docs.docker.com/engine/install.",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Conserver l'état du dernier serveur", "keepStatusWhenErr": "Conserver l'état du dernier serveur",
"keepStatusWhenErrTip": "Uniquement en cas d'erreur lors de l'exécution du script", "keepStatusWhenErrTip": "Uniquement en cas d'erreur lors de l'exécution du script",
"keyAuth": "Authentification par clé", "keyAuth": "Authentification par clé",
"lastFailure": "Dernier échec",
"lastSuccess": "Dernier succès",
"letterCache": "Mise en cache des lettres", "letterCache": "Mise en cache des lettres",
"letterCacheTip": "Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.", "letterCacheTip": "Recommandé de désactiver, mais après désactivation, il sera impossible de saisir des caractères CJK.",
"license": "Licence",
"location": "Emplacement", "location": "Emplacement",
"loss": "Perte", "loss": "Perte",
"madeWithLove": "Fabriqué avec ❤️ par {myGithub}", "madeWithLove": "Fabriqué avec ❤️ par {myGithub}",
"manual": "Manuel",
"max": "max", "max": "max",
"maxConcurrency": "Concurrence maximale",
"maxRetryCount": "Nombre de reconnexions au serveur", "maxRetryCount": "Nombre de reconnexions au serveur",
"maxRetryCountEqual0": "Il va réessayer encore et encore.", "maxRetryCountEqual0": "Il va réessayer encore et encore.",
"min": "min", "min": "min",
@@ -115,6 +148,7 @@
"net": "Réseau", "net": "Réseau",
"netViewType": "Type de vue réseau", "netViewType": "Type de vue réseau",
"newContainer": "Nouveau conteneur", "newContainer": "Nouveau conteneur",
"noConnectionStatsData": "Aucune donnée de statistiques de connexion",
"noLineChart": "Ne pas utiliser de graphiques linéaires", "noLineChart": "Ne pas utiliser de graphiques linéaires",
"noLineChartForCpu": "Ne pas utiliser de graphiques linéaires pour l'unité centrale", "noLineChartForCpu": "Ne pas utiliser de graphiques linéaires pour l'unité centrale",
"noPrivateKeyTip": "La clé privée n'existe pas, elle a peut-être été supprimée ou il y a une erreur de configuration.", "noPrivateKeyTip": "La clé privée n'existe pas, elle a peut-être été supprimée ou il y a une erreur de configuration.",
@@ -136,8 +170,8 @@
"plugInType": "Type d'insertion", "plugInType": "Type d'insertion",
"port": "Port", "port": "Port",
"preferDiskAmount": "Prioriser laffichage de la capacité du disque", "preferDiskAmount": "Prioriser laffichage de la capacité du disque",
"preview": "Aperçu",
"privateKey": "Clé privée", "privateKey": "Clé privée",
"privateKeyNotFoundFmt": "Clé privée [{keyId}] introuvable.",
"process": "Processus", "process": "Processus",
"prune": "Élaguer", "prune": "Élaguer",
"pushToken": "Jeton d'identification", "pushToken": "Jeton d'identification",
@@ -146,6 +180,7 @@
"pveVersionLow": "Cette fonctionnalité est actuellement en phase de test et n'a été testée que sur PVE 8+. Veuillez l'utiliser avec prudence.", "pveVersionLow": "Cette fonctionnalité est actuellement en phase de test et n'a été testée que sur PVE 8+. Veuillez l'utiliser avec prudence.",
"read": "Lire", "read": "Lire",
"reboot": "Redémarrer", "reboot": "Redémarrer",
"recentConnections": "Connexions récentes",
"rememberPwdInMem": "Mémoriser le mot de passe en mémoire", "rememberPwdInMem": "Mémoriser le mot de passe en mémoire",
"rememberPwdInMemTip": "Utilisé pour les conteneurs, la suspension, etc.", "rememberPwdInMemTip": "Utilisé pour les conteneurs, la suspension, etc.",
"rememberWindowSize": "Se souvenir de la taille de la fenêtre", "rememberWindowSize": "Se souvenir de la taille de la fenêtre",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Ordre des widgets de la page de détails du serveur", "serverDetailOrder": "Ordre des widgets de la page de détails du serveur",
"serverFuncBtns": "Boutons de fonction du serveur", "serverFuncBtns": "Boutons de fonction du serveur",
"serverOrder": "Ordre du serveur", "serverOrder": "Ordre du serveur",
"serverTabRequired": "L'onglet serveur ne peut pas être supprimé",
"servers": "serveurs",
"sftpDlPrepare": "Préparation de la connexion...", "sftpDlPrepare": "Préparation de la connexion...",
"sftpEditorTip": "Si vide, utilisez léditeur de fichiers intégré de lapplication. Si une valeur est présente, utilisez léditeur du serveur distant, par exemple `vim` (il est recommandé de détecter automatiquement selon `EDITOR`).", "sftpEditorTip": "Si vide, utilisez léditeur de fichiers intégré de lapplication. Si une valeur est présente, utilisez léditeur du serveur distant, par exemple `vim` (il est recommandé de détecter automatiquement selon `EDITOR`).",
"sftpRmrDirSummary": "Utilisez `rm -r` pour supprimer un dossier en SFTP.", "sftpRmrDirSummary": "Utilisez `rm -r` pour supprimer un dossier en SFTP.",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Souhaitez-vous donner la permission de lire ~/.ssh/config et d'importer automatiquement les paramètres du serveur ?", "sshConfigImportPermission": "Souhaitez-vous donner la permission de lire ~/.ssh/config et d'importer automatiquement les paramètres du serveur ?",
"sshConfigImportTip": "Proposer de lire ~/.ssh/config lors de la première création de serveur", "sshConfigImportTip": "Proposer de lire ~/.ssh/config lors de la première création de serveur",
"sshConfigImported": "{count} serveurs importés depuis la configuration SSH", "sshConfigImported": "{count} serveurs importés depuis la configuration SSH",
"sshHostKeyChangedDesc": "La clé d'hôte SSH de {serverName} a changé. Ne continuez que si vous faites confiance à ce serveur.",
"sshHostKeyFingerprintMd5Base64": "Empreinte (MD5 Base64) : {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Empreinte (MD5 hex) : {fingerprint}",
"sshHostKeyType": "Type de clé d'hôte SSH",
"sshHostKeyNewDesc": "Une nouvelle clé d'hôte SSH a été reçue de {serverName}. Vérifiez l'empreinte avant de faire confiance.",
"sshHostKeyStoredFingerprint": "Empreinte enregistrée : {fingerprint}",
"sshConfigManualSelect": "Souhaitez-vous sélectionner manuellement le fichier de configuration SSH ?", "sshConfigManualSelect": "Souhaitez-vous sélectionner manuellement le fichier de configuration SSH ?",
"sshConfigNoServers": "Aucun serveur trouvé dans la configuration SSH", "sshConfigNoServers": "Aucun serveur trouvé dans la configuration SSH",
"sshConfigPermissionDenied": "Impossible d'accéder au fichier de configuration SSH en raison des permissions macOS.", "sshConfigPermissionDenied": "Impossible d'accéder au fichier de configuration SSH en raison des permissions macOS.",
@@ -206,10 +249,10 @@
"suspend": "Suspendre", "suspend": "Suspendre",
"suspendTip": "La fonction de suspension nécessite des privilèges root et le support de systemd.", "suspendTip": "La fonction de suspension nécessite des privilèges root et le support de systemd.",
"switchTo": "Passer à {val}", "switchTo": "Passer à {val}",
"sync": "Sync",
"syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.", "syncTip": "Un redémarrage peut être nécessaire pour que certains changements prennent effet.",
"system": "Système", "system": "Système",
"tag": "Étiquettes", "tag": "Étiquettes",
"tapToStartDiscovery": "Appuyez sur le bouton de recherche pour découvrir les serveurs SSH sur votre réseau",
"temperature": "Température", "temperature": "Température",
"termFontSizeTip": "Ce paramètre affectera la taille du terminal (largeur et hauteur). Vous pouvez zoomer sur la page du terminal pour ajuster la taille de la police de la session en cours.", "termFontSizeTip": "Ce paramètre affectera la taille du terminal (largeur et hauteur). Vous pouvez zoomer sur la page du terminal pour ajuster la taille de la police de la session en cours.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Temps", "time": "Temps",
"times": "Fois", "times": "Fois",
"total": "Total", "total": "Total",
"totalAttempts": "Total",
"traffic": "Trafic", "traffic": "Trafic",
"trySudo": "Essayer d'utiliser sudo", "trySudo": "Essayer d'utiliser sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Mettre à jour", "update": "Mettre à jour",
"updateIntervalEqual0": "Vous avez défini à 0, la mise à jour ne se fera pas automatiquement.\nImpossible de calculer l'état du CPU.", "updateIntervalEqual0": "Vous avez défini à 0, la mise à jour ne se fera pas automatiquement.\nImpossible de calculer l'état du CPU.",
"updateServerStatusInterval": "Intervalle de mise à jour de l'état du serveur", "updateServerStatusInterval": "Intervalle de mise à jour de l'état du serveur",
"upload": "Télécharger",
"upsideDown": "À l'envers", "upsideDown": "À l'envers",
"uptime": "Temps d'activité", "uptime": "Temps d'activité",
"useCdn": "Utiliser CDN", "useCdn": "Utiliser CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Par défaut avec Podman", "usePodmanByDefault": "Par défaut avec Podman",
"used": "Utilisé", "used": "Utilisé",
"view": "Vue", "view": "Vue",
"viewDetails": "Voir les détails",
"viewErr": "Voir erreur", "viewErr": "Voir erreur",
"virtKeyHelpClipboard": "Copiez dans le presse-papiers si le terminal sélectionné n'est pas vide, sinon collez le contenu du presse-papiers dans le terminal.", "virtKeyHelpClipboard": "Copiez dans le presse-papiers si le terminal sélectionné n'est pas vide, sinon collez le contenu du presse-papiers dans le terminal.",
"virtKeyHelpIME": "Activer/désactiver le clavier", "virtKeyHelpIME": "Activer/désactiver le clavier",
@@ -249,39 +293,5 @@
"wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.", "wolTip": "Après avoir configuré le WOL (Wake-on-LAN), une requête WOL est envoyée chaque fois que le serveur est connecté.",
"write": "Écrire", "write": "Écrire",
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.", "writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.",
"writeScriptTip": "Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l'état du système. Vous pouvez examiner le contenu du script.", "writeScriptTip": "Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l'état du système. Vous pouvez examiner le contenu du script."
"connectionStats": "Statistiques de connexion",
"connectionStatsDesc": "Voir le taux de réussite de connexion du serveur et l'historique",
"noConnectionStatsData": "Aucune donnée de statistiques de connexion",
"totalAttempts": "Total",
"lastSuccess": "Dernier succès",
"lastFailure": "Dernier échec",
"recentConnections": "Connexions récentes",
"viewDetails": "Voir les détails",
"connectionDetails": "Détails de connexion",
"clearThisServerStats": "Effacer les statistiques de ce serveur",
"clearAllStatsTitle": "Effacer toutes les statistiques",
"clearAllStatsContent": "Êtes-vous sûr de vouloir effacer toutes les statistiques de connexion des serveurs ? Cette action ne peut pas être annulée.",
"clearServerStatsTitle": "Effacer les statistiques de {serverName}",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Êtes-vous sûr de vouloir effacer les statistiques de connexion du serveur \"{serverName}\" ? Cette action ne peut pas être annulée.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Onglets d'accueil",
"homeTabsCustomizeDesc": "Personnalisez les onglets qui apparaissent sur la page d'accueil et leur ordre",
"reset": "Réinitialiser",
"availableTabs": "Onglets disponibles",
"atLeastOneTab": "Au moins un onglet doit être sélectionné",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Ditambahkan ke Daftar Tugas", "added2List": "Ditambahkan ke Daftar Tugas",
"addr": "Alamat", "addr": "Alamat",
"alreadyLastDir": "Sudah di direktori terakhir.", "alreadyLastDir": "Sudah di direktori terakhir.",
"askAi": "Tanya AI",
"askAiApiKey": "Kunci API",
"askAiAwaitingResponse": "Menunggu respons AI...",
"askAiBaseUrl": "URL dasar",
"askAiCommandInserted": "Perintah dimasukkan ke terminal",
"askAiConfigMissing": "Harap konfigurasikan {fields} di Pengaturan.",
"askAiConfirmExecute": "Konfirmasi sebelum menjalankan",
"askAiConversation": "Percakapan AI",
"askAiDisclaimer": "AI bisa saja salah. Gunakan dengan hati-hati.",
"askAiFollowUpHint": "Ajukan pertanyaan lanjutan...",
"askAiInsertTerminal": "Masukkan ke terminal",
"askAiModel": "Model",
"askAiNoResponse": "Tidak ada respons",
"askAiRecommendedCommand": "Perintah yang disarankan AI",
"askAiSelectedContent": "Konten yang dipilih",
"askAiUsageHint": "Digunakan di Terminal SSH",
"atLeastOneTab": "Setidaknya satu tab harus dipilih",
"authFailTip": "Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.", "authFailTip": "Otentikasi gagal, silakan periksa apakah kata sandi/kunci/host/pengguna, dll, salah.",
"autoBackupConflict": "Hanya satu pencadangan otomatis yang dapat diaktifkan pada saat yang bersamaan.", "autoBackupConflict": "Hanya satu pencadangan otomatis yang dapat diaktifkan pada saat yang bersamaan.",
"autoConnect": "Hubungkan otomatis", "autoConnect": "Hubungkan otomatis",
"autoRun": "Berjalan Otomatis", "autoRun": "Berjalan Otomatis",
"autoUpdateHomeWidget": "Widget Rumah Pembaruan Otomatis", "autoUpdateHomeWidget": "Widget Rumah Pembaruan Otomatis",
"availableTabs": "Tab Tersedia",
"backupEncrypted": "Cadangan telah dienkripsi", "backupEncrypted": "Cadangan telah dienkripsi",
"backupNotEncrypted": "Cadangan tidak dienkripsi", "backupNotEncrypted": "Cadangan tidak dienkripsi",
"backupPassword": "Kata sandi cadangan", "backupPassword": "Kata sandi cadangan",
@@ -23,10 +41,18 @@
"battery": "Baterai", "battery": "Baterai",
"bgRun": "Jalankan di Backgroud", "bgRun": "Jalankan di Backgroud",
"bgRunTip": "Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".", "bgRunTip": "Sakelar ini hanya berarti aplikasi akan mencoba berjalan di latar belakang, apakah aplikasi dapat berjalan di latar belakang tergantung pada apakah izin diaktifkan atau tidak. Untuk Android asli, nonaktifkan \"Pengoptimalan Baterai\" di aplikasi ini, dan untuk miui, ubah kebijakan penghematan daya ke \"Tidak Terbatas\".",
"clearAllStatsContent": "Apakah Anda yakin ingin menghapus semua statistik koneksi server? Tindakan ini tidak dapat dibatalkan.",
"clearAllStatsTitle": "Hapus Semua Statistik",
"clearServerStatsContent": "Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"{serverName}\"? Tindakan ini tidak dapat dibatalkan.",
"clearServerStatsTitle": "Hapus Statistik {serverName}",
"clearThisServerStats": "Hapus Statistik Server Ini",
"closeAfterSave": "Simpan dan tutup", "closeAfterSave": "Simpan dan tutup",
"cmd": "Memerintah", "cmd": "Memerintah",
"collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak", "collapseUITip": "Apakah akan menciutkan daftar panjang yang ada di UI secara default atau tidak",
"conn": "Koneksi", "conn": "Koneksi",
"connectionDetails": "Detail Koneksi",
"connectionStats": "Statistik Koneksi",
"connectionStatsDesc": "Lihat tingkat keberhasilan koneksi server dan riwayat",
"container": "Wadah", "container": "Wadah",
"containerTrySudoTip": "Contohnya: Di dalam aplikasi, pengguna diatur sebagai aaa, tetapi Docker diinstal di bawah pengguna root. Dalam kasus ini, Anda perlu mengaktifkan opsi ini.", "containerTrySudoTip": "Contohnya: Di dalam aplikasi, pengguna diatur sebagai aaa, tetapi Docker diinstal di bawah pengguna root. Dalam kasus ini, Anda perlu mengaktifkan opsi ini.",
"convert": "Mengubah", "convert": "Mengubah",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Perintah yang digunakan untuk membuka emulator terminal saat memulai sesi SSH.", "desktopTerminalTip": "Perintah yang digunakan untuk membuka emulator terminal saat memulai sesi SSH.",
"dirEmpty": "Pastikan dir kosong.", "dirEmpty": "Pastikan dir kosong.",
"disconnected": "Terputus", "disconnected": "Terputus",
"discoverSshServers": "Temukan Server SSH",
"discoveryFailed": "Penemuan gagal",
"discoverySettings": "Pengaturan Penemuan",
"discoverySummary": "Ringkasan Penemuan",
"disk": "Disk", "disk": "Disk",
"diskHealth": "Kesehatan disk", "diskHealth": "Kesehatan disk",
"diskIgnorePath": "Abaikan jalan untuk disk", "diskIgnorePath": "Abaikan jalan untuk disk",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Mode kolom ganda", "doubleColumnMode": "Mode kolom ganda",
"doubleColumnTip": "Opsi ini hanya mengaktifkan fitur, apakah itu benar-benar dapat diaktifkan tergantung pada lebar perangkat", "doubleColumnTip": "Opsi ini hanya mengaktifkan fitur, apakah itu benar-benar dapat diaktifkan tergantung pada lebar perangkat",
"editVirtKeys": "Edit kunci virtual", "editVirtKeys": "Edit kunci virtual",
"editor": "Editor",
"editorHighlightTip": "Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.", "editorHighlightTip": "Performa penyorotan kode saat ini lebih buruk, dan dapat dimatikan secara opsional untuk perbaikan.",
"emulator": "Emulator", "emulator": "Emulator",
"enableMdns": "Aktifkan mDNS",
"enableMdnsDesc": "Gunakan mDNS/Bonjour untuk menemukan layanan SSH",
"encode": "Menyandi", "encode": "Menyandi",
"envVars": "Variabel lingkungan", "envVars": "Variabel lingkungan",
"experimentalFeature": "Fitur eksperimental", "experimentalFeature": "Fitur eksperimental",
@@ -67,8 +98,8 @@
"fgService": "Layanan Latar Depan", "fgService": "Layanan Latar Depan",
"fgServiceTip": "Setelah diaktifkan, beberapa model perangkat mungkin crash. Menonaktifkannya dapat menyebabkan beberapa model tidak dapat mempertahankan koneksi SSH di latar belakang. Harap izinkan perizinan notifikasi ServerBox, menjalankan di latar belakang, dan bangun mandiri di pengaturan sistem.", "fgServiceTip": "Setelah diaktifkan, beberapa model perangkat mungkin crash. Menonaktifkannya dapat menyebabkan beberapa model tidak dapat mempertahankan koneksi SSH di latar belakang. Harap izinkan perizinan notifikasi ServerBox, menjalankan di latar belakang, dan bangun mandiri di pengaturan sistem.",
"fileTooLarge": "File '{file}' terlalu besar {size}, max {sizeMax}", "fileTooLarge": "File '{file}' terlalu besar {size}, max {sizeMax}",
"finishedAt": "Selesai pada",
"followSystem": "Ikuti sistem", "followSystem": "Ikuti sistem",
"font": "Font",
"fontSize": "Ukuran huruf", "fontSize": "Ukuran huruf",
"force": "sukarela", "force": "sukarela",
"fullScreen": "Mode Layar Penuh", "fullScreen": "Mode Layar Penuh",
@@ -79,13 +110,14 @@
"goto": "Pergi ke", "goto": "Pergi ke",
"hideTitleBar": "Sembunyikan bilah judul", "hideTitleBar": "Sembunyikan bilah judul",
"highlight": "Sorotan kode", "highlight": "Sorotan kode",
"homeTabs": "Tab Beranda",
"homeTabsCustomizeDesc": "Sesuaikan tab mana yang muncul di halaman beranda dan urutannya",
"homeWidgetUrlConfig": "Konfigurasi URL Widget Rumah", "homeWidgetUrlConfig": "Konfigurasi URL Widget Rumah",
"host": "Host", "host": "Host",
"httpFailedWithCode": "Permintaan gagal, kode status: {code}", "httpFailedWithCode": "Permintaan gagal, kode status: {code}",
"ignoreCert": "Abaikan sertifikat", "ignoreCert": "Abaikan sertifikat",
"image": "Gambar", "image": "Gambar",
"imagesList": "Daftar gambar", "imagesList": "Daftar gambar",
"init": "Menginisialisasi",
"inner": "Batin", "inner": "Batin",
"install": "Install", "install": "Install",
"installDockerWithUrl": "Silakan https://docs.docker.com/engine/install Docker pertama.", "installDockerWithUrl": "Silakan https://docs.docker.com/engine/install Docker pertama.",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Menyimpan status server terakhir", "keepStatusWhenErr": "Menyimpan status server terakhir",
"keepStatusWhenErrTip": "Hanya ketika terjadi kesalahan saat menjalankan skrip", "keepStatusWhenErrTip": "Hanya ketika terjadi kesalahan saat menjalankan skrip",
"keyAuth": "Auth kunci", "keyAuth": "Auth kunci",
"lastFailure": "Gagal Terakhir",
"lastSuccess": "Sukses Terakhir",
"letterCache": "Caching huruf", "letterCache": "Caching huruf",
"letterCacheTip": "Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.", "letterCacheTip": "Direkomendasikan untuk menonaktifkan, tetapi setelah dinonaktifkan, tidak mungkin untuk memasukkan karakter CJK.",
"license": "Lisensi",
"location": "Lokasi", "location": "Lokasi",
"loss": "kehilangan", "loss": "kehilangan",
"madeWithLove": "Dibuat dengan ❤️ oleh {myGithub}", "madeWithLove": "Dibuat dengan ❤️ oleh {myGithub}",
"manual": "Manual",
"max": "Max", "max": "Max",
"maxConcurrency": "Konkurensi Maksimum",
"maxRetryCount": "Jumlah penyambungan kembali server", "maxRetryCount": "Jumlah penyambungan kembali server",
"maxRetryCountEqual0": "Akan mencoba lagi lagi dan lagi.", "maxRetryCountEqual0": "Akan mencoba lagi lagi dan lagi.",
"min": "Min", "min": "Min",
@@ -115,6 +148,7 @@
"net": "Jaringan", "net": "Jaringan",
"netViewType": "Jenis tampilan bersih", "netViewType": "Jenis tampilan bersih",
"newContainer": "Wadah baru", "newContainer": "Wadah baru",
"noConnectionStatsData": "Tidak ada data statistik koneksi",
"noLineChart": "Jangan gunakan grafik garis", "noLineChart": "Jangan gunakan grafik garis",
"noLineChartForCpu": "Jangan gunakan diagram garis untuk CPU", "noLineChartForCpu": "Jangan gunakan diagram garis untuk CPU",
"noPrivateKeyTip": "Kunci privat tidak ada, mungkin telah dihapus atau ada kesalahan konfigurasi.", "noPrivateKeyTip": "Kunci privat tidak ada, mungkin telah dihapus atau ada kesalahan konfigurasi.",
@@ -136,8 +170,8 @@
"plugInType": "Jenis Penyisipan", "plugInType": "Jenis Penyisipan",
"port": "Port", "port": "Port",
"preferDiskAmount": "Prioritaskan tampilan kapasitas disk", "preferDiskAmount": "Prioritaskan tampilan kapasitas disk",
"preview": "Pratinjau",
"privateKey": "Kunci Pribadi", "privateKey": "Kunci Pribadi",
"privateKeyNotFoundFmt": "Kunci privat [{keyId}] tidak ditemukan.",
"process": "Proses", "process": "Proses",
"prune": "Pangkas", "prune": "Pangkas",
"pushToken": "Dorong token", "pushToken": "Dorong token",
@@ -146,6 +180,7 @@
"pveVersionLow": "Fitur ini saat ini sedang dalam tahap pengujian dan hanya diuji pada PVE 8+. Gunakan dengan hati-hati.", "pveVersionLow": "Fitur ini saat ini sedang dalam tahap pengujian dan hanya diuji pada PVE 8+. Gunakan dengan hati-hati.",
"read": "Baca", "read": "Baca",
"reboot": "Reboot", "reboot": "Reboot",
"recentConnections": "Koneksi Terkini",
"rememberPwdInMem": "Ingat kata sandi di dalam memori", "rememberPwdInMem": "Ingat kata sandi di dalam memori",
"rememberPwdInMemTip": "Digunakan untuk kontainer, menangguhkan, dll.", "rememberPwdInMemTip": "Digunakan untuk kontainer, menangguhkan, dll.",
"rememberWindowSize": "Ingat ukuran jendela", "rememberWindowSize": "Ingat ukuran jendela",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Detail pesanan widget halaman", "serverDetailOrder": "Detail pesanan widget halaman",
"serverFuncBtns": "Tombol fungsi server", "serverFuncBtns": "Tombol fungsi server",
"serverOrder": "Pesanan server", "serverOrder": "Pesanan server",
"serverTabRequired": "Tab server tidak dapat dihapus",
"servers": "server",
"sftpDlPrepare": "Bersiap untuk terhubung ...", "sftpDlPrepare": "Bersiap untuk terhubung ...",
"sftpEditorTip": "Jika kosong, gunakan editor file bawaan aplikasi. Jika ada nilai, gunakan editor server jarak jauh, misalnya `vim` (disarankan untuk mendeteksi secara otomatis sesuai `EDITOR`).", "sftpEditorTip": "Jika kosong, gunakan editor file bawaan aplikasi. Jika ada nilai, gunakan editor server jarak jauh, misalnya `vim` (disarankan untuk mendeteksi secara otomatis sesuai `EDITOR`).",
"sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP", "sftpRmrDirSummary": "Gunakan `rm -r` untuk menghapus dir di SFTP",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Apakah Anda ingin memberikan izin untuk membaca ~/.ssh/config dan secara otomatis mengimpor pengaturan server?", "sshConfigImportPermission": "Apakah Anda ingin memberikan izin untuk membaca ~/.ssh/config dan secara otomatis mengimpor pengaturan server?",
"sshConfigImportTip": "Prompt untuk membaca ~/.ssh/config saat pembuatan server pertama", "sshConfigImportTip": "Prompt untuk membaca ~/.ssh/config saat pembuatan server pertama",
"sshConfigImported": "Berhasil mengimpor {count} server dari konfigurasi SSH", "sshConfigImported": "Berhasil mengimpor {count} server dari konfigurasi SSH",
"sshHostKeyChangedDesc": "Kunci host SSH untuk {serverName} telah berubah. Lanjutkan hanya jika Anda mempercayai server ini.",
"sshHostKeyFingerprintMd5Base64": "Sidik jari (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Sidik jari (MD5 hex): {fingerprint}",
"sshHostKeyType": "Jenis kunci host SSH",
"sshHostKeyNewDesc": "Kunci host SSH baru diterima dari {serverName}. Periksa sidik jarinya sebelum mempercayai.",
"sshHostKeyStoredFingerprint": "Sidik jari tersimpan: {fingerprint}",
"sshConfigManualSelect": "Apakah Anda ingin memilih file konfigurasi SSH secara manual?", "sshConfigManualSelect": "Apakah Anda ingin memilih file konfigurasi SSH secara manual?",
"sshConfigNoServers": "Tidak ada server yang ditemukan dalam konfigurasi SSH", "sshConfigNoServers": "Tidak ada server yang ditemukan dalam konfigurasi SSH",
"sshConfigPermissionDenied": "Tidak dapat mengakses file konfigurasi SSH karena izin macOS.", "sshConfigPermissionDenied": "Tidak dapat mengakses file konfigurasi SSH karena izin macOS.",
@@ -206,10 +249,10 @@
"suspend": "Suspend", "suspend": "Suspend",
"suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.", "suspendTip": "Fungsi penangguhan memerlukan hak akses root dan dukungan systemd.",
"switchTo": "Beralih ke {val}", "switchTo": "Beralih ke {val}",
"sync": "Sinkronisasi",
"syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.", "syncTip": "Pengaktifan ulang mungkin diperlukan agar beberapa perubahan dapat diterapkan.",
"system": "Sistem", "system": "Sistem",
"tag": "Tag", "tag": "Tag",
"tapToStartDiscovery": "Tekan tombol pencarian untuk menemukan server SSH di jaringan Anda",
"temperature": "Suhu", "temperature": "Suhu",
"termFontSizeTip": "Pengaturan ini akan memengaruhi ukuran terminal (lebar dan tinggi). Anda dapat melakukan zoom pada halaman terminal untuk menyesuaikan ukuran font sesi saat ini.", "termFontSizeTip": "Pengaturan ini akan memengaruhi ukuran terminal (lebar dan tinggi). Anda dapat melakukan zoom pada halaman terminal untuk menyesuaikan ukuran font sesi saat ini.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Waktu", "time": "Waktu",
"times": "Waktu", "times": "Waktu",
"total": "Total", "total": "Total",
"totalAttempts": "Total",
"traffic": "Lalu lintas", "traffic": "Lalu lintas",
"trySudo": "Cobalah menggunakan sudo", "trySudo": "Cobalah menggunakan sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Memperbarui", "update": "Memperbarui",
"updateIntervalEqual0": "Anda mengatur ke 0, tidak akan memperbarui secara otomatis.\nTidak dapat menghitung status CPU.", "updateIntervalEqual0": "Anda mengatur ke 0, tidak akan memperbarui secara otomatis.\nTidak dapat menghitung status CPU.",
"updateServerStatusInterval": "Interval Pembaruan Status Server", "updateServerStatusInterval": "Interval Pembaruan Status Server",
"upload": "Mengunggah",
"upsideDown": "Terbalik", "upsideDown": "Terbalik",
"uptime": "Uptime", "uptime": "Uptime",
"useCdn": "Menggunakan CDN", "useCdn": "Menggunakan CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Menggunakan Podman sebagai bawaan", "usePodmanByDefault": "Menggunakan Podman sebagai bawaan",
"used": "Digunakan", "used": "Digunakan",
"view": "Tampilan", "view": "Tampilan",
"viewDetails": "Lihat Detail",
"viewErr": "Lihat kesalahan", "viewErr": "Lihat kesalahan",
"virtKeyHelpClipboard": "Salin ke clipboard jika terminal yang dipilih tidak kosong, jika tidak, tempel isi clipboard ke terminal.", "virtKeyHelpClipboard": "Salin ke clipboard jika terminal yang dipilih tidak kosong, jika tidak, tempel isi clipboard ke terminal.",
"virtKeyHelpIME": "Menyalakan/mematikan keyboard", "virtKeyHelpIME": "Menyalakan/mematikan keyboard",
@@ -249,39 +293,5 @@
"wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.", "wolTip": "Setelah mengonfigurasi WOL (Wake-on-LAN), permintaan WOL dikirim setiap kali server terhubung.",
"write": "Tulis", "write": "Tulis",
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.", "writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.",
"writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.", "writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut."
"connectionStats": "Statistik Koneksi",
"connectionStatsDesc": "Lihat tingkat keberhasilan koneksi server dan riwayat",
"noConnectionStatsData": "Tidak ada data statistik koneksi",
"totalAttempts": "Total",
"lastSuccess": "Sukses Terakhir",
"lastFailure": "Gagal Terakhir",
"recentConnections": "Koneksi Terkini",
"viewDetails": "Lihat Detail",
"connectionDetails": "Detail Koneksi",
"clearThisServerStats": "Hapus Statistik Server Ini",
"clearAllStatsTitle": "Hapus Semua Statistik",
"clearAllStatsContent": "Apakah Anda yakin ingin menghapus semua statistik koneksi server? Tindakan ini tidak dapat dibatalkan.",
"clearServerStatsTitle": "Hapus Statistik {serverName}",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Apakah Anda yakin ingin menghapus statistik koneksi untuk server \"{serverName}\"? Tindakan ini tidak dapat dibatalkan.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Tab Beranda",
"homeTabsCustomizeDesc": "Sesuaikan tab mana yang muncul di halaman beranda dan urutannya",
"reset": "Reset",
"availableTabs": "Tab Tersedia",
"atLeastOneTab": "Setidaknya satu tab harus dipilih",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "タスクリストに追加されました", "added2List": "タスクリストに追加されました",
"addr": "アドレス", "addr": "アドレス",
"alreadyLastDir": "すでに最上位のディレクトリです", "alreadyLastDir": "すでに最上位のディレクトリです",
"askAi": "AI に質問",
"askAiApiKey": "API キー",
"askAiAwaitingResponse": "AI の応答を待機中...",
"askAiBaseUrl": "ベース URL",
"askAiCommandInserted": "コマンドをターミナルに挿入しました",
"askAiConfigMissing": "設定で {fields} を構成してください。",
"askAiConfirmExecute": "実行前に確認",
"askAiConversation": "AI 会話",
"askAiDisclaimer": "AI が誤る可能性があります。注意してご利用ください。",
"askAiFollowUpHint": "追質問をする...",
"askAiInsertTerminal": "ターミナルに挿入",
"askAiModel": "モデル",
"askAiNoResponse": "応答なし",
"askAiRecommendedCommand": "AI 推奨コマンド",
"askAiSelectedContent": "選択した内容",
"askAiUsageHint": "SSH ターミナルで使用",
"atLeastOneTab": "少なくとも1つのタブを選択する必要があります",
"authFailTip": "認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。", "authFailTip": "認証に失敗しました。パスワード/鍵/ホスト/ユーザーなどが間違っていないか確認してください。",
"autoBackupConflict": "自動バックアップは一度に一つしか開始できません", "autoBackupConflict": "自動バックアップは一度に一つしか開始できません",
"autoConnect": "自動接続", "autoConnect": "自動接続",
"autoRun": "自動実行", "autoRun": "自動実行",
"autoUpdateHomeWidget": "ホームウィジェットを自動更新", "autoUpdateHomeWidget": "ホームウィジェットを自動更新",
"availableTabs": "利用可能なタブ",
"backupEncrypted": "バックアップは暗号化されています", "backupEncrypted": "バックアップは暗号化されています",
"backupNotEncrypted": "バックアップは暗号化されていません", "backupNotEncrypted": "バックアップは暗号化されていません",
"backupPassword": "バックアップパスワード", "backupPassword": "バックアップパスワード",
@@ -23,10 +41,18 @@
"battery": "バッテリー", "battery": "バッテリー",
"bgRun": "バックグラウンド実行", "bgRun": "バックグラウンド実行",
"bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。", "bgRunTip": "このスイッチはプログラムがバックグラウンドで実行を試みることを意味しますが、実際にバックグラウンドで実行できるかどうかは、権限が有効になっているかに依存します。AOSPベースのAndroid ROMでは、このアプリの「バッテリー最適化」をオフにしてください。MIUIでは、省エネモードを「無制限」に変更してください。",
"clearAllStatsContent": "すべてのサーバー接続統計を削除してもよろしいですか?この操作は元に戻せません。",
"clearAllStatsTitle": "すべての統計をクリア",
"clearServerStatsContent": "サーバー\"{serverName}\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。",
"clearServerStatsTitle": "{serverName}の統計をクリア",
"clearThisServerStats": "このサーバーの統計をクリア",
"closeAfterSave": "保存して閉じる", "closeAfterSave": "保存して閉じる",
"cmd": "コマンド", "cmd": "コマンド",
"collapseUITip": "UIの長いリストをデフォルトで折りたたむかどうか", "collapseUITip": "UIの長いリストをデフォルトで折りたたむかどうか",
"conn": "接続", "conn": "接続",
"connectionDetails": "接続の詳細",
"connectionStats": "接続統計",
"connectionStatsDesc": "サーバー接続成功率と履歴を表示",
"container": "コンテナ", "container": "コンテナ",
"containerTrySudoTip": "例アプリ内でユーザーをaaaに設定しているが、Dockerがrootユーザーでインストールされている場合、このオプションを有効にする必要があります", "containerTrySudoTip": "例アプリ内でユーザーをaaaに設定しているが、Dockerがrootユーザーでインストールされている場合、このオプションを有効にする必要があります",
"convert": "変換", "convert": "変換",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "SSHセッションを起動する際に使用されるターミナルエミュレーターを開くコマンド。", "desktopTerminalTip": "SSHセッションを起動する際に使用されるターミナルエミュレーターを開くコマンド。",
"dirEmpty": "フォルダーが空であることを確認してください", "dirEmpty": "フォルダーが空であることを確認してください",
"disconnected": "接続が切断されました", "disconnected": "接続が切断されました",
"discoverSshServers": "SSHサーバーの発見",
"discoveryFailed": "発見に失敗",
"discoverySettings": "発見設定",
"discoverySummary": "発見の概要",
"disk": "ディスク", "disk": "ディスク",
"diskHealth": "ディスクの健康状態", "diskHealth": "ディスクの健康状態",
"diskIgnorePath": "無視されたディスクパス", "diskIgnorePath": "無視されたディスクパス",
@@ -55,9 +85,10 @@
"doubleColumnMode": "ダブルカラムモード", "doubleColumnMode": "ダブルカラムモード",
"doubleColumnTip": "このオプションは機能を有効にするだけで、実際に有効にできるかどうかはデバイスの幅に依存します", "doubleColumnTip": "このオプションは機能を有効にするだけで、実際に有効にできるかどうかはデバイスの幅に依存します",
"editVirtKeys": "仮想キーを編集", "editVirtKeys": "仮想キーを編集",
"editor": "エディター",
"editorHighlightTip": "現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。", "editorHighlightTip": "現在のコードハイライトのパフォーマンスはかなり悪いため、改善するために無効にすることを選択できます。",
"emulator": "エミュレーター", "emulator": "エミュレーター",
"enableMdns": "mDNSを有効化",
"enableMdnsDesc": "mDNS/BonjourでSSHサービスを発見",
"encode": "エンコード", "encode": "エンコード",
"envVars": "環境変数", "envVars": "環境変数",
"experimentalFeature": "実験的な機能", "experimentalFeature": "実験的な機能",
@@ -67,8 +98,8 @@
"fgService": "フォアグラウンドサービス", "fgService": "フォアグラウンドサービス",
"fgServiceTip": "有効にすると、一部の機種でクラッシュする可能性があります。無効にすると、一部の機種でバックグラウンドでのSSH接続を維持できなくなる可能性があります。システム設定でServerBoxの通知権限、バックグラウンド実行、自己起動を許可してください。", "fgServiceTip": "有効にすると、一部の機種でクラッシュする可能性があります。無効にすると、一部の機種でバックグラウンドでのSSH接続を維持できなくなる可能性があります。システム設定でServerBoxの通知権限、バックグラウンド実行、自己起動を許可してください。",
"fileTooLarge": "ファイル '{file}' は大きすぎます '{size}'、{sizeMax} を超えています", "fileTooLarge": "ファイル '{file}' は大きすぎます '{size}'、{sizeMax} を超えています",
"finishedAt": "完了時刻",
"followSystem": "システムに従う", "followSystem": "システムに従う",
"font": "フォント",
"fontSize": "フォントサイズ", "fontSize": "フォントサイズ",
"force": "強制", "force": "強制",
"fullScreen": "フルスクリーンモード", "fullScreen": "フルスクリーンモード",
@@ -79,13 +110,14 @@
"goto": "移動", "goto": "移動",
"hideTitleBar": "タイトルバーを非表示にする", "hideTitleBar": "タイトルバーを非表示にする",
"highlight": "コードハイライト", "highlight": "コードハイライト",
"homeTabs": "ホームタブ",
"homeTabsCustomizeDesc": "ホームページに表示するタブとその順序をカスタマイズします",
"homeWidgetUrlConfig": "ホームウィジェットURL設定", "homeWidgetUrlConfig": "ホームウィジェットURL設定",
"host": "ホスト", "host": "ホスト",
"httpFailedWithCode": "リクエスト失敗、ステータスコード: {code}", "httpFailedWithCode": "リクエスト失敗、ステータスコード: {code}",
"ignoreCert": "証明書を無視する", "ignoreCert": "証明書を無視する",
"image": "イメージ", "image": "イメージ",
"imagesList": "イメージリスト", "imagesList": "イメージリスト",
"init": "初期化する",
"inner": "内蔵", "inner": "内蔵",
"install": "インストール", "install": "インストール",
"installDockerWithUrl": "最初に https://docs.docker.com/engine/install dockerをインストールしてください", "installDockerWithUrl": "最初に https://docs.docker.com/engine/install dockerをインストールしてください",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "エラー時に前回のサーバーステータスを保持", "keepStatusWhenErr": "エラー時に前回のサーバーステータスを保持",
"keepStatusWhenErrTip": "スクリプトの実行エラーに限ります", "keepStatusWhenErrTip": "スクリプトの実行エラーに限ります",
"keyAuth": "キー認証", "keyAuth": "キー認証",
"lastFailure": "最後の失敗",
"lastSuccess": "最後の成功",
"letterCache": "文字キャッシング", "letterCache": "文字キャッシング",
"letterCacheTip": "無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。", "letterCacheTip": "無効にすることを推奨しますが、無効にした後はCJK文字を入力することができなくなります。",
"license": "オープンソースライセンス",
"location": "場所", "location": "場所",
"loss": "パケットロス", "loss": "パケットロス",
"madeWithLove": "{myGithub}によって❤️で作成済み", "madeWithLove": "{myGithub}によって❤️で作成済み",
"manual": "マニュアル",
"max": "最大", "max": "最大",
"maxConcurrency": "最大同時実行数",
"maxRetryCount": "サーバーの再接続試行回数", "maxRetryCount": "サーバーの再接続試行回数",
"maxRetryCountEqual0": "無限に再試行します", "maxRetryCountEqual0": "無限に再試行します",
"min": "最小", "min": "最小",
@@ -115,6 +148,7 @@
"net": "ネットワーク", "net": "ネットワーク",
"netViewType": "ネットワークビュータイプ", "netViewType": "ネットワークビュータイプ",
"newContainer": "新しいコンテナを作成", "newContainer": "新しいコンテナを作成",
"noConnectionStatsData": "接続統計データがありません",
"noLineChart": "折れ線グラフを使用しない", "noLineChart": "折れ線グラフを使用しない",
"noLineChartForCpu": "CPUに折れ線グラフを使わない", "noLineChartForCpu": "CPUに折れ線グラフを使わない",
"noPrivateKeyTip": "秘密鍵が存在しません。削除されたか、設定ミスがある可能性があります。", "noPrivateKeyTip": "秘密鍵が存在しません。削除されたか、設定ミスがある可能性があります。",
@@ -136,8 +170,8 @@
"plugInType": "挿入タイプ", "plugInType": "挿入タイプ",
"port": "ポート", "port": "ポート",
"preferDiskAmount": "ディスク容量を優先的に表示", "preferDiskAmount": "ディスク容量を優先的に表示",
"preview": "プレビュー",
"privateKey": "秘密鍵", "privateKey": "秘密鍵",
"privateKeyNotFoundFmt": "秘密鍵 [{keyId}] が見つかりません。",
"process": "プロセス", "process": "プロセス",
"prune": "剪定する", "prune": "剪定する",
"pushToken": "プッシュトークン", "pushToken": "プッシュトークン",
@@ -146,6 +180,7 @@
"pveVersionLow": "この機能は現在テスト段階にあり、PVE 8+でのみテストされています。ご利用の際は慎重に。", "pveVersionLow": "この機能は現在テスト段階にあり、PVE 8+でのみテストされています。ご利用の際は慎重に。",
"read": "読み取り", "read": "読み取り",
"reboot": "再起動", "reboot": "再起動",
"recentConnections": "最近の接続",
"rememberPwdInMem": "メモリにパスワードを記憶する", "rememberPwdInMem": "メモリにパスワードを記憶する",
"rememberPwdInMemTip": "コンテナ、一時停止などに使用されます。", "rememberPwdInMemTip": "コンテナ、一時停止などに使用されます。",
"rememberWindowSize": "ウィンドウサイズを記憶する", "rememberWindowSize": "ウィンドウサイズを記憶する",
@@ -166,6 +201,8 @@
"serverDetailOrder": "詳細ページのウィジェット順序", "serverDetailOrder": "詳細ページのウィジェット順序",
"serverFuncBtns": "サーバー機能ボタン", "serverFuncBtns": "サーバー機能ボタン",
"serverOrder": "サーバー順序", "serverOrder": "サーバー順序",
"serverTabRequired": "サーバータブは削除できません",
"servers": "サーバー",
"sftpDlPrepare": "サーバーへの接続を準備中...", "sftpDlPrepare": "サーバーへの接続を準備中...",
"sftpEditorTip": "空の場合は、アプリ内蔵のファイルエディタを使用します。値がある場合は、リモートサーバーのエディタ(例:`vim`)を使用します(`EDITOR` に従って自動検出することをお勧めします)。", "sftpEditorTip": "空の場合は、アプリ内蔵のファイルエディタを使用します。値がある場合は、リモートサーバーのエディタ(例:`vim`)を使用します(`EDITOR` に従って自動検出することをお勧めします)。",
"sftpRmrDirSummary": "SFTPで`rm -r`を使用してフォルダーを削除", "sftpRmrDirSummary": "SFTPで`rm -r`を使用してフォルダーを削除",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "~/.ssh/configを読み取ってサーバー設定を自動的にインポートする権限を与えますか", "sshConfigImportPermission": "~/.ssh/configを読み取ってサーバー設定を自動的にインポートする権限を与えますか",
"sshConfigImportTip": "初回サーバー作成時に~/.ssh/configの読み取りを促す", "sshConfigImportTip": "初回サーバー作成時に~/.ssh/configの読み取りを促す",
"sshConfigImported": "SSH設定から{count}個のサーバーをインポートしました", "sshConfigImported": "SSH設定から{count}個のサーバーをインポートしました",
"sshHostKeyChangedDesc": "{serverName} の SSH ホスト鍵が変更されました。このサーバーを信頼できる場合のみ続行してください。",
"sshHostKeyFingerprintMd5Base64": "フィンガープリント (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "フィンガープリント (MD5 16進): {fingerprint}",
"sshHostKeyType": "SSH ホストキーの種類",
"sshHostKeyNewDesc": "{serverName} から新しい SSH ホスト鍵を受信しました。信頼する前にフィンガープリントを確認してください。",
"sshHostKeyStoredFingerprint": "保存済みフィンガープリント: {fingerprint}",
"sshConfigManualSelect": "SSH設定ファイルを手動で選択しますか", "sshConfigManualSelect": "SSH設定ファイルを手動で選択しますか",
"sshConfigNoServers": "SSH設定でサーバーが見つかりませんでした", "sshConfigNoServers": "SSH設定でサーバーが見つかりませんでした",
"sshConfigPermissionDenied": "macOSの権限により、SSH設定ファイルにアクセスできません。", "sshConfigPermissionDenied": "macOSの権限により、SSH設定ファイルにアクセスできません。",
@@ -206,10 +249,10 @@
"suspend": "中断", "suspend": "中断",
"suspendTip": "suspend機能はroot権限とsystemdのサポートが必要です。", "suspendTip": "suspend機能はroot権限とsystemdのサポートが必要です。",
"switchTo": "{val}に切り替える", "switchTo": "{val}に切り替える",
"sync": "同期する",
"syncTip": "再起動が必要な場合があります。一部の変更はその後に有効になります。", "syncTip": "再起動が必要な場合があります。一部の変更はその後に有効になります。",
"system": "システム", "system": "システム",
"tag": "タグ", "tag": "タグ",
"tapToStartDiscovery": "検索ボタンをタップしてネットワーク上のSSHサーバーを発見",
"temperature": "温度", "temperature": "温度",
"termFontSizeTip": "この設定は端末のサイズ(幅と高さ)に影響します。現在のセッションのフォントサイズを調整するために、端末ページを拡大縮小できます。", "termFontSizeTip": "この設定は端末のサイズ(幅と高さ)に影響します。現在のセッションのフォントサイズを調整するために、端末ページを拡大縮小できます。",
"terminal": "ターミナル", "terminal": "ターミナル",
@@ -220,6 +263,7 @@
"time": "時間", "time": "時間",
"times": "回", "times": "回",
"total": "合計", "total": "合計",
"totalAttempts": "総計",
"traffic": "トラフィック", "traffic": "トラフィック",
"trySudo": "sudoを試みる", "trySudo": "sudoを試みる",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "更新", "update": "更新",
"updateIntervalEqual0": "0に設定すると、サーバーの状態は自動的に更新されず、CPU使用率も計算できません。", "updateIntervalEqual0": "0に設定すると、サーバーの状態は自動的に更新されず、CPU使用率も計算できません。",
"updateServerStatusInterval": "サーバー状態の更新間隔", "updateServerStatusInterval": "サーバー状態の更新間隔",
"upload": "アップロード",
"upsideDown": "上下逆転", "upsideDown": "上下逆転",
"uptime": "稼働時間", "uptime": "稼働時間",
"useCdn": "CDNの使用", "useCdn": "CDNの使用",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "デフォルトでPodmanを使用", "usePodmanByDefault": "デフォルトでPodmanを使用",
"used": "使用済み", "used": "使用済み",
"view": "ビュー", "view": "ビュー",
"viewDetails": "詳細を表示",
"viewErr": "エラーを表示", "viewErr": "エラーを表示",
"virtKeyHelpClipboard": "端末に選択された文字がある場合は、選択された文字をクリップボードにコピーします。そうでない場合は、クリップボードの内容を端末に貼り付けます。", "virtKeyHelpClipboard": "端末に選択された文字がある場合は、選択された文字をクリップボードにコピーします。そうでない場合は、クリップボードの内容を端末に貼り付けます。",
"virtKeyHelpIME": "キーボードのオン/オフ", "virtKeyHelpIME": "キーボードのオン/オフ",
@@ -249,39 +293,5 @@
"wolTip": "WOLWake-on-LANを設定した後、サーバーに接続するたびにWOLリクエストが送信されます。", "wolTip": "WOLWake-on-LANを設定した後、サーバーに接続するたびにWOLリクエストが送信されます。",
"write": "書き込み", "write": "書き込み",
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。", "writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。", "writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。"
"connectionStats": "接続統計",
"connectionStatsDesc": "サーバー接続成功率と履歴を表示",
"noConnectionStatsData": "接続統計データがありません",
"totalAttempts": "総計",
"lastSuccess": "最後の成功",
"lastFailure": "最後の失敗",
"recentConnections": "最近の接続",
"viewDetails": "詳細を表示",
"connectionDetails": "接続の詳細",
"clearThisServerStats": "このサーバーの統計をクリア",
"clearAllStatsTitle": "すべての統計をクリア",
"clearAllStatsContent": "すべてのサーバー接続統計を削除してもよろしいですか?この操作は元に戻せません。",
"clearServerStatsTitle": "{serverName}の統計をクリア",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "サーバー\"{serverName}\"の接続統計を削除してもよろしいですか?この操作は元に戻せません。",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "ホームタブ",
"homeTabsCustomizeDesc": "ホームページに表示するタブとその順序をカスタマイズします",
"reset": "リセット",
"availableTabs": "利用可能なタブ",
"atLeastOneTab": "少なくとも1つのタブを選択する必要があります",
"serverTabRequired": "サーバータブは削除できません"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Toegevoegd aan takenlijst", "added2List": "Toegevoegd aan takenlijst",
"addr": "Adres", "addr": "Adres",
"alreadyLastDir": "Al in de laatst gebruikte map.", "alreadyLastDir": "Al in de laatst gebruikte map.",
"askAi": "AI vragen",
"askAiApiKey": "API-sleutel",
"askAiAwaitingResponse": "Wachten op AI-reactie...",
"askAiBaseUrl": "Basis-URL",
"askAiCommandInserted": "Commando in terminal ingevoegd",
"askAiConfigMissing": "Configureer {fields} in de instellingen.",
"askAiConfirmExecute": "Bevestigen voor uitvoeren",
"askAiConversation": "AI-gesprek",
"askAiDisclaimer": "AI kan fouten maken. Gebruik het zorgvuldig.",
"askAiFollowUpHint": "Stel een vervolgvraag...",
"askAiInsertTerminal": "In terminal invoegen",
"askAiModel": "Model",
"askAiNoResponse": "Geen reactie",
"askAiRecommendedCommand": "Door AI voorgestelde opdracht",
"askAiSelectedContent": "Geselecteerde inhoud",
"askAiUsageHint": "Gebruikt in de SSH-terminal",
"atLeastOneTab": "Er moet minimaal één tabblad worden geselecteerd",
"authFailTip": "Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.", "authFailTip": "Authenticatie mislukt, controleer of het wachtwoord/sleutel/host/gebruiker, enz., incorrect zijn.",
"autoBackupConflict": "Er kan slechts één automatische back-up tegelijk worden ingeschakeld.", "autoBackupConflict": "Er kan slechts één automatische back-up tegelijk worden ingeschakeld.",
"autoConnect": "Automatisch verbinden", "autoConnect": "Automatisch verbinden",
"autoRun": "Automatisch uitvoeren", "autoRun": "Automatisch uitvoeren",
"autoUpdateHomeWidget": "Automatische update van home-widget", "autoUpdateHomeWidget": "Automatische update van home-widget",
"availableTabs": "Beschikbare tabbladen",
"backupEncrypted": "Back-up is versleuteld", "backupEncrypted": "Back-up is versleuteld",
"backupNotEncrypted": "Back-up is niet versleuteld", "backupNotEncrypted": "Back-up is niet versleuteld",
"backupPassword": "Back-up wachtwoord", "backupPassword": "Back-up wachtwoord",
@@ -23,10 +41,18 @@
"battery": "Batterij", "battery": "Batterij",
"bgRun": "Uitvoeren op de achtergrond", "bgRun": "Uitvoeren op de achtergrond",
"bgRunTip": "Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".", "bgRunTip": "Deze schakelaar betekent alleen dat het programma zal proberen op de achtergrond uit te voeren, of het in de achtergrond kan worden uitgevoerd, hangt af van of de toestemming is ingeschakeld of niet. Voor native Android, schakel \"Batterijoptimalisatie\" uit in deze app, en voor miui, wijzig de energiebesparingsbeleid naar \"Onbeperkt\".",
"clearAllStatsContent": "Weet u zeker dat u alle serververbindingsstatistieken wilt wissen? Deze actie kan niet ongedaan worden gemaakt.",
"clearAllStatsTitle": "Alle statistieken wissen",
"clearServerStatsContent": "Weet u zeker dat u de verbindingsstatistieken voor server \"{serverName}\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.",
"clearServerStatsTitle": "Statistieken van {serverName} wissen",
"clearThisServerStats": "Statistieken van deze server wissen",
"closeAfterSave": "Opslaan en sluiten", "closeAfterSave": "Opslaan en sluiten",
"cmd": "Opdracht", "cmd": "Opdracht",
"collapseUITip": "Of lange lijsten in de UI standaard moeten worden ingeklapt", "collapseUITip": "Of lange lijsten in de UI standaard moeten worden ingeklapt",
"conn": "Verbinding", "conn": "Verbinding",
"connectionDetails": "Verbindingsdetails",
"connectionStats": "Verbindingsstatistieken",
"connectionStatsDesc": "Bekijk server verbindingssucces ratio en geschiedenis",
"container": "Container", "container": "Container",
"containerTrySudoTip": "Bijvoorbeeld: in de app is de gebruiker ingesteld op aaa, maar Docker is geïnstalleerd onder de rootgebruiker. In dit geval moet u deze optie inschakelen.", "containerTrySudoTip": "Bijvoorbeeld: in de app is de gebruiker ingesteld op aaa, maar Docker is geïnstalleerd onder de rootgebruiker. In dit geval moet u deze optie inschakelen.",
"convert": "Converteren", "convert": "Converteren",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Opdracht die wordt gebruikt om de terminalemulator te openen bij het starten van SSH-sessies.", "desktopTerminalTip": "Opdracht die wordt gebruikt om de terminalemulator te openen bij het starten van SSH-sessies.",
"dirEmpty": "Zorg ervoor dat de map leeg is.", "dirEmpty": "Zorg ervoor dat de map leeg is.",
"disconnected": "Verbroken", "disconnected": "Verbroken",
"discoverSshServers": "SSH-servers ontdekken",
"discoveryFailed": "Ontdekking mislukt",
"discoverySettings": "Ontdekkingsinstellingen",
"discoverySummary": "Ontdekkingssamenvatting",
"disk": "Schijf", "disk": "Schijf",
"diskHealth": "Schijfgezondheid", "diskHealth": "Schijfgezondheid",
"diskIgnorePath": "Pad negeren voor schijf", "diskIgnorePath": "Pad negeren voor schijf",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Dubbele kolommodus", "doubleColumnMode": "Dubbele kolommodus",
"doubleColumnTip": "Deze optie schakelt alleen de functie in, of deze daadwerkelijk kan worden ingeschakeld, hangt af van de breedte van het apparaat", "doubleColumnTip": "Deze optie schakelt alleen de functie in, of deze daadwerkelijk kan worden ingeschakeld, hangt af van de breedte van het apparaat",
"editVirtKeys": "Virtuele toetsen bewerken", "editVirtKeys": "Virtuele toetsen bewerken",
"editor": "Editor",
"editorHighlightTip": "De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.", "editorHighlightTip": "De huidige codehighlighting-prestaties zijn slechter en kunnen optioneel worden uitgeschakeld om te verbeteren.",
"emulator": "Emulator", "emulator": "Emulator",
"enableMdns": "mDNS inschakelen",
"enableMdnsDesc": "Gebruik mDNS/Bonjour om SSH-services te ontdekken",
"encode": "Coderen", "encode": "Coderen",
"envVars": "Omgevingsvariabele", "envVars": "Omgevingsvariabele",
"experimentalFeature": "Experimentele functie", "experimentalFeature": "Experimentele functie",
@@ -67,8 +98,8 @@
"fgService": "Voorgrondservice", "fgService": "Voorgrondservice",
"fgServiceTip": "Na het inschakelen kunnen sommige apparaatmodellen crashen. Uitschakelen kan ertoe leiden dat sommige modellen SSH-verbindingen niet op de achtergrond kunnen behouden. Sta ServerBox notificatierechten, achtergronduitvoering en zelf-ontwaken toe in systeeminstellingen.", "fgServiceTip": "Na het inschakelen kunnen sommige apparaatmodellen crashen. Uitschakelen kan ertoe leiden dat sommige modellen SSH-verbindingen niet op de achtergrond kunnen behouden. Sta ServerBox notificatierechten, achtergronduitvoering en zelf-ontwaken toe in systeeminstellingen.",
"fileTooLarge": "Bestand '{file}' te groot {size}, max {sizeMax}", "fileTooLarge": "Bestand '{file}' te groot {size}, max {sizeMax}",
"finishedAt": "Voltooid om",
"followSystem": "Volg systeem", "followSystem": "Volg systeem",
"font": "Lettertype",
"fontSize": "Lettergrootte", "fontSize": "Lettergrootte",
"force": "Forceer", "force": "Forceer",
"fullScreen": "Volledig schermmodus", "fullScreen": "Volledig schermmodus",
@@ -79,13 +110,14 @@
"goto": "Ga naar", "goto": "Ga naar",
"hideTitleBar": "Titelbalk verbergen", "hideTitleBar": "Titelbalk verbergen",
"highlight": "Code-highlight", "highlight": "Code-highlight",
"homeTabs": "Home-tabbladen",
"homeTabsCustomizeDesc": "Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde",
"homeWidgetUrlConfig": "Home-widget-url configureren", "homeWidgetUrlConfig": "Home-widget-url configureren",
"host": "Host", "host": "Host",
"httpFailedWithCode": "verzoek mislukt, statuscode: {code}", "httpFailedWithCode": "verzoek mislukt, statuscode: {code}",
"ignoreCert": "Certificaat negeren", "ignoreCert": "Certificaat negeren",
"image": "Afbeelding", "image": "Afbeelding",
"imagesList": "Lijst met afbeeldingen", "imagesList": "Lijst met afbeeldingen",
"init": "Initialiseren",
"inner": "Intern", "inner": "Intern",
"install": "Installeren", "install": "Installeren",
"installDockerWithUrl": "Installeer eerst docker via https://docs.docker.com/engine/install.", "installDockerWithUrl": "Installeer eerst docker via https://docs.docker.com/engine/install.",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Behoud de laatste serverstatus", "keepStatusWhenErr": "Behoud de laatste serverstatus",
"keepStatusWhenErrTip": "Alleen in geval van een fout tijdens de scriptuitvoering", "keepStatusWhenErrTip": "Alleen in geval van een fout tijdens de scriptuitvoering",
"keyAuth": "Sleutelauthenticatie", "keyAuth": "Sleutelauthenticatie",
"lastFailure": "Laatst gefaald",
"lastSuccess": "Laatst succesvol",
"letterCache": "Lettercaching", "letterCache": "Lettercaching",
"letterCacheTip": "Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.", "letterCacheTip": "Aanbevolen om uit te schakelen, maar na het uitschakelen is het niet mogelijk om CJK-tekens in te voeren.",
"license": "Licentie",
"location": "Locatie", "location": "Locatie",
"loss": "verlies", "loss": "verlies",
"madeWithLove": "Gemaakt met ❤️ door {myGithub}", "madeWithLove": "Gemaakt met ❤️ door {myGithub}",
"manual": "Handleiding",
"max": "max", "max": "max",
"maxConcurrency": "Maximale gelijktijdigheid",
"maxRetryCount": "Aantal serverherverbindingen", "maxRetryCount": "Aantal serverherverbindingen",
"maxRetryCountEqual0": "Zal opnieuw blijven proberen.", "maxRetryCountEqual0": "Zal opnieuw blijven proberen.",
"min": "min", "min": "min",
@@ -115,6 +148,7 @@
"net": "Netwerk", "net": "Netwerk",
"netViewType": "Netweergavetype", "netViewType": "Netweergavetype",
"newContainer": "Nieuwe container", "newContainer": "Nieuwe container",
"noConnectionStatsData": "Geen verbindingsstatistiekgegevens",
"noLineChart": "lijndiagrammen gebruiken", "noLineChart": "lijndiagrammen gebruiken",
"noLineChartForCpu": "Gebruik geen lijndiagrammen voor CPU", "noLineChartForCpu": "Gebruik geen lijndiagrammen voor CPU",
"noPrivateKeyTip": "De privésleutel bestaat niet, deze is mogelijk verwijderd of er is een configuratiefout.", "noPrivateKeyTip": "De privésleutel bestaat niet, deze is mogelijk verwijderd of er is een configuratiefout.",
@@ -136,8 +170,8 @@
"plugInType": "Invoegingstype", "plugInType": "Invoegingstype",
"port": "Poort", "port": "Poort",
"preferDiskAmount": "Geef de schijfcapaciteit prioriteit bij weergave", "preferDiskAmount": "Geef de schijfcapaciteit prioriteit bij weergave",
"preview": "Voorbeeld",
"privateKey": "Privésleutel", "privateKey": "Privésleutel",
"privateKeyNotFoundFmt": "Privésleutel [{keyId}] niet gevonden.",
"process": "Proces", "process": "Proces",
"prune": "Snoeien", "prune": "Snoeien",
"pushToken": "Push-token", "pushToken": "Push-token",
@@ -146,6 +180,7 @@
"pveVersionLow": "Deze functie bevindt zich momenteel in de testfase en is alleen getest op PVE 8+. Gebruik het met voorzichtigheid.", "pveVersionLow": "Deze functie bevindt zich momenteel in de testfase en is alleen getest op PVE 8+. Gebruik het met voorzichtigheid.",
"read": "Lezen", "read": "Lezen",
"reboot": "Herstart", "reboot": "Herstart",
"recentConnections": "Recente verbindingen",
"rememberPwdInMem": "Wachtwoord onthouden in geheugen", "rememberPwdInMem": "Wachtwoord onthouden in geheugen",
"rememberPwdInMemTip": "Gebruikt voor containers, opschorting, enz.", "rememberPwdInMemTip": "Gebruikt voor containers, opschorting, enz.",
"rememberWindowSize": "Venstergrootte onthouden", "rememberWindowSize": "Venstergrootte onthouden",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Volgorde van widget op detailpagina", "serverDetailOrder": "Volgorde van widget op detailpagina",
"serverFuncBtns": "Server functieknoppen", "serverFuncBtns": "Server functieknoppen",
"serverOrder": "Servervolgorde", "serverOrder": "Servervolgorde",
"serverTabRequired": "Servertabblad kan niet worden verwijderd",
"servers": "servers",
"sftpDlPrepare": "Voorbereiden om verbinding te maken...", "sftpDlPrepare": "Voorbereiden om verbinding te maken...",
"sftpEditorTip": "Indien leeg, gebruik de ingebouwde bestandseditor van de app. Indien een waarde aanwezig is, gebruik de editor van de externe server, bijvoorbeeld `vim` (aanbevolen om automatisch te detecteren volgens `EDITOR`).", "sftpEditorTip": "Indien leeg, gebruik de ingebouwde bestandseditor van de app. Indien een waarde aanwezig is, gebruik de editor van de externe server, bijvoorbeeld `vim` (aanbevolen om automatisch te detecteren volgens `EDITOR`).",
"sftpRmrDirSummary": "Gebruik `rm -r` om een map te verwijderen in SFTP.", "sftpRmrDirSummary": "Gebruik `rm -r` om een map te verwijderen in SFTP.",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Wilt u toestemming geven om ~/.ssh/config te lezen en automatisch serverinstellingen te importeren?", "sshConfigImportPermission": "Wilt u toestemming geven om ~/.ssh/config te lezen en automatisch serverinstellingen te importeren?",
"sshConfigImportTip": "Prompt om ~/.ssh/config te lezen bij het aanmaken van de eerste server", "sshConfigImportTip": "Prompt om ~/.ssh/config te lezen bij het aanmaken van de eerste server",
"sshConfigImported": "{count} servers geïmporteerd uit SSH-configuratie", "sshConfigImported": "{count} servers geïmporteerd uit SSH-configuratie",
"sshHostKeyChangedDesc": "De SSH-hostsleutel voor {serverName} is gewijzigd. Ga alleen verder als u deze server vertrouwt.",
"sshHostKeyFingerprintMd5Base64": "Vingerafdruk (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Vingerafdruk (MD5 hex): {fingerprint}",
"sshHostKeyType": "Type SSH-hostsleutel",
"sshHostKeyNewDesc": "Er is een nieuwe SSH-hostsleutel ontvangen van {serverName}. Controleer de vingerafdruk voordat u vertrouwt.",
"sshHostKeyStoredFingerprint": "Opgeslagen vingerafdruk: {fingerprint}",
"sshConfigManualSelect": "Wilt u het SSH-configuratiebestand handmatig selecteren?", "sshConfigManualSelect": "Wilt u het SSH-configuratiebestand handmatig selecteren?",
"sshConfigNoServers": "Geen servers gevonden in SSH-configuratie", "sshConfigNoServers": "Geen servers gevonden in SSH-configuratie",
"sshConfigPermissionDenied": "Kan geen toegang krijgen tot SSH-configuratiebestand vanwege macOS-rechten.", "sshConfigPermissionDenied": "Kan geen toegang krijgen tot SSH-configuratiebestand vanwege macOS-rechten.",
@@ -206,10 +249,10 @@
"suspend": "Ophangen", "suspend": "Ophangen",
"suspendTip": "De opschortfunctie vereist rootrechten en systemd-ondersteuning.", "suspendTip": "De opschortfunctie vereist rootrechten en systemd-ondersteuning.",
"switchTo": "Overschakelen naar {val}", "switchTo": "Overschakelen naar {val}",
"sync": "Sync",
"syncTip": "Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.", "syncTip": "Een herstart kan nodig zijn voor sommige wijzigingen om van kracht te worden.",
"system": "Systeem", "system": "Systeem",
"tag": "Labels", "tag": "Labels",
"tapToStartDiscovery": "Tik op de zoekknop om SSH-servers op uw netwerk te ontdekken",
"temperature": "Temperatuur", "temperature": "Temperatuur",
"termFontSizeTip": "Deze instelling heeft invloed op de terminalgrootte (breedte en hoogte). U kunt inzoomen op de terminalpagina om de lettergrootte van de huidige sessie aan te passen.", "termFontSizeTip": "Deze instelling heeft invloed op de terminalgrootte (breedte en hoogte). U kunt inzoomen op de terminalpagina om de lettergrootte van de huidige sessie aan te passen.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Tijd", "time": "Tijd",
"times": "Keer", "times": "Keer",
"total": "Totaal", "total": "Totaal",
"totalAttempts": "Totaal",
"traffic": "Verkeer", "traffic": "Verkeer",
"trySudo": "Probeer sudo te gebruiken", "trySudo": "Probeer sudo te gebruiken",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Bijwerken", "update": "Bijwerken",
"updateIntervalEqual0": "Het staat op 0, het zal niet automatisch bijwerken\nCPU status kan niet berekend worden.", "updateIntervalEqual0": "Het staat op 0, het zal niet automatisch bijwerken\nCPU status kan niet berekend worden.",
"updateServerStatusInterval": "Interne server status bijwerking interval", "updateServerStatusInterval": "Interne server status bijwerking interval",
"upload": "Upload",
"upsideDown": "Ondersteboven", "upsideDown": "Ondersteboven",
"uptime": "Uptime", "uptime": "Uptime",
"useCdn": "Gebruikt CDN", "useCdn": "Gebruikt CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Valt terug op Podman", "usePodmanByDefault": "Valt terug op Podman",
"used": "Gebruikt", "used": "Gebruikt",
"view": "Weergave", "view": "Weergave",
"viewDetails": "Details bekijken",
"viewErr": "Zie foutmelding", "viewErr": "Zie foutmelding",
"virtKeyHelpClipboard": "Kopiëren naar het klembord als de geselecteerde terminal niet leeg is, anders de inhoud van het klembord plakken in de terminal.", "virtKeyHelpClipboard": "Kopiëren naar het klembord als de geselecteerde terminal niet leeg is, anders de inhoud van het klembord plakken in de terminal.",
"virtKeyHelpIME": "Toetsenbord aan/uit zetten", "virtKeyHelpIME": "Toetsenbord aan/uit zetten",
@@ -249,39 +293,5 @@
"wolTip": "Na het configureren van WOL (Wake-on-LAN), wordt elke keer dat de server wordt verbonden een WOL-verzoek verzonden.", "wolTip": "Na het configureren van WOL (Wake-on-LAN), wordt elke keer dat de server wordt verbonden een WOL-verzoek verzonden.",
"write": "Schrijven", "write": "Schrijven",
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.", "writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.",
"writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.", "writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren."
"connectionStats": "Verbindingsstatistieken",
"connectionStatsDesc": "Bekijk server verbindingssucces ratio en geschiedenis",
"noConnectionStatsData": "Geen verbindingsstatistiekgegevens",
"totalAttempts": "Totaal",
"lastSuccess": "Laatst succesvol",
"lastFailure": "Laatst gefaald",
"recentConnections": "Recente verbindingen",
"viewDetails": "Details bekijken",
"connectionDetails": "Verbindingsdetails",
"clearThisServerStats": "Statistieken van deze server wissen",
"clearAllStatsTitle": "Alle statistieken wissen",
"clearAllStatsContent": "Weet u zeker dat u alle serververbindingsstatistieken wilt wissen? Deze actie kan niet ongedaan worden gemaakt.",
"clearServerStatsTitle": "Statistieken van {serverName} wissen",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Weet u zeker dat u de verbindingsstatistieken voor server \"{serverName}\" wilt wissen? Deze actie kan niet ongedaan worden gemaakt.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Home-tabbladen",
"homeTabsCustomizeDesc": "Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde",
"reset": "Resetten",
"availableTabs": "Beschikbare tabbladen",
"atLeastOneTab": "Er moet minimaal één tabblad worden geselecteerd",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Adicionado à lista de tarefas", "added2List": "Adicionado à lista de tarefas",
"addr": "Endereço", "addr": "Endereço",
"alreadyLastDir": "Já é o diretório mais alto", "alreadyLastDir": "Já é o diretório mais alto",
"askAi": "Perguntar à IA",
"askAiApiKey": "Chave de API",
"askAiAwaitingResponse": "Aguardando resposta da IA...",
"askAiBaseUrl": "URL base",
"askAiCommandInserted": "Comando inserido no terminal",
"askAiConfigMissing": "Configure {fields} nas configurações.",
"askAiConfirmExecute": "Confirmar antes de executar",
"askAiConversation": "Conversa com a IA",
"askAiDisclaimer": "A IA pode errar. Use com cautela.",
"askAiFollowUpHint": "Faça uma pergunta adicional...",
"askAiInsertTerminal": "Inserir no terminal",
"askAiModel": "Modelo",
"askAiNoResponse": "Sem resposta",
"askAiRecommendedCommand": "Comando sugerido pela IA",
"askAiSelectedContent": "Conteúdo selecionado",
"askAiUsageHint": "Usado no terminal SSH",
"atLeastOneTab": "Pelo menos uma aba deve ser selecionada",
"authFailTip": "Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.", "authFailTip": "Autenticação falhou, por favor verifique se a senha/chave/host/usuário, etc., estão incorretos.",
"autoBackupConflict": "Apenas um backup automático pode ser ativado por vez", "autoBackupConflict": "Apenas um backup automático pode ser ativado por vez",
"autoConnect": "Conexão automática", "autoConnect": "Conexão automática",
"autoRun": "Execução automática", "autoRun": "Execução automática",
"autoUpdateHomeWidget": "Atualização automática do widget da tela inicial", "autoUpdateHomeWidget": "Atualização automática do widget da tela inicial",
"availableTabs": "Abas disponíveis",
"backupEncrypted": "Backup está criptografado", "backupEncrypted": "Backup está criptografado",
"backupNotEncrypted": "Backup não está criptografado", "backupNotEncrypted": "Backup não está criptografado",
"backupPassword": "Senha de backup", "backupPassword": "Senha de backup",
@@ -23,10 +41,18 @@
"battery": "Bateria", "battery": "Bateria",
"bgRun": "Execução em segundo plano", "bgRun": "Execução em segundo plano",
"bgRunTip": "Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a 'Otimização de bateria' para este app, no MIUI, altere a estratégia de economia de energia para 'Sem restrições'.", "bgRunTip": "Este interruptor indica que o programa tentará rodar em segundo plano, mas a capacidade de fazer isso depende das permissões concedidas. No Android nativo, desative a 'Otimização de bateria' para este app, no MIUI, altere a estratégia de economia de energia para 'Sem restrições'.",
"clearAllStatsContent": "Tem certeza de que deseja limpar todas as estatísticas de conexão do servidor? Esta ação não pode ser desfeita.",
"clearAllStatsTitle": "Limpar todas as estatísticas",
"clearServerStatsContent": "Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"{serverName}\"? Esta ação não pode ser desfeita.",
"clearServerStatsTitle": "Limpar estatísticas de {serverName}",
"clearThisServerStats": "Limpar estatísticas deste servidor",
"closeAfterSave": "Salvar e fechar", "closeAfterSave": "Salvar e fechar",
"cmd": "Comando", "cmd": "Comando",
"collapseUITip": "Deve colapsar listas longas na UI por padrão?", "collapseUITip": "Deve colapsar listas longas na UI por padrão?",
"conn": "Conectar", "conn": "Conectar",
"connectionDetails": "Detalhes da conexão",
"connectionStats": "Estatísticas de conexão",
"connectionStatsDesc": "Ver taxa de sucesso de conexão do servidor e histórico",
"container": "Contêiner", "container": "Contêiner",
"containerTrySudoTip": "Por exemplo: se o usuário for definido como aaa dentro do app, mas o Docker estiver instalado sob o usuário root, esta opção precisará ser ativada", "containerTrySudoTip": "Por exemplo: se o usuário for definido como aaa dentro do app, mas o Docker estiver instalado sob o usuário root, esta opção precisará ser ativada",
"convert": "Converter", "convert": "Converter",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Comando usado para abrir o emulador de terminal ao iniciar sessões SSH.", "desktopTerminalTip": "Comando usado para abrir o emulador de terminal ao iniciar sessões SSH.",
"dirEmpty": "Certifique-se de que a pasta está vazia", "dirEmpty": "Certifique-se de que a pasta está vazia",
"disconnected": "Desconectado", "disconnected": "Desconectado",
"discoverSshServers": "Descobrir servidores SSH",
"discoveryFailed": "Descoberta falhou",
"discoverySettings": "Configurações de descoberta",
"discoverySummary": "Resumo da descoberta",
"disk": "Disco", "disk": "Disco",
"diskHealth": "Saúde do disco", "diskHealth": "Saúde do disco",
"diskIgnorePath": "Caminhos de disco ignorados", "diskIgnorePath": "Caminhos de disco ignorados",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Modo de coluna dupla", "doubleColumnMode": "Modo de coluna dupla",
"doubleColumnTip": "Esta opção apenas ativa a funcionalidade, se ela será ativada depende também da largura do dispositivo", "doubleColumnTip": "Esta opção apenas ativa a funcionalidade, se ela será ativada depende também da largura do dispositivo",
"editVirtKeys": "Editar teclas virtuais", "editVirtKeys": "Editar teclas virtuais",
"editor": "Editor",
"editorHighlightTip": "O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.", "editorHighlightTip": "O desempenho do destaque de código atualmente é ruim, pode optar por desativá-lo para melhorar.",
"emulator": "Emulador", "emulator": "Emulador",
"enableMdns": "Ativar mDNS",
"enableMdnsDesc": "Usar mDNS/Bonjour para descobrir serviços SSH",
"encode": "Codificar", "encode": "Codificar",
"envVars": "Variável de ambiente", "envVars": "Variável de ambiente",
"experimentalFeature": "Recurso experimental", "experimentalFeature": "Recurso experimental",
@@ -67,8 +98,8 @@
"fgService": "Serviço em primeiro plano", "fgService": "Serviço em primeiro plano",
"fgServiceTip": "Após ativar, alguns modelos de dispositivos podem travar. Desativar pode fazer com que alguns modelos não consigam manter conexões SSH em segundo plano. Por favor, permita as permissões de notificação do ServerBox, execução em segundo plano e auto-despertar nas configurações do sistema.", "fgServiceTip": "Após ativar, alguns modelos de dispositivos podem travar. Desativar pode fazer com que alguns modelos não consigam manter conexões SSH em segundo plano. Por favor, permita as permissões de notificação do ServerBox, execução em segundo plano e auto-despertar nas configurações do sistema.",
"fileTooLarge": "Arquivo '{file}' muito grande '{size}', excedendo {sizeMax}", "fileTooLarge": "Arquivo '{file}' muito grande '{size}', excedendo {sizeMax}",
"finishedAt": "Terminado em",
"followSystem": "Seguir sistema", "followSystem": "Seguir sistema",
"font": "Fonte",
"fontSize": "Tamanho da fonte", "fontSize": "Tamanho da fonte",
"force": "Forçar", "force": "Forçar",
"fullScreen": "Modo tela cheia", "fullScreen": "Modo tela cheia",
@@ -79,13 +110,14 @@
"goto": "Ir para", "goto": "Ir para",
"hideTitleBar": "Ocultar barra de título", "hideTitleBar": "Ocultar barra de título",
"highlight": "Destaque de código", "highlight": "Destaque de código",
"homeTabs": "Abas iniciais",
"homeTabsCustomizeDesc": "Personalize quais abas aparecem na página inicial e sua ordem",
"homeWidgetUrlConfig": "Configuração de URL do widget da tela inicial", "homeWidgetUrlConfig": "Configuração de URL do widget da tela inicial",
"host": "Host", "host": "Host",
"httpFailedWithCode": "Falha na solicitação, código de status: {code}", "httpFailedWithCode": "Falha na solicitação, código de status: {code}",
"ignoreCert": "Ignorar certificado", "ignoreCert": "Ignorar certificado",
"image": "Imagem", "image": "Imagem",
"imagesList": "Lista de imagens", "imagesList": "Lista de imagens",
"init": "Inicializar",
"inner": "Interno", "inner": "Interno",
"install": "Instalar", "install": "Instalar",
"installDockerWithUrl": "Por favor, instale o Docker primeiro em https://docs.docker.com/engine/install", "installDockerWithUrl": "Por favor, instale o Docker primeiro em https://docs.docker.com/engine/install",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Manter o status anterior do servidor", "keepStatusWhenErr": "Manter o status anterior do servidor",
"keepStatusWhenErrTip": "Limitado a erros de execução de scripts", "keepStatusWhenErrTip": "Limitado a erros de execução de scripts",
"keyAuth": "Autenticação por chave", "keyAuth": "Autenticação por chave",
"lastFailure": "Última falha",
"lastSuccess": "Último sucesso",
"letterCache": "Cache de letras", "letterCache": "Cache de letras",
"letterCacheTip": "Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.", "letterCacheTip": "Recomendado desativar, mas após desativar, será impossível inserir caracteres CJK.",
"license": "Licença de código aberto",
"location": "Localização", "location": "Localização",
"loss": "Taxa de perda", "loss": "Taxa de perda",
"madeWithLove": "Feito com ❤️ por {myGithub}", "madeWithLove": "Feito com ❤️ por {myGithub}",
"manual": "Manual",
"max": "Máximo", "max": "Máximo",
"maxConcurrency": "Concorrência máxima",
"maxRetryCount": "Número de tentativas de reconexão com o servidor", "maxRetryCount": "Número de tentativas de reconexão com o servidor",
"maxRetryCountEqual0": "Irá tentar indefinidamente", "maxRetryCountEqual0": "Irá tentar indefinidamente",
"min": "Mínimo", "min": "Mínimo",
@@ -115,6 +148,7 @@
"net": "Rede", "net": "Rede",
"netViewType": "Tipo de visualização de rede", "netViewType": "Tipo de visualização de rede",
"newContainer": "Novo contêiner", "newContainer": "Novo contêiner",
"noConnectionStatsData": "Não há dados de estatísticas de conexão",
"noLineChart": "Não usar gráficos de linha", "noLineChart": "Não usar gráficos de linha",
"noLineChartForCpu": "Não utilizar gráficos de linhas para a CPU", "noLineChartForCpu": "Não utilizar gráficos de linhas para a CPU",
"noPrivateKeyTip": "A chave privada não existe, pode ter sido deletada ou há um erro de configuração.", "noPrivateKeyTip": "A chave privada não existe, pode ter sido deletada ou há um erro de configuração.",
@@ -136,8 +170,8 @@
"plugInType": "Tipo de Inserção", "plugInType": "Tipo de Inserção",
"port": "Porta", "port": "Porta",
"preferDiskAmount": "Priorizar a exibição da capacidade do disco", "preferDiskAmount": "Priorizar a exibição da capacidade do disco",
"preview": "Pré-visualização",
"privateKey": "Chave privada", "privateKey": "Chave privada",
"privateKeyNotFoundFmt": "Chave privada [{keyId}] não encontrada.",
"process": "Processo", "process": "Processo",
"prune": "Podar", "prune": "Podar",
"pushToken": "Token de notificação push", "pushToken": "Token de notificação push",
@@ -146,6 +180,7 @@
"pveVersionLow": "Esta funcionalidade está atualmente em fase de teste e foi testada apenas no PVE 8+. Por favor, use com cautela.", "pveVersionLow": "Esta funcionalidade está atualmente em fase de teste e foi testada apenas no PVE 8+. Por favor, use com cautela.",
"read": "Leitura", "read": "Leitura",
"reboot": "Reiniciar", "reboot": "Reiniciar",
"recentConnections": "Conexões recentes",
"rememberPwdInMem": "Lembrar senha na memória", "rememberPwdInMem": "Lembrar senha na memória",
"rememberPwdInMemTip": "Usado para contêineres, suspensão, etc.", "rememberPwdInMemTip": "Usado para contêineres, suspensão, etc.",
"rememberWindowSize": "Lembrar o tamanho da janela", "rememberWindowSize": "Lembrar o tamanho da janela",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Ordem dos componentes na página de detalhes do servidor", "serverDetailOrder": "Ordem dos componentes na página de detalhes do servidor",
"serverFuncBtns": "Botões de função do servidor", "serverFuncBtns": "Botões de função do servidor",
"serverOrder": "Ordem do servidor", "serverOrder": "Ordem do servidor",
"serverTabRequired": "A aba do servidor não pode ser removida",
"servers": "servidores",
"sftpDlPrepare": "Preparando para conectar ao servidor...", "sftpDlPrepare": "Preparando para conectar ao servidor...",
"sftpEditorTip": "Se vazio, use o editor de arquivos integrado do aplicativo. Se houver um valor, use o editor do servidor remoto, por exemplo, `vim` (recomendado detectar automaticamente de acordo com `EDITOR`).", "sftpEditorTip": "Se vazio, use o editor de arquivos integrado do aplicativo. Se houver um valor, use o editor do servidor remoto, por exemplo, `vim` (recomendado detectar automaticamente de acordo com `EDITOR`).",
"sftpRmrDirSummary": "Usar `rm -r` em SFTP para excluir pastas", "sftpRmrDirSummary": "Usar `rm -r` em SFTP para excluir pastas",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Gostaria de dar permissão para ler ~/.ssh/config e importar automaticamente as configurações do servidor?", "sshConfigImportPermission": "Gostaria de dar permissão para ler ~/.ssh/config e importar automaticamente as configurações do servidor?",
"sshConfigImportTip": "Sugestão para ler ~/.ssh/config na criação do primeiro servidor", "sshConfigImportTip": "Sugestão para ler ~/.ssh/config na criação do primeiro servidor",
"sshConfigImported": "Importados {count} servidores da configuração SSH", "sshConfigImported": "Importados {count} servidores da configuração SSH",
"sshHostKeyChangedDesc": "A chave de host SSH de {serverName} foi alterada. Continue apenas se confiar neste servidor.",
"sshHostKeyFingerprintMd5Base64": "Impressão digital (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Impressão digital (MD5 hex): {fingerprint}",
"sshHostKeyType": "Tipo de chave de host SSH",
"sshHostKeyNewDesc": "Uma nova chave de host SSH foi recebida de {serverName}. Verifique a impressão digital antes de confiar.",
"sshHostKeyStoredFingerprint": "Impressão digital armazenada: {fingerprint}",
"sshConfigManualSelect": "Gostaria de selecionar manualmente o arquivo de configuração SSH?", "sshConfigManualSelect": "Gostaria de selecionar manualmente o arquivo de configuração SSH?",
"sshConfigNoServers": "Nenhum servidor encontrado na configuração SSH", "sshConfigNoServers": "Nenhum servidor encontrado na configuração SSH",
"sshConfigPermissionDenied": "Não é possível acessar o arquivo de configuração SSH devido às permissões do macOS.", "sshConfigPermissionDenied": "Não é possível acessar o arquivo de configuração SSH devido às permissões do macOS.",
@@ -206,10 +249,10 @@
"suspend": "Suspender", "suspend": "Suspender",
"suspendTip": "A função de suspensão requer permissões de root e suporte do systemd.", "suspendTip": "A função de suspensão requer permissões de root e suporte do systemd.",
"switchTo": "Mudar para {val}", "switchTo": "Mudar para {val}",
"sync": "Sincronizar",
"syncTip": "Pode ser necessário reiniciar para algumas mudanças surtirem efeito.", "syncTip": "Pode ser necessário reiniciar para algumas mudanças surtirem efeito.",
"system": "Sistema", "system": "Sistema",
"tag": "Tag", "tag": "Tag",
"tapToStartDiscovery": "Toque no botão de pesquisa para descobrir servidores SSH na sua rede",
"temperature": "Temperatura", "temperature": "Temperatura",
"termFontSizeTip": "Esta configuração afetará o tamanho do terminal (largura e altura). Você pode dar zoom na página do terminal para ajustar o tamanho da fonte da sessão atual.", "termFontSizeTip": "Esta configuração afetará o tamanho do terminal (largura e altura). Você pode dar zoom na página do terminal para ajustar o tamanho da fonte da sessão atual.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Tempo", "time": "Tempo",
"times": "Vezes", "times": "Vezes",
"total": "Total", "total": "Total",
"totalAttempts": "Total",
"traffic": "Tráfego", "traffic": "Tráfego",
"trySudo": "Tentar usar sudo", "trySudo": "Tentar usar sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Atualizar", "update": "Atualizar",
"updateIntervalEqual0": "Se definido como 0, o estado do servidor não será atualizado automaticamente.\nE o uso da CPU não poderá ser calculado.", "updateIntervalEqual0": "Se definido como 0, o estado do servidor não será atualizado automaticamente.\nE o uso da CPU não poderá ser calculado.",
"updateServerStatusInterval": "Intervalo de atualização do estado do servidor", "updateServerStatusInterval": "Intervalo de atualização do estado do servidor",
"upload": "Upload",
"upsideDown": "Inverter verticalmente", "upsideDown": "Inverter verticalmente",
"uptime": "Tempo de atividade", "uptime": "Tempo de atividade",
"useCdn": "Utilizando CDN", "useCdn": "Utilizando CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Usar Podman por padrão", "usePodmanByDefault": "Usar Podman por padrão",
"used": "Usado", "used": "Usado",
"view": "Visualização", "view": "Visualização",
"viewDetails": "Ver detalhes",
"viewErr": "Ver erro", "viewErr": "Ver erro",
"virtKeyHelpClipboard": "Se houver texto selecionado no terminal, copia para a área de transferência, caso contrário, cola o conteúdo da área de transferência no terminal.", "virtKeyHelpClipboard": "Se houver texto selecionado no terminal, copia para a área de transferência, caso contrário, cola o conteúdo da área de transferência no terminal.",
"virtKeyHelpIME": "Ligar/desligar o teclado", "virtKeyHelpIME": "Ligar/desligar o teclado",
@@ -249,39 +293,5 @@
"wolTip": "Após configurar o WOL (Wake-on-LAN), um pedido de WOL é enviado cada vez que o servidor é conectado.", "wolTip": "Após configurar o WOL (Wake-on-LAN), um pedido de WOL é enviado cada vez que o servidor é conectado.",
"write": "Escrita", "write": "Escrita",
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.", "writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.",
"writeScriptTip": "Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.", "writeScriptTip": "Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script."
"connectionStats": "Estatísticas de conexão",
"connectionStatsDesc": "Ver taxa de sucesso de conexão do servidor e histórico",
"noConnectionStatsData": "Não há dados de estatísticas de conexão",
"totalAttempts": "Total",
"lastSuccess": "Último sucesso",
"lastFailure": "Última falha",
"recentConnections": "Conexões recentes",
"viewDetails": "Ver detalhes",
"connectionDetails": "Detalhes da conexão",
"clearThisServerStats": "Limpar estatísticas deste servidor",
"clearAllStatsTitle": "Limpar todas as estatísticas",
"clearAllStatsContent": "Tem certeza de que deseja limpar todas as estatísticas de conexão do servidor? Esta ação não pode ser desfeita.",
"clearServerStatsTitle": "Limpar estatísticas de {serverName}",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Tem certeza de que deseja limpar as estatísticas de conexão para o servidor \"{serverName}\"? Esta ação não pode ser desfeita.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Abas iniciais",
"homeTabsCustomizeDesc": "Personalize quais abas aparecem na página inicial e sua ordem",
"reset": "Redefinir",
"availableTabs": "Abas disponíveis",
"atLeastOneTab": "Pelo menos uma aba deve ser selecionada",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Добавлено в список задач", "added2List": "Добавлено в список задач",
"addr": "Адрес", "addr": "Адрес",
"alreadyLastDir": "Уже в корневом каталоге", "alreadyLastDir": "Уже в корневом каталоге",
"askAi": "Спросить ИИ",
"askAiApiKey": "Ключ API",
"askAiAwaitingResponse": "Ожидание ответа ИИ...",
"askAiBaseUrl": "Базовый URL",
"askAiCommandInserted": "Команда вставлена в терминал",
"askAiConfigMissing": "Настройте {fields} в настройках.",
"askAiConfirmExecute": "Подтвердите перед выполнением",
"askAiConversation": "Разговор с ИИ",
"askAiDisclaimer": "ИИ может ошибаться. Используйте с осторожностью.",
"askAiFollowUpHint": "Задайте дополнительный вопрос...",
"askAiInsertTerminal": "Вставить в терминал",
"askAiModel": "Модель",
"askAiNoResponse": "Нет ответа",
"askAiRecommendedCommand": "Команда, предложенная ИИ",
"askAiSelectedContent": "Выбранное содержимое",
"askAiUsageHint": "Используется в SSH-терминале",
"atLeastOneTab": "Должна быть выбрана хотя бы одна вкладка",
"authFailTip": "Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.", "authFailTip": "Аутентификация не удалась, пожалуйста, проверьте, правильны ли пароль/ключ/хост/пользователь и т.д.",
"autoBackupConflict": "Может быть включено только одно автоматическое резервное копирование", "autoBackupConflict": "Может быть включено только одно автоматическое резервное копирование",
"autoConnect": "Автоматическое подключение", "autoConnect": "Автоматическое подключение",
"autoRun": "Автозапуск", "autoRun": "Автозапуск",
"autoUpdateHomeWidget": "Автоматическое обновление виджета на главном экране", "autoUpdateHomeWidget": "Автоматическое обновление виджета на главном экране",
"availableTabs": "Доступные вкладки",
"backupEncrypted": "Резервная копия зашифрована", "backupEncrypted": "Резервная копия зашифрована",
"backupNotEncrypted": "Резервная копия не зашифрована", "backupNotEncrypted": "Резервная копия не зашифрована",
"backupPassword": "Пароль резервной копии", "backupPassword": "Пароль резервной копии",
@@ -23,10 +41,18 @@
"battery": "Батарея", "battery": "Батарея",
"bgRun": "Работа в фоновом режиме", "bgRun": "Работа в фоновом режиме",
"bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».", "bgRunTip": "Этот переключатель означает, что программа будет пытаться работать в фоновом режиме, но фактическое выполнение зависит от того, включено ли разрешение. Для нативного Android отключите «Оптимизацию батареи» для этого приложения, для MIUI измените контроль активности на «Нет ограничений».",
"clearAllStatsContent": "Вы уверены, что хотите очистить всю статистику соединений сервера? Это действие не может быть отменено.",
"clearAllStatsTitle": "Очистить всю статистику",
"clearServerStatsContent": "Вы уверены, что хотите очистить статистику соединений для сервера \"{serverName}\"? Это действие не может быть отменено.",
"clearServerStatsTitle": "Очистить статистику {serverName}",
"clearThisServerStats": "Очистить статистику этого сервера",
"closeAfterSave": "Сохранить и закрыть", "closeAfterSave": "Сохранить и закрыть",
"cmd": "Команда", "cmd": "Команда",
"collapseUITip": "Свернуть длинные списки в UI по умолчанию", "collapseUITip": "Свернуть длинные списки в UI по умолчанию",
"conn": "Подключение", "conn": "Подключение",
"connectionDetails": "Детали соединения",
"connectionStats": "Статистика соединений",
"connectionStatsDesc": "Просмотр коэффициента успешности подключения к серверу и истории",
"container": "Контейнер", "container": "Контейнер",
"containerTrySudoTip": "Например: если пользователь в приложении установлен как aaa, но Docker установлен под пользователем root, тогда нужно включить эту опцию", "containerTrySudoTip": "Например: если пользователь в приложении установлен как aaa, но Docker установлен под пользователем root, тогда нужно включить эту опцию",
"convert": "Конвертировать", "convert": "Конвертировать",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Команда для открытия эмулятора терминала при запуске SSH-сеансов.", "desktopTerminalTip": "Команда для открытия эмулятора терминала при запуске SSH-сеансов.",
"dirEmpty": "Пожалуйста, убедитесь, что папка пуста", "dirEmpty": "Пожалуйста, убедитесь, что папка пуста",
"disconnected": "Отключено", "disconnected": "Отключено",
"discoverSshServers": "Обнаружить SSH серверы",
"discoveryFailed": "Обнаружение не удалось",
"discoverySettings": "Настройки обнаружения",
"discoverySummary": "Сводка обнаружения",
"disk": "Диск", "disk": "Диск",
"diskHealth": "Состояние диска", "diskHealth": "Состояние диска",
"diskIgnorePath": "Игнорировать путь к диску", "diskIgnorePath": "Игнорировать путь к диску",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Режим двойной колонки", "doubleColumnMode": "Режим двойной колонки",
"doubleColumnTip": "Эта опция лишь включает функцию; фактическое применение зависит от ширины устройства", "doubleColumnTip": "Эта опция лишь включает функцию; фактическое применение зависит от ширины устройства",
"editVirtKeys": "Редактировать виртуальные клавиши", "editVirtKeys": "Редактировать виртуальные клавиши",
"editor": "Редактор",
"editorHighlightTip": "Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.", "editorHighlightTip": "Текущая производительность подсветки кода неудовлетворительна, можно отключить для улучшения.",
"emulator": "Эмулятор", "emulator": "Эмулятор",
"enableMdns": "Включить mDNS",
"enableMdnsDesc": "Использовать mDNS/Bonjour для обнаружения SSH служб",
"encode": "Кодировать", "encode": "Кодировать",
"envVars": "Переменная окружения", "envVars": "Переменная окружения",
"experimentalFeature": "Экспериментальная функция", "experimentalFeature": "Экспериментальная функция",
@@ -67,8 +98,8 @@
"fgService": "Сервис переднего плана", "fgService": "Сервис переднего плана",
"fgServiceTip": "После включения некоторые модели устройств могут вылетать. Отключение может привести к тому, что некоторые модели не смогут поддерживать SSH-соединения в фоновом режиме. Пожалуйста, разрешите ServerBox права на уведомления, фоновую работу и самопробуждение в системных настройках.", "fgServiceTip": "После включения некоторые модели устройств могут вылетать. Отключение может привести к тому, что некоторые модели не смогут поддерживать SSH-соединения в фоновом режиме. Пожалуйста, разрешите ServerBox права на уведомления, фоновую работу и самопробуждение в системных настройках.",
"fileTooLarge": "Файл '{file}' слишком большой '{size}', превышает {sizeMax}", "fileTooLarge": "Файл '{file}' слишком большой '{size}', превышает {sizeMax}",
"finishedAt": "Завершено в",
"followSystem": "Следовать за системой", "followSystem": "Следовать за системой",
"font": "Шрифт",
"fontSize": "Размер шрифта", "fontSize": "Размер шрифта",
"force": "Принудительно", "force": "Принудительно",
"fullScreen": "Полноэкранный режим", "fullScreen": "Полноэкранный режим",
@@ -79,13 +110,14 @@
"goto": "Перейти к", "goto": "Перейти к",
"hideTitleBar": "Скрыть заголовок", "hideTitleBar": "Скрыть заголовок",
"highlight": "Подсветка кода", "highlight": "Подсветка кода",
"homeTabs": "Вкладки дома",
"homeTabsCustomizeDesc": "Настройте, какие вкладки появляются на главной странице и их порядок",
"homeWidgetUrlConfig": "Конфигурация URL виджета домашнего экрана", "homeWidgetUrlConfig": "Конфигурация URL виджета домашнего экрана",
"host": "Хост", "host": "Хост",
"httpFailedWithCode": "ошибка запроса, код: {code}", "httpFailedWithCode": "ошибка запроса, код: {code}",
"ignoreCert": "Игнорировать сертификат", "ignoreCert": "Игнорировать сертификат",
"image": "Образ", "image": "Образ",
"imagesList": "Список образов", "imagesList": "Список образов",
"init": "Инициализировать",
"inner": "Встроенный", "inner": "Встроенный",
"install": "установить", "install": "установить",
"installDockerWithUrl": "Пожалуйста, сначала установите Docker по адресу https://docs.docker.com/engine/install", "installDockerWithUrl": "Пожалуйста, сначала установите Docker по адресу https://docs.docker.com/engine/install",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Сохранять статус сервера при ошибке", "keepStatusWhenErr": "Сохранять статус сервера при ошибке",
"keepStatusWhenErrTip": "Применимо только в случае ошибки выполнения скрипта", "keepStatusWhenErrTip": "Применимо только в случае ошибки выполнения скрипта",
"keyAuth": "Аутентификация по ключу", "keyAuth": "Аутентификация по ключу",
"lastFailure": "Последний сбой",
"lastSuccess": "Последний успех",
"letterCache": "Кэширование букв", "letterCache": "Кэширование букв",
"letterCacheTip": "Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.", "letterCacheTip": "Рекомендуется отключить, но после отключения будет невозможно вводить символы CJK.",
"license": "Лицензия",
"location": "Местоположение", "location": "Местоположение",
"loss": "Потери пакетов", "loss": "Потери пакетов",
"madeWithLove": "Создано с ❤️ by {myGithub}", "madeWithLove": "Создано с ❤️ by {myGithub}",
"manual": "Вручную",
"max": "максимум", "max": "максимум",
"maxConcurrency": "Максимальная параллельность",
"maxRetryCount": "Максимальное количество попыток переподключения к серверу", "maxRetryCount": "Максимальное количество попыток переподключения к серверу",
"maxRetryCountEqual0": "Будет бесконечно пытаться переподключиться", "maxRetryCountEqual0": "Будет бесконечно пытаться переподключиться",
"min": "минимум", "min": "минимум",
@@ -115,6 +148,7 @@
"net": "Сеть", "net": "Сеть",
"netViewType": "Тип визуализации сети", "netViewType": "Тип визуализации сети",
"newContainer": "Создать контейнер", "newContainer": "Создать контейнер",
"noConnectionStatsData": "Нет данных статистики соединений",
"noLineChart": "Не использовать линейные графики", "noLineChart": "Не использовать линейные графики",
"noLineChartForCpu": "Не используйте линейные графики для ЦП", "noLineChartForCpu": "Не используйте линейные графики для ЦП",
"noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.", "noPrivateKeyTip": "Приватный ключ не существует, возможно, он был удален или есть ошибка в настройках.",
@@ -136,8 +170,8 @@
"plugInType": "Тип вставки", "plugInType": "Тип вставки",
"port": "Порт", "port": "Порт",
"preferDiskAmount": "Приоритетное отображение объёма диска", "preferDiskAmount": "Приоритетное отображение объёма диска",
"preview": "Предпросмотр",
"privateKey": "Приватный ключ", "privateKey": "Приватный ключ",
"privateKeyNotFoundFmt": "Закрытый ключ [{keyId}] не найден.",
"process": "Процесс", "process": "Процесс",
"prune": "Обрезать", "prune": "Обрезать",
"pushToken": "Токен уведомлений", "pushToken": "Токен уведомлений",
@@ -146,6 +180,7 @@
"pveVersionLow": "Эта функция в настоящее время находится на стадии тестирования и была протестирована только на PVE 8+. Используйте ее с осторожностью.", "pveVersionLow": "Эта функция в настоящее время находится на стадии тестирования и была протестирована только на PVE 8+. Используйте ее с осторожностью.",
"read": "Чтение", "read": "Чтение",
"reboot": "Перезагрузка", "reboot": "Перезагрузка",
"recentConnections": "Недавние соединения",
"rememberPwdInMem": "Запомнить пароль в памяти", "rememberPwdInMem": "Запомнить пароль в памяти",
"rememberPwdInMemTip": "Используется для контейнеров, приостановки и т. д.", "rememberPwdInMemTip": "Используется для контейнеров, приостановки и т. д.",
"rememberWindowSize": "Запомнить размер окна", "rememberWindowSize": "Запомнить размер окна",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Порядок элементов на странице деталей сервера", "serverDetailOrder": "Порядок элементов на странице деталей сервера",
"serverFuncBtns": "Кнопки функций сервера", "serverFuncBtns": "Кнопки функций сервера",
"serverOrder": "Порядок серверов", "serverOrder": "Порядок серверов",
"serverTabRequired": "Вкладку сервера нельзя удалить",
"servers": "серверов",
"sftpDlPrepare": "Подготовка подключения...", "sftpDlPrepare": "Подготовка подключения...",
"sftpEditorTip": "Если пусто, используйте встроенный редактор файлов приложения. Если значение указано, используйте редактор удаленного сервера, например, `vim` (рекомендуется автоматически определять согласно `EDITOR`).", "sftpEditorTip": "Если пусто, используйте встроенный редактор файлов приложения. Если значение указано, используйте редактор удаленного сервера, например, `vim` (рекомендуется автоматически определять согласно `EDITOR`).",
"sftpRmrDirSummary": "Использовать `rm -r` в SFTP для удаления папок", "sftpRmrDirSummary": "Использовать `rm -r` в SFTP для удаления папок",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Хотите ли вы дать разрешение на чтение ~/.ssh/config и автоматический импорт настроек сервера?", "sshConfigImportPermission": "Хотите ли вы дать разрешение на чтение ~/.ssh/config и автоматический импорт настроек сервера?",
"sshConfigImportTip": "Предложение прочитать ~/.ssh/config при создании первого сервера", "sshConfigImportTip": "Предложение прочитать ~/.ssh/config при создании первого сервера",
"sshConfigImported": "Импортировано {count} серверов из SSH-конфигурации", "sshConfigImported": "Импортировано {count} серверов из SSH-конфигурации",
"sshHostKeyChangedDesc": "SSH-ключ хоста для {serverName} изменился. Продолжайте только если доверяете этому серверу.",
"sshHostKeyFingerprintMd5Base64": "Отпечаток (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Отпечаток (MD5 hex): {fingerprint}",
"sshHostKeyType": "Тип ключа хоста SSH",
"sshHostKeyNewDesc": "Получен новый SSH-ключ хоста от {serverName}. Проверьте отпечаток перед продолжением.",
"sshHostKeyStoredFingerprint": "Сохранённый отпечаток: {fingerprint}",
"sshConfigManualSelect": "Хотели бы вы вручную выбрать файл конфигурации SSH?", "sshConfigManualSelect": "Хотели бы вы вручную выбрать файл конфигурации SSH?",
"sshConfigNoServers": "Серверы не найдены в SSH-конфигурации", "sshConfigNoServers": "Серверы не найдены в SSH-конфигурации",
"sshConfigPermissionDenied": "Невозможно получить доступ к файлу конфигурации SSH из-за разрешений macOS.", "sshConfigPermissionDenied": "Невозможно получить доступ к файлу конфигурации SSH из-за разрешений macOS.",
@@ -206,10 +249,10 @@
"suspend": "Приостановить", "suspend": "Приостановить",
"suspendTip": "Функция приостановки требует прав root и поддержки systemd.", "suspendTip": "Функция приостановки требует прав root и поддержки systemd.",
"switchTo": "Переключиться на {val}", "switchTo": "Переключиться на {val}",
"sync": "Синхронизировать",
"syncTip": "Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.", "syncTip": "Возможно, потребуется перезагрузка, чтобы некоторые изменения вступили в силу.",
"system": "Система", "system": "Система",
"tag": "Теги", "tag": "Теги",
"tapToStartDiscovery": "Нажмите кнопку поиска, чтобы обнаружить SSH серверы в вашей сети",
"temperature": "Температура", "temperature": "Температура",
"termFontSizeTip": "Эта настройка повлияет на размер терминала (ширина и высота). Вы можете масштабировать страницу терминала, чтобы调整 размер шрифта текущей сессии.", "termFontSizeTip": "Эта настройка повлияет на размер терминала (ширина и высота). Вы можете масштабировать страницу терминала, чтобы调整 размер шрифта текущей сессии.",
"terminal": "Терминал", "terminal": "Терминал",
@@ -220,6 +263,7 @@
"time": "Время", "time": "Время",
"times": "Раз", "times": "Раз",
"total": "Всего", "total": "Всего",
"totalAttempts": "Общее",
"traffic": "Трафик", "traffic": "Трафик",
"trySudo": "Попробовать использовать sudo", "trySudo": "Попробовать использовать sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Обновление", "update": "Обновление",
"updateIntervalEqual0": "Если установлено 0, статус сервера не будет автоматически обновляться.\nТакже не будет рассчитано использование ЦП.", "updateIntervalEqual0": "Если установлено 0, статус сервера не будет автоматически обновляться.\nТакже не будет рассчитано использование ЦП.",
"updateServerStatusInterval": "Интервал обновления статуса сервера", "updateServerStatusInterval": "Интервал обновления статуса сервера",
"upload": "Загрузить",
"upsideDown": "Перевернуть", "upsideDown": "Перевернуть",
"uptime": "Время работы", "uptime": "Время работы",
"useCdn": "Использование CDN", "useCdn": "Использование CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Использовать Podman по умолчанию", "usePodmanByDefault": "Использовать Podman по умолчанию",
"used": "Использовано", "used": "Использовано",
"view": "Вид", "view": "Вид",
"viewDetails": "Просмотр деталей",
"viewErr": "Просмотр ошибок", "viewErr": "Просмотр ошибок",
"virtKeyHelpClipboard": "Если в терминале выделен текст, то он копируется в буфер обмена, в противном случае содержимое буфера вставляется в терминал.", "virtKeyHelpClipboard": "Если в терминале выделен текст, то он копируется в буфер обмена, в противном случае содержимое буфера вставляется в терминал.",
"virtKeyHelpIME": "Включить/выключить клавиатуру", "virtKeyHelpIME": "Включить/выключить клавиатуру",
@@ -249,39 +293,5 @@
"wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.", "wolTip": "После настройки WOL (Wake-on-LAN) при каждом подключении к серверу отправляется запрос WOL.",
"write": "Запись", "write": "Запись",
"writeScriptFailTip": "Запись скрипта не удалась, возможно, из-за отсутствия прав или потому что, директории не существует.", "writeScriptFailTip": "Запись скрипта не удалась, возможно, из-за отсутствия прав или потому что, директории не существует.",
"writeScriptTip": "После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.", "writeScriptTip": "После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта."
"connectionStats": "Статистика соединений",
"connectionStatsDesc": "Просмотр коэффициента успешности подключения к серверу и истории",
"noConnectionStatsData": "Нет данных статистики соединений",
"totalAttempts": "Общее",
"lastSuccess": "Последний успех",
"lastFailure": "Последний сбой",
"recentConnections": "Недавние соединения",
"viewDetails": "Просмотр деталей",
"connectionDetails": "Детали соединения",
"clearThisServerStats": "Очистить статистику этого сервера",
"clearAllStatsTitle": "Очистить всю статистику",
"clearAllStatsContent": "Вы уверены, что хотите очистить всю статистику соединений сервера? Это действие не может быть отменено.",
"clearServerStatsTitle": "Очистить статистику {serverName}",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Вы уверены, что хотите очистить статистику соединений для сервера \"{serverName}\"? Это действие не может быть отменено.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Вкладки дома",
"homeTabsCustomizeDesc": "Настройте, какие вкладки появляются на главной странице и их порядок",
"reset": "Сброс",
"availableTabs": "Доступные вкладки",
"atLeastOneTab": "Должна быть выбрана хотя бы одна вкладка",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Görev listesine eklendi", "added2List": "Görev listesine eklendi",
"addr": "Adres", "addr": "Adres",
"alreadyLastDir": "Zaten son dizindesiniz.", "alreadyLastDir": "Zaten son dizindesiniz.",
"askAi": "Yapay zekaya sor",
"askAiApiKey": "API anahtarı",
"askAiAwaitingResponse": "Yapay zekâ yanıtı bekleniyor...",
"askAiBaseUrl": "Temel URL",
"askAiCommandInserted": "Komut terminale eklendi",
"askAiConfigMissing": "Lütfen Ayarlar'da {fields} öğesini yapılandırın.",
"askAiConfirmExecute": "Çalıştırmadan önce onayla",
"askAiConversation": "YZ sohbeti",
"askAiDisclaimer": "Yapay zeka hata yapabilir. Lütfen dikkatli kullanın.",
"askAiFollowUpHint": "Yeni bir soru sor...",
"askAiInsertTerminal": "Terminale ekle",
"askAiModel": "Model",
"askAiNoResponse": "Yanıt yok",
"askAiRecommendedCommand": "YZ önerilen komut",
"askAiSelectedContent": "Seçilen içerik",
"askAiUsageHint": "SSH Terminalinde kullanılır",
"atLeastOneTab": "En az bir sekme seçilmelidir",
"authFailTip": "Kimlik doğrulama başarısız oldu, lütfen kimlik bilgilerinin doğru olup olmadığını kontrol edin", "authFailTip": "Kimlik doğrulama başarısız oldu, lütfen kimlik bilgilerinin doğru olup olmadığını kontrol edin",
"autoBackupConflict": "Aynı anda yalnızca bir otomatik yedekleme açık olabilir.", "autoBackupConflict": "Aynı anda yalnızca bir otomatik yedekleme açık olabilir.",
"autoConnect": "Otomatik bağlan", "autoConnect": "Otomatik bağlan",
"autoRun": "Otomatik çalıştır", "autoRun": "Otomatik çalıştır",
"autoUpdateHomeWidget": "Ana ekran bileşenini otomatik güncelle", "autoUpdateHomeWidget": "Ana ekran bileşenini otomatik güncelle",
"availableTabs": "Mevcut Sekmeler",
"backupEncrypted": "Yedekleme şifrelenmiş", "backupEncrypted": "Yedekleme şifrelenmiş",
"backupNotEncrypted": "Yedekleme şifreli değil", "backupNotEncrypted": "Yedekleme şifreli değil",
"backupPassword": "Yedekleme parolası", "backupPassword": "Yedekleme parolası",
@@ -23,10 +41,18 @@
"battery": "Pil", "battery": "Pil",
"bgRun": "Arka planda çalıştır", "bgRun": "Arka planda çalıştır",
"bgRunTip": "Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.", "bgRunTip": "Bu anahtar yalnızca programın arka planda çalışmayı deneyeceği anlamına gelir. Arka planda çalışıp çalışamayacağı, iznin etkinleştirilip etkinleştirilmediğine bağlıdır. AOSP tabanlı Android ROM'lar için lütfen bu uygulamada \"Pil Optimizasyonu\"nu devre dışı bırakın. MIUI / HyperOS için lütfen güç tasarrufu politikasını \"Sınırsız\" olarak değiştirin.",
"clearAllStatsContent": "Tüm sunucu bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"clearAllStatsTitle": "Tüm İstatistikleri Temizle",
"clearServerStatsContent": "\"{serverName}\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"clearServerStatsTitle": "{serverName} İstatistiklerini Temizle",
"clearThisServerStats": "Bu Sunucu İstatistiklerini Temizle",
"closeAfterSave": "Kaydet ve kapat", "closeAfterSave": "Kaydet ve kapat",
"cmd": "Komut", "cmd": "Komut",
"collapseUITip": "Arayüzde uzun listelerin varsayılan olarak daraltılıp daraltılmayacağı", "collapseUITip": "Arayüzde uzun listelerin varsayılan olarak daraltılıp daraltılmayacağı",
"conn": "Bağlantı", "conn": "Bağlantı",
"connectionDetails": "Bağlantı Detayları",
"connectionStats": "Bağlantı İstatistikleri",
"connectionStatsDesc": "Sunucu bağlantı başarı oranını ve geçmişi görüntüle",
"container": "Konteyner", "container": "Konteyner",
"containerTrySudoTip": "Örneğin: Uygulamada kullanıcı aaa olarak ayarlanmış, ancak Docker root kullanıcısı altında kurulmuş. Bu durumda bu seçeneği etkinleştirmeniz gerekir.", "containerTrySudoTip": "Örneğin: Uygulamada kullanıcı aaa olarak ayarlanmış, ancak Docker root kullanıcısı altında kurulmuş. Bu durumda bu seçeneği etkinleştirmeniz gerekir.",
"convert": "Dönüştür", "convert": "Dönüştür",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "SSH oturumları başlatılırken terminal öykünücüsünü açmak için kullanılan komut.", "desktopTerminalTip": "SSH oturumları başlatılırken terminal öykünücüsünü açmak için kullanılan komut.",
"dirEmpty": "Klasörün boş olduğundan emin olun.", "dirEmpty": "Klasörün boş olduğundan emin olun.",
"disconnected": "Bağlantı kesildi", "disconnected": "Bağlantı kesildi",
"discoverSshServers": "SSH Sunucularını Keşfet",
"discoveryFailed": "Keşif başarısız",
"discoverySettings": "Keşif Ayarları",
"discoverySummary": "Keşif Özeti",
"disk": "Disk", "disk": "Disk",
"diskHealth": "Disk sağlığı", "diskHealth": "Disk sağlığı",
"diskIgnorePath": "Disk için yok sayılan yol", "diskIgnorePath": "Disk için yok sayılan yol",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Çift sütun modu", "doubleColumnMode": "Çift sütun modu",
"doubleColumnTip": "Bu seçenek yalnızca özelliği etkinleştirir, gerçekten etkinleşip etkinleşmeyeceği cihazın genişliğine bağlıdır", "doubleColumnTip": "Bu seçenek yalnızca özelliği etkinleştirir, gerçekten etkinleşip etkinleşmeyeceği cihazın genişliğine bağlıdır",
"editVirtKeys": "Sanal tuşları düzenle", "editVirtKeys": "Sanal tuşları düzenle",
"editor": "Düzenleyici",
"editorHighlightTip": "Mevcut kod vurgulama performansı ideal değil ve isteğe bağlı olarak kapatılabilir.", "editorHighlightTip": "Mevcut kod vurgulama performansı ideal değil ve isteğe bağlı olarak kapatılabilir.",
"emulator": "Emülatör", "emulator": "Emülatör",
"enableMdns": "mDNS'yi Etkinleştir",
"enableMdnsDesc": "SSH hizmetlerini keşfetmek için mDNS/Bonjour kullan",
"encode": "Kodla", "encode": "Kodla",
"envVars": "Ortam değişkeni", "envVars": "Ortam değişkeni",
"experimentalFeature": "Deneysel özellik", "experimentalFeature": "Deneysel özellik",
@@ -67,8 +98,8 @@
"fgService": "Ön Plan Servisi", "fgService": "Ön Plan Servisi",
"fgServiceTip": "Etkinleştirildikten sonra bazı cihaz modellerinde çökme olabilir. Devre dışı bırakmak, bazı modellerde SSH bağlantılarının arka planda sürdürülememesine neden olabilir. Lütfen sistem ayarlarında ServerBox bildirim izinlerini, arka planda çalışmayı ve otomatik uyanmayı etkinleştirin.", "fgServiceTip": "Etkinleştirildikten sonra bazı cihaz modellerinde çökme olabilir. Devre dışı bırakmak, bazı modellerde SSH bağlantılarının arka planda sürdürülememesine neden olabilir. Lütfen sistem ayarlarında ServerBox bildirim izinlerini, arka planda çalışmayı ve otomatik uyanmayı etkinleştirin.",
"fileTooLarge": "'{file}' dosyası çok büyük {size}, maksimum {sizeMax}", "fileTooLarge": "'{file}' dosyası çok büyük {size}, maksimum {sizeMax}",
"finishedAt": "Tamamlandı:",
"followSystem": "Sistemi takip et", "followSystem": "Sistemi takip et",
"font": "Yazı tipi",
"fontSize": "Yazı tipi boyutu", "fontSize": "Yazı tipi boyutu",
"force": "Zorla", "force": "Zorla",
"fullScreen": "Tam ekran modu", "fullScreen": "Tam ekran modu",
@@ -79,13 +110,14 @@
"goto": "Git", "goto": "Git",
"hideTitleBar": "Başlık çubuğunu gizle", "hideTitleBar": "Başlık çubuğunu gizle",
"highlight": "Kod vurgulama", "highlight": "Kod vurgulama",
"homeTabs": "Ana Sayfa Sekmeleri",
"homeTabsCustomizeDesc": "Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin",
"homeWidgetUrlConfig": "Ana ekran bileşeni URL'sini yapılandır", "homeWidgetUrlConfig": "Ana ekran bileşeni URL'sini yapılandır",
"host": "Ana bilgisayar", "host": "Ana bilgisayar",
"httpFailedWithCode": "İstek başarısız oldu, durum kodu: {code}", "httpFailedWithCode": "İstek başarısız oldu, durum kodu: {code}",
"ignoreCert": "Sertifikayı yok say", "ignoreCert": "Sertifikayı yok say",
"image": "Görüntü", "image": "Görüntü",
"imagesList": "Görüntü listesi", "imagesList": "Görüntü listesi",
"init": "Başlat",
"inner": "İç", "inner": "İç",
"install": "Kur", "install": "Kur",
"installDockerWithUrl": "Lütfen önce https://docs.docker.com/engine/install adresinden Docker'ı kurun.", "installDockerWithUrl": "Lütfen önce https://docs.docker.com/engine/install adresinden Docker'ı kurun.",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Son sunucu durumunu koru", "keepStatusWhenErr": "Son sunucu durumunu koru",
"keepStatusWhenErrTip": "Yalnızca betik yürütülmesi sırasında bir hata olduğunda", "keepStatusWhenErrTip": "Yalnızca betik yürütülmesi sırasında bir hata olduğunda",
"keyAuth": "Anahtar Kimlik Doğrulama", "keyAuth": "Anahtar Kimlik Doğrulama",
"lastFailure": "Son Başarısızlık",
"lastSuccess": "Son Başarı",
"letterCache": "Harf önbelleği", "letterCache": "Harf önbelleği",
"letterCacheTip": "Devre dışı bırakılması önerilir, ancak devre dışı bırakıldığında CJK karakterlerini girmek mümkün olmayacaktır.", "letterCacheTip": "Devre dışı bırakılması önerilir, ancak devre dışı bırakıldığında CJK karakterlerini girmek mümkün olmayacaktır.",
"license": "Lisans",
"location": "Konum", "location": "Konum",
"loss": "Kayıp", "loss": "Kayıp",
"madeWithLove": "{myGithub} tarafından ❤️ ile yapıldı", "madeWithLove": "{myGithub} tarafından ❤️ ile yapıldı",
"manual": "Manuel",
"max": "maks", "max": "maks",
"maxConcurrency": "Maksimum Eşzamanlılık",
"maxRetryCount": "Sunucu yeniden bağlantı sayısı", "maxRetryCount": "Sunucu yeniden bağlantı sayısı",
"maxRetryCountEqual0": "Tekrar tekrar deneyecek.", "maxRetryCountEqual0": "Tekrar tekrar deneyecek.",
"min": "min", "min": "min",
@@ -115,6 +148,7 @@
"net": "Ağ", "net": "Ağ",
"netViewType": "Ağ görüntüleme türü", "netViewType": "Ağ görüntüleme türü",
"newContainer": "Yeni konteyner", "newContainer": "Yeni konteyner",
"noConnectionStatsData": "Bağlantı istatistik verisi yok",
"noLineChart": "Çizgi grafikleri kullanma", "noLineChart": "Çizgi grafikleri kullanma",
"noLineChartForCpu": "CPU için çizgi grafikleri kullanma", "noLineChartForCpu": "CPU için çizgi grafikleri kullanma",
"noPrivateKeyTip": "Özel anahtar mevcut değil, silinmiş olabilir veya yapılandırma hatası vardır.", "noPrivateKeyTip": "Özel anahtar mevcut değil, silinmiş olabilir veya yapılandırma hatası vardır.",
@@ -136,8 +170,8 @@
"plugInType": "Eklenti Türü", "plugInType": "Eklenti Türü",
"port": "Port", "port": "Port",
"preferDiskAmount": "Disk kapasitesini öncelikli olarak göster", "preferDiskAmount": "Disk kapasitesini öncelikli olarak göster",
"preview": "Önizleme",
"privateKey": "Özel Anahtar", "privateKey": "Özel Anahtar",
"privateKeyNotFoundFmt": "Özel anahtar [{keyId}] bulunamadı.",
"process": "İşlem", "process": "İşlem",
"prune": "Budamak", "prune": "Budamak",
"pushToken": "Push belirteci", "pushToken": "Push belirteci",
@@ -146,6 +180,7 @@
"pveVersionLow": "Bu özellik şu anda test aşamasında ve yalnızca PVE 8+ üzerinde test edildi. Lütfen dikkatli kullanın.", "pveVersionLow": "Bu özellik şu anda test aşamasında ve yalnızca PVE 8+ üzerinde test edildi. Lütfen dikkatli kullanın.",
"read": "Oku", "read": "Oku",
"reboot": "Yeniden başlat", "reboot": "Yeniden başlat",
"recentConnections": "Son Bağlantılar",
"rememberPwdInMem": "Şifreyi bellekte hatırla", "rememberPwdInMem": "Şifreyi bellekte hatırla",
"rememberPwdInMemTip": "Konteynerler, askıya alma vb. için kullanılır.", "rememberPwdInMemTip": "Konteynerler, askıya alma vb. için kullanılır.",
"rememberWindowSize": "Pencere boyutunu hatırla", "rememberWindowSize": "Pencere boyutunu hatırla",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Ayrıntı sayfası bileşen sırası", "serverDetailOrder": "Ayrıntı sayfası bileşen sırası",
"serverFuncBtns": "Sunucu işlev düğmeleri", "serverFuncBtns": "Sunucu işlev düğmeleri",
"serverOrder": "Sunucu sırası", "serverOrder": "Sunucu sırası",
"serverTabRequired": "Sunucu sekmesi kaldırılamaz",
"servers": "sunucu",
"sftpDlPrepare": "Bağlantı hazırlanıyor...", "sftpDlPrepare": "Bağlantı hazırlanıyor...",
"sftpEditorTip": "Boşsa, uygulamanın yerleşik dosya düzenleyicisi kullanılır. Bir değer varsa, uzak sunucunun düzenleyicisi kullanılır, örn. `vim` (otomatik olarak `EDITOR`'a göre algılanması önerilir).", "sftpEditorTip": "Boşsa, uygulamanın yerleşik dosya düzenleyicisi kullanılır. Bir değer varsa, uzak sunucunun düzenleyicisi kullanılır, örn. `vim` (otomatik olarak `EDITOR`'a göre algılanması önerilir).",
"sftpRmrDirSummary": "SFTP'de bir klasörü silmek için `rm -r` kullan.", "sftpRmrDirSummary": "SFTP'de bir klasörü silmek için `rm -r` kullan.",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "~/.ssh/config dosyasını okumak ve sunucu ayarlarını otomatik olarak içe aktarmak için izin vermek ister misiniz?", "sshConfigImportPermission": "~/.ssh/config dosyasını okumak ve sunucu ayarlarını otomatik olarak içe aktarmak için izin vermek ister misiniz?",
"sshConfigImportTip": "İlk sunucu oluşturulurken ~/.ssh/config okuma istemi", "sshConfigImportTip": "İlk sunucu oluşturulurken ~/.ssh/config okuma istemi",
"sshConfigImported": "SSH yapılandırmasından {count} sunucu içe aktarıldı", "sshConfigImported": "SSH yapılandırmasından {count} sunucu içe aktarıldı",
"sshHostKeyChangedDesc": "{serverName} için SSH ana bilgisayar anahtarı değişti. Yalnızca bu sunucuya güveniyorsanız devam edin.",
"sshHostKeyFingerprintMd5Base64": "Parmak izi (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Parmak izi (MD5 hex): {fingerprint}",
"sshHostKeyType": "SSH ana bilgisayar anahtarı türü",
"sshHostKeyNewDesc": "{serverName} üzerinden yeni bir SSH ana bilgisayar anahtarı alındı. Güvenmeden önce parmak izini kontrol edin.",
"sshHostKeyStoredFingerprint": "Kaydedilen parmak izi: {fingerprint}",
"sshConfigManualSelect": "SSH yapılandırma dosyasını manuel olarak seçmek ister misiniz?", "sshConfigManualSelect": "SSH yapılandırma dosyasını manuel olarak seçmek ister misiniz?",
"sshConfigNoServers": "SSH yapılandırmasında sunucu bulunamadı", "sshConfigNoServers": "SSH yapılandırmasında sunucu bulunamadı",
"sshConfigPermissionDenied": "macOS izinleri nedeniyle SSH yapılandırma dosyasına erişilemiyor.", "sshConfigPermissionDenied": "macOS izinleri nedeniyle SSH yapılandırma dosyasına erişilemiyor.",
@@ -206,10 +249,10 @@
"suspend": "Askıya al", "suspend": "Askıya al",
"suspendTip": "Askıya alma işlevi, root izni ve systemd desteği gerektirir.", "suspendTip": "Askıya alma işlevi, root izni ve systemd desteği gerektirir.",
"switchTo": "{val}'a geç", "switchTo": "{val}'a geç",
"sync": "Senkronize et",
"syncTip": "Bazı değişikliklerin etkili olması için yeniden başlatma gerekebilir.", "syncTip": "Bazı değişikliklerin etkili olması için yeniden başlatma gerekebilir.",
"system": "Sistem", "system": "Sistem",
"tag": "Etiketler", "tag": "Etiketler",
"tapToStartDiscovery": "Ağınızdaki SSH sunucularını keşfetmek için arama düğmesine dokunun",
"temperature": "Sıcaklık", "temperature": "Sıcaklık",
"termFontSizeTip": "Bu ayar terminal boyutunu (genişlik ve yükseklik) etkiler. Terminal sayfasında yakınlaştırarak mevcut oturumun yazı tipi boyutunu ayarlayabilirsiniz.", "termFontSizeTip": "Bu ayar terminal boyutunu (genişlik ve yükseklik) etkiler. Terminal sayfasında yakınlaştırarak mevcut oturumun yazı tipi boyutunu ayarlayabilirsiniz.",
"terminal": "Terminal", "terminal": "Terminal",
@@ -220,6 +263,7 @@
"time": "Zaman", "time": "Zaman",
"times": "Kez", "times": "Kez",
"total": "Toplam", "total": "Toplam",
"totalAttempts": "Toplam",
"traffic": "Trafik", "traffic": "Trafik",
"trySudo": "Sudo ile dene", "trySudo": "Sudo ile dene",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Güncelle", "update": "Güncelle",
"updateIntervalEqual0": "0 olarak ayarladınız, otomatik güncelleme yapılmayacak.\nCPU durumu hesaplanamaz.", "updateIntervalEqual0": "0 olarak ayarladınız, otomatik güncelleme yapılmayacak.\nCPU durumu hesaplanamaz.",
"updateServerStatusInterval": "Sunucu durumu güncelleme aralığı", "updateServerStatusInterval": "Sunucu durumu güncelleme aralığı",
"upload": "Yükle",
"upsideDown": "Başaşağı", "upsideDown": "Başaşağı",
"uptime": "Çalışma süresi", "uptime": "Çalışma süresi",
"useCdn": "CDN kullan", "useCdn": "CDN kullan",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Varsayılan olarak Podman kullan", "usePodmanByDefault": "Varsayılan olarak Podman kullan",
"used": "Kullanılan", "used": "Kullanılan",
"view": "Görünüm", "view": "Görünüm",
"viewDetails": "Detayları Görüntüle",
"viewErr": "Hatayı gör", "viewErr": "Hatayı gör",
"virtKeyHelpClipboard": "Seçili terminal boş değilse panoya kopyala, aksi takdirde panodaki içeriği terminale yapıştır.", "virtKeyHelpClipboard": "Seçili terminal boş değilse panoya kopyala, aksi takdirde panodaki içeriği terminale yapıştır.",
"virtKeyHelpIME": "Klavyeyi aç/kapat", "virtKeyHelpIME": "Klavyeyi aç/kapat",
@@ -249,39 +293,5 @@
"wolTip": "WOL (Wake-on-LAN) yapılandırıldıktan sonra, sunucuya her bağlanıldığında bir WOL isteği gönderilir.", "wolTip": "WOL (Wake-on-LAN) yapılandırıldıktan sonra, sunucuya her bağlanıldığında bir WOL isteği gönderilir.",
"write": "Yaz", "write": "Yaz",
"writeScriptFailTip": "Betik yazma başarısız oldu, muhtemelen izin eksikliği veya dizin mevcut değil.", "writeScriptFailTip": "Betik yazma başarısız oldu, muhtemelen izin eksikliği veya dizin mevcut değil.",
"writeScriptTip": "Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.", "writeScriptTip": "Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz."
"connectionStats": "Bağlantı İstatistikleri",
"connectionStatsDesc": "Sunucu bağlantı başarı oranını ve geçmişi görüntüle",
"noConnectionStatsData": "Bağlantı istatistik verisi yok",
"totalAttempts": "Toplam",
"lastSuccess": "Son Başarı",
"lastFailure": "Son Başarısızlık",
"recentConnections": "Son Bağlantılar",
"viewDetails": "Detayları Görüntüle",
"connectionDetails": "Bağlantı Detayları",
"clearThisServerStats": "Bu Sunucu İstatistiklerini Temizle",
"clearAllStatsTitle": "Tüm İstatistikleri Temizle",
"clearAllStatsContent": "Tüm sunucu bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"clearServerStatsTitle": "{serverName} İstatistiklerini Temizle",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "\"{serverName}\" sunucusu için bağlantı istatistiklerini temizlemek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Ana Sayfa Sekmeleri",
"homeTabsCustomizeDesc": "Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin",
"reset": "Sıfırla",
"availableTabs": "Mevcut Sekmeler",
"atLeastOneTab": "En az bir sekme seçilmelidir",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "Додано до списку завдань", "added2List": "Додано до списку завдань",
"addr": "Адреса", "addr": "Адреса",
"alreadyLastDir": "Вже в останньому каталозі.", "alreadyLastDir": "Вже в останньому каталозі.",
"askAi": "Запитати ШІ",
"askAiApiKey": "Ключ API",
"askAiAwaitingResponse": "Очікування відповіді ШІ...",
"askAiBaseUrl": "Базова URL",
"askAiCommandInserted": "Команду вставлено в термінал",
"askAiConfigMissing": "Налаштуйте {fields} у налаштуваннях.",
"askAiConfirmExecute": "Підтвердити перед виконанням",
"askAiConversation": "Розмова з ШІ",
"askAiDisclaimer": "ШІ може помилятися. Користуйтеся обережно.",
"askAiFollowUpHint": "Поставте додаткове запитання...",
"askAiInsertTerminal": "Вставити в термінал",
"askAiModel": "Модель",
"askAiNoResponse": "Відповідь відсутня",
"askAiRecommendedCommand": "Команда, запропонована ШІ",
"askAiSelectedContent": "Вибраний вміст",
"askAiUsageHint": "Використовується в SSH-терміналі",
"atLeastOneTab": "Потрібно вибрати принаймні одну вкладку",
"authFailTip": "Авторизація не вдалася, будь ласка, перевірте правильність облікових даних", "authFailTip": "Авторизація не вдалася, будь ласка, перевірте правильність облікових даних",
"autoBackupConflict": "Тільки одне автоматичне резервне копіювання може бути активне одночасно.", "autoBackupConflict": "Тільки одне автоматичне резервне копіювання може бути активне одночасно.",
"autoConnect": "Авто підключення", "autoConnect": "Авто підключення",
"autoRun": "Авто запуск", "autoRun": "Авто запуск",
"autoUpdateHomeWidget": "Автоматичне оновлення віджетів на головному екрані", "autoUpdateHomeWidget": "Автоматичне оновлення віджетів на головному екрані",
"availableTabs": "Доступні вкладки",
"backupEncrypted": "Резервна копія зашифрована", "backupEncrypted": "Резервна копія зашифрована",
"backupNotEncrypted": "Резервна копія не зашифрована", "backupNotEncrypted": "Резервна копія не зашифрована",
"backupPassword": "Пароль резервного копіювання", "backupPassword": "Пароль резервного копіювання",
@@ -23,10 +41,18 @@
"battery": "Акумулятор", "battery": "Акумулятор",
"bgRun": "Запуск у фоновому режимі", "bgRun": "Запуск у фоновому режимі",
"bgRunTip": "Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".", "bgRunTip": "Цей перемикач лише вказує на те, що програма намагатиметься працювати у фоновому режимі. Чи може вона працювати у фоновому режимі, залежить від прав доступу. Для AOSP-орієнтованих Android ROM, будь ласка, вимкніть \"Оптимізацію акумулятора\" в цьому додатку. Для MIUI / HyperOS, будь ласка, змініть політику економії енергії на \"Нескінченна\".",
"clearAllStatsContent": "Ви впевнені, що хочете очистити всю статистику з'єднань сервера? Цю дію не можна скасувати.",
"clearAllStatsTitle": "Очистити всю статистику",
"clearServerStatsContent": "Ви впевнені, що хочете очистити статистику з'єднань для сервера \"{serverName}\"? Цю дію не можна скасувати.",
"clearServerStatsTitle": "Очистити статистику {serverName}",
"clearThisServerStats": "Очистити статистику цього сервера",
"closeAfterSave": "Зберегти та закрити", "closeAfterSave": "Зберегти та закрити",
"cmd": "Команда", "cmd": "Команда",
"collapseUITip": "Сховати довгі списки, що є у UI за замовчуванням", "collapseUITip": "Сховати довгі списки, що є у UI за замовчуванням",
"conn": "З'єднання", "conn": "З'єднання",
"connectionDetails": "Деталі з'єднання",
"connectionStats": "Статистика з'єднань",
"connectionStatsDesc": "Переглянути коефіцієнт успішності підключення до сервера та історію",
"container": "Контейнер", "container": "Контейнер",
"containerTrySudoTip": "Наприклад: У застосунку користувач це aaa, але Docker встановлений під користувачем root. У цьому випадку вам потрібно активувати цю опцію.", "containerTrySudoTip": "Наприклад: У застосунку користувач це aaa, але Docker встановлений під користувачем root. У цьому випадку вам потрібно активувати цю опцію.",
"convert": "Конвертувати", "convert": "Конвертувати",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "Команда для відкриття емулятора термінала під час запуску SSH-сеансів.", "desktopTerminalTip": "Команда для відкриття емулятора термінала під час запуску SSH-сеансів.",
"dirEmpty": "Переконайтеся, що директорія пуста.", "dirEmpty": "Переконайтеся, що директорія пуста.",
"disconnected": "Відключено", "disconnected": "Відключено",
"discoverSshServers": "Виявити SSH сервери",
"discoveryFailed": "Виявлення не вдалось",
"discoverySettings": "Налаштування виявлення",
"discoverySummary": "Підсумок виявлення",
"disk": "Диск", "disk": "Диск",
"diskHealth": "Стан диска", "diskHealth": "Стан диска",
"diskIgnorePath": "Ігнорувати шлях для диска", "diskIgnorePath": "Ігнорувати шлях для диска",
@@ -55,9 +85,10 @@
"doubleColumnMode": "Режим подвійної колонки", "doubleColumnMode": "Режим подвійної колонки",
"doubleColumnTip": "Ця опція лише активує функцію, чи можна її насправді включити, залежить від ширини пристрою", "doubleColumnTip": "Ця опція лише активує функцію, чи можна її насправді включити, залежить від ширини пристрою",
"editVirtKeys": "Редагувати віртуальні клавіші", "editVirtKeys": "Редагувати віртуальні клавіші",
"editor": "Редактор",
"editorHighlightTip": "Поточна підсвітка коду не ідеальна і може бути вимкнена для покращення.", "editorHighlightTip": "Поточна підсвітка коду не ідеальна і може бути вимкнена для покращення.",
"emulator": "Емулятор", "emulator": "Емулятор",
"enableMdns": "Увімкнути mDNS",
"enableMdnsDesc": "Використовувати mDNS/Bonjour для виявлення SSH сервісів",
"encode": "Кодувати", "encode": "Кодувати",
"envVars": "Змінні середовища", "envVars": "Змінні середовища",
"experimentalFeature": "Експериментальна функція", "experimentalFeature": "Експериментальна функція",
@@ -67,8 +98,8 @@
"fgService": "Служба переднього плану", "fgService": "Служба переднього плану",
"fgServiceTip": "Після увімкнення деякі моделі пристроїв можуть вилітати. Вимкнення може призвести до того, що деякі моделі не зможуть підтримувати SSH-з'єднання у фоновому режимі. Будь ласка, дозвольте ServerBox права на сповіщення, фонову роботу та самопробудження в системних налаштуваннях.", "fgServiceTip": "Після увімкнення деякі моделі пристроїв можуть вилітати. Вимкнення може призвести до того, що деякі моделі не зможуть підтримувати SSH-з'єднання у фоновому режимі. Будь ласка, дозвольте ServerBox права на сповіщення, фонову роботу та самопробудження в системних налаштуваннях.",
"fileTooLarge": "Файл '{file}' занадто великий ({size}), макс {sizeMax}", "fileTooLarge": "Файл '{file}' занадто великий ({size}), макс {sizeMax}",
"finishedAt": "Завершено о",
"followSystem": "Слідувати системі", "followSystem": "Слідувати системі",
"font": "Шрифт",
"fontSize": "Розмір шрифту", "fontSize": "Розмір шрифту",
"force": "Примусово", "force": "Примусово",
"fullScreen": "Повноекранний режим", "fullScreen": "Повноекранний режим",
@@ -79,13 +110,14 @@
"goto": "Перейти до", "goto": "Перейти до",
"hideTitleBar": "Сховати заголовок", "hideTitleBar": "Сховати заголовок",
"highlight": "Підсвітка коду", "highlight": "Підсвітка коду",
"homeTabs": "Домашні вкладки",
"homeTabsCustomizeDesc": "Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок",
"homeWidgetUrlConfig": "Налаштувати URL віджета на головному екрані", "homeWidgetUrlConfig": "Налаштувати URL віджета на головному екрані",
"host": "Хост", "host": "Хост",
"httpFailedWithCode": "Запит не вдався, код статусу: {code}", "httpFailedWithCode": "Запит не вдався, код статусу: {code}",
"ignoreCert": "Ігнорувати сертифікат", "ignoreCert": "Ігнорувати сертифікат",
"image": "Зображення", "image": "Зображення",
"imagesList": "Список зображень", "imagesList": "Список зображень",
"init": "Ініціалізувати",
"inner": "Внутрішній", "inner": "Внутрішній",
"install": "Встановити", "install": "Встановити",
"installDockerWithUrl": "Будь ласка, спочатку встановіть Docker. (https://docs.docker.com/engine/install)", "installDockerWithUrl": "Будь ласка, спочатку встановіть Docker. (https://docs.docker.com/engine/install)",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "Зберегати останній стан сервера", "keepStatusWhenErr": "Зберегати останній стан сервера",
"keepStatusWhenErrTip": "Тільки в разі виникнення помилки під час виконання скрипту", "keepStatusWhenErrTip": "Тільки в разі виникнення помилки під час виконання скрипту",
"keyAuth": "Аутентифікація ключем", "keyAuth": "Аутентифікація ключем",
"lastFailure": "Остання помилка",
"lastSuccess": "Останній успіх",
"letterCache": "Кешування букв", "letterCache": "Кешування букв",
"letterCacheTip": "Рекомендується відключити, але після вимкнення стане неможливим введення CJK (китайських, японських, корейських) символів.", "letterCacheTip": "Рекомендується відключити, але після вимкнення стане неможливим введення CJK (китайських, японських, корейських) символів.",
"license": "Ліцензія",
"location": "Місцезнаходження", "location": "Місцезнаходження",
"loss": "втрата пакетів", "loss": "втрата пакетів",
"madeWithLove": "Зроблено з ❤️ від {myGithub}", "madeWithLove": "Зроблено з ❤️ від {myGithub}",
"manual": "Посібник",
"max": "макс.", "max": "макс.",
"maxConcurrency": "Максимальна паралельність",
"maxRetryCount": "Кількість повторних спроб підключення до сервера", "maxRetryCount": "Кількість повторних спроб підключення до сервера",
"maxRetryCountEqual0": "Знову і знову буде намагатися повторно підключитися.", "maxRetryCountEqual0": "Знову і знову буде намагатися повторно підключитися.",
"min": "мін.", "min": "мін.",
@@ -115,6 +148,7 @@
"net": "Мережа", "net": "Мережа",
"netViewType": "Тип перегляду мережі", "netViewType": "Тип перегляду мережі",
"newContainer": "Новий контейнер", "newContainer": "Новий контейнер",
"noConnectionStatsData": "Немає даних статистики з'єднань",
"noLineChart": "Не використовувати лінійні діаграми", "noLineChart": "Не використовувати лінійні діаграми",
"noLineChartForCpu": "Не використовувати лінійні діаграми для ЦП", "noLineChartForCpu": "Не використовувати лінійні діаграми для ЦП",
"noPrivateKeyTip": "Приватного ключа немає, можливо, він був видалений або сталася помилка конфігурації.", "noPrivateKeyTip": "Приватного ключа немає, можливо, він був видалений або сталася помилка конфігурації.",
@@ -136,8 +170,8 @@
"plugInType": "Тип вставки", "plugInType": "Тип вставки",
"port": "Порт", "port": "Порт",
"preferDiskAmount": "Пріоритетно показувати ємність диска", "preferDiskAmount": "Пріоритетно показувати ємність диска",
"preview": "Попередній перегляд",
"privateKey": "Приватний ключ", "privateKey": "Приватний ключ",
"privateKeyNotFoundFmt": "Приватний ключ [{keyId}] не знайдено.",
"process": "Процес", "process": "Процес",
"prune": "Обрізати", "prune": "Обрізати",
"pushToken": "Надіслати токен", "pushToken": "Надіслати токен",
@@ -146,6 +180,7 @@
"pveVersionLow": "Ця функція наразі перебуває на стадії тестування та випробувалася лише на PVE 8+. Будь ласка, використовуйте її з обережністю.", "pveVersionLow": "Ця функція наразі перебуває на стадії тестування та випробувалася лише на PVE 8+. Будь ласка, використовуйте її з обережністю.",
"read": "Читати", "read": "Читати",
"reboot": "Перезавантажити", "reboot": "Перезавантажити",
"recentConnections": "Останні з'єднання",
"rememberPwdInMem": "Запам'ятати пароль у пам'яті", "rememberPwdInMem": "Запам'ятати пароль у пам'яті",
"rememberPwdInMemTip": "Використовується для контейнерів, призупинення тощо.", "rememberPwdInMemTip": "Використовується для контейнерів, призупинення тощо.",
"rememberWindowSize": "Запам'ятати розмір вікна", "rememberWindowSize": "Запам'ятати розмір вікна",
@@ -166,6 +201,8 @@
"serverDetailOrder": "Порядок віджетів на сторінці деталі", "serverDetailOrder": "Порядок віджетів на сторінці деталі",
"serverFuncBtns": "Кнопки функцій сервера", "serverFuncBtns": "Кнопки функцій сервера",
"serverOrder": "Порядок сервера", "serverOrder": "Порядок сервера",
"serverTabRequired": "Вкладку сервера не можна видалити",
"servers": "серверів",
"sftpDlPrepare": "Підготовка до підключення...", "sftpDlPrepare": "Підготовка до підключення...",
"sftpEditorTip": "Якщо порожньо, використовуйте вбудований редактор файлів програми. Якщо є значення, використовуйте редактор віддаленого сервера, наприклад, `vim` (рекомендується автоматично визначити відповідно до `EDITOR`).", "sftpEditorTip": "Якщо порожньо, використовуйте вбудований редактор файлів програми. Якщо є значення, використовуйте редактор віддаленого сервера, наприклад, `vim` (рекомендується автоматично визначити відповідно до `EDITOR`).",
"sftpRmrDirSummary": "Використовуйте `rm -r`, щоб видалити папку в SFTP.", "sftpRmrDirSummary": "Використовуйте `rm -r`, щоб видалити папку в SFTP.",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "Чи хочете ви надати дозвіл на читання ~/.ssh/config та автоматичний імпорт налаштувань сервера?", "sshConfigImportPermission": "Чи хочете ви надати дозвіл на читання ~/.ssh/config та автоматичний імпорт налаштувань сервера?",
"sshConfigImportTip": "Пропозиція прочитати ~/.ssh/config при створенні першого сервера", "sshConfigImportTip": "Пропозиція прочитати ~/.ssh/config при створенні першого сервера",
"sshConfigImported": "Імпортовано {count} серверів з SSH-конфігурації", "sshConfigImported": "Імпортовано {count} серверів з SSH-конфігурації",
"sshHostKeyChangedDesc": "SSH-ключ хоста для {serverName} змінено. Продовжуйте лише якщо довіряєте цьому серверу.",
"sshHostKeyFingerprintMd5Base64": "Відбиток (MD5 Base64): {fingerprint}",
"sshHostKeyFingerprintMd5Hex": "Відбиток (MD5 hex): {fingerprint}",
"sshHostKeyType": "Тип ключа хоста SSH",
"sshHostKeyNewDesc": "Отримано новий SSH-ключ хоста від {serverName}. Перевірте відбиток перед тим, як довіряти.",
"sshHostKeyStoredFingerprint": "Збережений відбиток: {fingerprint}",
"sshConfigManualSelect": "Чи хочете ви вручну вибрати файл конфігурації SSH?", "sshConfigManualSelect": "Чи хочете ви вручну вибрати файл конфігурації SSH?",
"sshConfigNoServers": "Сервери не знайдені в SSH-конфігурації", "sshConfigNoServers": "Сервери не знайдені в SSH-конфігурації",
"sshConfigPermissionDenied": "Неможливо отримати доступ до файлу конфігурації SSH через дозволи macOS.", "sshConfigPermissionDenied": "Неможливо отримати доступ до файлу конфігурації SSH через дозволи macOS.",
@@ -206,10 +249,10 @@
"suspend": "Призупинити", "suspend": "Призупинити",
"suspendTip": "Функція призупинення потребує адміністративних прав та підтримки systemd.", "suspendTip": "Функція призупинення потребує адміністративних прав та підтримки systemd.",
"switchTo": "Переключитися на {val}", "switchTo": "Переключитися на {val}",
"sync": "Синхронізація",
"syncTip": "Може знадобитися перезапуск, щоб деякі зміни набрали чинності.", "syncTip": "Може знадобитися перезапуск, щоб деякі зміни набрали чинності.",
"system": "Система", "system": "Система",
"tag": "Теги", "tag": "Теги",
"tapToStartDiscovery": "Натисніть кнопку пошуку, щоб виявити SSH сервери у вашій мережі",
"temperature": "Температура", "temperature": "Температура",
"termFontSizeTip": "Це налаштування вплине на розмір терміналу (ширину та висоту). Ви можете масштабувати на сторінці терміналу, щоб налаштувати розмір шрифту поточної сесії.", "termFontSizeTip": "Це налаштування вплине на розмір терміналу (ширину та висоту). Ви можете масштабувати на сторінці терміналу, щоб налаштувати розмір шрифту поточної сесії.",
"terminal": "Термінал", "terminal": "Термінал",
@@ -220,6 +263,7 @@
"time": "Час", "time": "Час",
"times": "Рази", "times": "Рази",
"total": "Всього", "total": "Всього",
"totalAttempts": "Загальна кількість",
"traffic": "Трафік", "traffic": "Трафік",
"trySudo": "Спробуйте використовувати sudo", "trySudo": "Спробуйте використовувати sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "Оновити", "update": "Оновити",
"updateIntervalEqual0": "Ви встановили 0, автоматичне оновлення не відбудеться.\nНе можна розрахувати статус ЦП.", "updateIntervalEqual0": "Ви встановили 0, автоматичне оновлення не відбудеться.\nНе можна розрахувати статус ЦП.",
"updateServerStatusInterval": "Інтервал оновлення статусу сервера", "updateServerStatusInterval": "Інтервал оновлення статусу сервера",
"upload": "Завантаження",
"upsideDown": "Доверху дном", "upsideDown": "Доверху дном",
"uptime": "Час роботи", "uptime": "Час роботи",
"useCdn": "Використання CDN", "useCdn": "Використання CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "Використовувати Podman за замовчуванням", "usePodmanByDefault": "Використовувати Podman за замовчуванням",
"used": "Використано", "used": "Використано",
"view": "Переглянути", "view": "Переглянути",
"viewDetails": "Переглянути деталі",
"viewErr": "Переглянути помилку", "viewErr": "Переглянути помилку",
"virtKeyHelpClipboard": "Копіювати в буфер обміну, якщо вибраний термінал не порожній, в іншому випадку вставити вміст буфера обміну в термінал.", "virtKeyHelpClipboard": "Копіювати в буфер обміну, якщо вибраний термінал не порожній, в іншому випадку вставити вміст буфера обміну в термінал.",
"virtKeyHelpIME": "Увімкнути/вимкнути клавіатуру", "virtKeyHelpIME": "Увімкнути/вимкнути клавіатуру",
@@ -249,39 +293,5 @@
"wolTip": "Після налаштування WOL (Wake-on-LAN), при кожному підключенні до сервера відправляється запит WOL.", "wolTip": "Після налаштування WOL (Wake-on-LAN), при кожному підключенні до сервера відправляється запит WOL.",
"write": "Записати", "write": "Записати",
"writeScriptFailTip": "Запис у скрипт не вдався, можливо, через брак дозволів або каталог не існує.", "writeScriptFailTip": "Запис у скрипт не вдався, можливо, через брак дозволів або каталог не існує.",
"writeScriptTip": "Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.", "writeScriptTip": "Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта."
"connectionStats": "Статистика з'єднань",
"connectionStatsDesc": "Переглянути коефіцієнт успішності підключення до сервера та історію",
"noConnectionStatsData": "Немає даних статистики з'єднань",
"totalAttempts": "Загальна кількість",
"lastSuccess": "Останній успіх",
"lastFailure": "Остання помилка",
"recentConnections": "Останні з'єднання",
"viewDetails": "Переглянути деталі",
"connectionDetails": "Деталі з'єднання",
"clearThisServerStats": "Очистити статистику цього сервера",
"clearAllStatsTitle": "Очистити всю статистику",
"clearAllStatsContent": "Ви впевнені, що хочете очистити всю статистику з'єднань сервера? Цю дію не можна скасувати.",
"clearServerStatsTitle": "Очистити статистику {serverName}",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "Ви впевнені, що хочете очистити статистику з'єднань для сервера \"{serverName}\"? Цю дію не можна скасувати.",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "Домашні вкладки",
"homeTabsCustomizeDesc": "Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок",
"reset": "Скинути",
"availableTabs": "Доступні вкладки",
"atLeastOneTab": "Потрібно вибрати принаймні одну вкладку",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "已添加至任务列表", "added2List": "已添加至任务列表",
"addr": "地址", "addr": "地址",
"alreadyLastDir": "已是顶级目录", "alreadyLastDir": "已是顶级目录",
"askAi": "问 AI",
"askAiApiKey": "API 密钥",
"askAiAwaitingResponse": "等待 AI 响应...",
"askAiBaseUrl": "基础 URL",
"askAiCommandInserted": "命令已插入终端",
"askAiConfigMissing": "请前往设置配置 {fields}",
"askAiConfirmExecute": "执行前确认",
"askAiConversation": "AI 对话",
"askAiDisclaimer": "AI 可能会犯错,请谨慎使用。",
"askAiFollowUpHint": "继续提问...",
"askAiInsertTerminal": "插入终端",
"askAiModel": "模型",
"askAiNoResponse": "无回复内容",
"askAiRecommendedCommand": "AI 推荐命令",
"askAiSelectedContent": "选中的内容",
"askAiUsageHint": "用于 SSH 终端",
"atLeastOneTab": "至少需要选择一个标签",
"authFailTip": "认证失败,请检查连接信息是否正确", "authFailTip": "认证失败,请检查连接信息是否正确",
"autoBackupConflict": "仅可启用一个自动备份任务", "autoBackupConflict": "仅可启用一个自动备份任务",
"autoConnect": "自动连接", "autoConnect": "自动连接",
"autoRun": "自动运行", "autoRun": "自动运行",
"autoUpdateHomeWidget": "自动更新桌面小部件", "autoUpdateHomeWidget": "自动更新桌面小部件",
"availableTabs": "可用标签",
"backupEncrypted": "备份已加密", "backupEncrypted": "备份已加密",
"backupNotEncrypted": "备份未加密", "backupNotEncrypted": "备份未加密",
"backupPassword": "备份密码", "backupPassword": "备份密码",
@@ -23,10 +41,18 @@
"battery": "电池", "battery": "电池",
"bgRun": "后台运行", "bgRun": "后台运行",
"bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请将省电策略改为“无限制”。", "bgRunTip": "此开关只代表程序会尝试在后台运行,具体能否后台运行取决于是否开启了权限。原生 Android 请关闭本 App 的“电池优化”MIUI / HyperOS 请将省电策略改为“无限制”。",
"clearAllStatsContent": "确定要清空所有服务器的连接统计数据吗?此操作无法撤销。",
"clearAllStatsTitle": "清空所有统计",
"clearServerStatsContent": "确定要清空服务器 \"{serverName}\" 的连接统计数据吗?此操作无法撤销。",
"clearServerStatsTitle": "清空 {serverName} 统计",
"clearThisServerStats": "清空此服务器统计",
"closeAfterSave": "保存后关闭", "closeAfterSave": "保存后关闭",
"cmd": "命令", "cmd": "命令",
"collapseUITip": "是否默认折叠 UI 中的长列表", "collapseUITip": "是否默认折叠 UI 中的长列表",
"conn": "连接", "conn": "连接",
"connectionDetails": "连接详情",
"connectionStats": "连接统计",
"connectionStatsDesc": "查看服务器连接成功率和历史记录",
"container": "容器", "container": "容器",
"containerTrySudoTip": "例如:在应用内将用户设置为 aaa但是 Docker 安装在root用户下这时就需要启用此选项", "containerTrySudoTip": "例如:在应用内将用户设置为 aaa但是 Docker 安装在root用户下这时就需要启用此选项",
"convert": "转换", "convert": "转换",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "启动 SSH 连接所用的终端模拟器命令", "desktopTerminalTip": "启动 SSH 连接所用的终端模拟器命令",
"dirEmpty": "请确保目录为空", "dirEmpty": "请确保目录为空",
"disconnected": "已断开连接", "disconnected": "已断开连接",
"discoverSshServers": "发现SSH服务器",
"discoveryFailed": "发现失败",
"discoverySettings": "发现设置",
"discoverySummary": "发现摘要",
"disk": "磁盘", "disk": "磁盘",
"diskHealth": "磁盘健康", "diskHealth": "磁盘健康",
"diskIgnorePath": "忽略的磁盘路径", "diskIgnorePath": "忽略的磁盘路径",
@@ -55,9 +85,10 @@
"doubleColumnMode": "双列模式", "doubleColumnMode": "双列模式",
"doubleColumnTip": "此选项仅用于启用该功能,是否生效取决于设备宽度", "doubleColumnTip": "此选项仅用于启用该功能,是否生效取决于设备宽度",
"editVirtKeys": "编辑虚拟按键", "editVirtKeys": "编辑虚拟按键",
"editor": "编辑器",
"editorHighlightTip": "代码高亮功能可能影响性能,可选择关闭。", "editorHighlightTip": "代码高亮功能可能影响性能,可选择关闭。",
"emulator": "模拟器", "emulator": "模拟器",
"enableMdns": "启用mDNS",
"enableMdnsDesc": "使用mDNS/Bonjour发现SSH服务",
"encode": "编码", "encode": "编码",
"envVars": "环境变量", "envVars": "环境变量",
"experimentalFeature": "实验性功能", "experimentalFeature": "实验性功能",
@@ -67,8 +98,8 @@
"fgService": "前台服务", "fgService": "前台服务",
"fgServiceTip": "开启后,可能会导致部分机型闪退。关闭可能导致部分机型无法后台保持 SSH 连接。请在系统设置内允许 ServerBox 通知权限、后台运行、自我唤醒。", "fgServiceTip": "开启后,可能会导致部分机型闪退。关闭可能导致部分机型无法后台保持 SSH 连接。请在系统设置内允许 ServerBox 通知权限、后台运行、自我唤醒。",
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}", "fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
"finishedAt": "完成于",
"followSystem": "跟随系统", "followSystem": "跟随系统",
"font": "字体",
"fontSize": "字体大小", "fontSize": "字体大小",
"force": "强制", "force": "强制",
"fullScreen": "全屏模式", "fullScreen": "全屏模式",
@@ -79,13 +110,14 @@
"goto": "前往", "goto": "前往",
"hideTitleBar": "隐藏标题栏", "hideTitleBar": "隐藏标题栏",
"highlight": "代码高亮", "highlight": "代码高亮",
"homeTabs": "主页标签",
"homeTabsCustomizeDesc": "自定义主页上显示的标签及其顺序",
"homeWidgetUrlConfig": "桌面部件链接配置", "homeWidgetUrlConfig": "桌面部件链接配置",
"host": "主机", "host": "主机",
"httpFailedWithCode": "请求失败,状态码: {code}", "httpFailedWithCode": "请求失败,状态码: {code}",
"ignoreCert": "忽略证书", "ignoreCert": "忽略证书",
"image": "镜像", "image": "镜像",
"imagesList": "镜像列表", "imagesList": "镜像列表",
"init": "初始化",
"inner": "内置", "inner": "内置",
"install": "安装", "install": "安装",
"installDockerWithUrl": "请先前往 https://docs.docker.com/engine/install 安装 Docker", "installDockerWithUrl": "请先前往 https://docs.docker.com/engine/install 安装 Docker",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "保留上次的服务器状态", "keepStatusWhenErr": "保留上次的服务器状态",
"keepStatusWhenErrTip": "仅限于执行脚本出错", "keepStatusWhenErrTip": "仅限于执行脚本出错",
"keyAuth": "密钥认证", "keyAuth": "密钥认证",
"lastFailure": "最后失败",
"lastSuccess": "最后成功",
"letterCache": "输入法字符缓存", "letterCache": "输入法字符缓存",
"letterCacheTip": "推荐关闭,但是关闭后无法输入 CJK 等文字", "letterCacheTip": "推荐关闭,但是关闭后无法输入 CJK 等文字",
"license": "证书",
"location": "位置", "location": "位置",
"loss": "丢包率", "loss": "丢包率",
"madeWithLove": "用❤️制作 by {myGithub}", "madeWithLove": "用❤️制作 by {myGithub}",
"manual": "手动",
"max": "最大", "max": "最大",
"maxConcurrency": "最大并发数",
"maxRetryCount": "服务器尝试重连次数", "maxRetryCount": "服务器尝试重连次数",
"maxRetryCountEqual0": "将无限次重试", "maxRetryCountEqual0": "将无限次重试",
"min": "最小", "min": "最小",
@@ -115,6 +148,7 @@
"net": "网络", "net": "网络",
"netViewType": "网络视图类型", "netViewType": "网络视图类型",
"newContainer": "新建容器", "newContainer": "新建容器",
"noConnectionStatsData": "暂无连接统计数据",
"noLineChart": "不使用折线图", "noLineChart": "不使用折线图",
"noLineChartForCpu": "CPU 不使用折线图", "noLineChartForCpu": "CPU 不使用折线图",
"noPrivateKeyTip": "私钥不存在,可能已被删除/配置错误", "noPrivateKeyTip": "私钥不存在,可能已被删除/配置错误",
@@ -136,8 +170,8 @@
"plugInType": "插入类型", "plugInType": "插入类型",
"port": "端口", "port": "端口",
"preferDiskAmount": "优先显示硬盘容量", "preferDiskAmount": "优先显示硬盘容量",
"preview": "预览",
"privateKey": "私钥", "privateKey": "私钥",
"privateKeyNotFoundFmt": "未找到私钥 [{keyId}]。",
"process": "进程", "process": "进程",
"prune": "修剪", "prune": "修剪",
"pushToken": "消息推送 Token", "pushToken": "消息推送 Token",
@@ -146,6 +180,7 @@
"pveVersionLow": "当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用", "pveVersionLow": "当前该功能处于测试阶段,仅在 PVE 8+ 上测试过,请谨慎使用",
"read": "读", "read": "读",
"reboot": "重启", "reboot": "重启",
"recentConnections": "最近连接记录",
"rememberPwdInMem": "在内存中记住密码", "rememberPwdInMem": "在内存中记住密码",
"rememberPwdInMemTip": "用于容器、挂起等", "rememberPwdInMemTip": "用于容器、挂起等",
"rememberWindowSize": "记住窗口大小", "rememberWindowSize": "记住窗口大小",
@@ -166,6 +201,8 @@
"serverDetailOrder": "详情页部件顺序", "serverDetailOrder": "详情页部件顺序",
"serverFuncBtns": "服务器功能按钮", "serverFuncBtns": "服务器功能按钮",
"serverOrder": "服务器顺序", "serverOrder": "服务器顺序",
"serverTabRequired": "服务器标签不能被移除",
"servers": "服务器",
"sftpDlPrepare": "准备连接至服务器...", "sftpDlPrepare": "准备连接至服务器...",
"sftpEditorTip": "如果为空, 使用App内置的文件编辑器. 如果有值, 这是用远程服务器的编辑器, 例如 `vim` (建议根据 `EDITOR` 自动获取).", "sftpEditorTip": "如果为空, 使用App内置的文件编辑器. 如果有值, 这是用远程服务器的编辑器, 例如 `vim` (建议根据 `EDITOR` 自动获取).",
"sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 来删除文件夹", "sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 来删除文件夹",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "是否允许读取 ~/.ssh/config 并自动导入服务器设置?", "sshConfigImportPermission": "是否允许读取 ~/.ssh/config 并自动导入服务器设置?",
"sshConfigImportTip": "首次创建服务器时提示读取 ~/.ssh/config", "sshConfigImportTip": "首次创建服务器时提示读取 ~/.ssh/config",
"sshConfigImported": "从 SSH 配置导入了 {count} 个服务器", "sshConfigImported": "从 SSH 配置导入了 {count} 个服务器",
"sshHostKeyChangedDesc": "服务器 {serverName} 的 SSH 主机密钥已更改,仅在信任该服务器时继续。",
"sshHostKeyFingerprintMd5Base64": "指纹MD5 Base64{fingerprint}",
"sshHostKeyFingerprintMd5Hex": "指纹MD5 十六进制):{fingerprint}",
"sshHostKeyType": "SSH 主机密钥类型",
"sshHostKeyNewDesc": "收到来自 {serverName} 的新 SSH 主机密钥,在信任前请检查指纹。",
"sshHostKeyStoredFingerprint": "已存储的指纹:{fingerprint}",
"sshConfigManualSelect": "是否要手动选择 SSH 配置文件?", "sshConfigManualSelect": "是否要手动选择 SSH 配置文件?",
"sshConfigNoServers": "SSH 配置中未找到服务器", "sshConfigNoServers": "SSH 配置中未找到服务器",
"sshConfigPermissionDenied": "由于 macOS 权限限制,无法访问 SSH 配置文件。", "sshConfigPermissionDenied": "由于 macOS 权限限制,无法访问 SSH 配置文件。",
@@ -206,10 +249,10 @@
"suspend": "挂起", "suspend": "挂起",
"suspendTip": "suspend 功能需要 root 权限及 systemd 支持。", "suspendTip": "suspend 功能需要 root 权限及 systemd 支持。",
"switchTo": "切换到 {val}", "switchTo": "切换到 {val}",
"sync": "同步",
"syncTip": "可能需要重新启动,某些更改才能生效。", "syncTip": "可能需要重新启动,某些更改才能生效。",
"system": "系统", "system": "系统",
"tag": "标签", "tag": "标签",
"tapToStartDiscovery": "点击搜索按钮发现网络中的SSH服务器",
"temperature": "温度", "temperature": "温度",
"termFontSizeTip": "此设置会影响终端大小(宽和高)。可以在终端页面缩放来调整当前会话的字体大小", "termFontSizeTip": "此设置会影响终端大小(宽和高)。可以在终端页面缩放来调整当前会话的字体大小",
"terminal": "终端", "terminal": "终端",
@@ -220,6 +263,7 @@
"time": "时间", "time": "时间",
"times": "次", "times": "次",
"total": "总共", "total": "总共",
"totalAttempts": "总次数",
"traffic": "流量", "traffic": "流量",
"trySudo": "尝试使用 sudo", "trySudo": "尝试使用 sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "更新", "update": "更新",
"updateIntervalEqual0": "设置为 0 将不自动刷新服务器状态。\n且无法计算 CPU 使用率。", "updateIntervalEqual0": "设置为 0 将不自动刷新服务器状态。\n且无法计算 CPU 使用率。",
"updateServerStatusInterval": "服务器状态刷新间隔", "updateServerStatusInterval": "服务器状态刷新间隔",
"upload": "上传",
"upsideDown": "上下交换", "upsideDown": "上下交换",
"uptime": "启动时长", "uptime": "启动时长",
"useCdn": "使用 CDN", "useCdn": "使用 CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "默认使用 Podman", "usePodmanByDefault": "默认使用 Podman",
"used": "已用", "used": "已用",
"view": "视图", "view": "视图",
"viewDetails": "查看详情",
"viewErr": "查看错误", "viewErr": "查看错误",
"virtKeyHelpClipboard": "如果终端有选中字符,则复制选中字符至剪切板,否则粘贴剪切板内容至终端。", "virtKeyHelpClipboard": "如果终端有选中字符,则复制选中字符至剪切板,否则粘贴剪切板内容至终端。",
"virtKeyHelpIME": "打开/关闭键盘", "virtKeyHelpIME": "打开/关闭键盘",
@@ -249,39 +293,5 @@
"wolTip": "配置 WOL 后,每次连接服务器时将自动发送唤醒请求", "wolTip": "配置 WOL 后,每次连接服务器时将自动发送唤醒请求",
"write": "写", "write": "写",
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等", "writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等",
"writeScriptTip": "在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。", "writeScriptTip": "在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。"
"connectionStats": "连接统计",
"connectionStatsDesc": "查看服务器连接成功率和历史记录",
"noConnectionStatsData": "暂无连接统计数据",
"totalAttempts": "总次数",
"lastSuccess": "最后成功",
"lastFailure": "最后失败",
"recentConnections": "最近连接记录",
"viewDetails": "查看详情",
"connectionDetails": "连接详情",
"clearThisServerStats": "清空此服务器统计",
"clearAllStatsTitle": "清空所有统计",
"clearAllStatsContent": "确定要清空所有服务器的连接统计数据吗?此操作无法撤销。",
"clearServerStatsTitle": "清空 {serverName} 统计",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "确定要清空服务器 \"{serverName}\" 的连接统计数据吗?此操作无法撤销。",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "主页标签",
"homeTabsCustomizeDesc": "自定义主页上显示的标签及其顺序",
"reset": "重置",
"availableTabs": "可用标签",
"atLeastOneTab": "至少需要选择一个标签",
"serverTabRequired": "服务器标签不能被移除"
} }

View File

@@ -6,11 +6,29 @@
"added2List": "已新增至任務清單", "added2List": "已新增至任務清單",
"addr": "位址", "addr": "位址",
"alreadyLastDir": "已是頂層目錄", "alreadyLastDir": "已是頂層目錄",
"askAi": "詢問 AI",
"askAiApiKey": "API 金鑰",
"askAiAwaitingResponse": "等待 AI 回應...",
"askAiBaseUrl": "基礎 URL",
"askAiCommandInserted": "指令已插入終端機",
"askAiConfigMissing": "請前往設定配置 {fields}",
"askAiConfirmExecute": "執行前確認",
"askAiConversation": "AI 對話",
"askAiDisclaimer": "AI 可能會犯錯,請謹慎使用。",
"askAiFollowUpHint": "繼續提問...",
"askAiInsertTerminal": "插入終端機",
"askAiModel": "模型",
"askAiNoResponse": "無回覆內容",
"askAiRecommendedCommand": "AI 推薦指令",
"askAiSelectedContent": "選取的內容",
"askAiUsageHint": "於 SSH 終端機中使用",
"atLeastOneTab": "至少需要選擇一個標籤",
"authFailTip": "認證失敗,請檢查連線資訊是否正確", "authFailTip": "認證失敗,請檢查連線資訊是否正確",
"autoBackupConflict": "僅能啟用一項自動備份任務", "autoBackupConflict": "僅能啟用一項自動備份任務",
"autoConnect": "自動連線", "autoConnect": "自動連線",
"autoRun": "自動執行", "autoRun": "自動執行",
"autoUpdateHomeWidget": "自動更新桌面小工具", "autoUpdateHomeWidget": "自動更新桌面小工具",
"availableTabs": "可用標籤",
"backupEncrypted": "備份已加密", "backupEncrypted": "備份已加密",
"backupNotEncrypted": "備份未加密", "backupNotEncrypted": "備份未加密",
"backupPassword": "備份密碼", "backupPassword": "備份密碼",
@@ -23,10 +41,18 @@
"battery": "電池", "battery": "電池",
"bgRun": "背景執行", "bgRun": "背景執行",
"bgRunTip": "此開關僅代表程式會嘗試於背景執行,能否成功取決於系統權限。在原生 Android 上,請關閉本應用的「電池最佳化」;在 MIUI / HyperOS 上,請將省電策略調整為「無限制」。", "bgRunTip": "此開關僅代表程式會嘗試於背景執行,能否成功取決於系統權限。在原生 Android 上,請關閉本應用的「電池最佳化」;在 MIUI / HyperOS 上,請將省電策略調整為「無限制」。",
"clearAllStatsContent": "確定要清空所有伺服器的連線統計資料嗎?此操作無法撤銷。",
"clearAllStatsTitle": "清空所有統計",
"clearServerStatsContent": "確定要清空伺服器 \"{serverName}\" 的連線統計資料嗎?此操作無法撤銷。",
"clearServerStatsTitle": "清空 {serverName} 統計",
"clearThisServerStats": "清空此伺服器統計",
"closeAfterSave": "儲存後關閉", "closeAfterSave": "儲存後關閉",
"cmd": "指令", "cmd": "指令",
"collapseUITip": "是否預設折疊 UI 中存在的長列表", "collapseUITip": "是否預設折疊 UI 中存在的長列表",
"conn": "連線", "conn": "連線",
"connectionDetails": "連線詳情",
"connectionStats": "連線統計",
"connectionStatsDesc": "檢視伺服器連線成功率和歷史記錄",
"container": "容器", "container": "容器",
"containerTrySudoTip": "例如App 內設定使用者為 aaa但是 Docker 安裝在 root 使用者,這時就需要開啟此選項", "containerTrySudoTip": "例如App 內設定使用者為 aaa但是 Docker 安裝在 root 使用者,這時就需要開啟此選項",
"convert": "轉換", "convert": "轉換",
@@ -42,6 +68,10 @@
"desktopTerminalTip": "啟動 SSH 連線時用於打開終端機模擬器的指令。", "desktopTerminalTip": "啟動 SSH 連線時用於打開終端機模擬器的指令。",
"dirEmpty": "請確保目錄為空", "dirEmpty": "請確保目錄為空",
"disconnected": "已中斷連線", "disconnected": "已中斷連線",
"discoverSshServers": "發現SSH服務器",
"discoveryFailed": "發現失敗",
"discoverySettings": "發現設定",
"discoverySummary": "發現摘要",
"disk": "磁碟", "disk": "磁碟",
"diskHealth": "磁碟健康", "diskHealth": "磁碟健康",
"diskIgnorePath": "忽略的磁碟路徑", "diskIgnorePath": "忽略的磁碟路徑",
@@ -55,9 +85,10 @@
"doubleColumnMode": "雙列模式", "doubleColumnMode": "雙列模式",
"doubleColumnTip": "此選項僅用於啟用此功能,是否生效取決於裝置寬度", "doubleColumnTip": "此選項僅用於啟用此功能,是否生效取決於裝置寬度",
"editVirtKeys": "編輯虛擬按鍵", "editVirtKeys": "編輯虛擬按鍵",
"editor": "編輯器",
"editorHighlightTip": "程式碼高亮功能可能影響效能,可選擇性關閉。", "editorHighlightTip": "程式碼高亮功能可能影響效能,可選擇性關閉。",
"emulator": "模擬器", "emulator": "模擬器",
"enableMdns": "啟用mDNS",
"enableMdnsDesc": "使用mDNS/Bonjour發現SSH服務",
"encode": "編碼", "encode": "編碼",
"envVars": "環境變數", "envVars": "環境變數",
"experimentalFeature": "實驗性功能", "experimentalFeature": "實驗性功能",
@@ -67,8 +98,8 @@
"fgService": "前台服務", "fgService": "前台服務",
"fgServiceTip": "開啟後,可能會導致部分機型閃退。關閉可能導致部分機型無法背景保持 SSH 連線。請在系統設定內允許 ServerBox 通知權限、背景執行、自我喚醒。", "fgServiceTip": "開啟後,可能會導致部分機型閃退。關閉可能導致部分機型無法背景保持 SSH 連線。請在系統設定內允許 ServerBox 通知權限、背景執行、自我喚醒。",
"fileTooLarge": "檔案 '{file}' 過大 '{size}',超過了 {sizeMax}", "fileTooLarge": "檔案 '{file}' 過大 '{size}',超過了 {sizeMax}",
"finishedAt": "完成於",
"followSystem": "跟隨系統", "followSystem": "跟隨系統",
"font": "字型",
"fontSize": "字型大小", "fontSize": "字型大小",
"force": "強制", "force": "強制",
"fullScreen": "全螢幕模式", "fullScreen": "全螢幕模式",
@@ -79,13 +110,14 @@
"goto": "前往", "goto": "前往",
"hideTitleBar": "隱藏標題欄", "hideTitleBar": "隱藏標題欄",
"highlight": "程式碼標記", "highlight": "程式碼標記",
"homeTabs": "主頁標籤",
"homeTabsCustomizeDesc": "自訂主頁上顯示的標籤及其順序",
"homeWidgetUrlConfig": "桌面小工具連結配置", "homeWidgetUrlConfig": "桌面小工具連結配置",
"host": "主機", "host": "主機",
"httpFailedWithCode": "請求失敗,狀態碼:{code}", "httpFailedWithCode": "請求失敗,狀態碼:{code}",
"ignoreCert": "忽略憑證", "ignoreCert": "忽略憑證",
"image": "映像檔", "image": "映像檔",
"imagesList": "映像檔列表", "imagesList": "映像檔列表",
"init": "初始化",
"inner": "內建", "inner": "內建",
"install": "安裝", "install": "安裝",
"installDockerWithUrl": "請先前往 https://docs.docker.com/engine/install 安裝 Docker", "installDockerWithUrl": "請先前往 https://docs.docker.com/engine/install 安裝 Docker",
@@ -95,14 +127,15 @@
"keepStatusWhenErr": "保留上次的伺服器狀態", "keepStatusWhenErr": "保留上次的伺服器狀態",
"keepStatusWhenErrTip": "僅在執行腳本出錯時", "keepStatusWhenErrTip": "僅在執行腳本出錯時",
"keyAuth": "金鑰認證", "keyAuth": "金鑰認證",
"lastFailure": "最後失敗",
"lastSuccess": "最後成功",
"letterCache": "輸入法字符快取", "letterCache": "輸入法字符快取",
"letterCacheTip": "建議關閉,但關閉後將無法輸入 CJK 等文字。", "letterCacheTip": "建議關閉,但關閉後將無法輸入 CJK 等文字。",
"license": "憑證",
"location": "位置", "location": "位置",
"loss": "逾時", "loss": "逾時",
"madeWithLove": "用❤️製作 by {myGithub}", "madeWithLove": "用❤️製作 by {myGithub}",
"manual": "手動",
"max": "最大", "max": "最大",
"maxConcurrency": "最大並發數",
"maxRetryCount": "伺服器嘗試重連次數", "maxRetryCount": "伺服器嘗試重連次數",
"maxRetryCountEqual0": "將無限次重試", "maxRetryCountEqual0": "將無限次重試",
"min": "最小", "min": "最小",
@@ -115,6 +148,7 @@
"net": "網路", "net": "網路",
"netViewType": "網路檢視類型", "netViewType": "網路檢視類型",
"newContainer": "新建容器", "newContainer": "新建容器",
"noConnectionStatsData": "暫無連線統計資料",
"noLineChart": "不使用折線圖", "noLineChart": "不使用折線圖",
"noLineChartForCpu": "CPU 不使用折線圖", "noLineChartForCpu": "CPU 不使用折線圖",
"noPrivateKeyTip": "私鑰不存在,可能已被刪除/配置錯誤。", "noPrivateKeyTip": "私鑰不存在,可能已被刪除/配置錯誤。",
@@ -136,8 +170,8 @@
"plugInType": "插入類型", "plugInType": "插入類型",
"port": "埠", "port": "埠",
"preferDiskAmount": "優先顯示硬碟容量", "preferDiskAmount": "優先顯示硬碟容量",
"preview": "預覽",
"privateKey": "私鑰", "privateKey": "私鑰",
"privateKeyNotFoundFmt": "未找到私鑰 [{keyId}]。",
"process": "處理程序", "process": "處理程序",
"prune": "修剪", "prune": "修剪",
"pushToken": "消息推送 Token", "pushToken": "消息推送 Token",
@@ -146,6 +180,7 @@
"pveVersionLow": "此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。", "pveVersionLow": "此功能目前處於測試階段,僅在 PVE 8+ 上進行過測試。請謹慎使用。",
"read": "讀取", "read": "讀取",
"reboot": "重開", "reboot": "重開",
"recentConnections": "最近連線記錄",
"rememberPwdInMem": "在記憶體中記住密碼", "rememberPwdInMem": "在記憶體中記住密碼",
"rememberPwdInMemTip": "用於容器、暫停等", "rememberPwdInMemTip": "用於容器、暫停等",
"rememberWindowSize": "記住視窗大小", "rememberWindowSize": "記住視窗大小",
@@ -166,6 +201,8 @@
"serverDetailOrder": "詳情頁部件順序", "serverDetailOrder": "詳情頁部件順序",
"serverFuncBtns": "伺服器功能按鈕", "serverFuncBtns": "伺服器功能按鈕",
"serverOrder": "伺服器順序", "serverOrder": "伺服器順序",
"serverTabRequired": "服務器標籤不能被移除",
"servers": "服務器",
"sftpDlPrepare": "準備連線至伺服器...", "sftpDlPrepare": "準備連線至伺服器...",
"sftpEditorTip": "如果為空, 使用App內建的檔案編輯器。如果有值, 則使用遠端伺服器的編輯器, 例如 `vim`(建議根據 `EDITOR` 自動獲取)。", "sftpEditorTip": "如果為空, 使用App內建的檔案編輯器。如果有值, 則使用遠端伺服器的編輯器, 例如 `vim`(建議根據 `EDITOR` 自動獲取)。",
"sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 來刪除檔案夾", "sftpRmrDirSummary": "在 SFTP 中使用 `rm -r` 來刪除檔案夾",
@@ -189,6 +226,12 @@
"sshConfigImportPermission": "您是否希望允許讀取 ~/.ssh/config 並自動匯入伺服器設定?", "sshConfigImportPermission": "您是否希望允許讀取 ~/.ssh/config 並自動匯入伺服器設定?",
"sshConfigImportTip": "在建立第一個伺服器時提示讀取 ~/.ssh/config", "sshConfigImportTip": "在建立第一個伺服器時提示讀取 ~/.ssh/config",
"sshConfigImported": "已從SSH設定匯入{count}個伺服器", "sshConfigImported": "已從SSH設定匯入{count}個伺服器",
"sshHostKeyChangedDesc": "伺服器 {serverName} 的 SSH 主機金鑰已變更,僅在信任該伺服器時繼續。",
"sshHostKeyFingerprintMd5Base64": "指紋MD5 Base64{fingerprint}",
"sshHostKeyFingerprintMd5Hex": "指紋MD5 十六進位):{fingerprint}",
"sshHostKeyType": "SSH 主機金鑰類型",
"sshHostKeyNewDesc": "收到來自 {serverName} 的新 SSH 主機金鑰,信任前請先檢查指紋。",
"sshHostKeyStoredFingerprint": "已儲存的指紋:{fingerprint}",
"sshConfigManualSelect": "是否要手動選擇 SSH 設定檔案?", "sshConfigManualSelect": "是否要手動選擇 SSH 設定檔案?",
"sshConfigNoServers": "SSH設定中未找到伺服器", "sshConfigNoServers": "SSH設定中未找到伺服器",
"sshConfigPermissionDenied": "由於 macOS 權限限制,無法存取 SSH 設定檔案。", "sshConfigPermissionDenied": "由於 macOS 權限限制,無法存取 SSH 設定檔案。",
@@ -206,10 +249,10 @@
"suspend": "當機", "suspend": "當機",
"suspendTip": "suspend 功能需要 root 權限及 systemd 支援。", "suspendTip": "suspend 功能需要 root 權限及 systemd 支援。",
"switchTo": "切換到 {val}", "switchTo": "切換到 {val}",
"sync": "同步",
"syncTip": "可能需要重新啟動,某些更改才能生效。", "syncTip": "可能需要重新啟動,某些更改才能生效。",
"system": "系統", "system": "系統",
"tag": "標籤", "tag": "標籤",
"tapToStartDiscovery": "點擊搜尋按鈕發現網路中的SSH服務器",
"temperature": "溫度", "temperature": "溫度",
"termFontSizeTip": "此設定將影響終端機大小(寬度和高度)。您可以在終端機頁面縮放,來調整目前會話的字型大小。", "termFontSizeTip": "此設定將影響終端機大小(寬度和高度)。您可以在終端機頁面縮放,來調整目前會話的字型大小。",
"terminal": "终端機", "terminal": "终端機",
@@ -220,6 +263,7 @@
"time": "時間", "time": "時間",
"times": "次", "times": "次",
"total": "總共", "total": "總共",
"totalAttempts": "總次數",
"traffic": "流量", "traffic": "流量",
"trySudo": "嘗試使用 sudo", "trySudo": "嘗試使用 sudo",
"ttl": "TTL", "ttl": "TTL",
@@ -228,7 +272,6 @@
"update": "更新", "update": "更新",
"updateIntervalEqual0": "設定為 0 將不自動刷新伺服器狀態,\n也無法計算 CPU 使用率。", "updateIntervalEqual0": "設定為 0 將不自動刷新伺服器狀態,\n也無法計算 CPU 使用率。",
"updateServerStatusInterval": "伺服器狀態更新間隔", "updateServerStatusInterval": "伺服器狀態更新間隔",
"upload": "上傳",
"upsideDown": "上下交換", "upsideDown": "上下交換",
"uptime": "運作時間", "uptime": "運作時間",
"useCdn": "使用 CDN", "useCdn": "使用 CDN",
@@ -237,6 +280,7 @@
"usePodmanByDefault": "預設使用 Podman", "usePodmanByDefault": "預設使用 Podman",
"used": "已使用", "used": "已使用",
"view": "檢視", "view": "檢視",
"viewDetails": "檢視詳情",
"viewErr": "查看錯誤", "viewErr": "查看錯誤",
"virtKeyHelpClipboard": "如果終端機有選中字元,則復製選中字元至剪貼簿,否則貼上剪貼簿內容至終端機。", "virtKeyHelpClipboard": "如果終端機有選中字元,則復製選中字元至剪貼簿,否則貼上剪貼簿內容至終端機。",
"virtKeyHelpIME": "打開/關閉鍵盤", "virtKeyHelpIME": "打開/關閉鍵盤",
@@ -249,39 +293,5 @@
"wolTip": "設定 WOL 後,每次連線伺服器時將自動發送喚醒請求", "wolTip": "設定 WOL 後,每次連線伺服器時將自動發送喚醒請求",
"write": "寫入", "write": "寫入",
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。", "writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。",
"writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。", "writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。"
"connectionStats": "連線統計",
"connectionStatsDesc": "檢視伺服器連線成功率和歷史記錄",
"noConnectionStatsData": "暫無連線統計資料",
"totalAttempts": "總次數",
"lastSuccess": "最後成功",
"lastFailure": "最後失敗",
"recentConnections": "最近連線記錄",
"viewDetails": "檢視詳情",
"connectionDetails": "連線詳情",
"clearThisServerStats": "清空此伺服器統計",
"clearAllStatsTitle": "清空所有統計",
"clearAllStatsContent": "確定要清空所有伺服器的連線統計資料嗎?此操作無法撤銷。",
"clearServerStatsTitle": "清空 {serverName} 統計",
"@clearServerStatsTitle": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"clearServerStatsContent": "確定要清空伺服器 \"{serverName}\" 的連線統計資料嗎?此操作無法撤銷。",
"@clearServerStatsContent": {
"placeholders": {
"serverName": {
"type": "String"
}
}
},
"homeTabs": "主頁標籤",
"homeTabsCustomizeDesc": "自訂主頁上顯示的標籤及其順序",
"reset": "重置",
"availableTabs": "可用標籤",
"atLeastOneTab": "至少需要選擇一個標籤",
"serverTabRequired": "服務器標籤不能被移除"
} }

View File

@@ -10,6 +10,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_ce_flutter/hive_flutter.dart'; import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:server_box/app.dart'; import 'package:server_box/app.dart';
import 'package:server_box/core/utils/executable_manager.dart';
import 'package:server_box/data/model/app/menu/server_func.dart'; import 'package:server_box/data/model/app/menu/server_func.dart';
import 'package:server_box/data/model/app/server_detail_card.dart'; import 'package:server_box/data/model/app/server_detail_card.dart';
import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
@@ -57,6 +58,9 @@ Future<void> _initData() async {
await PrefStore.shared.init(); // Call this before accessing any store await PrefStore.shared.init(); // Call this before accessing any store
await Stores.init(); await Stores.init();
// Initialize executable manager
await ExecutableManager.initialize();
// It may effect the following logic, so await it. // It may effect the following logic, so await it.
// DO DB migration before load any provider. // DO DB migration before load any provider.
await _doDbMigrate(); await _doDbMigrate();

View File

@@ -60,7 +60,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
], ],
[CenterGreyTitle(libL10n.import), _buildBulkImportServers, _buildImportSnippet], [CenterGreyTitle(libL10n.import), _buildBulkImportServers, _buildImportSnippet],
], ],
); );
} }
Widget get _buildBakPwd { Widget get _buildBakPwd {
@@ -240,7 +240,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
), ),
), ),
ListTile( ListTile(
title: Text(l10n.manual), title: Text(libL10n.manual),
trailing: webdavLoading.listenVal((loading) { trailing: webdavLoading.listenVal((loading) {
if (loading) return SizedLoading.small; if (loading) return SizedLoading.small;
@@ -301,7 +301,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
), ),
), ),
ListTile( ListTile(
title: Text(l10n.manual), title: Text(libL10n.manual),
trailing: gistLoading.listenVal((loading) { trailing: gistLoading.listenVal((loading) {
if (loading) return SizedLoading.small; if (loading) return SizedLoading.small;
@@ -400,7 +400,7 @@ final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKee
child: SingleChildScrollView(child: Text(libL10n.askContinue('${libL10n.import} [$snippetNames]'))), child: SingleChildScrollView(child: Text(libL10n.askContinue('${libL10n.import} [$snippetNames]'))),
actions: Btn.ok( actions: Btn.ok(
onTap: () { onTap: () {
final notifier = ref.read(snippetNotifierProvider.notifier); final notifier = ref.read(snippetProvider.notifier);
for (final snippet in snippets) { for (final snippet in snippets) {
notifier.add(snippet); notifier.add(snippet);
} }
@@ -450,7 +450,7 @@ extension on _BackupPageState {
await Webdav.shared.upload(relativePath: bakName); await Webdav.shared.upload(relativePath: bakName);
Loggers.app.info('Upload webdav backup success'); Loggers.app.info('Upload webdav backup success');
} catch (e, s) { } catch (e, s) {
context.showErrDialog(e, s, l10n.upload); context.showErrDialog(e, s, libL10n.upload);
Loggers.app.warning('Upload webdav backup failed', e, s); Loggers.app.warning('Upload webdav backup failed', e, s);
} finally { } finally {
webdavLoading.value = false; webdavLoading.value = false;
@@ -489,7 +489,7 @@ extension on _BackupPageState {
await GistRs.shared.upload(relativePath: bakName); await GistRs.shared.upload(relativePath: bakName);
Loggers.app.info('Upload gist backup success'); Loggers.app.info('Upload gist backup success');
} catch (e, s) { } catch (e, s) {
context.showErrDialog(e, s, l10n.upload); context.showErrDialog(e, s, libL10n.upload);
Loggers.app.warning('Upload gist backup failed', e, s); Loggers.app.warning('Upload gist backup failed', e, s);
} finally { } finally {
gistLoading.value = false; gistLoading.value = false;

View File

@@ -77,7 +77,7 @@ extension on _ContainerPageState {
Future<void> _showAddCmdPreview(String cmd) async { Future<void> _showAddCmdPreview(String cmd) async {
await context.showRoundDialog( await context.showRoundDialog(
title: l10n.preview, title: libL10n.preview,
child: Text(cmd), child: Text(cmd),
actions: [ actions: [
TextButton(onPressed: () => context.pop(), child: Text(libL10n.cancel)), TextButton(onPressed: () => context.pop(), child: Text(libL10n.cancel)),

View File

@@ -43,8 +43,8 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final serverState = ref.read(serverNotifierProvider(widget.args.spi.id)); final serverState = ref.read(serverProvider(widget.args.spi.id));
_provider = containerNotifierProvider( _provider = containerProvider(
serverState.client, serverState.client,
widget.args.spi.user, widget.args.spi.user,
widget.args.spi.id, widget.args.spi.id,

View File

@@ -1,4 +1,5 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/foundation.dart' show kReleaseMode;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:responsive_framework/responsive_framework.dart'; import 'package:responsive_framework/responsive_framework.dart';
@@ -31,15 +32,19 @@ class _HomePageState extends ConsumerState<HomePage>
bool _shouldAuth = false; bool _shouldAuth = false;
DateTime? _pausedTime; DateTime? _pausedTime;
late final _notifier = ref.read(serversNotifierProvider.notifier); late final _notifier = ref.read(serversProvider.notifier);
late final _provider = ref.read(serversNotifierProvider); late final _provider = ref.read(serversProvider);
late List<AppTab> _tabs = Stores.setting.homeTabs.fetch(); late List<AppTab> _tabs = Stores.setting.homeTabs.fetch();
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
Future(() => _notifier.closeServer()); // In release builds (real app exit), close connections.
// In debug (hot reload), avoid forcing disconnects.
if (kReleaseMode) {
Future(() => _notifier.closeServer());
}
_pageController.dispose(); _pageController.dispose();
WakelockPlus.disable(); WakelockPlus.disable();

View File

@@ -131,7 +131,7 @@ class _PingPageState extends ConsumerState<PingPage> with AutomaticKeepAliveClie
return; return;
} }
if (ref.read(serversNotifierProvider).serverOrder.isEmpty) { if (ref.read(serversProvider).serverOrder.isEmpty) {
context.showSnackBar(l10n.pingNoServer); context.showSnackBar(l10n.pingNoServer);
return; return;
} }
@@ -143,8 +143,8 @@ class _PingPageState extends ConsumerState<PingPage> with AutomaticKeepAliveClie
} }
await Future.wait( await Future.wait(
ref.read(serversNotifierProvider).servers.values.map((spi) async { ref.read(serversProvider).servers.values.map((spi) async {
final serverState = ref.read(serverNotifierProvider(spi.id)); final serverState = ref.read(serverProvider(spi.id));
if (serverState.client == null) { if (serverState.client == null) {
return; return;
} }

View File

@@ -40,7 +40,7 @@ class _PrivateKeyEditPageState extends ConsumerState<PrivateKeyEditPage> {
final _loading = ValueNotifier<Widget?>(null); final _loading = ValueNotifier<Widget?>(null);
late final _notifier = ref.read(privateKeyNotifierProvider.notifier); late final _notifier = ref.read(privateKeyProvider.notifier);
PrivateKeyInfo? get pki => widget.args?.pki; PrivateKeyInfo? get pki => widget.args?.pki;

View File

@@ -32,7 +32,7 @@ class _PrivateKeyListState extends ConsumerState<PrivateKeysListPage> with After
} }
Widget _buildBody() { Widget _buildBody() {
final privateKeyState = ref.watch(privateKeyNotifierProvider); final privateKeyState = ref.watch(privateKeyProvider);
final pkis = privateKeyState.keys; final pkis = privateKeyState.keys;
if (pkis.isEmpty) { if (pkis.isEmpty) {

View File

@@ -37,7 +37,7 @@ class _ProcessPageState extends ConsumerState<ProcessPage> {
ProcSortMode _procSortMode = ProcSortMode.cpu; ProcSortMode _procSortMode = ProcSortMode.cpu;
List<ProcSortMode> _sortModes = List.from(ProcSortMode.values); List<ProcSortMode> _sortModes = List.from(ProcSortMode.values);
late final _provider = serverNotifierProvider(widget.args.spi.id); late final _provider = serverProvider(widget.args.spi.id);
@override @override
void dispose() { void dispose() {

View File

@@ -33,7 +33,7 @@ final class _PvePageState extends ConsumerState<PvePage> {
late MediaQueryData _media; late MediaQueryData _media;
Timer? _timer; Timer? _timer;
late final _provider = pveNotifierProvider(widget.args.spi); late final _provider = pveProvider(widget.args.spi);
late final _notifier = ref.read(_provider.notifier); late final _notifier = ref.read(_provider.notifier);
@override @override

Some files were not shown because too many files have changed in this diff Show More