mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 08:42:43 +08:00
[feat] add system language support across desktop and mobile
This commit is contained in:
@@ -427,7 +427,7 @@ int main(int argc, char* argv[]) {
|
||||
CSimpleIniA config;
|
||||
config.SetUnicode(true);
|
||||
config.LoadFile((paths.GetCachePath() / "config.ini").string().c_str());
|
||||
console.SetLanguage(static_cast<int>(config.GetLongValue("Settings", "language", 0)));
|
||||
console.SetLanguage(static_cast<int>(config.GetLongValue("Settings", "language", 10)));
|
||||
std::cout << console.Text("console_cli_help");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ int ConfigCenter::Load() {
|
||||
const long language_value =
|
||||
ini_->GetLongValue(section_, "language", static_cast<long>(language_));
|
||||
if (language_value < static_cast<long>(LANGUAGE::CHINESE) ||
|
||||
language_value > static_cast<long>(LANGUAGE::RUSSIAN)) {
|
||||
language_value > static_cast<long>(LANGUAGE::SYSTEM)) {
|
||||
language_ = LANGUAGE::ENGLISH;
|
||||
} else {
|
||||
language_ = static_cast<LANGUAGE>(language_value);
|
||||
@@ -261,7 +261,7 @@ int ConfigCenter::Save() {
|
||||
|
||||
int ConfigCenter::SetLanguage(LANGUAGE language) {
|
||||
const int value = static_cast<int>(language);
|
||||
if (value < 0 || value > 2 || StoreValues({{"language", std::to_string(value)}}) != 0) return -1;
|
||||
if (value < 0 || value > static_cast<int>(LANGUAGE::SYSTEM) || StoreValues({{"language", std::to_string(value)}}) != 0) return -1;
|
||||
language_ = language;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ namespace crossdesk {
|
||||
|
||||
class ConfigCenter {
|
||||
public:
|
||||
enum class LANGUAGE { CHINESE = 0, ENGLISH = 1, RUSSIAN = 2 };
|
||||
enum class LANGUAGE {
|
||||
CHINESE = 0, ENGLISH = 1, RUSSIAN = 2, TRADITIONAL_CHINESE = 3,
|
||||
JAPANESE = 4, KOREAN = 5, GERMAN = 6, FRENCH = 7, SPANISH = 8,
|
||||
BRAZILIAN_PORTUGUESE = 9, SYSTEM = 10
|
||||
};
|
||||
enum class CONNECTION_MODE { DEVICE_ID = 0, DIRECT_IP = 1 };
|
||||
enum class VIDEO_ENCODE_FORMAT { H264 = 0, AV1 = 1 };
|
||||
enum class TURN_MODE {
|
||||
@@ -93,7 +97,7 @@ class ConfigCenter {
|
||||
std::unique_ptr<IniStorage> ini_;
|
||||
const char* section_ = "Settings";
|
||||
|
||||
LANGUAGE language_ = LANGUAGE::CHINESE;
|
||||
LANGUAGE language_ = LANGUAGE::SYSTEM;
|
||||
CONNECTION_MODE connection_mode_ = CONNECTION_MODE::DEVICE_ID;
|
||||
bool enable_ip_direct_ = true;
|
||||
ScreenCaptureMethod screen_capture_method_ = ScreenCaptureMethod::Auto;
|
||||
|
||||
@@ -1201,9 +1201,8 @@ void GuiApplication::InitializeSettings() {
|
||||
settings_.LoadRecentConnectionAliases();
|
||||
localization_language_index_ =
|
||||
localization::detail::ClampLanguageIndex(language_button_value_);
|
||||
language_button_value_ = localization_language_index_;
|
||||
localization_language_ =
|
||||
static_cast<ConfigCenter::LANGUAGE>(localization_language_index_);
|
||||
static_cast<ConfigCenter::LANGUAGE>(language_button_value_);
|
||||
privacy_.SetText({
|
||||
localization::privacy_screen_unlock_hint[localization_language_index_]});
|
||||
}
|
||||
@@ -1563,11 +1562,7 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
if (config_center_->SetConnectionMode(mode) != 0) {
|
||||
LOG_WARN("Could not save connection mode preference");
|
||||
main->set_offline_warning(UiText(
|
||||
localization_language_index_ == 0
|
||||
? "无法保存连接模式,下次启动可能不会恢复当前模式。"
|
||||
: localization_language_index_ == 2
|
||||
? "Не удалось сохранить режим подключения. При следующем запуске он может не восстановиться."
|
||||
: "Could not save the connection mode. It may not be restored on the next launch."));
|
||||
localization::Literal("无法保存连接模式,下次启动可能不会恢复当前模式。", localization_language_index_)));
|
||||
}
|
||||
});
|
||||
main->on_toggle_gallery_fullscreen(
|
||||
@@ -3078,13 +3073,9 @@ void GuiApplication::UpdateLocalization() {
|
||||
return;
|
||||
}
|
||||
#if _WIN32
|
||||
// All views refer to static literals; selecting the text needs no allocation
|
||||
// and only runs when the language changes. Tick still checks connection state.
|
||||
ui_->shutdown_reason = language == 0
|
||||
? L"远程控制连接中,保留连接以确认或取消关机。"
|
||||
: language == 2
|
||||
? L"Удалённый сеанс активен. Соединение сохранено для подтверждения или отмены выключения."
|
||||
: L"Remote session active. Keeping the connection available to confirm or cancel shutdown.";
|
||||
static std::vector<std::wstring> shutdown_reasons(localization::GetSupportedLanguages().size());
|
||||
ui_->shutdown_reason = localization::GetWindowsLocalizedLabel(
|
||||
language, "shutdown_reason", L"Remote session active.", shutdown_reasons);
|
||||
#endif
|
||||
ui_localization::ApplyMainWindowStrings(ui_->main, language);
|
||||
ui_localization::ApplyStreamWindowStrings(ui_->main, language);
|
||||
@@ -3247,13 +3238,12 @@ void GuiApplication::SyncMainWindow() {
|
||||
|
||||
// Keep this filter separate from DeskHub's current group, keyed by name so
|
||||
// adding/removing groups does not silently select a different one by index.
|
||||
const auto tr_group = [this](const char* zh, const char* en, const char* ru) {
|
||||
return UiText(localization_language_index_ == 0 ? zh
|
||||
: localization_language_index_ == 2 ? ru : en);
|
||||
const auto tr_group = [this](const char* zh) {
|
||||
return UiText(localization::Literal(zh, localization_language_index_).c_str());
|
||||
};
|
||||
std::vector<slint::SharedString> recent_groups{
|
||||
tr_group("全部设备", "All devices", "Все устройства"),
|
||||
tr_group("未分组", "Ungrouped", "Без группы")};
|
||||
tr_group("全部设备"),
|
||||
tr_group("未分组")};
|
||||
const bool lan_mode = ui_->main->get_lan_mode();
|
||||
int recent_group_index = 0;
|
||||
if (!lan_mode && ui_->recent_group_filter) {
|
||||
@@ -4565,9 +4555,7 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
// common save succeeds. Restore the previous wall file if it fails.
|
||||
if (!wall_settings || (wall_changed && !updated_wall.Save(ui_->gallery_path))) {
|
||||
main->set_gallery_settings_error(UiText(
|
||||
localization_language_index_ == 0 ? "保存失败,请重试"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить. Повторите попытку"
|
||||
: "Could not save; try again"));
|
||||
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||
main->set_settings_tab(1);
|
||||
return false;
|
||||
}
|
||||
@@ -4586,9 +4574,7 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
|
||||
LOG_ERROR("Failed to restore DeskHub settings after IP direct save failure");
|
||||
main->set_settings_save_error(UiText(
|
||||
localization_language_index_ == 0 ? "无法保存 IP 连接设置,请重试。"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить настройки подключения по IP. Повторите попытку."
|
||||
: "Could not save IP connection settings. Try again."));
|
||||
localization::Literal("无法保存 IP 连接设置,请重试。", localization_language_index_)));
|
||||
return false;
|
||||
}
|
||||
#if defined(_WIN32) || defined(__APPLE__) || (defined(__linux__) && !defined(__OHOS__))
|
||||
@@ -4596,22 +4582,18 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
|
||||
LOG_ERROR("Failed to restore DeskHub settings after lock preference save failure");
|
||||
main->set_settings_save_error(UiText(
|
||||
localization_language_index_ == 0 ? "无法保存断开后锁屏设置,请重试。"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить настройку блокировки при отключении. Повторите попытку."
|
||||
: "Could not save lock-on-disconnect settings. Try again."));
|
||||
localization::Literal("无法保存断开后锁屏设置,请重试。", localization_language_index_)));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (config_center_->SetPrivacyLocalUnlock(main->get_privacy_local_unlock_allowed()) != 0) {
|
||||
if (wall_changed) ui_->gallery.Save(ui_->gallery_path);
|
||||
main->set_settings_save_error(UiText(
|
||||
localization_language_index_ == 0 ? "无法保存隐私屏解锁设置,请重试。"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить настройку разблокировки. Повторите попытку."
|
||||
: "Could not save privacy unlock settings. Try again."));
|
||||
localization::Literal("无法保存隐私屏解锁设置,请重试。", localization_language_index_)));
|
||||
return false;
|
||||
}
|
||||
language_button_value_ =
|
||||
localization::detail::ClampLanguageIndex(main->get_language_index());
|
||||
std::clamp(main->get_language_index(), 0, localization::kSystemLanguage);
|
||||
video_encode_format_button_value_ = std::clamp(main->get_codec_index(), 0, 1);
|
||||
enable_self_hosted_ = main->get_self_hosted_enabled();
|
||||
#if !defined(__OHOS__)
|
||||
@@ -4621,7 +4603,7 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
|
||||
|
||||
localization_language_ =
|
||||
static_cast<ConfigCenter::LANGUAGE>(language_button_value_);
|
||||
localization_language_index_ = language_button_value_;
|
||||
localization_language_index_ = localization::detail::ClampLanguageIndex(language_button_value_);
|
||||
config_center_->SetLanguage(localization_language_);
|
||||
privacy_.SetText(
|
||||
{localization::privacy_screen_unlock_hint[localization_language_index_],
|
||||
@@ -4773,10 +4755,7 @@ void GuiApplication::ConnectFromUi(const std::string& input) {
|
||||
const std::string remote_id = CompactPeerId(input);
|
||||
if (!gallery::Workspace::ValidId(remote_id)) {
|
||||
ui_->main->set_connection_input_error(
|
||||
UiText(localization_language_index_ == 0 ? "请输入有效的 9 位设备 ID"
|
||||
: localization_language_index_ == 2
|
||||
? "Введите корректный 9-значный ID устройства"
|
||||
: "Enter a valid 9-digit device ID"));
|
||||
UiText(localization::Literal("请输入有效的 9 位设备 ID", localization_language_index_)));
|
||||
return;
|
||||
}
|
||||
ui_->main->set_connection_input_error("");
|
||||
@@ -5330,41 +5309,31 @@ bool GuiApplication::AddGalleryDevice(const std::string& input,
|
||||
const std::string& password,
|
||||
const std::string& group,
|
||||
bool remember_password) {
|
||||
const auto error = [this](const char* zh, const char* en, const char* ru) {
|
||||
const auto error = [this](const char* zh) {
|
||||
ui_->wall->set_gallery_notice(UiText(
|
||||
localization_language_index_ == 0 ? zh
|
||||
: localization_language_index_ == 2 ? ru : en));
|
||||
localization::Literal(zh, localization_language_index_).c_str()));
|
||||
return false;
|
||||
};
|
||||
const auto id = gallery::Workspace::NormalizeId(input);
|
||||
if (!gallery::Workspace::ValidId(id))
|
||||
return error("请输入有效的 9 位设备 ID",
|
||||
"Enter a valid 9-digit device ID",
|
||||
"Введите корректный 9-значный ID устройства");
|
||||
return error("请输入有效的 9 位设备 ID");
|
||||
if (password.empty() || password.size() > 6 ||
|
||||
password.find_first_not_of(
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") !=
|
||||
std::string::npos)
|
||||
return error("请输入 1–6 位英文字母或数字密码",
|
||||
"Use 1–6 letters (A–Z, a–z) or digits (0–9)",
|
||||
"Пароль: 1–6 латинских букв или цифр");
|
||||
return error("请输入 1–6 位英文字母或数字密码");
|
||||
if (!signal_connected_)
|
||||
return error("尚未连接服务器,请稍后重试",
|
||||
"Not connected to the server; try again shortly",
|
||||
"Нет соединения с сервером. Повторите позже");
|
||||
return error("尚未连接服务器,请稍后重试");
|
||||
using State = SlintUi::BatchOperation::State;
|
||||
for (const auto& operation : ui_->batch_operations)
|
||||
if (operation.id == id && operation.action == "connect" &&
|
||||
(operation.state == State::Queued || operation.state == State::Running))
|
||||
return error("该设备正在连接,请稍候",
|
||||
"This device is already connecting",
|
||||
"Устройство уже подключается");
|
||||
return error("该设备正在连接,请稍候");
|
||||
|
||||
auto updated = ui_->gallery;
|
||||
updated.Add(id, Trim(group));
|
||||
if (!updated.Save(ui_->gallery_path))
|
||||
return error("保存失败,请重试", "Could not save; try again",
|
||||
"Не удалось сохранить. Повторите попытку");
|
||||
return error("保存失败,请重试");
|
||||
ui_->gallery = std::move(updated);
|
||||
ui_->gallery_hidden_active_ids.erase(id);
|
||||
if (auto props = FindRemoteSession(id)) {
|
||||
@@ -5406,9 +5375,7 @@ bool GuiApplication::ImportGalleryRecent(const std::string& input_group) {
|
||||
updated.AddMany(ids, group);
|
||||
if (!updated.Save(ui_->gallery_path)) {
|
||||
ui_->wall->set_gallery_notice(UiText(
|
||||
localization_language_index_ == 0 ? "保存失败,请重试"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить. Повторите попытку"
|
||||
: "Could not save; try again"));
|
||||
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||
return false;
|
||||
}
|
||||
ui_->gallery = std::move(updated);
|
||||
@@ -5488,9 +5455,7 @@ bool GuiApplication::SaveDeviceGroups(const std::string& new_group,
|
||||
auto updated = ui_->gallery;
|
||||
if (!updated.SetGroups(id, names) || !updated.Save(ui_->gallery_path)) {
|
||||
main->set_device_group_error(UiText(
|
||||
localization_language_index_ == 0 ? "保存失败,请重试"
|
||||
: localization_language_index_ == 2 ? "Не удалось сохранить. Повторите попытку"
|
||||
: "Could not save; try again"));
|
||||
localization::Literal("保存失败,请重试", localization_language_index_)));
|
||||
return false;
|
||||
}
|
||||
// Removing an empty group shifts indices. Retain the filter by name instead
|
||||
@@ -5528,10 +5493,8 @@ void GuiApplication::GalleryAction(const std::string& action,
|
||||
ui_->gallery_network_synced = {};
|
||||
return;
|
||||
}
|
||||
const auto tr = [this](const char* zh, const char* en, const char* ru) {
|
||||
return UiText(localization_language_index_ == 0 ? zh
|
||||
: localization_language_index_ == 2 ? ru
|
||||
: en);
|
||||
const auto tr = [this](const char* zh) {
|
||||
return UiText(localization::Literal(zh, localization_language_index_).c_str());
|
||||
};
|
||||
if (action == "recent-open") {
|
||||
ReleaseControl();
|
||||
@@ -5749,7 +5712,7 @@ void GuiApplication::GalleryAction(const std::string& action,
|
||||
SyncGallery();
|
||||
};
|
||||
if (action == "file") {
|
||||
OpenFileDialog(std::string(tr("选择分发文件", "Select a file to send", "Выберите файл для отправки")),
|
||||
OpenFileDialog(std::string(tr("选择分发文件")),
|
||||
[enqueue](std::string file) {
|
||||
if (!file.empty()) enqueue(file);
|
||||
});
|
||||
@@ -6026,11 +5989,8 @@ void GuiApplication::SyncGallery() {
|
||||
ui_->wall->set_gallery_recent_available(available);
|
||||
ui_->wall->set_gallery_recent_selected(selected);
|
||||
}
|
||||
const auto tr = [this](const char* zh, const char* en,
|
||||
const char* ru) -> std::string {
|
||||
return localization_language_index_ == 0 ? zh
|
||||
: localization_language_index_ == 2 ? ru
|
||||
: en;
|
||||
const auto tr = [this](const char* zh) -> std::string {
|
||||
return localization::Literal(zh, localization_language_index_).c_str();
|
||||
};
|
||||
const auto name = [this](const std::string& id) {
|
||||
for (const auto& [_, recent] : recent_connections_)
|
||||
@@ -6070,10 +6030,10 @@ void GuiApplication::SyncGallery() {
|
||||
device.id = UiText(FormatPeerId(id.c_str()));
|
||||
const auto mode = props->traversal_mode_.load();
|
||||
device.mode = UiText(mode == TraversalMode::P2P
|
||||
? tr("直连", "Direct", "Прямой")
|
||||
? tr("直连")
|
||||
: mode == TraversalMode::UnknownMode
|
||||
? tr("等待统计", "Awaiting stats", "Ожидание статистики")
|
||||
: tr("中继", "Relay", "Ретрансляция"));
|
||||
? tr("等待统计")
|
||||
: tr("中继"));
|
||||
device.inbound = device.outbound = device.loss = "—";
|
||||
device.video_latency = "—";
|
||||
// Prefer the wall's own frame path. An independently displayed stream
|
||||
@@ -6241,7 +6201,7 @@ void GuiApplication::SyncGallery() {
|
||||
label += member.group;
|
||||
}
|
||||
std::vector<slint::SharedString> groups{
|
||||
UiText(tr("全部设备", "All devices", "Все устройства"))};
|
||||
UiText(tr("全部设备"))};
|
||||
for (const auto& group : wall.groups) groups.push_back(UiText(group));
|
||||
const auto old_groups = ui_->wall->get_gallery_groups();
|
||||
bool changed_groups = !old_groups || old_groups->row_count() != groups.size();
|
||||
@@ -6325,8 +6285,8 @@ void GuiApplication::SyncGallery() {
|
||||
tile.online = tile.connected || online.value_or(false);
|
||||
tile.presence_known = tile.connected || online.has_value();
|
||||
tile.status = UiText(
|
||||
tile.connected ? tr("等待画面", "Waiting for video", "Ожидание видео")
|
||||
: tr("未连接", "Not connected", "Нет соединения"));
|
||||
tile.connected ? tr("等待画面")
|
||||
: tr("未连接"));
|
||||
const bool queued = !tile.connected && std::any_of(
|
||||
ui_->batch_operations.begin(), ui_->batch_operations.end(),
|
||||
[&](const auto& operation) {
|
||||
@@ -6335,9 +6295,9 @@ void GuiApplication::SyncGallery() {
|
||||
});
|
||||
// A queued retry takes precedence over errors from the previous session.
|
||||
if (queued)
|
||||
tile.status = UiText(tr("队列中", "Queued", "В очереди"));
|
||||
tile.status = UiText(tr("队列中"));
|
||||
else if (props && props->connection_auth_.Get().password_required)
|
||||
tile.status = UiText(tr("需要密码", "Password required", "Нужен пароль"));
|
||||
tile.status = UiText(tr("需要密码"));
|
||||
else if (props && !tile.connected)
|
||||
tile.status = UiText(ConnectionStatusText(
|
||||
props->connection_status_.load(), localization_language_index_));
|
||||
@@ -6365,8 +6325,8 @@ void GuiApplication::SyncGallery() {
|
||||
tile.has_frame = true;
|
||||
tile.stale = now - props->gallery_frame_at_ > 5s;
|
||||
tile.status =
|
||||
UiText(tile.stale ? tr("画面已停留", "Frame stale", "Кадр устарел")
|
||||
: tr("只读预览", "View only", "Просмотр"));
|
||||
UiText(tile.stale ? tr("画面已停留")
|
||||
: tr("只读预览"));
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
|
||||
@@ -1,116 +1,91 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
#ifndef _LOCALIZATION_H_
|
||||
#define _LOCALIZATION_H_
|
||||
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-05-29
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
#ifndef _LOCALIZATION_H_
|
||||
#define _LOCALIZATION_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "localization_data.h"
|
||||
#include "system_language.h"
|
||||
|
||||
#if _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
namespace localization {
|
||||
|
||||
|
||||
namespace crossdesk {
|
||||
namespace localization {
|
||||
|
||||
struct LanguageOption {
|
||||
std::string code;
|
||||
std::string display_name;
|
||||
};
|
||||
|
||||
class LocalizedString {
|
||||
public:
|
||||
constexpr explicit LocalizedString(const char* key) : key_(key) {}
|
||||
const std::string& operator[](int language_index) const;
|
||||
|
||||
private:
|
||||
const char* key_;
|
||||
};
|
||||
|
||||
inline const std::vector<LanguageOption>& GetSupportedLanguages() {
|
||||
static const std::vector<LanguageOption> kSupportedLanguages = {
|
||||
{"zh-CN", reinterpret_cast<const char*>(u8"中文")},
|
||||
{"en-US", "English"},
|
||||
{"ru-RU", reinterpret_cast<const char*>(u8"Русский")}};
|
||||
return kSupportedLanguages;
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr explicit LocalizedString(const char* key) : key_(key) {}
|
||||
const std::string& operator[](int language_index) const;
|
||||
|
||||
private:
|
||||
const char* key_;
|
||||
};
|
||||
|
||||
inline const std::vector<LanguageOption>& GetSupportedLanguages() {
|
||||
static const std::vector<LanguageOption> kSupportedLanguages = {
|
||||
{"zh-CN", "简体中文"}, {"en-US", "English"}, {"ru-RU", "Русский"},
|
||||
{"zh-Hant", "繁體中文"}, {"ja", "日本語"}, {"ko", "한국어"},
|
||||
{"de", "Deutsch"}, {"fr", "Français"}, {"es", "Español"},
|
||||
{"pt-BR", "Português (Brasil)"}};
|
||||
return kSupportedLanguages;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline int ClampLanguageIndex(int language_index) {
|
||||
if (language_index >= 0 &&
|
||||
language_index < static_cast<int>(GetSupportedLanguages().size())) {
|
||||
return language_index;
|
||||
}
|
||||
return 0;
|
||||
if (language_index == kSystemLanguage) return SystemLanguage();
|
||||
return language_index >= 0 && language_index < kSystemLanguage
|
||||
? language_index : kEnglishLanguage;
|
||||
}
|
||||
|
||||
using TranslationTable =
|
||||
std::unordered_map<std::string,
|
||||
std::unordered_map<std::string, std::string>>;
|
||||
|
||||
inline std::unordered_map<std::string, std::string> MakeLocalizedValues(
|
||||
const TranslationRow& row) {
|
||||
return {{"zh-CN", reinterpret_cast<const char*>(row.zh)},
|
||||
{"en-US", reinterpret_cast<const char*>(row.en)},
|
||||
{"ru-RU", reinterpret_cast<const char*>(row.ru)}};
|
||||
}
|
||||
|
||||
inline TranslationTable BuildTranslationTable() {
|
||||
TranslationTable table;
|
||||
for (const auto& row : kTranslationRows) {
|
||||
table[row.key] = MakeLocalizedValues(row);
|
||||
}
|
||||
|
||||
using TranslationTable = std::unordered_map<std::string, std::vector<std::string>>;
|
||||
inline const TranslationTable& LiteralTable() {
|
||||
static const TranslationTable table = [] {
|
||||
TranslationTable result;
|
||||
for (const auto& row : kTranslationRows)
|
||||
result.emplace(row.key, std::vector<std::string>(std::begin(row.text), std::end(row.text)));
|
||||
return result;
|
||||
}();
|
||||
return table;
|
||||
}
|
||||
|
||||
inline const TranslationTable& GetTranslationTable() {
|
||||
static const TranslationTable table = BuildTranslationTable();
|
||||
return table;
|
||||
}
|
||||
|
||||
inline const std::string& GetTranslatedText(const std::string& key,
|
||||
int language_index) {
|
||||
static const std::string kEmptyText = "";
|
||||
|
||||
const auto& table = GetTranslationTable();
|
||||
const auto key_it = table.find(key);
|
||||
if (key_it == table.end()) {
|
||||
return kEmptyText;
|
||||
}
|
||||
|
||||
const auto& localized_values = key_it->second;
|
||||
const std::string& language_code =
|
||||
GetSupportedLanguages()[ClampLanguageIndex(language_index)].code;
|
||||
|
||||
const auto exact_it = localized_values.find(language_code);
|
||||
if (exact_it != localized_values.end()) {
|
||||
return exact_it->second;
|
||||
}
|
||||
|
||||
const auto english_it = localized_values.find("en-US");
|
||||
if (english_it != localized_values.end()) {
|
||||
return english_it->second;
|
||||
}
|
||||
|
||||
const auto chinese_it = localized_values.find("zh-CN");
|
||||
if (chinese_it != localized_values.end()) {
|
||||
return chinese_it->second;
|
||||
}
|
||||
|
||||
return kEmptyText;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline const std::string& GetLiteralText(const std::string& key, int language) {
|
||||
static const std::string empty;
|
||||
const auto it = LiteralTable().find(key);
|
||||
if (it == LiteralTable().end()) return empty;
|
||||
const auto& values = it->second;
|
||||
return values[ClampLanguageIndex(language)];
|
||||
}
|
||||
inline const std::string& GetTranslatedText(const std::string& key, int language) {
|
||||
static const std::unordered_map<std::string, std::string> names = [] {
|
||||
std::unordered_map<std::string, std::string> result;
|
||||
for (const auto& row : kNamedTranslations) result.emplace(row.name, row.literal);
|
||||
return result;
|
||||
}();
|
||||
static const std::string empty;
|
||||
const auto it = names.find(key);
|
||||
return it == names.end() ? empty : GetLiteralText(it->second, language);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline const std::string& Literal(const std::string& key, int language) {
|
||||
return detail::GetLiteralText(key, language);
|
||||
}
|
||||
|
||||
inline const std::string& LocalizedString::operator[](
|
||||
int language_index) const {
|
||||
return detail::GetTranslatedText(key_, language_index);
|
||||
@@ -130,11 +105,11 @@ inline std::string FormatDisplayLabel(size_t display_index,
|
||||
return label;
|
||||
}
|
||||
|
||||
#define CROSSDESK_DECLARE_LOCALIZED_STRING(name, zh, en, ru) \
|
||||
inline const LocalizedString name(#name);
|
||||
CROSSDESK_LOCALIZATION_ALL(CROSSDESK_DECLARE_LOCALIZED_STRING)
|
||||
#undef CROSSDESK_DECLARE_LOCALIZED_STRING
|
||||
|
||||
#define CROSSDESK_DECLARE_LOCALIZED_STRING(name) \
|
||||
inline const LocalizedString name(#name);
|
||||
CROSSDESK_LOCALIZATION_ALL(CROSSDESK_DECLARE_LOCALIZED_STRING)
|
||||
#undef CROSSDESK_DECLARE_LOCALIZED_STRING
|
||||
|
||||
#if _WIN32
|
||||
inline const wchar_t* GetWindowsLocalizedLabel(
|
||||
int language_index, const char* key, const wchar_t* fallback,
|
||||
@@ -187,8 +162,8 @@ inline const wchar_t* GetExitProgramLabel(int language_index) {
|
||||
cache);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace localization
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace localization
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-10-27
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SYSTEM_LANGUAGE_H_
|
||||
#define _SYSTEM_LANGUAGE_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
namespace localization {
|
||||
|
||||
inline constexpr int kSystemLanguage = 10;
|
||||
inline constexpr int kEnglishLanguage = 1;
|
||||
|
||||
// Keep 0/1/2 compatible with existing Chinese/English/Russian config files.
|
||||
inline int MatchLanguage(std::string tag) {
|
||||
for (char& c : tag) {
|
||||
if (c == '_') c = '-';
|
||||
else if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
|
||||
}
|
||||
tag = tag.substr(0, tag.find_first_of(".@"));
|
||||
const auto base = tag.substr(0, tag.find('-'));
|
||||
if (base == "zh") {
|
||||
const auto parts = "-" + tag + "-";
|
||||
if (parts.find("-hans-") != std::string::npos) return 0;
|
||||
return parts.find("-hant-") != std::string::npos ||
|
||||
parts.find("-tw-") != std::string::npos ||
|
||||
parts.find("-hk-") != std::string::npos ||
|
||||
parts.find("-mo-") != std::string::npos ? 3 : 0;
|
||||
}
|
||||
const char* codes[] = {"zh", "en", "ru", "zh", "ja", "ko", "de", "fr", "es", "pt"};
|
||||
for (int i = 1; i < kSystemLanguage; ++i)
|
||||
if (i != 3 && base == codes[i]) return i;
|
||||
if (base == "c" || base == "posix") return kEnglishLanguage;
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline int MatchPreferredLanguages(const std::vector<std::string>& tags) {
|
||||
for (const auto& tag : tags) {
|
||||
const int match = MatchLanguage(tag);
|
||||
if (match >= 0) return match;
|
||||
}
|
||||
return kEnglishLanguage;
|
||||
}
|
||||
|
||||
inline int DetectSystemLanguage() {
|
||||
std::vector<std::string> tags;
|
||||
#if defined(_WIN32)
|
||||
ULONG count = 0, length = 0;
|
||||
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &count, nullptr, &length);
|
||||
std::vector<wchar_t> names(length);
|
||||
if (length && GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &count, names.data(), &length)) {
|
||||
for (const wchar_t* name = names.data(); *name; name += wcslen(name) + 1) {
|
||||
std::string tag;
|
||||
for (const wchar_t* c = name; *c; ++c) tag += static_cast<char>(*c);
|
||||
tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
if (CFArrayRef names = CFLocaleCopyPreferredLanguages()) {
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(names); ++i) {
|
||||
char tag[128] = {};
|
||||
auto name = static_cast<CFStringRef>(CFArrayGetValueAtIndex(names, i));
|
||||
if (CFStringGetCString(name, tag, sizeof(tag), kCFStringEncodingUTF8)) tags.emplace_back(tag);
|
||||
}
|
||||
CFRelease(names);
|
||||
}
|
||||
#else
|
||||
// The Harmony desktop host supplies LANGUAGE from the system i18n API.
|
||||
const char* locale = std::getenv("LC_ALL");
|
||||
if (!locale || !*locale) locale = std::getenv("LC_MESSAGES");
|
||||
if (!locale || !*locale) locale = std::getenv("LANG");
|
||||
if (locale && (std::string(locale) == "C" || std::string(locale) == "POSIX")) return kEnglishLanguage;
|
||||
if (const char* preferred = std::getenv("LANGUAGE")) {
|
||||
std::string list(preferred);
|
||||
size_t start = 0;
|
||||
do {
|
||||
const auto end = list.find(':', start);
|
||||
tags.push_back(list.substr(start, end - start));
|
||||
if (end == std::string::npos) break;
|
||||
start = end + 1;
|
||||
} while (start < list.size());
|
||||
}
|
||||
if (locale) tags.emplace_back(locale);
|
||||
#endif
|
||||
return MatchPreferredLanguages(tags);
|
||||
}
|
||||
|
||||
inline int SystemLanguage() {
|
||||
// Language changes take effect on the next application launch; explicit
|
||||
// application preferences always win over this process-wide default.
|
||||
static const int language = DetectSystemLanguage();
|
||||
return language;
|
||||
}
|
||||
|
||||
} // namespace localization
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -461,8 +461,7 @@ int SettingsManager::Load() {
|
||||
std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/",
|
||||
owner_.aes128_key_, owner_.aes128_iv_);
|
||||
|
||||
owner_.language_button_value_ = localization::detail::ClampLanguageIndex(
|
||||
static_cast<int>(owner_.config_center_->GetLanguage()));
|
||||
owner_.language_button_value_ = static_cast<int>(owner_.config_center_->GetLanguage());
|
||||
owner_.video_encode_format_button_value_ =
|
||||
static_cast<int>(owner_.config_center_->GetVideoEncodeFormat());
|
||||
owner_.enable_self_hosted_ = owner_.config_center_->IsSelfHosted();
|
||||
|
||||
@@ -19,12 +19,12 @@ export struct AboutComponent {
|
||||
}
|
||||
|
||||
export global AboutStrings {
|
||||
out property <string> software-license: GalleryStrings.tr("软件许可", "Software license", "Лицензия программы");
|
||||
out property <string> components: GalleryStrings.tr("开源组件", "Open-source components", "Компоненты с открытым кодом");
|
||||
out property <string> source: GalleryStrings.tr("源码与构建说明", "Source code and build instructions", "Исходный код и сборка");
|
||||
out property <string> notices: GalleryStrings.tr("许可与版权声明", "Licenses and copyright notices", "Лицензии и авторские права");
|
||||
out property <string> view-source: GalleryStrings.tr("查看源码", "View source code", "Открыть исходный код");
|
||||
out property <string> back: GalleryStrings.tr("返回", "Back", "Назад");
|
||||
out property <string> software-license: GalleryStrings.texts[130];
|
||||
out property <string> components: GalleryStrings.texts[131];
|
||||
out property <string> source: GalleryStrings.texts[132];
|
||||
out property <string> notices: GalleryStrings.texts[133];
|
||||
out property <string> view-source: GalleryStrings.texts[134];
|
||||
out property <string> back: GalleryStrings.texts[135];
|
||||
}
|
||||
|
||||
export component AboutNavigationGroup inherits FlatSurface {
|
||||
@@ -137,7 +137,7 @@ export component AboutPanel inherits SettingsDialog {
|
||||
if root.page == AboutPage.components: VerticalLayout {
|
||||
padding: 0px; spacing: 10px;
|
||||
Text {
|
||||
text: GalleryStrings.tr("以下列出本版本的主要开源组件。各组件遵循各自的开源许可。", "Main open-source components used in this build. Each component is covered by its own license.", "Основные открытые компоненты этой сборки. Каждый распространяется по своей лицензии.");
|
||||
text: GalleryStrings.texts[136];
|
||||
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||
}
|
||||
Rectangle {
|
||||
@@ -146,9 +146,9 @@ export component AboutPanel inherits SettingsDialog {
|
||||
notice := VerticalLayout {
|
||||
x: 16px; y: 16px; width: parent.width - 32px;
|
||||
height: self.preferred-height; spacing: 12px;
|
||||
Text { text: GalleryStrings.tr("OpenFEC 告知", "OpenFEC notice", "Уведомление OpenFEC"); color: WorkbenchStyle.text; font-size: 14px; font-weight: 600; }
|
||||
Text { text: GalleryStrings.texts[137]; color: WorkbenchStyle.text; font-size: 14px; font-weight: 600; }
|
||||
Text {
|
||||
text: GalleryStrings.tr("本应用使用 OpenFEC 提供前向纠错功能,遵循 CeCILL-C 1.0 许可。该许可规定了有限保证及责任限制,详情见许可全文。", "This application uses OpenFEC for forward error correction under CeCILL-C 1.0, which provides limited warranties and limits liability. See the full license for details.", "Приложение использует OpenFEC для прямого исправления ошибок по лицензии CeCILL-C 1.0 с ограниченной гарантией и ответственностью. Подробности — в тексте лицензии.");
|
||||
text: GalleryStrings.texts[138];
|
||||
color: WorkbenchStyle.muted; font-size: 13px; wrap: word-wrap;
|
||||
font-family: "CrossDesk Message Spacing";
|
||||
}
|
||||
@@ -158,7 +158,7 @@ export component AboutPanel inherits SettingsDialog {
|
||||
font-family: "CrossDesk Message Spacing";
|
||||
}
|
||||
if root.openfec-index >= 0: AboutNavigationRow {
|
||||
title: GalleryStrings.tr("OpenFEC 许可与源码", "OpenFEC license and source", "Лицензия и исходный код OpenFEC");
|
||||
title: GalleryStrings.texts[139];
|
||||
clicked => { root.component-index = root.openfec-index; root.page = AboutPage.component; }
|
||||
}
|
||||
}
|
||||
@@ -185,24 +185,24 @@ export component AboutPanel inherits SettingsDialog {
|
||||
clicked => { root.document-index = index; root.page = AboutPage.document; }
|
||||
}
|
||||
if root.selected.documents.length == 0: Text {
|
||||
text: GalleryStrings.tr("本组件未内置许可文本,请通过“查看源码”查阅许可与版权声明。", "License text is not bundled for this component. Use View source code to read its license and copyright notices.", "Текст лицензии не включён. Откройте исходный код для просмотра лицензии и авторских прав.");
|
||||
text: GalleryStrings.texts[140];
|
||||
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
if root.page == AboutPage.source: VerticalLayout {
|
||||
padding: 0px; spacing: 12px;
|
||||
Text {
|
||||
text: root.revision != "" ? GalleryStrings.tr("源码版本:", "Source revision: ", "Ревизия исходного кода: ") + root.revision
|
||||
: GalleryStrings.tr("本版本源码信息暂不可用。", "Source revision is unavailable for this build.", "Ревизия исходного кода этой сборки недоступна.");
|
||||
text: root.revision != "" ? GalleryStrings.texts[141] + root.revision
|
||||
: GalleryStrings.texts[142];
|
||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||
}
|
||||
AboutNavigationRow { title: AboutStrings.view-source; external: true; clicked => { root.open-link(root.source-url); } }
|
||||
AboutNavigationRow {
|
||||
title: GalleryStrings.tr("构建说明", "Build instructions", "Инструкции по сборке"); external: true;
|
||||
title: GalleryStrings.texts[143]; external: true;
|
||||
clicked => { root.open-link(root.build-url); }
|
||||
}
|
||||
AboutNavigationRow {
|
||||
title: GalleryStrings.tr("项目主页", "Project website", "Сайт проекта"); external: true;
|
||||
title: GalleryStrings.texts[144]; external: true;
|
||||
clicked => { root.open-link("https://crossdesk.cn"); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ export component DeviceCardActions inherits Rectangle {
|
||||
spacing: 0px;
|
||||
CardAction {
|
||||
icon: FontAwesomeIcons.pen;
|
||||
tooltip: GalleryStrings.tr("修改名称", "Rename", "Переименовать");
|
||||
tooltip: GalleryStrings.texts[35];
|
||||
clicked => { root.edit-alias(); }
|
||||
}
|
||||
CardAction {
|
||||
icon: FontAwesomeIcons.tags;
|
||||
tooltip: GalleryStrings.tr("修改分组", "Edit groups", "Изменить группы");
|
||||
tooltip: GalleryStrings.texts[30];
|
||||
clicked => { root.edit-groups(); }
|
||||
}
|
||||
CardAction {
|
||||
@@ -51,7 +51,7 @@ export component DeviceCardActions inherits Rectangle {
|
||||
CardAction {
|
||||
icon: root.connected ? FontAwesomeIcons.xmark : FontAwesomeIcons.arrow-right-long;
|
||||
icon-size: 13px;
|
||||
tooltip: root.connected ? GalleryStrings.tr("断开", "Disconnect", "Отключить") : GalleryStrings.tr("连接", "Connect", "Подключить");
|
||||
tooltip: root.connected ? GalleryStrings.texts[36] : GalleryStrings.texts[37];
|
||||
clicked => {
|
||||
if root.connected { root.disconnect-device(); }
|
||||
else { root.connect-device(); }
|
||||
|
||||
@@ -31,7 +31,7 @@ export component DeviceGroupDialog inherits Rectangle {
|
||||
TouchArea {}
|
||||
Text {
|
||||
x: 18px; y: 16px; height: 24px;
|
||||
text: GalleryStrings.tr("修改分组", "Edit groups", "Изменить группы");
|
||||
text: GalleryStrings.texts[30];
|
||||
color: #24272d; font-size: 17px; font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
@@ -64,7 +64,7 @@ export component DeviceGroupDialog inherits Rectangle {
|
||||
}
|
||||
Text {
|
||||
x: 39px; width: parent.width - 48px; height: parent.height;
|
||||
text: option.name == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : option.name;
|
||||
text: option.name == "" ? GalleryStrings.texts[31] : option.name;
|
||||
color: parent.selected ? ControlStyle.selected-text : #30343b;
|
||||
font-weight: parent.selected ? 600 : 400;
|
||||
font-size: 12px; overflow: elide; vertical-alignment: center;
|
||||
@@ -91,7 +91,7 @@ export component DeviceGroupDialog inherits Rectangle {
|
||||
x: 18px; y: group-list.y + group-list.height + 12px;
|
||||
width: parent.width - 36px; height: 30px;
|
||||
text <=> root.new-group;
|
||||
placeholder-text: GalleryStrings.tr("新建分组(可选)", "New group (optional)", "Новая группа (необязательно)");
|
||||
placeholder-text: GalleryStrings.texts[32];
|
||||
}
|
||||
if root.error != "": Text {
|
||||
x: 18px; y: group-list.y + group-list.height + 48px;
|
||||
@@ -102,8 +102,8 @@ export component DeviceGroupDialog inherits Rectangle {
|
||||
x: 18px; y: parent.height - 46px; width: parent.width - 36px; height: 28px;
|
||||
spacing: 10px;
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
CompactButton { width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.cancel(); } }
|
||||
CompactButton { width: 88px; height: 28px; primary: true; text: GalleryStrings.tr("保存", "Save", "Сохранить"); clicked => { root.save(root.new-group); } }
|
||||
CompactButton { width: 72px; height: 28px; text: GalleryStrings.texts[33]; clicked => { root.cancel(); } }
|
||||
CompactButton { width: 88px; height: 28px; primary: true; text: GalleryStrings.texts[34]; clicked => { root.save(root.new-group); } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
export global GalleryStrings {
|
||||
in-out property <int> language: 0;
|
||||
in-out property <[string]> texts;
|
||||
in-out property <string> retry-prefix;
|
||||
in-out property <string> retry-suffix;
|
||||
public pure function retry-after(seconds: int) -> string {
|
||||
return self.retry-prefix + seconds + self.retry-suffix;
|
||||
}
|
||||
// Compatibility for embedding callers with their own three-language text.
|
||||
public pure function tr(zh: string, en: string, ru: string) -> string {
|
||||
return self.language == 0 ? zh : self.language == 2 ? ru : en;
|
||||
}
|
||||
public pure function device-count(count: int) -> string {
|
||||
if self.language == 0 { return count + " 台设备"; }
|
||||
if self.language == 3 { return count + " 臺裝置"; }
|
||||
if self.language == 4 { return count + " 台"; }
|
||||
if self.language == 5 { return count + "개 기기"; }
|
||||
if self.language == 6 { return count + (count == 1 ? " Gerät" : " Geräte"); }
|
||||
if self.language == 7 { return count + (count == 1 ? " appareil" : " appareils"); }
|
||||
if self.language == 8 { return count + (count == 1 ? " dispositivo" : " dispositivos"); }
|
||||
if self.language == 9 { return count + (count == 1 ? " dispositivo" : " dispositivos"); }
|
||||
if self.language == 2 {
|
||||
let last-two = mod(count, 100);
|
||||
let last = mod(count, 10);
|
||||
|
||||
@@ -65,10 +65,10 @@ component NetworkRow inherits Rectangle {
|
||||
encryption-text := Text {
|
||||
x: 6px; width: parent.width - 12px; height: parent.height;
|
||||
text: !root.device.encryption-known
|
||||
? GalleryStrings.tr("待确认", "Unknown", "Неизвестно")
|
||||
? GalleryStrings.texts[38]
|
||||
: root.device.encryption-enabled
|
||||
? GalleryStrings.tr("已加密", "Encrypted", "Зашифровано")
|
||||
: GalleryStrings.tr("未加密", "Unencrypted", "Без шифр.");
|
||||
? GalleryStrings.texts[39]
|
||||
: GalleryStrings.texts[40];
|
||||
color: !root.device.encryption-known ? #7a8490
|
||||
: root.device.encryption-enabled ? #18794e : #8a6428;
|
||||
font-size: 10px; horizontal-alignment: center;
|
||||
@@ -83,7 +83,7 @@ component NetworkRow inherits Rectangle {
|
||||
Text {
|
||||
y: root.heading ? 0px : 8px; height: root.heading ? parent.height : 20px;
|
||||
width: parent.width;
|
||||
text: root.heading ? GalleryStrings.tr("画面", "Video", "Видео") : root.device.resolution;
|
||||
text: root.heading ? GalleryStrings.texts[41] : root.device.resolution;
|
||||
font-size: root.heading ? 11px : 12px; font-weight: root.heading ? 600 : 400;
|
||||
color: root.heading ? #6b7785 : #293746;
|
||||
vertical-alignment: center; overflow: elide;
|
||||
@@ -98,7 +98,7 @@ component NetworkRow inherits Rectangle {
|
||||
width: max(100px, root.width * 0.18);
|
||||
if root.heading: Text {
|
||||
x: 0px; width: parent.width;
|
||||
text: GalleryStrings.tr("收发速率", "Transfer rate", "Скорость");
|
||||
text: GalleryStrings.texts[42];
|
||||
font-size: 11px; font-weight: 600; color: #6b7785;
|
||||
vertical-alignment: center; overflow: elide;
|
||||
}
|
||||
@@ -126,14 +126,14 @@ component NetworkRow inherits Rectangle {
|
||||
Text {
|
||||
y: root.heading ? 0px : 8px; height: root.heading ? parent.height : 20px;
|
||||
width: parent.width;
|
||||
text: root.heading ? GalleryStrings.tr("视频延时", "Video latency", "Задержка видео") : root.device.video-latency;
|
||||
text: root.heading ? GalleryStrings.texts[43] : root.device.video-latency;
|
||||
font-size: root.heading ? 11px : 12px; font-weight: root.heading ? 600 : 400;
|
||||
color: root.heading ? #6b7785 : #293746;
|
||||
vertical-alignment: center; overflow: elide;
|
||||
}
|
||||
if !root.heading: Text {
|
||||
y: 30px; height: 17px; width: parent.width;
|
||||
text: GalleryStrings.tr("丢包 ", "Loss ", "Потери ") + root.device.loss;
|
||||
text: GalleryStrings.texts[44] + root.device.loss;
|
||||
font-size: 11px; color: #7a8490;
|
||||
vertical-alignment: center; overflow: elide;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ component NetworkRow inherits Rectangle {
|
||||
width: (parent.width - 12px) / 2;
|
||||
encoder-label := Text {
|
||||
height: parent.height; width: self.preferred-width;
|
||||
text: GalleryStrings.tr("被控端编码器 ", "Remote encoder ", "Удалённый кодер ");
|
||||
text: GalleryStrings.texts[45];
|
||||
font-size: 10px; color: #586f87; vertical-alignment: center;
|
||||
}
|
||||
ScrollingText {
|
||||
@@ -167,7 +167,7 @@ component NetworkRow inherits Rectangle {
|
||||
width: (parent.width - 12px) / 2;
|
||||
decoder-label := Text {
|
||||
height: parent.height; width: self.preferred-width;
|
||||
text: GalleryStrings.tr("控制端解码器 ", "Local decoder ", "Локальный декодер ");
|
||||
text: GalleryStrings.texts[46];
|
||||
font-size: 10px; color: #586f87; vertical-alignment: center;
|
||||
}
|
||||
ScrollingText {
|
||||
@@ -182,11 +182,11 @@ component NetworkEmptyState inherits Rectangle {
|
||||
VerticalLayout {
|
||||
alignment: center; spacing: 8px;
|
||||
Text {
|
||||
text: GalleryStrings.tr("暂无已连接设备", "No connected devices", "Нет подключённых устройств");
|
||||
text: GalleryStrings.texts[47];
|
||||
font-size: 13px; color: #596878; horizontal-alignment: center;
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.tr("建立连接后,网络和画面信息将在此显示", "Network and video details appear here once connected", "После подключения здесь появятся данные сети и видео");
|
||||
text: GalleryStrings.texts[48];
|
||||
font-size: 11px; color: #8a95a1; horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
@@ -216,25 +216,25 @@ export component GalleryNetworkDialog inherits Rectangle {
|
||||
}
|
||||
Text {
|
||||
x: 20px; y: 17px; width: parent.width - 120px; height: 24px;
|
||||
text: GalleryStrings.tr("设备网络状态", "Device network status", "Состояние сети устройств");
|
||||
text: GalleryStrings.texts[49];
|
||||
font-size: 17px; font-weight: 600; color: #293746;
|
||||
}
|
||||
CompactButton {
|
||||
border-radius: UiRadius.control;
|
||||
x: parent.width - 80px; y: 17px; width: 60px; height: 26px;
|
||||
text: GalleryStrings.tr("关闭", "Close", "Закрыть"); clicked => { root.close(); }
|
||||
text: GalleryStrings.texts[50]; clicked => { root.close(); }
|
||||
}
|
||||
Text {
|
||||
x: 20px; y: 48px; width: parent.width - 40px; height: 18px;
|
||||
text: GalleryStrings.tr("已连接设备 ", "Connected devices ", "Подключённые устройства ") + root.devices.length
|
||||
+ GalleryStrings.tr(" 台", "", "");
|
||||
text: GalleryStrings.texts[51] + root.devices.length
|
||||
+ GalleryStrings.texts[52];
|
||||
font-size: 11px; color: #7a8490; overflow: elide;
|
||||
}
|
||||
NetworkRow {
|
||||
x: 12px; y: 78px; width: parent.width - 38px; height: 30px;
|
||||
heading: true; background: #f3f5f8; border-radius: UiRadius.control;
|
||||
device: {
|
||||
name: GalleryStrings.tr("设备 / ID", "Device / ID", "Устройство / ID"),
|
||||
name: GalleryStrings.texts[53],
|
||||
};
|
||||
}
|
||||
list := ScrollView {
|
||||
|
||||
@@ -9,31 +9,31 @@ export component GallerySettings inherits Rectangle {
|
||||
in property <string> error;
|
||||
SettingsGroup {
|
||||
y: 0px; width: parent.width;
|
||||
title: GalleryStrings.tr("画面传输", "Video transmission", "Передача видео"); rows: 3;
|
||||
title: GalleryStrings.texts[145]; rows: 3;
|
||||
SettingsRow {
|
||||
label: GalleryStrings.tr("分辨率上限", "Resolution limit", "Макс. разрешение");
|
||||
label: GalleryStrings.texts[146];
|
||||
label-reserve: self.width - 112px;
|
||||
SettingsSelect {
|
||||
border-radius: UiRadius.control;
|
||||
x: 136px; y: 4px; width: parent.width - 150px;
|
||||
current-index <=> root.resolution-index;
|
||||
model: [GalleryStrings.tr("自动", "Auto", "Авто"),
|
||||
model: [GalleryStrings.texts[147],
|
||||
"320 × 180", "640 × 360", "1280 × 720", "1920 × 1080", "2560 × 1440", "3840 × 2160",
|
||||
GalleryStrings.tr("原始分辨率", "Original resolution", "Исходное разрешение")];
|
||||
GalleryStrings.texts[148]];
|
||||
}
|
||||
}
|
||||
SettingsRow {
|
||||
label: GalleryStrings.tr("帧率", "Frame rate", "Частота кадров");
|
||||
label: GalleryStrings.texts[149];
|
||||
label-reserve: self.width - 112px;
|
||||
SettingsSelect {
|
||||
border-radius: UiRadius.control;
|
||||
x: 136px; y: 4px; width: parent.width - 150px;
|
||||
current-index <=> root.frame-rate-index;
|
||||
model: [GalleryStrings.tr("跟随设备设置", "Follow device settings", "Настройки устройства"), "15 FPS", "30 FPS", "60 FPS"];
|
||||
model: [GalleryStrings.texts[150], "15 FPS", "30 FPS", "60 FPS"];
|
||||
}
|
||||
}
|
||||
SettingsRow {
|
||||
label: GalleryStrings.tr("视频总带宽", "Total bandwidth", "Общий битрейт");
|
||||
label: GalleryStrings.texts[151];
|
||||
label-reserve: self.width - 112px; divider: false;
|
||||
SettingsSelect {
|
||||
border-radius: UiRadius.control;
|
||||
|
||||
@@ -156,7 +156,7 @@ export component GalleryView inherits Rectangle {
|
||||
in property <int> count: 0;
|
||||
in property <int> connected-count: 0;
|
||||
private property <string> connected-summary: root.connected-count + " / " + root.count
|
||||
+ GalleryStrings.tr(" 台已连接", " connected", " подключено");
|
||||
+ GalleryStrings.texts[152];
|
||||
callback network-open;
|
||||
in property <int> selected-count: 0;
|
||||
in property <bool> selected-audio-available: false;
|
||||
@@ -297,10 +297,10 @@ export component GalleryView inherits Rectangle {
|
||||
add-menu := GalleryMenuButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
width: self.preferred-button-width;
|
||||
text: GalleryStrings.tr("添加", "Add", "Добавить"); menu-width: 232px;
|
||||
text: GalleryStrings.texts[153]; menu-width: 232px;
|
||||
items: [
|
||||
{label: GalleryStrings.tr("通过设备 ID 添加", "Add by device ID", "Добавить по ID устройства"), enabled: true},
|
||||
{label: GalleryStrings.tr("从近期连接添加", "Add from recent", "Добавить из недавних"), enabled: true},
|
||||
{label: GalleryStrings.texts[154], enabled: true},
|
||||
{label: GalleryStrings.texts[155], enabled: true},
|
||||
];
|
||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||
selected(index) => {
|
||||
@@ -316,12 +316,12 @@ export component GalleryView inherits Rectangle {
|
||||
horizontal-stretch: 1; min-width: 0px;
|
||||
Text {
|
||||
x: 0px; y: 0px; width: parent.width; height: root.hidden-selected > 0 ? 13px : parent.height;
|
||||
text: GalleryStrings.tr("已选 ", "Selected ", "Выбрано ") + root.selected-count + GalleryStrings.tr(" 台", "", "");
|
||||
text: GalleryStrings.texts[156] + root.selected-count + GalleryStrings.texts[52];
|
||||
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
||||
}
|
||||
if root.hidden-selected > 0: Text {
|
||||
y: 14px; width: parent.width; height: 10px;
|
||||
text: GalleryStrings.tr("跨页 ", "Other pages ", "Вне страницы ") + root.hidden-selected;
|
||||
text: GalleryStrings.texts[157] + root.hidden-selected;
|
||||
color: #80858d; font-size: 10px; overflow: elide;
|
||||
}
|
||||
}
|
||||
@@ -329,27 +329,27 @@ export component GalleryView inherits Rectangle {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
enabled: root.count > 0;
|
||||
text: root.count > 0 && root.selected-count == root.count
|
||||
? GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор")
|
||||
: GalleryStrings.tr("全选", "Select all", "Выбрать все");
|
||||
? GalleryStrings.texts[158]
|
||||
: GalleryStrings.texts[159];
|
||||
alternate-text: root.count > 0 && root.selected-count == root.count
|
||||
? GalleryStrings.tr("全选", "Select all", "Выбрать все")
|
||||
: GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор");
|
||||
? GalleryStrings.texts[159]
|
||||
: GalleryStrings.texts[158];
|
||||
clicked => { root.action("all-select", ""); }
|
||||
}
|
||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; primary: true; enabled: root.selected-count > 0; text: GalleryStrings.tr("连接", "Connect", "Подключить"); clicked => { root.action("connect", ""); } }
|
||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; enabled: root.selected-count > 0; text: GalleryStrings.tr("断开", "Disconnect", "Отключить"); clicked => { root.confirm-action("disconnect", self.text); } }
|
||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; primary: true; enabled: root.selected-count > 0; text: GalleryStrings.texts[37]; clicked => { root.action("connect", ""); } }
|
||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; enabled: root.selected-count > 0; text: GalleryStrings.texts[36]; clicked => { root.confirm-action("disconnect", self.text); } }
|
||||
batch-menu := GalleryMenuButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
width: self.preferred-button-width; enabled: root.selected-count > 0;
|
||||
text: GalleryStrings.tr("更多", "More", "Ещё");
|
||||
text: GalleryStrings.texts[68];
|
||||
items: [
|
||||
{label: root.selected-audio-muted ? GalleryStrings.tr("取消静音", "Unmute", "Со звуком") : GalleryStrings.tr("静音", "Mute", "Без звука"), enabled: root.selected-audio-available},
|
||||
{label: GalleryStrings.tr("分发文件", "Send file", "Отправить файл"), enabled: root.selected-count > 0},
|
||||
{label: root.selected-audio-muted ? GalleryStrings.texts[160] : GalleryStrings.texts[161], enabled: root.selected-audio-available},
|
||||
{label: GalleryStrings.texts[162], enabled: root.selected-count > 0},
|
||||
];
|
||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||
selected(index) => {
|
||||
if index == 0 { root.action(root.selected-audio-muted ? "unmute" : "mute", ""); }
|
||||
else { root.confirm-action("file", GalleryStrings.tr("分发文件", "Send file", "Отправить файл")); }
|
||||
else { root.confirm-action("file", GalleryStrings.texts[162]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,8 +357,8 @@ export component GalleryView inherits Rectangle {
|
||||
x: parent.width - self.width; y: 0px;
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
primary: root.batch;
|
||||
alternate-text: GalleryStrings.tr("选择", "Select", "Выбрать");
|
||||
text: root.batch ? GalleryStrings.tr("完成", "Done", "Готово") : GalleryStrings.tr("选择", "Select", "Выбрать");
|
||||
alternate-text: GalleryStrings.texts[163];
|
||||
text: root.batch ? GalleryStrings.texts[164] : GalleryStrings.texts[163];
|
||||
clicked => { root.batch = !root.batch; root.action("selection", ""); }
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,7 @@ export component GalleryView inherits Rectangle {
|
||||
function show-hint() {
|
||||
if self.has-hover && !self.pressed && (self.edge-x != 0 || self.edge-y != 0) {
|
||||
root.device-tooltip-id = "resize-grid";
|
||||
root.device-tooltip-text = GalleryStrings.tr("拖动表格分隔线,同步调整所有设备大小", "Drag a table divider to resize all devices", "Потяните за разделитель таблицы, чтобы изменить размер всех устройств");
|
||||
root.device-tooltip-text = GalleryStrings.texts[165];
|
||||
root.device-tooltip-x = grid.x + self.mouse-x;
|
||||
root.device-tooltip-y = grid.y + self.mouse-y + 16px;
|
||||
} else if root.device-tooltip-id == "resize-grid" { root.device-tooltip-id = ""; }
|
||||
@@ -461,8 +461,8 @@ export component GalleryView inherits Rectangle {
|
||||
else if root.drag-source != "" { root.move-device-drag(self.mouse-x, self.mouse-y); }
|
||||
}
|
||||
for tile[index] in root.tiles: tile-card := Rectangle {
|
||||
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
||||
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
||||
private property <string> presence: tile.online ? GalleryStrings.texts[166]
|
||||
: tile.presence-known ? GalleryStrings.texts[167] : GalleryStrings.texts[100];
|
||||
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
||||
accessible-role: button; accessible-label: self.details;
|
||||
accessible-action-default => { root.action(root.batch && !root.simplified ? "select" : "focus", tile.id); }
|
||||
@@ -522,7 +522,7 @@ export component GalleryView inherits Rectangle {
|
||||
changed over-actions => { if self.has-hover { self.show-details(); } }
|
||||
if tile-touch.has-hover && !root.dragging && !root.resizing && !root.batch && !root.simplified: DeviceCardActions {
|
||||
x: parent.width - self.width; y: parent.height - 23px;
|
||||
remove-label: GalleryStrings.tr("从桌面墙移除", "Remove from DeskHub", "Убрать из DeskHub");
|
||||
remove-label: GalleryStrings.texts[168];
|
||||
connected: tile.connected;
|
||||
edit-alias => { root.action("release", ""); root.edit-alias(tile.id, tile.name); }
|
||||
edit-groups => { root.edit-groups(tile.id, tile.name); }
|
||||
@@ -557,8 +557,8 @@ export component GalleryView inherits Rectangle {
|
||||
}
|
||||
if root.count == 0: Text {
|
||||
text: root.simplified
|
||||
? GalleryStrings.tr("暂无设备,退出精简模式后可添加", "No devices. Exit compact mode to add devices", "Нет устройств. Выйдите из компактного режима, чтобы добавить их")
|
||||
: GalleryStrings.tr("添加设备,建立你的桌面墙", "Add devices to build your DeskHub", "Добавьте устройства в DeskHub");
|
||||
? GalleryStrings.texts[169]
|
||||
: GalleryStrings.texts[170];
|
||||
color: #808080; horizontal-alignment: center; vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
@@ -568,7 +568,7 @@ export component GalleryView inherits Rectangle {
|
||||
height: footer.y - 12px - self.y;
|
||||
HorizontalLayout {
|
||||
x: 0px; y: 0px; width: parent.width - root.view-controls-space; height: 24px; spacing: 8px;
|
||||
GalleryButton { width: 52px; text: GalleryStrings.tr("返回", "Back", "Назад"); clicked => { root.action("focus", ""); } }
|
||||
GalleryButton { width: 52px; text: GalleryStrings.texts[135]; clicked => { root.action("focus", ""); } }
|
||||
Rectangle {
|
||||
horizontal-stretch: 1; min-width: 0px;
|
||||
Text { x: 0px; width: parent.width; height: parent.height; text: root.focused.name; color: root.focused.online ? #303943 : DeviceCardStyle.offline-text; font-size: 12px; font-weight: 600; overflow: elide; horizontal-alignment: left; vertical-alignment: center; }
|
||||
@@ -579,10 +579,10 @@ export component GalleryView inherits Rectangle {
|
||||
background: root.controlling ? #e7f1fd : #f0f2f5;
|
||||
focus-state := Text {
|
||||
x: 8px; width: parent.width - 16px; height: parent.height;
|
||||
text: root.group-controlling ? GalleryStrings.tr("群控中", "Group control", "Групп. управление")
|
||||
: root.controlling ? GalleryStrings.tr("正在操控", "Controlling", "Управление")
|
||||
text: root.group-controlling ? GalleryStrings.texts[171]
|
||||
: root.controlling ? GalleryStrings.texts[172]
|
||||
: root.focused.connected && root.focused.has-frame && !root.focused.stale
|
||||
? GalleryStrings.tr("只读预览", "View only", "Просмотр") : root.focused.status;
|
||||
? GalleryStrings.texts[173] : root.focused.status;
|
||||
font-size: 10px; color: root.controlling ? #3e77b3 : #6b7480; vertical-alignment: center; overflow: elide;
|
||||
}
|
||||
}
|
||||
@@ -590,14 +590,14 @@ export component GalleryView inherits Rectangle {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
primary: root.group-controlling;
|
||||
enabled: root.group-controlling || (root.focused.connected && root.focused.has-frame && !root.focused.stale);
|
||||
text: root.group-controlling ? GalleryStrings.tr("关闭群控", "Stop group control", "Откл. групп. ввод") : GalleryStrings.tr("开启群控", "Group control", "Групп. ввод");
|
||||
alternate-text: root.group-controlling ? GalleryStrings.tr("开启群控", "Group control", "Групп. ввод") : GalleryStrings.tr("关闭群控", "Stop group control", "Откл. групп. ввод");
|
||||
text: root.group-controlling ? GalleryStrings.texts[174] : GalleryStrings.texts[175];
|
||||
alternate-text: root.group-controlling ? GalleryStrings.texts[175] : GalleryStrings.texts[174];
|
||||
accessible-role: button;
|
||||
accessible-label: self.text + GalleryStrings.tr(":同步键鼠到所有已连接设备", ": mirror input to all connected devices", ": ввод на всех подключённых устройствах");
|
||||
accessible-label: self.text + GalleryStrings.texts[176];
|
||||
accessible-action-default => { self.clicked(); }
|
||||
clicked => { root.action(root.group-controlling ? "group-release" : "group-control", root.focused.id); }
|
||||
}
|
||||
GalleryButton { width: 100px; primary: true; enabled: !root.focused.connected || root.controlling || (root.focused.has-frame && !root.focused.stale); text: root.focused.connected ? (root.controlling ? GalleryStrings.tr("释放操控", "Release", "Отпустить") : GalleryStrings.tr("接管操控", "Control", "Управлять")) : GalleryStrings.tr("连接", "Connect", "Подключить"); clicked => { root.action(root.focused.connected ? (root.controlling ? "release" : "control") : "connect", root.focused.id); } }
|
||||
GalleryButton { width: 100px; primary: true; enabled: !root.focused.connected || root.controlling || (root.focused.has-frame && !root.focused.stale); text: root.focused.connected ? (root.controlling ? GalleryStrings.texts[177] : GalleryStrings.texts[178]) : GalleryStrings.texts[37]; clicked => { root.action(root.focused.connected ? (root.controlling ? "release" : "control") : "connect", root.focused.id); } }
|
||||
}
|
||||
FlatSurface {
|
||||
x: 0px; y: rail.visible ? focus-divider.y + focus-divider.height : 36px;
|
||||
@@ -671,8 +671,8 @@ export component GalleryView inherits Rectangle {
|
||||
width: tile.id == root.focused.id ? 0px : root.rail-slot-width;
|
||||
height: root.rail-card-height; visible: tile.id != root.focused.id;
|
||||
rail-card := FlatSurface {
|
||||
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
||||
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
||||
private property <string> presence: tile.online ? GalleryStrings.texts[166]
|
||||
: tile.presence-known ? GalleryStrings.texts[167] : GalleryStrings.texts[100];
|
||||
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
||||
width: root.rail-preview-width + 10px; height: root.rail-card-height; border-radius: UiRadius.surface;
|
||||
background: tile.online ? #f5f6f8 : DeviceCardStyle.offline-background; outline-color: UiSurface.edge;
|
||||
@@ -744,7 +744,7 @@ export component GalleryView inherits Rectangle {
|
||||
function show-hint() {
|
||||
if self.has-hover && !root.focus-resizing {
|
||||
root.device-tooltip-id = "resize-focus";
|
||||
root.device-tooltip-text = GalleryStrings.tr("上下拖动,调整缩略图与主画面大小", "Drag up or down to resize the thumbnails and main preview", "Потяните вверх или вниз, чтобы изменить размер миниатюр и основного экрана");
|
||||
root.device-tooltip-text = GalleryStrings.texts[179];
|
||||
root.device-tooltip-x = focus-view.x + self.mouse-x;
|
||||
root.device-tooltip-y = focus-view.y + focus-divider.y + self.mouse-y + 16px;
|
||||
} else if root.device-tooltip-id == "resize-focus" { root.device-tooltip-id = ""; }
|
||||
@@ -788,7 +788,7 @@ export component GalleryView inherits Rectangle {
|
||||
connection-count := Text {
|
||||
x: 0px; width: min(self.preferred-width, parent.width - network-button.width - 12px); height: parent.height;
|
||||
text: root.group-controlling
|
||||
? GalleryStrings.tr("群控中 · 所有已连接设备", "Group control · All connected devices", "Групповое управление · Все подключения")
|
||||
? GalleryStrings.texts[180]
|
||||
: root.connected-summary;
|
||||
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
||||
}
|
||||
@@ -796,7 +796,7 @@ export component GalleryView inherits Rectangle {
|
||||
x: connection-count.x + connection-count.width + 12px;
|
||||
width: 64px; height: parent.height;
|
||||
outline-color: UiSurface.edge; border-radius: UiRadius.control;
|
||||
text: GalleryStrings.tr("网络状态", "Network", "Сеть");
|
||||
text: GalleryStrings.texts[181];
|
||||
accessible-role: button;
|
||||
accessible-label: self.text;
|
||||
accessible-action-default => { self.clicked(); }
|
||||
@@ -817,9 +817,9 @@ export component GalleryView inherits Rectangle {
|
||||
compact-toggle := GalleryToolbarButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
primary: root.simplified;
|
||||
alternate-text: GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид");
|
||||
text: root.simplified ? GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид")
|
||||
: GalleryStrings.tr("精简模式", "Compact", "Компактно");
|
||||
alternate-text: GalleryStrings.texts[182];
|
||||
text: root.simplified ? GalleryStrings.texts[182]
|
||||
: GalleryStrings.texts[183];
|
||||
clicked => { root.toggle-simplified(); }
|
||||
}
|
||||
IconButton {
|
||||
@@ -827,7 +827,7 @@ export component GalleryView inherits Rectangle {
|
||||
border-radius: UiRadius.control;
|
||||
normal-background: ControlStyle.surface;
|
||||
icon: root.fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
||||
tooltip: root.fullscreen ? GalleryStrings.tr("退出全屏", "Exit fullscreen", "Выйти из полноэкранного режима") : GalleryStrings.tr("全屏", "Fullscreen", "На весь экран");
|
||||
tooltip: root.fullscreen ? GalleryStrings.texts[184] : GalleryStrings.texts[185];
|
||||
clicked => { root.toggle-fullscreen(); }
|
||||
}
|
||||
}
|
||||
@@ -859,9 +859,9 @@ export component GalleryView inherits Rectangle {
|
||||
Text { x: 80px; y: 8px; width: parent.width - 88px; height: 18px; text: root.dragged-tile.name; font-size: 12px; color: #303943; overflow: elide; }
|
||||
drag-hint := Text {
|
||||
x: 80px; y: 29px; width: parent.width - 88px; height: max(16px, self.preferred-height);
|
||||
text: root.drag-page-direction != 0 ? GalleryStrings.tr("停留翻页,松开取消", "Hold to turn page; release to cancel", "Удерживайте для перелистывания; отпустите для отмены")
|
||||
: root.drag-target >= 0 ? GalleryStrings.tr("松开鼠标交换位置", "Release to swap", "Отпустите для обмена")
|
||||
: GalleryStrings.tr("拖到另一台设备上", "Drag onto another device", "Перетащите на другое устройство");
|
||||
text: root.drag-page-direction != 0 ? GalleryStrings.texts[186]
|
||||
: root.drag-target >= 0 ? GalleryStrings.texts[187]
|
||||
: GalleryStrings.texts[188];
|
||||
font-size: 10px; color: #527ca8; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
@@ -870,7 +870,7 @@ export component GalleryView inherits Rectangle {
|
||||
width: min(420px, parent.width - 32px); height: 36px; z: 10;
|
||||
background: #fff2f0; outline-color: UiSurface.edge; border-radius: UiRadius.surface;
|
||||
Text { x: 12px; y: 0px; width: parent.width - 24px; height: parent.height; font-size: 12px; color: #9d4339; vertical-alignment: center;
|
||||
text: GalleryStrings.tr("换位未保存,请重试", "Couldn't save positions. Please try again.", "Не удалось сохранить порядок. Повторите попытку."); overflow: elide;
|
||||
text: GalleryStrings.texts[189]; overflow: elide;
|
||||
}
|
||||
}
|
||||
// Device details are rendered by MainWindow; resize hints stay local.
|
||||
@@ -896,36 +896,36 @@ export component GalleryView inherits Rectangle {
|
||||
root.adding = false;
|
||||
}
|
||||
}
|
||||
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("添加设备", "Add device", "Добавить устройство"); font-size: 16px; }
|
||||
Text { x: 18px; y: 56px; width: 72px; height: 30px; text: GalleryStrings.tr("设备 ID", "Device ID", "ID"); vertical-alignment: center; font-size: 12px; }
|
||||
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.texts[190]; font-size: 16px; }
|
||||
Text { x: 18px; y: 56px; width: 72px; height: 30px; text: GalleryStrings.texts[191]; vertical-alignment: center; font-size: 12px; }
|
||||
device-id := RoundedLineEdit {
|
||||
x: 96px; y: 56px; width: 286px; height: 30px;
|
||||
placeholder-text: GalleryStrings.tr("9 位设备 ID", "9-digit device ID", "ID из 9 цифр");
|
||||
placeholder-text: GalleryStrings.texts[192];
|
||||
input-type: InputType.number;
|
||||
accepted => { password.focus(); }
|
||||
}
|
||||
Text { x: 18px; y: 100px; width: 72px; height: 30px; text: GalleryStrings.tr("密码", "Password", "Пароль"); vertical-alignment: center; font-size: 12px; }
|
||||
Text { x: 18px; y: 100px; width: 72px; height: 30px; text: GalleryStrings.texts[193]; vertical-alignment: center; font-size: 12px; }
|
||||
password := RoundedLineEdit {
|
||||
x: 96px; y: 100px; width: 286px; height: 30px;
|
||||
placeholder-text: GalleryStrings.tr("设备连接密码", "Device password", "Пароль устройства");
|
||||
placeholder-text: GalleryStrings.texts[194];
|
||||
input-type: InputType.password;
|
||||
accepted => { group.focus(); }
|
||||
}
|
||||
Text { x: 18px; y: 144px; width: 72px; height: 30px; text: GalleryStrings.tr("分组", "Group", "Группа"); vertical-alignment: center; font-size: 12px; }
|
||||
Text { x: 18px; y: 144px; width: 72px; height: 30px; text: GalleryStrings.texts[101]; vertical-alignment: center; font-size: 12px; }
|
||||
group := RoundedLineEdit {
|
||||
x: 96px; y: 144px; width: 286px; height: 30px;
|
||||
placeholder-text: GalleryStrings.tr("分组名称(可选)", "Group name (optional)", "Группа (необязательно)");
|
||||
placeholder-text: GalleryStrings.texts[195];
|
||||
accepted => { add-dialog.submit(); }
|
||||
}
|
||||
remember := ImGuiCheckBox { x: 96px; y: 188px; }
|
||||
Text { x: 123px; y: 188px; height: 21px; text: GalleryStrings.tr("记住密码", "Remember password", "Запомнить пароль"); font-size: 12px; vertical-alignment: center;
|
||||
Text { x: 123px; y: 188px; height: 21px; text: GalleryStrings.texts[196]; font-size: 12px; vertical-alignment: center;
|
||||
TouchArea { clicked => { remember.checked = !remember.checked; } }
|
||||
}
|
||||
Text { x: 18px; y: 216px; width: 364px; height: 28px; text: root.notice; color: #8e4740; font-size: 11px; wrap: word-wrap; overflow: elide; }
|
||||
GalleryButton { x: 136px; y: 260px; width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { password.text = ""; root.adding = false; } }
|
||||
GalleryButton { x: 136px; y: 260px; width: 72px; height: 28px; text: GalleryStrings.texts[33]; clicked => { password.text = ""; root.adding = false; } }
|
||||
GalleryButton {
|
||||
x: 218px; y: 260px; width: 164px; height: 28px; primary: true;
|
||||
text: GalleryStrings.tr("添加并连接", "Add and connect", "Добавить и подключить");
|
||||
text: GalleryStrings.texts[197];
|
||||
enabled: device-id.text != "" && password.text != "";
|
||||
clicked => { add-dialog.submit(); }
|
||||
}
|
||||
@@ -937,12 +937,12 @@ export component GalleryView inherits Rectangle {
|
||||
FlatSurface {
|
||||
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 378px;
|
||||
background: white; border-radius: UiRadius.surface; outline-color: UiSurface.edge;
|
||||
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("从近期连接添加", "Add from recent connections", "Добавить из недавних"); font-size: 16px; }
|
||||
Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.tr("加入桌面墙后,可通过批量选择连接", "Add to DeskHub, then select devices to connect.", "Добавьте в DeskHub, затем выберите устройства для подключения."); color: #62666d; font-size: 11px; }
|
||||
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.texts[155]; font-size: 16px; }
|
||||
Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.texts[198]; color: #62666d; font-size: 11px; }
|
||||
ImGuiCheckBox { x: 18px; y: 74px; enabled: root.recent-available > 0; checked: root.recent-devices.length > 0 && root.recent-selected == root.recent-available; }
|
||||
Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.tr("全选", "Select all", "Выбрать все"); color: root.recent-available > 0 ? #24272d : #92969e; font-size: 12px; vertical-alignment: center; }
|
||||
Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.texts[159]; color: root.recent-available > 0 ? #24272d : #92969e; font-size: 12px; vertical-alignment: center; }
|
||||
TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-available > 0; clicked => { root.action("recent-all", ""); } }
|
||||
Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.tr("待添加 ", "To add ", "К добавлению ") + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; }
|
||||
Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.texts[199] + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; }
|
||||
FlatSurface {
|
||||
x: 18px; y: 102px; width: 404px; height: 160px;
|
||||
outline-color: UiSurface.edge; border-radius: UiRadius.surface; clip: true;
|
||||
@@ -961,13 +961,13 @@ export component GalleryView inherits Rectangle {
|
||||
Rectangle { x: parent.width - 74px; y: 19px; width: 6px; height: 6px; border-radius: 3px; background: !device.added && device.online ? #21b567 : #92969e; }
|
||||
Text {
|
||||
x: parent.width - 61px; y: 12px; width: 57px; height: 20px;
|
||||
text: device.added ? GalleryStrings.tr("已添加", "Added", "Добавлено") : device.online ? GalleryStrings.tr("在线", "Online", "В сети") : GalleryStrings.tr("离线", "Offline", "Не в сети");
|
||||
text: device.added ? GalleryStrings.texts[200] : device.online ? GalleryStrings.texts[166] : GalleryStrings.texts[167];
|
||||
color: device.added ? #92969e : device.online ? #17854c : #808080; font-size: 11px; vertical-alignment: center;
|
||||
}
|
||||
row-touch := TouchArea { enabled: !device.added; clicked => { root.action("recent-select", device.id); } }
|
||||
}
|
||||
}
|
||||
if root.recent-devices.length == 0: Text { text: GalleryStrings.tr("暂无近期连接", "No recent connections", "Нет недавних подключений"); color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; }
|
||||
if root.recent-devices.length == 0: Text { text: GalleryStrings.texts[89]; color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; }
|
||||
Rectangle {
|
||||
x: -UiRadius.surface; y: -UiRadius.surface;
|
||||
width: parent.width + 2 * UiRadius.surface;
|
||||
@@ -981,11 +981,11 @@ export component GalleryView inherits Rectangle {
|
||||
outline-color: UiSurface.edge;
|
||||
}
|
||||
}
|
||||
Text { x: 18px; y: 274px; width: 60px; height: 30px; text: GalleryStrings.tr("分组", "Group", "Группа"); font-size: 12px; vertical-alignment: center; }
|
||||
import-group := RoundedLineEdit { x: 84px; y: 274px; width: 338px; height: 30px; text: root.import-group; placeholder-text: GalleryStrings.tr("分组名称(可选)", "Group name (optional)", "Группа (необязательно)"); }
|
||||
Text { x: 18px; y: 274px; width: 60px; height: 30px; text: GalleryStrings.texts[101]; font-size: 12px; vertical-alignment: center; }
|
||||
import-group := RoundedLineEdit { x: 84px; y: 274px; width: 338px; height: 30px; text: root.import-group; placeholder-text: GalleryStrings.texts[195]; }
|
||||
Text { x: 18px; y: 309px; width: 404px; height: 20px; text: root.notice; font-size: 11px; color: #8e4740; overflow: elide; }
|
||||
GalleryButton { x: 192px; y: 336px; width: 72px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.importing = false; } }
|
||||
GalleryButton { x: 274px; y: 336px; width: 148px; primary: true; text: GalleryStrings.tr("加入桌面墙", "Add to DeskHub", "В DeskHub"); enabled: root.recent-selected > 0; clicked => { if root.import-recent(import-group.text) { root.importing = false; } } }
|
||||
GalleryButton { x: 192px; y: 336px; width: 72px; text: GalleryStrings.texts[33]; clicked => { root.importing = false; } }
|
||||
GalleryButton { x: 274px; y: 336px; width: 148px; primary: true; text: GalleryStrings.texts[201]; enabled: root.recent-selected > 0; clicked => { if root.import-recent(import-group.text) { root.importing = false; } } }
|
||||
}
|
||||
}
|
||||
if root.confirming: Rectangle {
|
||||
@@ -996,7 +996,7 @@ export component GalleryView inherits Rectangle {
|
||||
background: white; border-radius: UiRadius.surface; outline-color: UiSurface.edge;
|
||||
Text {
|
||||
x: 20px; y: 18px; width: parent.width - 132px; height: 24px;
|
||||
text: root.action-code == "disconnect" ? GalleryStrings.tr("断开连接", "Disconnect devices", "Отключение") : root.action-name;
|
||||
text: root.action-code == "disconnect" ? GalleryStrings.texts[96] : root.action-name;
|
||||
color: #24272d; font-size: 16px; font-weight: 600; vertical-alignment: center;
|
||||
}
|
||||
Rectangle {
|
||||
@@ -1021,16 +1021,16 @@ export component GalleryView inherits Rectangle {
|
||||
width: GalleryStrings.language == 0 ? 68px : 104px; height: 20px; border-radius: 10px;
|
||||
background: device.connected ? #eaf6ee : #f0f2f5;
|
||||
Rectangle { x: 9px; y: 7px; width: 6px; height: 6px; border-radius: 3px; background: device.connected ? #27a762 : #a0a6b0; }
|
||||
Text { x: 21px; width: parent.width - 27px; height: parent.height; text: device.connected ? GalleryStrings.tr("已连接", "Connected", "Подключено") : GalleryStrings.tr("未连接", "Not connected", "Нет соединения"); color: device.connected ? #348259 : #858d98; font-size: 10px; vertical-alignment: center; }
|
||||
Text { x: 21px; width: parent.width - 27px; height: parent.height; text: device.connected ? GalleryStrings.texts[202] : GalleryStrings.texts[203]; color: device.connected ? #348259 : #858d98; font-size: 10px; vertical-alignment: center; }
|
||||
}
|
||||
if index + 1 < root.selected-devices.length: SurfaceSeparator { x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; outline-color: UiSurface.separator; }
|
||||
}
|
||||
}
|
||||
}
|
||||
GalleryButton { x: parent.width - 208px; y: parent.height - 46px; width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.confirming = false; } }
|
||||
GalleryButton { x: parent.width - 208px; y: parent.height - 46px; width: 72px; height: 28px; text: GalleryStrings.texts[33]; clicked => { root.confirming = false; } }
|
||||
GalleryButton {
|
||||
x: parent.width - 126px; y: parent.height - 46px; width: 108px; height: 28px; primary: true;
|
||||
text: root.action-code == "disconnect" ? GalleryStrings.tr("确认断开", "Disconnect", "Отключить") : GalleryStrings.tr("确认执行", "Continue", "Продолжить");
|
||||
text: root.action-code == "disconnect" ? GalleryStrings.texts[204] : GalleryStrings.texts[205];
|
||||
clicked => { root.confirming = false; root.action(root.action-code, ""); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,24 +5,24 @@ export global LanStrings {
|
||||
// Slint Text does not expose a line-height property.
|
||||
public pure function access-error-lines(code: string) -> [string] {
|
||||
if code == "LanPortInUse" { return [
|
||||
GalleryStrings.tr("此端口已被占用,", "This port is in use.", "Порт занят."),
|
||||
GalleryStrings.tr("请更换端口。", "Choose another port.", "Выберите другой порт."),
|
||||
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
|
||||
GalleryStrings.texts[0],
|
||||
GalleryStrings.texts[1],
|
||||
GalleryStrings.texts[2]
|
||||
]; }
|
||||
if code == "LanPortCheckFailed" { return [
|
||||
GalleryStrings.tr("无法确认此端口是否可用,", "Could not check this port.", "Не удалось проверить порт."),
|
||||
GalleryStrings.tr("请重试或检查本机防火墙。", "Retry or check the local firewall.", "Повторите попытку или проверьте брандмауэр."),
|
||||
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
|
||||
GalleryStrings.texts[3],
|
||||
GalleryStrings.texts[4],
|
||||
GalleryStrings.texts[2]
|
||||
]; }
|
||||
if code == "LanPortPermissionDenied" { return [
|
||||
GalleryStrings.tr("无法使用此端口,", "Cannot use this port.", "Порт недоступен."),
|
||||
GalleryStrings.tr("请选择其他端口或检查系统权限。", "Choose another port or check system permissions.", "Смените порт или проверьте разрешения."),
|
||||
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
|
||||
GalleryStrings.texts[5],
|
||||
GalleryStrings.texts[6],
|
||||
GalleryStrings.texts[2]
|
||||
]; }
|
||||
if code == "Selected LAN interface is unavailable" { return [
|
||||
GalleryStrings.tr("未找到可用的局域网连接。", "No local network connection was found.", "Нет доступного подключения к LAN."),
|
||||
GalleryStrings.tr("请连接 Wi-Fi 或网线,", "Connect to Wi-Fi or Ethernet,", "Подключитесь к Wi-Fi или Ethernet"),
|
||||
GalleryStrings.tr("或在主界面选择本机 IP。", "or select a local IP on the main screen.", "или выберите локальный IP в главном окне.")
|
||||
GalleryStrings.texts[7],
|
||||
GalleryStrings.texts[8],
|
||||
GalleryStrings.texts[9]
|
||||
]; }
|
||||
// Keep all branches the same size for Slint's C++ model generator.
|
||||
return ["", "", ""];
|
||||
@@ -30,29 +30,29 @@ export global LanStrings {
|
||||
public pure function error-message(code: string) -> string {
|
||||
let lines = LanStrings.access-error-lines(code);
|
||||
if lines[0] != "" {
|
||||
let separator = GalleryStrings.tr("", " ", " ");
|
||||
let separator = GalleryStrings.texts[10];
|
||||
return lines[0] + separator + lines[1] + separator + lines[2];
|
||||
}
|
||||
if code == "" { return ""; }
|
||||
if code == "InvalidCredentials" { return GalleryStrings.tr("密码不正确,或对方密码已更新。请查看对方的局域网访问密码后重试。", "The password is incorrect or has changed. Check the other desktop's current LAN password and try again.", "Пароль неверен или изменён. Проверьте текущий пароль LAN другого компьютера и повторите попытку."); }
|
||||
if code == "AuthenticationRateLimited" { return GalleryStrings.tr("尝试次数过多,请等待倒计时结束后重试。", "Too many attempts. Try again when the countdown ends.", "Слишком много попыток. Дождитесь окончания обратного отсчёта."); }
|
||||
if code == "CredentialsChanged" { return GalleryStrings.tr("对方密码已更新,请使用新密码重试。", "The password changed. Try again with the new password.", "Пароль изменился. Повторите с новым паролем."); }
|
||||
if code == "Busy" { return GalleryStrings.tr("设备正忙,请稍后重试。", "The desktop is busy. Try again shortly.", "Компьютер занят. Повторите позже."); }
|
||||
if code == "LAN password must contain exactly 6 ASCII letters or digits" { return GalleryStrings.tr("访问密码必须为 6 位英文字母或数字,区分大小写。", "Use exactly 6 letters (A–Z, a–z) or digits (0–9). Case-sensitive.", "Пароль: ровно 6 латинских букв или цифр. Регистр учитывается."); }
|
||||
if code == "ConnectionFailed" || code == "ConnectionTimeout" || code == "IceConnectionTimeout" || code == "Timeout" || code == "ConnectionClosed" || code == "HeartbeatTimeout" || code == "SignalSendFailed" { return GalleryStrings.tr("无法连接,请检查对方 IP、程序是否运行,以及防火墙是否放行。", "Could not connect. Check the IP, whether the other desktop is running CrossDesk, and its firewall settings.", "Не удалось подключиться. Проверьте IP, работу CrossDesk на другом компьютере и брандмауэр."); }
|
||||
if code == "Enter a numeric IPv4 address or IPv4:port" || code == "Invalid LAN endpoint" { return GalleryStrings.tr("请输入有效的 IPv4,例如 192.168.1.20;指定端口可用 IPv4:端口,端口范围 1–65535。", "Enter a valid IPv4 address such as 192.168.1.20, or IPv4:port (1–65535).", "Введите IPv4, например 192.168.1.20, или IPv4:порт (1–65535)."); }
|
||||
if code == "Invalid LAN port" { return GalleryStrings.tr("请输入 1–65535 之间的整数端口。", "Enter a port number from 1 to 65535.", "Введите целый номер порта от 1 до 65535."); }
|
||||
if code == "LAN password is not ready" { return GalleryStrings.tr("请点击主界面密码旁的修改按钮设置密码。", "Set a password using Edit next to the password on the main screen.", "Задайте пароль кнопкой редактирования рядом с паролем в главном окне."); }
|
||||
if code == "LAN connection limit reached" { return GalleryStrings.tr("IP 连接数量已达上限,请先断开部分连接后重试。", "The IP connection limit has been reached. Disconnect some sessions and try again.", "Достигнут лимит IP-подключений. Завершите часть сеансов и повторите попытку."); }
|
||||
if code == "UnsupportedNetworkPath" || code == "NetworkPolicyFailed" { return GalleryStrings.tr("当前网络路径不支持 IP 连接,请检查双方的局域网连接。", "This network path does not support IP connections. Check both devices' LAN connections.", "Этот сетевой маршрут не поддерживает IP-подключения. Проверьте подключения обоих устройств к LAN."); }
|
||||
if code == "IncompatibleProtocol" || code == "RequiredCryptoUnsupported" || code == "TLS 1.3 is required for LAN connections" { return GalleryStrings.tr("双方的协议或加密能力不兼容,请将双方 CrossDesk 升级至最新版本后重试。", "The protocol or encryption capabilities are incompatible. Update CrossDesk on both devices and try again.", "Протоколы или возможности шифрования несовместимы. Обновите CrossDesk на обоих устройствах и повторите попытку."); }
|
||||
if code == "InvalidCertificate" || code == "FingerprintMissing" || code == "FingerprintMismatch" { return GalleryStrings.tr("无法验证对方设备的身份,请确认连接地址和对方设备后重试。", "Could not verify the remote device's identity. Check the address and the remote device before retrying.", "Не удалось проверить подлинность удалённого устройства. Проверьте адрес и устройство перед повторной попыткой."); }
|
||||
if code == "DtlsHandshakeFailed" || code == "SrtpInstallFailed" || code == "SrtpKeyExportFailed" || code == "SecureTransportFailed" { return GalleryStrings.tr("无法建立加密连接,请检查双方网络并重试。", "Could not establish an encrypted connection. Check both devices' networks and try again.", "Не удалось установить зашифрованное соединение. Проверьте сеть на обоих устройствах и повторите попытку."); }
|
||||
if code == "InvalidSignal" || code == "InvalidLocalSignal" || code == "Invalid LAN protocol message" { return GalleryStrings.tr("连接协商数据无效,请重试并确认双方 CrossDesk 版本兼容。", "Invalid connection negotiation data. Try again and check that both CrossDesk versions are compatible.", "Неверные данные согласования соединения. Повторите попытку и проверьте совместимость версий CrossDesk."); }
|
||||
if code == "ListenerChanged" { return GalleryStrings.tr("连接地址或监听端口已更改,请确认最新地址后重试。", "The connection address or listening port has changed. Check the current address and try again.", "Адрес подключения или порт прослушивания изменился. Проверьте текущий адрес и повторите попытку."); }
|
||||
if code == "PeerPreparationFailed" { return GalleryStrings.tr("无法准备远程会话,请稍后重试。", "Could not prepare the remote session. Try again shortly.", "Не удалось подготовить удалённый сеанс. Повторите позже."); }
|
||||
if code == "Cannot copy text" { return GalleryStrings.tr("复制失败,请重试。", "Could not copy. Try again.", "Не удалось скопировать. Повторите попытку."); }
|
||||
if code == "InvalidCredentials" { return GalleryStrings.texts[11]; }
|
||||
if code == "AuthenticationRateLimited" { return GalleryStrings.texts[12]; }
|
||||
if code == "CredentialsChanged" { return GalleryStrings.texts[13]; }
|
||||
if code == "Busy" { return GalleryStrings.texts[14]; }
|
||||
if code == "LAN password must contain exactly 6 ASCII letters or digits" { return GalleryStrings.texts[15]; }
|
||||
if code == "ConnectionFailed" || code == "ConnectionTimeout" || code == "IceConnectionTimeout" || code == "Timeout" || code == "ConnectionClosed" || code == "HeartbeatTimeout" || code == "SignalSendFailed" { return GalleryStrings.texts[16]; }
|
||||
if code == "Enter a numeric IPv4 address or IPv4:port" || code == "Invalid LAN endpoint" { return GalleryStrings.texts[17]; }
|
||||
if code == "Invalid LAN port" { return GalleryStrings.texts[18]; }
|
||||
if code == "LAN password is not ready" { return GalleryStrings.texts[19]; }
|
||||
if code == "LAN connection limit reached" { return GalleryStrings.texts[20]; }
|
||||
if code == "UnsupportedNetworkPath" || code == "NetworkPolicyFailed" { return GalleryStrings.texts[21]; }
|
||||
if code == "IncompatibleProtocol" || code == "RequiredCryptoUnsupported" || code == "TLS 1.3 is required for LAN connections" { return GalleryStrings.texts[22]; }
|
||||
if code == "InvalidCertificate" || code == "FingerprintMissing" || code == "FingerprintMismatch" { return GalleryStrings.texts[23]; }
|
||||
if code == "DtlsHandshakeFailed" || code == "SrtpInstallFailed" || code == "SrtpKeyExportFailed" || code == "SecureTransportFailed" { return GalleryStrings.texts[24]; }
|
||||
if code == "InvalidSignal" || code == "InvalidLocalSignal" || code == "Invalid LAN protocol message" { return GalleryStrings.texts[25]; }
|
||||
if code == "ListenerChanged" { return GalleryStrings.texts[26]; }
|
||||
if code == "PeerPreparationFailed" { return GalleryStrings.texts[27]; }
|
||||
if code == "Cannot copy text" { return GalleryStrings.texts[28]; }
|
||||
// Keep diagnostic details for unexpected system errors, with a localized explanation.
|
||||
return GalleryStrings.tr("IP 连接操作失败,详情:", "IP connection operation failed. Details: ", "Ошибка операции IP-подключения. Подробности: ") + code;
|
||||
return GalleryStrings.texts[29] + code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ component PasswordInput inherits FlatSurface {
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
title: root.gallery-mode ? "CrossDesk " + GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub") : "CrossDesk";
|
||||
title: root.gallery-mode ? "CrossDesk " + GalleryStrings.texts[54] : "CrossDesk";
|
||||
icon: @image-url("../../../resources/linux/crossdesk_256x256.png");
|
||||
preferred-width: root.minimum-window-width;
|
||||
preferred-height: root.minimum-window-height;
|
||||
@@ -455,7 +455,7 @@ export component MainWindow inherits Window {
|
||||
root.lan-remember-password = false;
|
||||
let error = root.lan-prepare-connect(address);
|
||||
root.connection-input-error = error == "Enter a numeric IPv4 address or IPv4:port"
|
||||
? GalleryStrings.tr("请输入有效的 IPv4 或 IPv4:端口", "Enter a valid IPv4 or IPv4:port", "Укажите IPv4 или IPv4:порт")
|
||||
? GalleryStrings.texts[55]
|
||||
: LanStrings.error-message(error);
|
||||
if error == "" {
|
||||
root.lan-open = true;
|
||||
@@ -527,8 +527,8 @@ export component MainWindow inherits Window {
|
||||
private property <bool> prompt-validating: root.lan-open ? root.lan-connecting : root.connection-validating;
|
||||
private property <bool> prompt-pending: root.lan-open ? root.lan-connecting : root.connection-pending;
|
||||
private property <string> prompt-status-text: !root.lan-open ? root.connection-status-text
|
||||
: root.lan-connecting ? GalleryStrings.tr("正在连接…", "Connecting…", "Подключение…")
|
||||
: root.lan-retry-after > 0 ? GalleryStrings.tr("请在 " + root.lan-retry-after + " 秒后重试", "Try again in " + root.lan-retry-after + "s", "Повторите через " + root.lan-retry-after + " с")
|
||||
: root.lan-connecting ? GalleryStrings.texts[56]
|
||||
: root.lan-retry-after > 0 ? GalleryStrings.retry-after(root.lan-retry-after)
|
||||
: root.lan-message == "InvalidCredentials" ? UiStrings.reinput-password
|
||||
: root.lan-message == "" || root.lan-message == "AuthenticationRateLimited" ? UiStrings.input-password
|
||||
: LanStrings.error-message(root.lan-message);
|
||||
@@ -556,7 +556,7 @@ export component MainWindow inherits Window {
|
||||
in property <[RecentConnection]> recent-connections;
|
||||
out property <length> recent-preview-width: max(1px, (recent-scroll.width - 24px) / 3);
|
||||
out property <length> recent-preview-height: 80px;
|
||||
in property <[string]> recent-groups: [GalleryStrings.tr("全部设备", "All devices", "Все устройства"), GalleryStrings.tr("未分组", "Ungrouped", "Без группы")];
|
||||
in property <[string]> recent-groups: [GalleryStrings.texts[57], GalleryStrings.texts[31]];
|
||||
in-out property <int> recent-group-index: 0;
|
||||
callback recent-group-selected(int);
|
||||
changed recent-group-index => {
|
||||
@@ -628,7 +628,8 @@ export component MainWindow inherits Window {
|
||||
in property <string> portable-service-status: "";
|
||||
in-out property <bool> portable-service-do-not-remind: false;
|
||||
|
||||
in-out property <int> language-index: 0;
|
||||
in-out property <int> language-index: 10;
|
||||
in property <[string]> language-options;
|
||||
in property <bool> capture-method-visible: false;
|
||||
in-out property <int> capture-method-index: 0;
|
||||
in-out property <int> codec-index: 0;
|
||||
@@ -640,11 +641,11 @@ export component MainWindow inherits Window {
|
||||
in property <bool> autostart-system-managed: false;
|
||||
in property <AutostartStatus> autostart-status: AutostartStatus.checking;
|
||||
out property <string> autostart-status-text:
|
||||
root.autostart-status == AutostartStatus.checking ? GalleryStrings.tr("正在读取…", "Checking…", "Проверка…") :
|
||||
root.autostart-status == AutostartStatus.enabled ? GalleryStrings.tr("已开启", "Enabled", "Включён") :
|
||||
root.autostart-status == AutostartStatus.disabled ? GalleryStrings.tr("未开启", "Disabled", "Выключен") :
|
||||
root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.tr("设备不支持", "Unsupported", "Не поддерживается") :
|
||||
GalleryStrings.tr("读取失败", "Read failed", "Ошибка чтения");
|
||||
root.autostart-status == AutostartStatus.checking ? GalleryStrings.texts[58] :
|
||||
root.autostart-status == AutostartStatus.enabled ? GalleryStrings.texts[59] :
|
||||
root.autostart-status == AutostartStatus.disabled ? GalleryStrings.texts[60] :
|
||||
root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.texts[61] :
|
||||
GalleryStrings.texts[62];
|
||||
in-out property <bool> autostart-guide-open: false;
|
||||
changed autostart-guide-open => { if root.autostart-guide-open { root.dialog-focus-pending = true; } }
|
||||
in-out property <bool> daemon-enabled: false;
|
||||
@@ -770,7 +771,7 @@ export component MainWindow inherits Window {
|
||||
focus-sink.focus();
|
||||
}
|
||||
private property <CopyFeedback> copy-feedback: idle;
|
||||
private property <bool> compact-language: root.language-index == 0;
|
||||
private property <bool> compact-language: GalleryStrings.language == 0 || GalleryStrings.language == 3 || GalleryStrings.language == 4 || GalleryStrings.language == 5;
|
||||
private property <bool> recent-tooltip-visible: false;
|
||||
private property <string> recent-tooltip-name: "";
|
||||
private property <string> recent-tooltip-host: "";
|
||||
@@ -1114,9 +1115,9 @@ export component MainWindow inherits Window {
|
||||
width: 136px; height: 28px;
|
||||
background: WorkbenchStyle.field-surface;
|
||||
enabled: !root.connection-pending && !root.lan-connecting;
|
||||
tooltip: root.lan-mode ? GalleryStrings.tr("切换到 ID 连接模式", "Switch to ID connection mode", "Переключить в режим ID") : GalleryStrings.tr("切换到 IP 连接模式", "Switch to IP connection mode", "Переключить в режим IP");
|
||||
tooltip: root.lan-mode ? GalleryStrings.texts[63] : GalleryStrings.texts[64];
|
||||
accessible-role: switch;
|
||||
accessible-label: GalleryStrings.tr("IP 连接模式", "IP connection mode", "Режим IP");
|
||||
accessible-label: GalleryStrings.texts[65];
|
||||
accessible-checked: root.lan-mode;
|
||||
z: 8;
|
||||
clicked => {
|
||||
@@ -1138,7 +1139,7 @@ export component MainWindow inherits Window {
|
||||
y: 2px;
|
||||
width: (parent.width - 6px) / 2;
|
||||
height: parent.height - 4px;
|
||||
text: lan ? GalleryStrings.tr("IP 连接", "IP mode", "Режим IP") : GalleryStrings.tr("ID 连接", "ID mode", "Режим ID");
|
||||
text: lan ? GalleryStrings.texts[66] : GalleryStrings.texts[67];
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: root.lan-mode == lan ? white : WorkbenchStyle.muted;
|
||||
@@ -1154,7 +1155,7 @@ export component MainWindow inherits Window {
|
||||
y: connection-mode-switch.y;
|
||||
width: 80px; height: 28px;
|
||||
normal-background: white;
|
||||
text: GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub");
|
||||
text: GalleryStrings.texts[54];
|
||||
z: 8;
|
||||
clicked => { root.open-gallery(); }
|
||||
}
|
||||
@@ -1164,7 +1165,7 @@ export component MainWindow inherits Window {
|
||||
width: 36px; height: 28px;
|
||||
normal-background: white;
|
||||
icon: FontAwesomeIcons.bars;
|
||||
tooltip: GalleryStrings.tr("更多", "More", "Ещё");
|
||||
tooltip: GalleryStrings.texts[68];
|
||||
z: 8;
|
||||
clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; }
|
||||
}
|
||||
@@ -1247,16 +1248,16 @@ export component MainWindow inherits Window {
|
||||
x: 0px; y: 0px;
|
||||
width: (parent.width - 16px) / 2; height: parent.height;
|
||||
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.local-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
|
||||
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("将 IP 和密码分享给连接方", "Share your IP and password to get connected", "Передайте IP и пароль для подключения") : GalleryStrings.tr("将 ID 和密码分享给连接方", "Share your ID and password to get connected", "Передайте ID и пароль для подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
||||
local-address-label := Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("本机 IP / 端口", "Local IP / Port", "IP / Порт") : UiStrings.local-id; font-size: 12px; color: WorkbenchStyle.muted; vertical-alignment: top; }
|
||||
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[69] : GalleryStrings.texts[70]; font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
||||
local-address-label := Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[71] : UiStrings.local-id; font-size: 12px; color: WorkbenchStyle.muted; vertical-alignment: top; }
|
||||
lan-switch-ip-metrics := Text {
|
||||
visible: false; accessible-role: none;
|
||||
text: GalleryStrings.tr("切换 IP", "Switch IP", "Сменить IP");
|
||||
text: GalleryStrings.texts[72];
|
||||
font-size: 12px; font-weight: 500;
|
||||
}
|
||||
lan-edit-port-metrics := Text {
|
||||
visible: false; accessible-role: none;
|
||||
text: GalleryStrings.tr("修改端口", "Edit port", "Изменить порт");
|
||||
text: GalleryStrings.texts[73];
|
||||
font-size: 12px; font-weight: 500;
|
||||
}
|
||||
lan-switch-ip-action := WorkbenchButton {
|
||||
@@ -1328,7 +1329,7 @@ export component MainWindow inherits Window {
|
||||
normal-outline-color: UiSurface.edge;
|
||||
hover-outline-color: UiSurface.hover;
|
||||
focus-outline-color: WorkbenchStyle.accent;
|
||||
accessible-label: GalleryStrings.tr("切换本机 IP", "Switch local IP", "Сменить локальный IP");
|
||||
accessible-label: GalleryStrings.texts[74];
|
||||
selected(index) => {
|
||||
root.copy-feedback = CopyFeedback.idle;
|
||||
// Save the draft for its original IP before changing
|
||||
@@ -1384,7 +1385,7 @@ export component MainWindow inherits Window {
|
||||
font-size: 18px * local-address-field.lan-font-scale; font-weight: 600;
|
||||
input-type: InputType.number; color: WorkbenchStyle.text;
|
||||
enabled: root.lan-mode && !root.lan-password-pending;
|
||||
accessible-label: GalleryStrings.tr("局域网端口", "LAN port", "Порт LAN");
|
||||
accessible-label: GalleryStrings.texts[75];
|
||||
vertical-alignment: center; horizontal-alignment: left;
|
||||
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
|
||||
changed has-focus => {
|
||||
@@ -1403,7 +1404,7 @@ export component MainWindow inherits Window {
|
||||
x: parent.width - 42px; y: 2px; width: 36px; quiet: true;
|
||||
icon: root.copy-feedback == CopyFeedback.success ? FontAwesomeIcons.check : FontAwesomeIcons.copy;
|
||||
icon-color: root.copy-feedback == CopyFeedback.success ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
||||
tooltip: root.lan-mode ? GalleryStrings.tr("复制 IP 和端口", "Copy IP and port", "Копировать IP и порт") : GalleryStrings.tr("复制本机 ID", "Copy local ID", "Копировать ID");
|
||||
tooltip: root.lan-mode ? GalleryStrings.texts[76] : GalleryStrings.texts[77];
|
||||
enabled: !root.lan-mode || root.lan-listener-address != "";
|
||||
tooltip-suppressed: root.copy-feedback != CopyFeedback.idle;
|
||||
// Let other controls receive the original click; moving
|
||||
@@ -1420,8 +1421,8 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
private property <string> edit-hint: !root.lan-mode || !root.sponsorship-unobstructed
|
||||
|| root.lan-password-pending ? ""
|
||||
: lan-ip-picker.has-hover ? GalleryStrings.tr("点击切换本机 IP", "Click to switch the local IP", "Нажмите, чтобы сменить IP")
|
||||
: lan-port-hover.has-hover && !lan-port-editor.has-focus ? GalleryStrings.tr("点击修改端口", "Click to edit the port", "Нажмите, чтобы изменить порт") : "";
|
||||
: lan-ip-picker.has-hover ? GalleryStrings.texts[78]
|
||||
: lan-port-hover.has-hover && !lan-port-editor.has-focus ? GalleryStrings.texts[79] : "";
|
||||
private property <bool> edit-hint-visible;
|
||||
changed edit-hint => { local-address-field.edit-hint-visible = false; }
|
||||
Timer {
|
||||
@@ -1453,7 +1454,7 @@ export component MainWindow inherits Window {
|
||||
WorkbenchButton {
|
||||
x: parent.width - 112px; y: 0px; width: 36px; quiet: true;
|
||||
icon: root.active-password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
|
||||
tooltip: root.active-password-visible ? GalleryStrings.tr("隐藏密码", "Hide password", "Скрыть пароль") : GalleryStrings.tr("显示密码", "Show password", "Показать пароль");
|
||||
tooltip: root.active-password-visible ? GalleryStrings.texts[80] : GalleryStrings.texts[81];
|
||||
clicked => { if root.lan-mode { root.lan-password-visible = !root.lan-password-visible; } else { root.toggle-password-visibility(); } }
|
||||
}
|
||||
WorkbenchButton {
|
||||
@@ -1465,7 +1466,7 @@ export component MainWindow inherits Window {
|
||||
WorkbenchButton {
|
||||
x: parent.width - 36px; y: 0px; width: 36px; quiet: true;
|
||||
icon: FontAwesomeIcons.pen;
|
||||
tooltip: GalleryStrings.tr("修改密码", "Change password", "Изменить пароль");
|
||||
tooltip: GalleryStrings.texts[82];
|
||||
enabled: root.lan-mode ? !root.lan-password-pending : !root.password-change-pending;
|
||||
clicked => { root.reset-password-open = true; }
|
||||
}
|
||||
@@ -1474,8 +1475,8 @@ export component MainWindow inherits Window {
|
||||
WorkbenchCard {
|
||||
x: local-card.width + 16px; y: 0px; width: local-card.width; height: parent.height;
|
||||
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.remote-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
|
||||
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("输入 IP:端口,通过局域网连接", "Enter IP:port to connect over LAN", "Введите IP:порт для подключения по LAN") : GalleryStrings.tr("输入设备 ID,连接远程桌面", "Enter a device ID to start a remote session", "Введите ID для удалённого подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
||||
Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт") : UiStrings.remote-id; font-size: 12px; color: WorkbenchStyle.muted; }
|
||||
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[83] : GalleryStrings.texts[84]; font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
|
||||
Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.texts[85] : UiStrings.remote-id; font-size: 12px; color: WorkbenchStyle.muted; }
|
||||
FlatSurface {
|
||||
x: 16px; y: 90px; width: parent.width - 32px; height: 40px;
|
||||
background: remote-id-editor.has-focus ? ControlStyle.input-focused : ControlStyle.input-surface;
|
||||
@@ -1485,14 +1486,14 @@ export component MainWindow inherits Window {
|
||||
animate outline-color { duration: 120ms; }
|
||||
if root.remote-id-input == "": Text {
|
||||
x: 12px; width: parent.width - 24px; height: parent.height;
|
||||
text: root.lan-mode ? "192.168.1.20:52888" : GalleryStrings.tr("请输入 9 位设备 ID", "Enter a 9-digit device ID", "Введите ID из 9 цифр");
|
||||
text: root.lan-mode ? "192.168.1.20:52888" : GalleryStrings.texts[86];
|
||||
font-size: 13px; color: ControlStyle.input-placeholder; vertical-alignment: center;
|
||||
}
|
||||
remote-id-editor := TextInput {
|
||||
x: 12px; width: parent.width - 24px; height: parent.height;
|
||||
text <=> root.remote-id-input; font-size: root.lan-mode ? 18px : 24px; font-weight: 600; letter-spacing: root.lan-mode ? 0px : 1px;
|
||||
input-type: root.lan-mode ? InputType.text : InputType.number; color: WorkbenchStyle.text;
|
||||
accessible-label: root.lan-mode ? GalleryStrings.tr("远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт") : UiStrings.remote-id;
|
||||
accessible-label: root.lan-mode ? GalleryStrings.texts[85] : UiStrings.remote-id;
|
||||
enabled: !root.connection-pending && !root.lan-connecting;
|
||||
vertical-alignment: center;
|
||||
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
|
||||
@@ -1512,13 +1513,13 @@ export component MainWindow inherits Window {
|
||||
Text {
|
||||
x: 16px; y: 133px; width: parent.width - 32px; height: 24px;
|
||||
text: root.connection-input-error != "" ? root.connection-input-error
|
||||
: root.lan-mode ? GalleryStrings.tr("不填端口时,默认使用 52888", "Port defaults to 52888 if omitted", "Без порта используется 52888") : "";
|
||||
: root.lan-mode ? GalleryStrings.texts[87] : "";
|
||||
font-size: 11px; color: root.connection-input-error != "" ? #be3d42 : WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
WorkbenchButton {
|
||||
x: 16px; y: 158px; width: parent.width - 32px; height: 36px;
|
||||
primary: true; icon: FontAwesomeIcons.arrow-right-long;
|
||||
text: root.connection-pending || root.lan-connecting ? GalleryStrings.tr("正在连接…", "Connecting…", "Подключение…") : UiStrings.connect;
|
||||
text: root.connection-pending || root.lan-connecting ? GalleryStrings.texts[56] : UiStrings.connect;
|
||||
enabled: !root.connection-pending && !root.lan-connecting;
|
||||
clicked => { root.submit-connection(); }
|
||||
}
|
||||
@@ -1566,19 +1567,19 @@ export component MainWindow inherits Window {
|
||||
Text {
|
||||
x: 84px; y: 36px; width: parent.width - 104px; height: 24px;
|
||||
text: root.lan-mode
|
||||
? GalleryStrings.tr("暂无近期 IP 连接", "No recent IP connections", "Нет недавних IP-подключений")
|
||||
? GalleryStrings.texts[88]
|
||||
: root.recent-group-index == 0
|
||||
? GalleryStrings.tr("暂无近期连接", "No recent connections", "Нет недавних подключений")
|
||||
: GalleryStrings.tr("该分组暂无近期连接", "No recent connections in this group", "В этой группе нет недавних подключений");
|
||||
? GalleryStrings.texts[89]
|
||||
: GalleryStrings.texts[90];
|
||||
color: WorkbenchStyle.text; font-size: 14px; font-weight: 600;
|
||||
}
|
||||
Text {
|
||||
x: 84px; y: 68px; width: parent.width - 104px; height: 20px;
|
||||
text: root.lan-mode
|
||||
? GalleryStrings.tr("通过 IP 连接过的设备会显示在这里。", "Devices you connect to by IP will appear here.", "Здесь появятся устройства, подключённые по IP.")
|
||||
? GalleryStrings.texts[91]
|
||||
: root.recent-group-index == 0
|
||||
? GalleryStrings.tr("连接过的设备会显示在这里,方便下次快速访问。", "Devices you connect to will appear here for quick access.", "Устройства появятся здесь после первого подключения.")
|
||||
: GalleryStrings.tr("切换设备分组,查看其他连接记录。", "Choose another group to see your other connections.", "Выберите другую группу для просмотра подключений.");
|
||||
? GalleryStrings.texts[92]
|
||||
: GalleryStrings.texts[93];
|
||||
color: WorkbenchStyle.muted; font-size: 12px;
|
||||
}
|
||||
}
|
||||
@@ -1762,12 +1763,12 @@ export component MainWindow inherits Window {
|
||||
compact-trigger: true;
|
||||
compact-menu: true;
|
||||
enabled: !root.gallery-mode;
|
||||
text: GalleryStrings.tr("更多操作", "More actions", "Другие действия");
|
||||
menu-width: root.compact-language ? 116px : root.language-index == 2 ? 156px : 128px;
|
||||
text: GalleryStrings.texts[94];
|
||||
menu-width: root.compact-language ? 116px : 156px;
|
||||
items: [
|
||||
{label: GalleryStrings.tr("修改名称", "Rename", "Переименовать"), enabled: true},
|
||||
{label: GalleryStrings.tr("修改分组", "Edit groups", "Изменить группы"), enabled: !connection.lan},
|
||||
{label: GalleryStrings.tr("删除记录", "Delete record", "Удалить запись"), enabled: true},
|
||||
{label: GalleryStrings.texts[35], enabled: true},
|
||||
{label: GalleryStrings.texts[30], enabled: !connection.lan},
|
||||
{label: GalleryStrings.texts[95], enabled: true},
|
||||
];
|
||||
item-icons: [FontAwesomeIcons.pen, FontAwesomeIcons.tags, FontAwesomeIcons.trash-can];
|
||||
destructive-index: 2;
|
||||
@@ -1794,7 +1795,7 @@ export component MainWindow inherits Window {
|
||||
primary: !connection.connected;
|
||||
destructive: connection.connected;
|
||||
icon: connection.connected ? FontAwesomeIcons.xmark : FontAwesomeIcons.arrow-right-long;
|
||||
tooltip: connection.connected ? GalleryStrings.tr("断开连接", "Disconnect", "Отключить") : UiStrings.connect;
|
||||
tooltip: connection.connected ? GalleryStrings.texts[96] : UiStrings.connect;
|
||||
tooltip-above: true;
|
||||
clicked => {
|
||||
root.copy-feedback = CopyFeedback.idle;
|
||||
@@ -1884,7 +1885,7 @@ export component MainWindow inherits Window {
|
||||
height: parent.height;
|
||||
width: parent.width - 160px;
|
||||
overflow: elide;
|
||||
text: root.lan-mode ? (root.lan-message != "" ? LanStrings.error-message(root.lan-message) : root.lan-listener-error != "" ? (root.lan-listener-address == "" ? "" : root.lan-listener-address + " · ") + LanStrings.error-message(root.lan-listener-error) : root.lan-listening ? GalleryStrings.tr("IP 连接已启用", "IP connections enabled", "Подключения по IP включены") : root.lan-password-pending ? GalleryStrings.tr("IP 连接准备中…", "Preparing IP connections…", "Подготовка подключений по IP…") : GalleryStrings.tr("IP 连接未就绪", "IP connections not ready", "Подключения по IP не готовы"))
|
||||
text: root.lan-mode ? (root.lan-message != "" ? LanStrings.error-message(root.lan-message) : root.lan-listener-error != "" ? (root.lan-listener-address == "" ? "" : root.lan-listener-address + " · ") + LanStrings.error-message(root.lan-listener-error) : root.lan-listening ? GalleryStrings.texts[97] : root.lan-password-pending ? GalleryStrings.texts[98] : GalleryStrings.texts[99])
|
||||
: root.signal-tls-error ? UiStrings.tls-error
|
||||
: root.signal-connected ? UiStrings.signal-connected
|
||||
: root.signal-reconnecting ? UiStrings.signal-reconnecting
|
||||
@@ -1969,7 +1970,7 @@ export component MainWindow inherits Window {
|
||||
tooltip-status-text := Text {
|
||||
x: 7px; width: parent.width - 14px; height: parent.height;
|
||||
text: device-tip.online ? UiStrings.online : device-tip.presence-known ? UiStrings.offline
|
||||
: GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
||||
: GalleryStrings.texts[100];
|
||||
color: device-tip.status-color; font-size: 10px; font-weight: 600;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
@@ -2013,14 +2014,14 @@ export component MainWindow inherits Window {
|
||||
tooltip-group-label := Text {
|
||||
x: 12px; y: tooltip-groups.y;
|
||||
width: device-tip.label-width; height: 16px;
|
||||
text: GalleryStrings.tr("分组", "Groups", "Группы");
|
||||
text: GalleryStrings.texts[101];
|
||||
color: WorkbenchStyle.muted; font-size: 10px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
tooltip-groups := Text {
|
||||
x: 20px + device-tip.label-width; y: tooltip-id-label.y + tooltip-id-label.height + 4px;
|
||||
width: parent.width - self.x - 12px; height: max(16px, self.preferred-height);
|
||||
text: device-tip.group-names == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : device-tip.group-names;
|
||||
text: device-tip.group-names == "" ? GalleryStrings.texts[31] : device-tip.group-names;
|
||||
color: device-tip.group-names == "" ? WorkbenchStyle.muted : WorkbenchStyle.accent;
|
||||
font-size: 10px;
|
||||
wrap: word-wrap; horizontal-alignment: right; vertical-alignment: center;
|
||||
@@ -2220,8 +2221,8 @@ export component MainWindow inherits Window {
|
||||
error: root.copy-feedback == CopyFeedback.failed;
|
||||
icon: self.error ? FontAwesomeIcons.xmark : FontAwesomeIcons.check;
|
||||
text: self.error
|
||||
? GalleryStrings.tr("复制失败,请重试", "Couldn’t copy. Try again", "Не удалось скопировать. Повторите попытку")
|
||||
: root.lan-mode ? GalleryStrings.tr("IP 和端口已复制", "IP and port copied", "IP и порт скопированы") : UiStrings.copied;
|
||||
? GalleryStrings.texts[102]
|
||||
: root.lan-mode ? GalleryStrings.texts[103] : UiStrings.copied;
|
||||
z: 30;
|
||||
}
|
||||
|
||||
@@ -2332,7 +2333,7 @@ export component MainWindow inherits Window {
|
||||
height: 140px;
|
||||
VerticalLayout {
|
||||
padding: 20px; spacing: 18px;
|
||||
DialogPromptText { prompt: root.delete-from-gallery ? (root.gallery-group-index > 0 ? GalleryStrings.tr("从当前分组移除此设备?", "Remove from this group?", "Убрать из этой группы?") : GalleryStrings.tr("从桌面墙移除此设备?", "Remove from DeskHub?", "Убрать из DeskHub?")) : UiStrings.confirm-delete; }
|
||||
DialogPromptText { prompt: root.delete-from-gallery ? (root.gallery-group-index > 0 ? GalleryStrings.texts[104] : GalleryStrings.texts[105]) : UiStrings.confirm-delete; }
|
||||
HorizontalLayout {
|
||||
alignment: center; spacing: 8px;
|
||||
PrimaryButton { text: UiStrings.ok; clicked => { if root.delete-from-gallery { root.gallery-action("remove", root.delete-remote-id); } else { root.recent-delete(root.delete-remote-id); } root.delete-open = false; } }
|
||||
@@ -2385,7 +2386,7 @@ export component MainWindow inherits Window {
|
||||
padding: 24px;
|
||||
spacing: 16px;
|
||||
Text {
|
||||
text: GalleryStrings.tr("确认断开连接?", "Disconnect this session?", "Отключить этот сеанс?");
|
||||
text: GalleryStrings.texts[106];
|
||||
color: WorkbenchStyle.text;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
@@ -2407,7 +2408,7 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.tr("当前远程控制会话将结束。", "The current remote session will end.", "Текущий сеанс удалённого управления будет завершён.");
|
||||
text: GalleryStrings.texts[107];
|
||||
color: WorkbenchStyle.muted;
|
||||
font-size: 13px;
|
||||
wrap: word-wrap;
|
||||
@@ -2420,7 +2421,7 @@ export component MainWindow inherits Window {
|
||||
clicked => { root.dismiss-disconnect(); }
|
||||
}
|
||||
disconnect-confirm := WorkbenchButton {
|
||||
text: GalleryStrings.tr("断开连接", "Disconnect", "Отключить");
|
||||
text: GalleryStrings.texts[96];
|
||||
destructive: true;
|
||||
clicked => {
|
||||
let remote-id = root.disconnect-remote-id;
|
||||
@@ -2548,9 +2549,9 @@ export component MainWindow inherits Window {
|
||||
spacing: 12px;
|
||||
for permission[index] in [
|
||||
{label: UiStrings.accessibility-permission, granted: root.accessibility-granted,
|
||||
description: GalleryStrings.tr("允许远程键盘和鼠标控制", "Allow remote keyboard and mouse input", "Разрешить удалённый ввод с клавиатуры и мыши")},
|
||||
description: GalleryStrings.texts[108]},
|
||||
{label: UiStrings.screen-recording-permission, granted: root.screen-recording-granted,
|
||||
description: GalleryStrings.tr("允许共享此 Mac 的屏幕画面", "Allow sharing this Mac’s screen", "Разрешить показ экрана этого Mac")},
|
||||
description: GalleryStrings.texts[109]},
|
||||
]: WorkbenchCard {
|
||||
height: max(80px, permission-labels.preferred-height + 32px);
|
||||
permission-labels := VerticalLayout {
|
||||
@@ -2571,7 +2572,7 @@ export component MainWindow inherits Window {
|
||||
height: 36px;
|
||||
permission-action-metrics := Text {
|
||||
visible: false; font-size: 13px; font-weight: 600;
|
||||
text: GalleryStrings.tr("前往设置", "Open settings", "Настройки");
|
||||
text: GalleryStrings.texts[110];
|
||||
}
|
||||
if !permission.granted: WorkbenchButton {
|
||||
width: parent.width; height: parent.height; primary: true;
|
||||
@@ -2590,7 +2591,7 @@ export component MainWindow inherits Window {
|
||||
vertical-alignment: center; accessible-role: none;
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.tr("已授权", "Granted", "Разрешено");
|
||||
text: GalleryStrings.texts[111];
|
||||
font-size: 12px; color: WorkbenchStyle.success; vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
@@ -2601,9 +2602,7 @@ export component MainWindow inherits Window {
|
||||
spacing: 12px;
|
||||
SurfaceSeparator { height: 1px; outline-color: UiSurface.separator; }
|
||||
Text {
|
||||
text: GalleryStrings.tr("请在系统设置中允许 CrossDesk 访问。如系统提示重新打开应用,请按提示操作。",
|
||||
"Allow CrossDesk access in System Settings. Reopen the app if macOS asks you to.",
|
||||
"Разрешите доступ для CrossDesk в Системных настройках. Перезапустите приложение, если macOS попросит об этом.");
|
||||
text: GalleryStrings.texts[112];
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
@@ -2645,7 +2644,7 @@ export component MainWindow inherits Window {
|
||||
background: WorkbenchStyle.accent; border-radius: UiRadius.control;
|
||||
animate x { duration: 180ms; easing: ease-in-out; }
|
||||
}
|
||||
for label[index] in [GalleryStrings.tr("常规", "General", "Общие"), GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub")]: Rectangle {
|
||||
for label[index] in [GalleryStrings.texts[113], GalleryStrings.texts[54]]: Rectangle {
|
||||
x: index == 1 ? parent.width / 2 + 1px : 2px;
|
||||
y: 2px; width: (parent.width - 6px) / 2; height: parent.height - 4px;
|
||||
accessible-role: button; accessible-label: label;
|
||||
@@ -2683,14 +2682,14 @@ export component MainWindow inherits Window {
|
||||
padding-right: settings-dialog.scrollbar-gutter; padding-bottom: 4px;
|
||||
spacing: 14px;
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("界面", "Interface", "Интерфейс"); rows: 1;
|
||||
title: GalleryStrings.texts[114]; rows: 1;
|
||||
SettingsRow {
|
||||
label: UiStrings.language; divider: false;
|
||||
SettingsSelect { x: parent.width - 116px; y: 4px; width: 104px; model: ["中文", "English", "Русский"]; current-index <=> root.language-index; }
|
||||
SettingsSelect { x: parent.width - 192px; y: 4px; width: 180px; model: root.language-options; current-index <=> root.language-index; }
|
||||
}
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("视频", "Video", "Видео");
|
||||
title: GalleryStrings.texts[115];
|
||||
rows: root.capture-method-visible ? 2 : 1;
|
||||
if root.capture-method-visible: SettingsRow {
|
||||
label: UiStrings.capture-method; enabled: !root.settings-session-active;
|
||||
@@ -2702,9 +2701,9 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("网络连接", "Network", "Сеть"); rows: 3;
|
||||
title: GalleryStrings.texts[116]; rows: 3;
|
||||
SettingsToggleRow {
|
||||
label: GalleryStrings.tr("启用 IP 连接", "Enable IP connections", "Подключение по IP");
|
||||
label: GalleryStrings.texts[117];
|
||||
checked <=> root.ip-direct-enabled-draft;
|
||||
}
|
||||
SettingsToggleRow { label: UiStrings.force-relay; checked <=> root.force-relay-enabled; enabled: !root.settings-session-active; }
|
||||
@@ -2715,7 +2714,7 @@ export component MainWindow inherits Window {
|
||||
self-host-edit := SettingsButton {
|
||||
x: parent.width - self.width - 60px; y: 4px;
|
||||
width: self-host-edit-metrics.preferred-width + 24px; height: 28px;
|
||||
text: GalleryStrings.tr("编辑", "Edit", "Изменить");
|
||||
text: GalleryStrings.texts[118];
|
||||
enabled: !root.settings-session-active;
|
||||
clicked => { root.self-host-settings-open = true; }
|
||||
self-host-edit-metrics := Text {
|
||||
@@ -2726,21 +2725,21 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.tr("关闭 IP 连接将隐藏模式按钮,并断开 IP 连接会话。", "Disabling IP connections hides the mode button and ends IP sessions.", "Отключение подключений по IP скроет кнопку режима и завершит IP-сеансы.");
|
||||
text: GalleryStrings.texts[119];
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("隐私保护", "Privacy", "Приватность"); rows: 3;
|
||||
title: GalleryStrings.texts[120]; rows: 3;
|
||||
SettingsToggleRow { label: UiStrings.privacy-on-connect; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
|
||||
SettingsToggleRow { label: UiStrings.privacy-local-unlock; checked <=> root.privacy-local-unlock-allowed; enabled: root.privacy-setting-available; }
|
||||
SettingsToggleRow { label: UiStrings.lock-on-disconnect; checked <=> root.lock-on-disconnect-enabled; enabled: root.lock-on-disconnect-available; divider: false; }
|
||||
}
|
||||
Text {
|
||||
text: GalleryStrings.tr("最后一个远程控制端断开后,自动锁定本机。", "Lock this device when the last remote controller disconnects.", "Блокировать это устройство после отключения последнего управляющего клиента.");
|
||||
text: GalleryStrings.texts[121];
|
||||
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("本地存储", "Local storage", "Локальное хранение"); rows: 2;
|
||||
title: GalleryStrings.texts[122]; rows: 2;
|
||||
SettingsToggleRow { label: UiStrings.save-remote-previews; checked <=> root.save-remote-previews; }
|
||||
SettingsRow {
|
||||
label: UiStrings.file-save-path; enabled: !root.settings-session-active; divider: false;
|
||||
@@ -2748,7 +2747,7 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
SettingsGroup {
|
||||
title: GalleryStrings.tr("启动与后台", "Startup & background", "Запуск и фоновая работа"); rows: 2;
|
||||
title: GalleryStrings.texts[123]; rows: 2;
|
||||
if !root.autostart-system-managed: SettingsToggleRow { label: UiStrings.autostart; checked <=> root.autostart-enabled; enabled: !root.settings-session-active; }
|
||||
if root.autostart-system-managed: SettingsToggleRow {
|
||||
label: UiStrings.autostart;
|
||||
@@ -2784,12 +2783,12 @@ export component MainWindow inherits Window {
|
||||
width: parent.width - 2 * settings-dialog.content-inset; height: 32px; spacing: 10px;
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
SettingsButton {
|
||||
width: 80px; text: UiStrings.cancel;
|
||||
width: max(80px, self.label-width); text: UiStrings.cancel;
|
||||
clicked => { root.cancel-settings(); root.settings-open = false; root.self-host-settings-open = false; }
|
||||
}
|
||||
SettingsButton {
|
||||
width: 92px; primary: true;
|
||||
text: GalleryStrings.tr("保存", "Save", "Сохранить");
|
||||
width: max(92px, self.label-width); primary: true;
|
||||
text: GalleryStrings.texts[34];
|
||||
clicked => { if root.save-settings() { root.settings-open = false; root.self-host-settings-open = false; } }
|
||||
}
|
||||
}
|
||||
@@ -2807,7 +2806,7 @@ export component MainWindow inherits Window {
|
||||
TouchArea { clicked => { focus-sink.focus(); } }
|
||||
Text {
|
||||
x: 24px; y: 18px; width: parent.width - 48px; height: 26px;
|
||||
text: GalleryStrings.tr("设置开机自启动", "Set up auto start", "Настройка автозапуска");
|
||||
text: GalleryStrings.texts[124];
|
||||
font-size: 18px; font-weight: 600; color: WorkbenchStyle.text;
|
||||
}
|
||||
ScrollView {
|
||||
@@ -2819,25 +2818,20 @@ export component MainWindow inherits Window {
|
||||
width: parent.width; padding: 0px; spacing: 12px;
|
||||
Text { text: root.autostart-status-text; color: WorkbenchStyle.accent; font-size: 13px; wrap: word-wrap; }
|
||||
Text {
|
||||
text: root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.tr(
|
||||
"当前设备不支持开机自启动。", "This device does not support auto start.", "Это устройство не поддерживает автозапуск.") :
|
||||
root.autostart-status == AutostartStatus.failed ? GalleryStrings.tr(
|
||||
"无法读取系统状态。请刷新重试,或在系统设置中核对。", "Could not read the system status. Refresh to retry, or check System Settings.", "Не удалось прочитать состояние. Повторите попытку или проверьте настройки системы.") :
|
||||
GalleryStrings.tr("请在系统设置中添加或移除本应用,返回后会自动刷新状态。", "Add or remove this app in system Settings. The status refreshes when you return.", "Добавьте или удалите приложение в настройках системы. После возвращения статус обновится.");
|
||||
text: root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.texts[125] :
|
||||
root.autostart-status == AutostartStatus.failed ? GalleryStrings.texts[126] :
|
||||
GalleryStrings.texts[127];
|
||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||
}
|
||||
if root.autostart-status != AutostartStatus.unsupported: Text {
|
||||
text: GalleryStrings.tr(
|
||||
"设置 → 应用和元服务 → 应用启动管理\n\n开启:点击“添加应用”,选择 CrossDesk,然后点击“完成”。\n\n关闭:在自启动列表中找到本应用,点击“移除”。",
|
||||
"Settings → Apps & services → App startup management\n\nEnable: choose Add app, select CrossDesk, then choose Done.\n\nDisable: find this app in the startup list and choose Remove.",
|
||||
"Настройки → Приложения и сервисы → Управление запуском\n\nВключить: добавьте CrossDesk в список и подтвердите выбор.\n\nВыключить: удалите приложение из списка автозапуска.");
|
||||
text: GalleryStrings.texts[128];
|
||||
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingsButton {
|
||||
x: 24px; y: parent.height - 48px; width: 112px;
|
||||
text: GalleryStrings.tr("刷新状态", "Refresh status", "Обновить статус");
|
||||
text: GalleryStrings.texts[129];
|
||||
enabled: root.autostart-status != AutostartStatus.checking;
|
||||
clicked => { root.refresh-autostart-status(); }
|
||||
}
|
||||
@@ -2882,7 +2876,7 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
SurfaceSeparator { x: 18px; y: parent.height - 66px; width: parent.width - 36px; height: 1px; outline-color: UiSurface.separator; }
|
||||
SettingsButton { x: parent.width - 114px; y: parent.height - 50px; width: 92px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } }
|
||||
SettingsButton { x: parent.width - 204px; y: parent.height - 50px; width: 80px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
|
||||
SettingsButton { x: parent.width - 124px - self.width; y: parent.height - 50px; width: max(80px, self.label-width); text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export component SettingsButton inherits FlatSurface {
|
||||
in property <bool> quiet: false;
|
||||
in property <bool> align-left: false;
|
||||
in property <bool> enabled: true;
|
||||
out property <length> label-width: button-label.preferred-width + 2 * root.content-padding + (root.has-icon ? 24px : 0px);
|
||||
private property <bool> has-icon: root.icon.width > 0;
|
||||
private property <length> content-padding: root.has-icon ? 8px : 12px;
|
||||
callback clicked();
|
||||
|
||||
@@ -40,7 +40,21 @@ inline int ApplyMainWindowStrings(
|
||||
int language_index) {
|
||||
const int language =
|
||||
localization::detail::ClampLanguageIndex(language_index);
|
||||
window->global<ui::GalleryStrings>().set_language(language);
|
||||
auto& gallery = window->global<ui::GalleryStrings>();
|
||||
gallery.set_language(language);
|
||||
const auto& retry = localization::Literal("请在 {0} 秒后重试", language);
|
||||
const auto split = retry.find("{0}");
|
||||
gallery.set_retry_prefix(Text(retry.substr(0, split)));
|
||||
gallery.set_retry_suffix(Text(split == std::string::npos ? "" : retry.substr(split + 3)));
|
||||
std::vector<slint::SharedString> literals;
|
||||
for (const auto* key : localization::detail::kSlintLiterals)
|
||||
literals.push_back(Text(localization::Literal(key, language)));
|
||||
gallery.set_texts(std::make_shared<slint::VectorModel<slint::SharedString>>(literals));
|
||||
std::vector<slint::SharedString> languages;
|
||||
for (const auto& option : localization::GetSupportedLanguages())
|
||||
languages.push_back(Text(option.display_name));
|
||||
languages.push_back(Text(localization::Literal("跟随系统", language)));
|
||||
window->set_language_options(std::make_shared<slint::VectorModel<slint::SharedString>>(languages));
|
||||
auto& strings = window->global<ui::UiStrings>();
|
||||
strings.set_window_minimize(Text(localization::window_minimize[language]));
|
||||
strings.set_window_close(Text(localization::window_close[language]));
|
||||
|
||||
@@ -25,15 +25,15 @@ std::optional<bool> Boolean(const std::string& value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const char* OnOff(bool value) { return value ? "on" : "off"; }
|
||||
const char* languages[] = {"zh-CN", "en-US", "ru-RU"};
|
||||
const char* languages[] = {"zh-CN", "en-US", "ru-RU", "zh-Hant", "ja", "ko", "de", "fr", "es", "pt-BR", "system"};
|
||||
const char* turn_modes[] = {"off", "auto", "udp", "tcp"};
|
||||
} // namespace
|
||||
|
||||
std::vector<ConsoleSetting> GetConsoleSettings(const ConfigCenter& config) {
|
||||
const auto language = std::clamp(static_cast<int>(config.GetLanguage()), 0, 2);
|
||||
const auto language = std::clamp(static_cast<int>(config.GetLanguage()), 0, 10);
|
||||
const auto turn = std::clamp(static_cast<int>(config.GetTurnMode()), 0, 3);
|
||||
return {
|
||||
{"language", "console_setting_language", languages[language], "zh-CN / en-US / ru-RU"},
|
||||
{"language", "console_setting_language", languages[language], "system / zh-CN / en-US / ru-RU / zh-Hant / ja / ko / de / fr / es / pt-BR"},
|
||||
{"codec", "console_setting_codec", config.GetVideoEncodeFormat() == ConfigCenter::VIDEO_ENCODE_FORMAT::AV1 ? "av1" : "h264", "h264 / av1"},
|
||||
{"turn", "console_setting_turn", turn_modes[turn], "off / auto / udp / tcp"},
|
||||
{"self_hosted", "console_setting_self_hosted", OnOff(config.IsSelfHosted()), "on / off"},
|
||||
@@ -70,6 +70,10 @@ SettingResult ApplyConsoleSetting(ConfigCenter& config, const std::string& key,
|
||||
int language = value == "zh-cn" || value == "zh" || value == "0" ? 0
|
||||
: value == "en-us" || value == "en" || value == "1" ? 1
|
||||
: value == "ru-ru" || value == "ru" || value == "2" ? 2 : -1;
|
||||
if (language < 0) {
|
||||
for (int i = 0; i <= 10; ++i)
|
||||
if (value == Lower(languages[i]) || value == std::to_string(i)) language = i;
|
||||
}
|
||||
if (language < 0) return result;
|
||||
canonical = languages[language];
|
||||
save = [&config, language] { return config.SetLanguage(static_cast<ConfigCenter::LANGUAGE>(language)); };
|
||||
|
||||
Reference in New Issue
Block a user