diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 4ea494fc5..ed78a8e09 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1367,7 +1367,7 @@ connect(BuildContext context, String id, } } -Future> getHttpHeaders() async { +Map getHttpHeaders() { return { 'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}' }; diff --git a/flutter/lib/common/hbbs/hbbs.dart b/flutter/lib/common/hbbs/hbbs.dart index 856f88d20..27238db67 100644 --- a/flutter/lib/common/hbbs/hbbs.dart +++ b/flutter/lib/common/hbbs/hbbs.dart @@ -1,12 +1,22 @@ import 'package:flutter_hbb/models/peer_model.dart'; +class HttpType { + static const kAuthReqTypeAccount = "account"; + static const kAuthReqTypeMobile = "mobile"; + static const kAuthReqTypeSMSCode = "sms_code"; + static const kAuthReqTypeEmailCode = "email_code"; + + static const kAuthResTypeToken = "access_token"; + static const kAuthResTypeEmailCheck = "email_check"; +} + class UserPayload { String name = ''; String email = ''; String note = ''; int? status; String grp = ''; - bool is_admin = false; + bool isAdmin = false; UserPayload.fromJson(Map json) : name = json['name'] ?? '', @@ -14,7 +24,7 @@ class UserPayload { note = json['note'] ?? '', status = json['status'], grp = json['grp'] ?? '', - is_admin = json['is_admin'] == true; + isAdmin = json['is_admin'] == true; } class PeerPayload { @@ -37,3 +47,73 @@ class PeerPayload { return Peer.fromJson({"id": p.id}); } } + +class LoginRequest { + String? username; + String? password; + String? id; + String? uuid; + bool? autoLogin; + String? type; + String? verificationCode; + String? deviceInfo; + + LoginRequest( + {this.username, + this.password, + this.id, + this.uuid, + this.autoLogin, + this.type, + this.verificationCode, + this.deviceInfo}); + + LoginRequest.fromJson(Map json) { + username = json['username']; + password = json['password']; + id = json['id']; + uuid = json['uuid']; + autoLogin = json['autoLogin']; + type = json['type']; + verificationCode = json['verificationCode']; + deviceInfo = json['deviceInfo']; + } + + Map toJson() { + final Map data = {}; + data['username'] = username ?? ''; + data['password'] = password ?? ''; + data['id'] = id ?? ''; + data['uuid'] = uuid ?? ''; + data['autoLogin'] = autoLogin ?? ''; + data['type'] = type ?? ''; + data['verificationCode'] = verificationCode ?? ''; + data['deviceInfo'] = deviceInfo ?? ''; + return data; + } +} + +class LoginResponse { + String? access_token; + String? type; + UserPayload? user; + + LoginResponse({this.access_token, this.type, this.user}); + + LoginResponse.fromJson(Map json) { + access_token = json['access_token']; + type = json['type']; + user = json['user'] != null ? UserPayload.fromJson(json['user']) : null; + } +} + +class RequestException implements Exception { + int statusCode; + String cause; + RequestException(this.statusCode, this.cause); + + @override + String toString() { + return "RequestException, statusCode: $statusCode, error: $cause"; + } +} diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index fbeca25b2..34d5af485 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -3,14 +3,12 @@ import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; -import 'package:flutter_hbb/desktop/widgets/login.dart'; import '../../consts.dart'; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import 'package:get/get.dart'; import '../../common.dart'; -import '../../desktop/pages/desktop_home_page.dart'; -import '../../mobile/pages/settings_page.dart'; +import 'login.dart'; class AddressBook extends StatefulWidget { final EdgeInsets? menuPadding; @@ -41,21 +39,12 @@ class _AddressBookState extends State { } }); - handleLogin() { - // TODO refactor login dialog for desktop and mobile - if (isDesktop) { - loginDialog(); - } else { - showLogin(gFFI.dialogManager); - } - } - Future buildBody(BuildContext context) async { return Obx(() { if (gFFI.userModel.userName.value.isEmpty) { return Center( child: InkWell( - onTap: handleLogin, + onTap: loginDialog, child: Text( translate("Login"), style: const TextStyle(decoration: TextDecoration.underline), diff --git a/flutter/lib/common/widgets/login.dart b/flutter/lib/common/widgets/login.dart new file mode 100644 index 000000000..ce27ceb2c --- /dev/null +++ b/flutter/lib/common/widgets/login.dart @@ -0,0 +1,676 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common/hbbs/hbbs.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:get/get.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../common.dart'; + +class _IconOP extends StatelessWidget { + final String icon; + final double iconWidth; + final EdgeInsets margin; + const _IconOP( + {Key? key, + required this.icon, + required this.iconWidth, + this.margin = const EdgeInsets.symmetric(horizontal: 4.0)}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: margin, + child: SvgPicture.asset( + 'assets/$icon.svg', + width: iconWidth, + ), + ); + } +} + +class ButtonOP extends StatelessWidget { + final String op; + final RxString curOP; + final double iconWidth; + final Color primaryColor; + final double height; + final Function() onTap; + + const ButtonOP({ + Key? key, + required this.op, + required this.curOP, + required this.iconWidth, + required this.primaryColor, + required this.height, + required this.onTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row(children: [ + Container( + height: height, + width: 200, + child: Obx(() => ElevatedButton( + style: ElevatedButton.styleFrom( + primary: curOP.value.isEmpty || curOP.value == op + ? primaryColor + : Colors.grey, + ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), + onPressed: curOP.value.isEmpty || curOP.value == op ? onTap : null, + child: Row( + children: [ + SizedBox( + width: 30, + child: _IconOP( + icon: op, + iconWidth: iconWidth, + margin: EdgeInsets.only(right: 5), + )), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Center( + child: Text('${translate("Continue with")} $op')))), + ], + ))), + ), + ]); + } +} + +class ConfigOP { + final String op; + final double iconWidth; + ConfigOP({required this.op, required this.iconWidth}); +} + +class WidgetOP extends StatefulWidget { + final ConfigOP config; + final RxString curOP; + final Function(String) cbLogin; + const WidgetOP({ + Key? key, + required this.config, + required this.curOP, + required this.cbLogin, + }) : super(key: key); + + @override + State createState() { + return _WidgetOPState(); + } +} + +class _WidgetOPState extends State { + Timer? _updateTimer; + String _stateMsg = ''; + String _failedMsg = ''; + String _url = ''; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + _updateTimer?.cancel(); + } + + _beginQueryState() { + _updateTimer = Timer.periodic(Duration(seconds: 1), (timer) { + _updateState(); + }); + } + + _updateState() { + bind.mainAccountAuthResult().then((result) { + if (result.isEmpty) { + return; + } + final resultMap = jsonDecode(result); + if (resultMap == null) { + return; + } + final String stateMsg = resultMap['state_msg']; + String failedMsg = resultMap['failed_msg']; + final String? url = resultMap['url']; + final authBody = resultMap['auth_body']; + if (_stateMsg != stateMsg || _failedMsg != failedMsg) { + if (_url.isEmpty && url != null && url.isNotEmpty) { + launchUrl(Uri.parse(url)); + _url = url; + } + if (authBody != null) { + _updateTimer?.cancel(); + final String username = authBody['user']['name']; + widget.curOP.value = ''; + widget.cbLogin(username); + } + + setState(() { + _stateMsg = stateMsg; + _failedMsg = failedMsg; + if (failedMsg.isNotEmpty) { + widget.curOP.value = ''; + _updateTimer?.cancel(); + } + }); + } + }); + } + + _resetState() { + _stateMsg = ''; + _failedMsg = ''; + _url = ''; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ButtonOP( + op: widget.config.op, + curOP: widget.curOP, + iconWidth: widget.config.iconWidth, + primaryColor: str2color(widget.config.op, 0x7f), + height: 36, + onTap: () async { + _resetState(); + widget.curOP.value = widget.config.op; + await bind.mainAccountAuth(op: widget.config.op); + _beginQueryState(); + }, + ), + Obx(() { + if (widget.curOP.isNotEmpty && + widget.curOP.value != widget.config.op) { + _failedMsg = ''; + } + return Offstage( + offstage: + _failedMsg.isEmpty && widget.curOP.value != widget.config.op, + child: Row( + children: [ + Text( + _stateMsg, + style: TextStyle(fontSize: 12), + ), + SizedBox(width: 8), + Text( + _failedMsg, + style: TextStyle( + fontSize: 14, + color: Colors.red, + ), + ), + ], + )); + }), + Obx( + () => Offstage( + offstage: widget.curOP.value != widget.config.op, + child: const SizedBox( + height: 5.0, + ), + ), + ), + Obx( + () => Offstage( + offstage: widget.curOP.value != widget.config.op, + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: 20), + child: ElevatedButton( + onPressed: () { + widget.curOP.value = ''; + _updateTimer?.cancel(); + _resetState(); + bind.mainAccountAuthCancel(); + }, + child: Text( + translate('Cancel'), + style: TextStyle(fontSize: 15), + ), + ), + ), + ), + ), + ], + ); + } +} + +class LoginWidgetOP extends StatelessWidget { + final List ops; + final RxString curOP; + final Function(String) cbLogin; + + LoginWidgetOP({ + Key? key, + required this.ops, + required this.curOP, + required this.cbLogin, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var children = ops + .map((op) => [ + WidgetOP( + config: op, + curOP: curOP, + cbLogin: cbLogin, + ), + const Divider( + indent: 5, + endIndent: 5, + ) + ]) + .expand((i) => i) + .toList(); + if (children.isNotEmpty) { + children.removeLast(); + } + return SingleChildScrollView( + child: Container( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: children, + ))); + } +} + +class LoginWidgetUserPass extends StatelessWidget { + final TextEditingController username; + final TextEditingController pass; + final String? usernameMsg; + final String? passMsg; + final bool isInProgress; + final RxString curOP; + final RxBool autoLogin; + final Function() onLogin; + final FocusNode? userFocusNode; + const LoginWidgetUserPass({ + Key? key, + this.userFocusNode, + required this.username, + required this.pass, + required this.usernameMsg, + required this.passMsg, + required this.isInProgress, + required this.curOP, + required this.autoLogin, + required this.onLogin, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8.0), + DialogTextField( + title: '${translate("Username")}:', + controller: username, + focusNode: userFocusNode, + prefixIcon: Icon(Icons.account_circle_outlined), + errorText: usernameMsg), + DialogTextField( + title: '${translate("Password")}:', + obscureText: true, + controller: pass, + prefixIcon: Icon(Icons.lock_outline), + errorText: passMsg), + Obx(() => CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + translate("Remember me"), + ), + value: autoLogin.value, + onChanged: (v) { + if (v == null) return; + autoLogin.value = v; + }, + )), + Offstage( + offstage: !isInProgress, + child: const LinearProgressIndicator()), + const SizedBox(height: 12.0), + FittedBox( + child: + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Container( + height: 38, + width: 200, + child: Obx(() => ElevatedButton( + child: Text( + translate('Login'), + style: TextStyle(fontSize: 16), + ), + onPressed: + curOP.value.isEmpty || curOP.value == 'rustdesk' + ? () { + onLogin(); + } + : null, + )), + ), + ])), + ], + )); + } +} + +class DialogTextField extends StatelessWidget { + final String title; + final bool obscureText; + final String? errorText; + final String? helperText; + final Widget? prefixIcon; + final TextEditingController controller; + final FocusNode? focusNode; + + DialogTextField( + {Key? key, + this.focusNode, + this.obscureText = false, + this.errorText, + this.helperText, + this.prefixIcon, + required this.title, + required this.controller}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: TextField( + decoration: InputDecoration( + labelText: title, + border: const OutlineInputBorder(), + prefixIcon: prefixIcon, + helperText: helperText, + helperMaxLines: 8, + errorText: errorText), + controller: controller, + focusNode: focusNode, + autofocus: true, + obscureText: obscureText, + ), + ), + ], + ).paddingSymmetric(vertical: 4.0); + } +} + +/// common login dialog for desktop +/// call this directly +Future loginDialog() async { + var username = TextEditingController(); + var password = TextEditingController(); + final userFocusNode = FocusNode()..requestFocus(); + Timer(Duration(milliseconds: 100), () => userFocusNode..requestFocus()); + + String? usernameMsg; + String? passwordMsg; + var isInProgress = false; + final autoLogin = true.obs; + final RxString curOP = ''.obs; + + final res = await gFFI.dialogManager.show((setState, close) { + username.addListener(() { + if (usernameMsg != null) { + setState(() => usernameMsg = null); + } + }); + + password.addListener(() { + if (passwordMsg != null) { + setState(() => passwordMsg = null); + } + }); + + onDialogCancel() { + isInProgress = false; + close(false); + } + + onLogin() async { + // validate + if (username.text.isEmpty) { + setState(() => usernameMsg = translate('Username missed')); + return; + } + if (password.text.isEmpty) { + setState(() => passwordMsg = translate('Password missed')); + return; + } + curOP.value = 'rustdesk'; + setState(() => isInProgress = true); + try { + final resp = await gFFI.userModel.login(LoginRequest( + username: username.text, + password: password.text, + id: await bind.mainGetMyId(), + uuid: await bind.mainGetUuid(), + autoLogin: autoLogin.value, + type: HttpType.kAuthReqTypeAccount)); + + switch (resp.type) { + case HttpType.kAuthResTypeToken: + if (resp.access_token != null) { + await bind.mainSetLocalOption( + key: 'access_token', value: resp.access_token!); + close(true); + return; + } + break; + case HttpType.kAuthResTypeEmailCheck: + setState(() => isInProgress = false); + final res = await verificationCodeDialog(resp.user); + if (res == true) { + close(true); + return; + } + break; + default: + passwordMsg = "Failed, bad response from server"; + break; + } + } on RequestException catch (err) { + passwordMsg = translate(err.cause); + debugPrintStack(label: err.toString()); + } catch (err) { + passwordMsg = "Unknown Error: $err"; + debugPrintStack(label: err.toString()); + } + curOP.value = ''; + setState(() => isInProgress = false); + } + + return CustomAlertDialog( + title: Text(translate('Login')), + contentBoxConstraints: BoxConstraints(minWidth: 400), + content: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox( + height: 8.0, + ), + LoginWidgetUserPass( + username: username, + pass: password, + usernameMsg: usernameMsg, + passMsg: passwordMsg, + isInProgress: isInProgress, + curOP: curOP, + autoLogin: autoLogin, + onLogin: onLogin, + userFocusNode: userFocusNode, + ), + const SizedBox( + height: 8.0, + ), + Center( + child: Text( + translate('or'), + style: TextStyle(fontSize: 16), + )), + const SizedBox( + height: 8.0, + ), + LoginWidgetOP( + ops: [ + ConfigOP(op: 'Github', iconWidth: 20), + ConfigOP(op: 'Google', iconWidth: 20), + ConfigOP(op: 'Okta', iconWidth: 38), + ], + curOP: curOP, + cbLogin: (String username) { + gFFI.userModel.userName.value = username; + close(true); + }, + ), + ], + ), + actions: [msgBoxButton(translate('Close'), onDialogCancel)], + onCancel: onDialogCancel, + ); + }); + + if (res != null) { + // update ab and group status + await gFFI.abModel.pullAb(); + await gFFI.groupModel.pull(); + } + + return res; +} + +Future verificationCodeDialog(UserPayload? user) async { + var autoLogin = true; + var isInProgress = false; + String? errorText; + + final code = TextEditingController(); + final focusNode = FocusNode()..requestFocus(); + Timer(Duration(milliseconds: 100), () => focusNode..requestFocus()); + + final res = await gFFI.dialogManager.show((setState, close) { + bool validate() { + return code.text.length >= 6; + } + + code.addListener(() { + if (errorText != null) { + setState(() => errorText = null); + } + }); + + void onVerify() async { + if (!validate()) { + setState( + () => errorText = translate('Too short, at least 6 characters.')); + return; + } + setState(() => isInProgress = true); + + try { + final resp = await gFFI.userModel.login(LoginRequest( + verificationCode: code.text, + username: user?.name, + id: await bind.mainGetMyId(), + uuid: await bind.mainGetUuid(), + autoLogin: autoLogin, + type: HttpType.kAuthReqTypeEmailCode)); + + switch (resp.type) { + case HttpType.kAuthResTypeToken: + if (resp.access_token != null) { + await bind.mainSetLocalOption( + key: 'access_token', value: resp.access_token!); + close(true); + return; + } + break; + default: + errorText = "Failed, bad response from server"; + break; + } + } on RequestException catch (err) { + errorText = translate(err.cause); + debugPrintStack(label: err.toString()); + } catch (err) { + errorText = "Unknown Error: $err"; + debugPrintStack(label: err.toString()); + } + + setState(() => isInProgress = false); + } + + return CustomAlertDialog( + title: Text(translate("Verification code")), + contentBoxConstraints: BoxConstraints(maxWidth: 300), + content: Column( + children: [ + Offstage( + offstage: user?.email == null, + child: TextField( + decoration: InputDecoration( + labelText: "Email", + prefixIcon: Icon(Icons.email), + border: InputBorder.none), + readOnly: true, + controller: TextEditingController(text: user?.email), + )), + const SizedBox(height: 8), + DialogTextField( + title: '${translate("Verification code")}:', + controller: code, + errorText: errorText, + focusNode: focusNode, + helperText: translate('verification_tip'), + ), + CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Row(children: [ + Expanded(child: Text(translate("Trust this device"))) + ]), + value: autoLogin, + onChanged: (v) { + if (v == null) return; + setState(() => autoLogin = !autoLogin); + }, + ), + Offstage( + offstage: !isInProgress, + child: const LinearProgressIndicator()), + ], + ), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: onVerify, child: Text(translate("Verify"))), + ]); + }); + + return res; +} diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 45588171b..6d38c40f9 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -8,7 +8,6 @@ import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; -import 'package:flutter_hbb/desktop/widgets/login.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:get/get.dart'; @@ -18,6 +17,7 @@ import 'package:url_launcher/url_launcher_string.dart'; import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart'; import '../../common/widgets/dialog.dart'; +import '../../common/widgets/login.dart'; const double _kTabWidth = 235; const double _kTabHeight = 42; diff --git a/flutter/lib/desktop/widgets/login.dart b/flutter/lib/desktop/widgets/login.dart deleted file mode 100644 index 62e6ebc53..000000000 --- a/flutter/lib/desktop/widgets/login.dart +++ /dev/null @@ -1,521 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:get/get.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import '../../common.dart'; - -final _kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0); - -class _IconOP extends StatelessWidget { - final String icon; - final double iconWidth; - const _IconOP({Key? key, required this.icon, required this.iconWidth}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 4.0), - child: SvgPicture.asset( - 'assets/$icon.svg', - width: iconWidth, - ), - ); - } -} - -class ButtonOP extends StatelessWidget { - final String op; - final RxString curOP; - final double iconWidth; - final Color primaryColor; - final double height; - final Function() onTap; - - const ButtonOP({ - Key? key, - required this.op, - required this.curOP, - required this.iconWidth, - required this.primaryColor, - required this.height, - required this.onTap, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Row(children: [ - Expanded( - child: Container( - height: height, - padding: _kMidButtonPadding, - child: Obx(() => ElevatedButton( - style: ElevatedButton.styleFrom( - primary: curOP.value.isEmpty || curOP.value == op - ? primaryColor - : Colors.grey, - ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), - onPressed: - curOP.value.isEmpty || curOP.value == op ? onTap : null, - child: Stack(children: [ - Center(child: Text('${translate("Continue with")} $op')), - Align( - alignment: Alignment.centerLeft, - child: SizedBox( - width: 120, - child: _IconOP( - icon: op, - iconWidth: iconWidth, - )), - ), - ]), - )), - ), - ) - ]); - } -} - -class ConfigOP { - final String op; - final double iconWidth; - ConfigOP({required this.op, required this.iconWidth}); -} - -class WidgetOP extends StatefulWidget { - final ConfigOP config; - final RxString curOP; - final Function(String) cbLogin; - const WidgetOP({ - Key? key, - required this.config, - required this.curOP, - required this.cbLogin, - }) : super(key: key); - - @override - State createState() { - return _WidgetOPState(); - } -} - -class _WidgetOPState extends State { - Timer? _updateTimer; - String _stateMsg = ''; - String _failedMsg = ''; - String _url = ''; - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - _updateTimer?.cancel(); - } - - _beginQueryState() { - _updateTimer = Timer.periodic(Duration(seconds: 1), (timer) { - _updateState(); - }); - } - - _updateState() { - bind.mainAccountAuthResult().then((result) { - if (result.isEmpty) { - return; - } - final resultMap = jsonDecode(result); - if (resultMap == null) { - return; - } - final String stateMsg = resultMap['state_msg']; - String failedMsg = resultMap['failed_msg']; - final String? url = resultMap['url']; - final authBody = resultMap['auth_body']; - if (_stateMsg != stateMsg || _failedMsg != failedMsg) { - if (_url.isEmpty && url != null && url.isNotEmpty) { - launchUrl(Uri.parse(url)); - _url = url; - } - if (authBody != null) { - _updateTimer?.cancel(); - final String username = authBody['user']['name']; - widget.curOP.value = ''; - widget.cbLogin(username); - } - - setState(() { - _stateMsg = stateMsg; - _failedMsg = failedMsg; - if (failedMsg.isNotEmpty) { - widget.curOP.value = ''; - _updateTimer?.cancel(); - } - }); - } - }); - } - - _resetState() { - _stateMsg = ''; - _failedMsg = ''; - _url = ''; - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - ButtonOP( - op: widget.config.op, - curOP: widget.curOP, - iconWidth: widget.config.iconWidth, - primaryColor: str2color(widget.config.op, 0x7f), - height: 36, - onTap: () async { - _resetState(); - widget.curOP.value = widget.config.op; - await bind.mainAccountAuth(op: widget.config.op); - _beginQueryState(); - }, - ), - Obx(() { - if (widget.curOP.isNotEmpty && - widget.curOP.value != widget.config.op) { - _failedMsg = ''; - } - return Offstage( - offstage: - _failedMsg.isEmpty && widget.curOP.value != widget.config.op, - child: Row( - children: [ - Text( - _stateMsg, - style: TextStyle(fontSize: 12), - ), - SizedBox(width: 8), - Text( - _failedMsg, - style: TextStyle( - fontSize: 14, - color: Colors.red, - ), - ), - ], - )); - }), - Obx( - () => Offstage( - offstage: widget.curOP.value != widget.config.op, - child: const SizedBox( - height: 5.0, - ), - ), - ), - Obx( - () => Offstage( - offstage: widget.curOP.value != widget.config.op, - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: 20), - child: ElevatedButton( - onPressed: () { - widget.curOP.value = ''; - _updateTimer?.cancel(); - _resetState(); - bind.mainAccountAuthCancel(); - }, - child: Text( - translate('Cancel'), - style: TextStyle(fontSize: 15), - ), - ), - ), - ), - ), - ], - ); - } -} - -class LoginWidgetOP extends StatelessWidget { - final List ops; - final RxString curOP; - final Function(String) cbLogin; - - LoginWidgetOP({ - Key? key, - required this.ops, - required this.curOP, - required this.cbLogin, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - var children = ops - .map((op) => [ - WidgetOP( - config: op, - curOP: curOP, - cbLogin: cbLogin, - ), - const Divider( - indent: 5, - endIndent: 5, - ) - ]) - .expand((i) => i) - .toList(); - if (children.isNotEmpty) { - children.removeLast(); - } - return SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: children, - )); - } -} - -class LoginWidgetUserPass extends StatelessWidget { - final String username; - final String pass; - final String usernameMsg; - final String passMsg; - final bool isInProgress; - final RxString curOP; - final Function(String, String) onLogin; - const LoginWidgetUserPass({ - Key? key, - required this.username, - required this.pass, - required this.usernameMsg, - required this.passMsg, - required this.isInProgress, - required this.curOP, - required this.onLogin, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - var userController = TextEditingController(text: username); - var pwdController = TextEditingController(text: pass); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 8.0, - ), - Container( - padding: _kMidButtonPadding, - child: Row( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100), - child: Text( - '${translate("Username")}:', - textAlign: TextAlign.start, - ).marginOnly(bottom: 16.0)), - const SizedBox( - width: 24.0, - ), - Expanded( - child: TextField( - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: usernameMsg.isNotEmpty ? usernameMsg : null), - controller: userController, - focusNode: FocusNode()..requestFocus(), - ), - ), - ], - ), - ), - const SizedBox( - height: 8.0, - ), - Container( - padding: _kMidButtonPadding, - child: Row( - children: [ - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100), - child: Text('${translate("Password")}:') - .marginOnly(bottom: 16.0)), - const SizedBox( - width: 24.0, - ), - Expanded( - child: TextField( - obscureText: true, - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: passMsg.isNotEmpty ? passMsg : null), - controller: pwdController, - ), - ), - ], - ), - ), - const SizedBox( - height: 4.0, - ), - Offstage( - offstage: !isInProgress, child: const LinearProgressIndicator()), - const SizedBox( - height: 12.0, - ), - Row(children: [ - Expanded( - child: Container( - height: 38, - padding: _kMidButtonPadding, - child: Obx(() => ElevatedButton( - style: curOP.value.isEmpty || curOP.value == 'rustdesk' - ? null - : ElevatedButton.styleFrom( - primary: Colors.grey, - ), - child: Text( - translate('Login'), - style: TextStyle(fontSize: 16), - ), - onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk' - ? () { - onLogin(userController.text, pwdController.text); - } - : null, - )), - ), - ), - ]), - ], - ); - } -} - -/// common login dialog for desktop -/// call this directly -Future loginDialog() async { - String username = ''; - var usernameMsg = ''; - String pass = ''; - var passMsg = ''; - var isInProgress = false; - var completer = Completer(); - final RxString curOP = ''.obs; - - gFFI.dialogManager.show((setState, close) { - cancel() { - isInProgress = false; - completer.complete(false); - close(); - } - - onLogin(String username0, String pass0) async { - setState(() { - usernameMsg = ''; - passMsg = ''; - isInProgress = true; - }); - cancel() { - curOP.value = ''; - if (isInProgress) { - setState(() { - isInProgress = false; - }); - } - } - - curOP.value = 'rustdesk'; - username = username0; - pass = pass0; - if (username.isEmpty) { - usernameMsg = translate('Username missed'); - cancel(); - return; - } - if (pass.isEmpty) { - passMsg = translate('Password missed'); - cancel(); - return; - } - try { - final resp = await gFFI.userModel.login(username, pass); - if (resp.containsKey('error')) { - passMsg = resp['error']; - cancel(); - return; - } - // {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w, - // token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}} - debugPrint('$resp'); - completer.complete(true); - } catch (err) { - debugPrintStack(label: err.toString()); - cancel(); - return; - } - close(); - } - - return CustomAlertDialog( - title: Text(translate('Login')), - content: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 8.0, - ), - LoginWidgetUserPass( - username: username, - pass: pass, - usernameMsg: usernameMsg, - passMsg: passMsg, - isInProgress: isInProgress, - curOP: curOP, - onLogin: onLogin, - ), - const SizedBox( - height: 8.0, - ), - Center( - child: Text( - translate('or'), - style: TextStyle(fontSize: 16), - )), - const SizedBox( - height: 8.0, - ), - LoginWidgetOP( - ops: [ - ConfigOP(op: 'Github', iconWidth: 20), - ConfigOP(op: 'Google', iconWidth: 20), - ConfigOP(op: 'Okta', iconWidth: 38), - ], - curOP: curOP, - cbLogin: (String username) { - gFFI.userModel.userName.value = username; - completer.complete(true); - close(); - }, - ), - ], - ), - ), - actions: [msgBoxButton(translate('Close'), cancel)], - onCancel: cancel, - ); - }); - return completer.future; -} diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 957910324..6fce887bf 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -7,9 +7,8 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; -import '../../common/widgets/address_book.dart'; +import '../../common/widgets/login.dart'; import '../../common/widgets/peer_tab_page.dart'; -import '../../common/widgets/peers_view.dart'; import '../../consts.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; @@ -258,7 +257,7 @@ class _WebMenuState extends State { } if (value == 'login') { if (gFFI.userModel.userName.value.isEmpty) { - showLogin(gFFI.dialogManager); + loginDialog(); } else { gFFI.userModel.logOut(); } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 9637ecb40..b14f3ee65 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../common.dart'; import '../../common/widgets/dialog.dart'; +import '../../common/widgets/login.dart'; import '../../models/model.dart'; import '../../models/platform_model.dart'; import '../widgets/dialog.dart'; @@ -300,7 +301,7 @@ class _SettingsState extends State with WidgetsBindingObserver { leading: Icon(Icons.person), onPressed: (context) { if (gFFI.userModel.userName.value.isEmpty) { - showLogin(gFFI.dialogManager); + loginDialog(); } else { gFFI.userModel.logOut(); } @@ -397,7 +398,7 @@ void showServerSettings(OverlayDialogManager dialogManager) async { void showLanguageSettings(OverlayDialogManager dialogManager) async { try { final langs = json.decode(await bind.mainGetLangs()) as List; - var lang = await bind.mainGetLocalOption(key: "lang"); + var lang = bind.mainGetLocalOption(key: "lang"); dialogManager.show((setState, close) { setLang(v) { if (lang != v) { @@ -482,77 +483,6 @@ void showAbout(OverlayDialogManager dialogManager) { }, clickMaskDismiss: true, backDismiss: true); } -void showLogin(OverlayDialogManager dialogManager) { - final passwordController = TextEditingController(); - final nameController = TextEditingController(); - var loading = false; - var error = ''; - dialogManager.show((setState, close) { - return CustomAlertDialog( - title: Text(translate('Login')), - content: Column(mainAxisSize: MainAxisSize.min, children: [ - TextField( - autofocus: true, - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.visiblePassword, - decoration: InputDecoration( - labelText: translate('Username'), - ), - controller: nameController, - ), - PasswordWidget(controller: passwordController, autoFocus: false), - ]), - actions: (loading - ? [CircularProgressIndicator()] - : (error != "" - ? [ - Text(translate(error), - style: TextStyle(color: Colors.red)) - ] - : [])) + - [ - TextButton( - style: flatButtonStyle, - onPressed: loading - ? null - : () { - close(); - setState(() { - loading = false; - }); - }, - child: Text(translate('Cancel')), - ), - TextButton( - style: flatButtonStyle, - onPressed: loading - ? null - : () async { - final name = nameController.text.trim(); - final pass = passwordController.text.trim(); - if (name != "" && pass != "") { - setState(() { - loading = true; - }); - final resp = await gFFI.userModel.login(name, pass); - setState(() { - loading = false; - }); - if (resp.containsKey('error')) { - error = resp['error']; - return; - } - } - close(); - }, - child: Text(translate('OK')), - ), - ], - ); - }); -} - class ScanButton extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index d8a0e8f99..175c8424b 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -27,8 +27,7 @@ class AbModel { abError.value = ""; final api = "${await bind.mainGetApiServer()}/api/ab/get"; try { - final resp = - await http.post(Uri.parse(api), headers: await getHttpHeaders()); + final resp = await http.post(Uri.parse(api), headers: getHttpHeaders()); if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { Map json = jsonDecode(resp.body); if (json.containsKey('error')) { @@ -102,7 +101,7 @@ class AbModel { Future pushAb() async { abLoading.value = true; final api = "${await bind.mainGetApiServer()}/api/ab"; - var authHeaders = await getHttpHeaders(); + var authHeaders = getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; final peersJsonData = peers.map((e) => e.toJson()).toList(); final body = jsonEncode({ diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index f220d62f1..4d9fab0e4 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -59,7 +59,7 @@ class GroupModel { if (gFFI.userModel.isAdmin.isFalse) 'grp': gFFI.userModel.groupName.value, }); - final resp = await http.get(uri, headers: await getHttpHeaders()); + final resp = await http.get(uri, headers: getHttpHeaders()); if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { Map json = jsonDecode(resp.body); if (json.containsKey('error')) { @@ -110,7 +110,7 @@ class GroupModel { 'grp': gFFI.userModel.groupName.value, 'target_user': username }); - final resp = await http.get(uri, headers: await getHttpHeaders()); + final resp = await http.get(uri, headers: getHttpHeaders()); if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { Map json = jsonDecode(resp.body); if (json.containsKey('error')) { diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index e5d2c9e15..b0eebee53 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:flutter_hbb/common/widgets/peer_tab_page.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; @@ -45,7 +46,9 @@ class UserModel { if (error != null) { throw error; } - await _parseUserInfo(data); + + final user = UserPayload.fromJson(data); + await _parseAndUpdateUser(user); } catch (e) { print('Failed to refreshCurrentUser: $e'); } finally { @@ -55,7 +58,6 @@ class UserModel { Future reset() async { await bind.mainSetLocalOption(key: 'access_token', value: ''); - await bind.mainSetLocalOption(key: 'user_info', value: ''); await gFFI.abModel.reset(); await gFFI.groupModel.reset(); userName.value = ''; @@ -63,11 +65,10 @@ class UserModel { statePeerTab.check(); } - Future _parseUserInfo(dynamic userinfo) async { - bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(userinfo)); - userName.value = userinfo['name'] ?? ''; - groupName.value = userinfo['grp'] ?? ''; - isAdmin.value = userinfo['is_admin'] == true; + Future _parseAndUpdateUser(UserPayload user) async { + userName.value = user.name; + groupName.value = user.grp; + isAdmin.value = user.isAdmin; } Future _updateOtherModels() async { @@ -85,7 +86,7 @@ class UserModel { 'id': await bind.mainGetMyId(), 'uuid': await bind.mainGetUuid(), }, - headers: await getHttpHeaders()) + headers: getHttpHeaders()) .timeout(Duration(seconds: 2)); } catch (e) { print("request /api/logout failed: err=$e"); @@ -95,26 +96,37 @@ class UserModel { } } - Future> login(String userName, String pass) async { + /// throw [RequestException] + Future login(LoginRequest loginRequest) async { final url = await bind.mainGetApiServer(); + final resp = await http.post(Uri.parse('$url/api/login'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(loginRequest.toJson())); + + final Map body; try { - final resp = await http.post(Uri.parse('$url/api/login'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - 'username': userName, - 'password': pass, - 'id': await bind.mainGetMyId(), - 'uuid': await bind.mainGetUuid() - })); - final body = jsonDecode(resp.body); - bind.mainSetLocalOption( - key: 'access_token', value: body['access_token'] ?? ''); - await _parseUserInfo(body['user']); - return body; - } catch (err) { - return {'error': '$err'}; - } finally { - await _updateOtherModels(); + body = jsonDecode(resp.body); + } catch (e) { + print("jsonDecode resp body failed: ${e.toString()}"); + rethrow; } + + if (resp.statusCode != 200) { + throw RequestException(resp.statusCode, body['error'] ?? ''); + } + + final LoginResponse loginResponse; + try { + loginResponse = LoginResponse.fromJson(body); + } catch (e) { + print("jsonDecode LoginResponse failed: ${e.toString()}"); + rethrow; + } + + if (loginResponse.user != null) { + await _parseAndUpdateUser(loginResponse.user!); + } + + return loginResponse; } } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 4c85bbf0c..9224d231a 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Connecta sempre a través de relay"), ("whitelist_tip", ""), ("Login", "Inicia sessió"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Sortir"), ("Tags", ""), ("Search ID", "Cerca ID"), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 4b7bda3ea..a486128b7 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "强制走中继连接"), ("whitelist_tip", "只有白名单里的ip才能访问我"), ("Login", "登录"), + ("Verify", "验证"), + ("Remember me", "记住我"), + ("Trust this device", "信任此设备"), + ("Verification code", "验证码"), + ("verification_tip", "检测到新设备登录,已向注册邮箱发送了登录验证码,输入验证码继续登录"), ("Logout", "登出"), ("Tags", "标签"), ("Search ID", "查找ID"), @@ -221,7 +226,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Network error", "网络错误"), ("Username missed", "用户名没有填写"), ("Password missed", "密码没有填写"), - ("Wrong credentials", "用户名或者密码错误"), + ("Wrong credentials", "提供的登入信息错误"), ("Edit Tag", "修改标签"), ("Unremember Password", "忘掉密码"), ("Favorites", "收藏"), @@ -273,7 +278,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Do you accept?", "是否接受?"), ("Open System Setting", "打开系统设置"), ("How to get Android input permission?", "如何获取安卓的输入权限?"), - ("android_input_permission_tip1", "為了讓遠程設備通過鼠標或者觸屏控制您的安卓設備,你需要允許 RustDesk 使用\"無障礙\"服務。"), + ("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备,你需要允許RustDesk使用\"无障碍\"服务。"), ("android_input_permission_tip2", "请在接下来的系统设置页面里,找到并进入 [已安装的服务] 页面,将 [RustDesk Input] 服务开启。"), ("android_new_connection_tip", "收到新的连接控制请求,对方想要控制你当前的设备。"), ("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 741dd284f..3622aef8a 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Vždy se spojovat prostřednictvím brány pro předávání (relay)"), ("whitelist_tip", "Přístup je umožněn pouze z IP adres, nacházejících se na seznamu povolených"), ("Login", "Přihlásit se"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Odhlásit se"), ("Tags", "Štítky"), ("Search ID", "Hledat identifikátor"), diff --git a/src/lang/da.rs b/src/lang/da.rs index 1783fd16c..f07d9914e 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Forbindelse via relæ-server"), ("whitelist_tip", "Kun IP'er på udgivelseslisten kan få adgang til mig"), ("Login", "Login"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "logger af"), ("Tags", "Nøgleord"), ("Search ID", "Søg ID"), diff --git a/src/lang/de.rs b/src/lang/de.rs index 29081da63..a91f167a2 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Immer über Relay-Server verbinden"), ("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."), ("Login", "Anmelden"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Abmelden"), ("Tags", "Schlagworte"), ("Search ID", "Suche ID"), diff --git a/src/lang/en.rs b/src/lang/en.rs index 6675ee172..b718fc0f9 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -36,6 +36,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("hide_cm_tip", "Allow hiding only if accepting sessions via password and using permanent password"), ("wayland_experiment_tip", "Wayland support is in experimental stage, please use X11 if you require unattended access."), ("Slogan_tip", "Made with heart in this chaotic world!"), + ("verification_tip", "A new device has been detected, and a verification code has been sent to the registered email address, enter the verification code to continue logging in."), ("software_render_tip", "If you have an Nvidia graphics card and the remote window closes immediately after connecting, installing the nouveau driver and choosing to use software rendering may help. A software restart is required."), ("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."), ].iter().cloned().collect(); diff --git a/src/lang/eo.rs b/src/lang/eo.rs index a4da577c8..2a41fdcf9 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Ĉiam konekti per relajso"), ("whitelist_tip", "Nur la IP en la blanka listo povas kontroli mian komputilon"), ("Login", "Konekti"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Malkonekti"), ("Tags", "Etikedi"), ("Search ID", "Serĉi ID"), diff --git a/src/lang/es.rs b/src/lang/es.rs index 9252f8f14..e0e410711 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Conéctese siempre a través de relay"), ("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"), ("Login", "Iniciar sesión"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Salir"), ("Tags", "Tags"), ("Search ID", "Buscar ID"), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 8d4ba5cb1..790d01682 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "برای اتصال استفاده شود Relay از"), ("whitelist_tip", "های مجاز می توانند به این دسکتاپ متصل شوند IP فقط"), ("Login", "ورود"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "خروج"), ("Tags", "برچسب ها"), ("Search ID", "جستجوی شناسه"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index d48d5cfae..499be7c54 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Forcer la connexion relais"), ("whitelist_tip", "Seul l'IP dans la liste blanche peut accéder à mon appareil"), ("Login", "Connexion"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Déconnexion"), ("Tags", "Étiqueter"), ("Search ID", "Rechercher un ID"), diff --git a/src/lang/gr.rs b/src/lang/gr.rs index 1ac063cd5..53369a4b3 100644 --- a/src/lang/gr.rs +++ b/src/lang/gr.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Σύνδεση πάντα μέσω αναμετάδοσης"), ("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"), ("Login", "Σύνδεση"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Αποσύνδεση"), ("Tags", "Ετικέτες"), ("Search ID", "Αναζήτηση ID"), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index be3028283..32d920994 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Mindig közvetítőn keresztüli csatlakozás"), ("whitelist_tip", "Csak az engedélyezési listán szereplő címek csatlakozhatnak"), ("Login", "Belépés"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Kilépés"), ("Tags", "Tagok"), ("Search ID", "Azonosító keresése..."), diff --git a/src/lang/id.rs b/src/lang/id.rs index 14eff9458..c33cccb66 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Selalu terhubung melalui relai"), ("whitelist_tip", "Hanya whitelisted IP yang dapat mengakses saya"), ("Login", "Masuk"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Keluar"), ("Tags", "Tag"), ("Search ID", "Cari ID"), diff --git a/src/lang/it.rs b/src/lang/it.rs index e82da1d66..ac3ea46fa 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Connetti sempre tramite relay"), ("whitelist_tip", "Solo gli indirizzi IP autorizzati possono connettersi a questo desktop"), ("Login", "Accedi"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Esci"), ("Tags", "Tag"), ("Search ID", "Cerca ID"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index dc3d81448..7dd1640f6 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "常に中継サーバー経由で接続"), ("whitelist_tip", "ホワイトリストに登録されたIPからのみ接続を許可します"), ("Login", "ログイン"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "ログアウト"), ("Tags", "タグ"), ("Search ID", "IDを検索"), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 11536f0a5..66ff3ca95 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "항상 relay를 통해 접속하기"), ("whitelist_tip", "화이트리스트에 있는 IP만 현 데스크탑에 접속 가능합니다"), ("Login", "로그인"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "로그아웃"), ("Tags", "태그"), ("Search ID", "ID 검색"), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index b0dd2ff70..ac688eb9f 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Әрқашан да релай сербері арқылы қосылу"), ("whitelist_tip", "Маған тек ақ-тізімделген IP қол жеткізе алады"), ("Login", "Кіру"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Шығу"), ("Tags", "Тақтар"), ("Search ID", "ID Іздеу"), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 2641d8358..afd6b4b03 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Zawsze łącz pośrednio"), ("whitelist_tip", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"), ("Login", "Zaloguj"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Wyloguj"), ("Tags", "Tagi"), ("Search ID", "Szukaj ID"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index c96aed8b6..bf7954b46 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Sempre conectar via relay"), ("whitelist_tip", "Somente IPs na whitelist podem me acessar"), ("Login", "Login"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Sair"), ("Tags", "Tags"), ("Search ID", "Procurar ID"), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 03d451662..207be548f 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Sempre conectar via relay"), ("whitelist_tip", "Somente IPs confiáveis podem me acessar"), ("Login", "Login"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Sair"), ("Tags", "Tags"), ("Search ID", "Pesquisar ID"), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index cb53b363e..6a9d2f297 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Всегда подключаться через ретрансляционный сервер"), ("whitelist_tip", "Только IP-адреса из белого списка могут получить доступ ко мне"), ("Login", "Войти"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Выйти"), ("Tags", "Метки"), ("Search ID", "Поиск по ID"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 1cfff5beb..40f19c625 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Vždy pripájať cez prepájací server"), ("whitelist_tip", "Len vymenované IP adresy majú oprávnenie sa pripojiť k vzdialenej správe"), ("Login", "Prihlásenie"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Odhlásenie"), ("Tags", "Štítky"), ("Search ID", "Hľadať ID"), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 754c25868..5e8efc17d 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Vedno poveži preko posrednika"), ("whitelist_tip", "Dostop je možen samo iz dovoljenih IPjev"), ("Login", "Prijavi"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Odjavi"), ("Tags", "Oznake"), ("Search ID", "Išči ID"), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index b277c6f2d..0725d02e5 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Gjithmonë lidheni me transmetues"), ("whitelist_tip", "Vetëm IP e listës së bardhë mund të më aksesoj."), ("Login", "Hyrje"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Dalje"), ("Tags", "Tage"), ("Search ID", "Kerko ID"), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index cc418d305..3b7201bb8 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Uvek se spoj preko posrednika"), ("whitelist_tip", "Samo dozvoljene IP mi mogu pristupiti"), ("Login", "Prijava"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Odjava"), ("Tags", "Oznake"), ("Search ID", "Traži ID"), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 0a41387c7..eeeec80cc 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Anslut alltid via relay"), ("whitelist_tip", "Bara vitlistade IPs kan koppla upp till mig"), ("Login", "Logga in"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Logga ut"), ("Tags", "Taggar"), ("Search ID", "Sök ID"), diff --git a/src/lang/template.rs b/src/lang/template.rs index 87a1563d7..d3be7ba17 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", ""), ("whitelist_tip", ""), ("Login", ""), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", ""), ("Tags", ""), ("Search ID", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index 6a98613ed..a4d0a033d 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -1,413 +1,417 @@ lazy_static::lazy_static! { - pub static ref T: std::collections::HashMap<&'static str, &'static str> = - [ - ("Status", "สถานะ"), - ("Your Desktop", "หน้าจอของคุณ"), - ("desk_tip", "คุณสามารถเข้าถึงเดสก์ท็อปของคุณได้ด้วย ID และรหัสผ่านต่อไปนี้"), - ("Password", "รหัสผ่าน"), - ("Ready", "พร้อม"), - ("Established", "เชื่อมต่อแล้ว"), - ("connecting_status", "กำลังเชื่อมต่อไปยังเครือข่าย RustDesk..."), - ("Enable Service", "เปิดใช้การงานเซอร์วิส"), - ("Start Service", "เริ่มต้นใช้งานเซอร์วิส"), - ("Service is running", "เซอร์วิสกำลังทำงาน"), - ("Service is not running", "เซอร์วิสไม่ทำงาน"), - ("not_ready_status", "ไม่พร้อมใช้งาน กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"), - ("Control Remote Desktop", "การควบคุมเดสก์ท็อปปลายทาง"), - ("Transfer File", "การถ่ายโอนไฟล์"), - ("Connect", "เชื่อมต่อ"), - ("Recent Sessions", "เซสชันล่าสุด"), - ("Address Book", "สมุดรายชื่อ"), - ("Confirmation", "การยืนยัน"), - ("TCP Tunneling", "อุโมงค์การเชื่อมต่อ TCP"), - ("Remove", "ลบ"), - ("Refresh random password", "รีเฟรชรหัสผ่านใหม่แบบสุ่ม"), - ("Set your own password", "ตั้งรหัสผ่านของคุณเอง"), - ("Enable Keyboard/Mouse", "เปิดการใช้งาน คีย์บอร์ด/เมาส์"), - ("Enable Clipboard", "เปิดการใช้งาน คลิปบอร์ด"), - ("Enable File Transfer", "เปิดการใช้งาน การถ่ายโอนไฟล์"), - ("Enable TCP Tunneling", "เปิดการใช้งาน อุโมงค์การเชื่อมต่อ TCP"), - ("IP Whitelisting", "IP ไวท์ลิสต์"), - ("ID/Relay Server", "เซิร์ฟเวอร์ ID/Relay"), - ("Import Server Config", "นำเข้าการตั้งค่าเซิร์ฟเวอร์"), - ("Export Server Config", "ส่งออกการตั้งค่าเซิร์ฟเวอร์"), - ("Import server configuration successfully", "นำเข้าการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"), - ("Export server configuration successfully", "ส่งออกการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"), - ("Invalid server configuration", "การตั้งค่าของเซิร์ฟเวอร์ไม่ถูกต้อง"), - ("Clipboard is empty", "คลิปบอร์ดว่างเปล่า"), - ("Stop service", "หยุดการใช้งานเซอร์วิส"), - ("Change ID", "เปลี่ยน ID"), - ("Website", "เว็บไซต์"), - ("About", "เกี่ยวกับ"), - ("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"), - ("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"), - ("Mute", "ปิดเสียง"), - ("Audio Input", "ออดิโออินพุท"), - ("Enhancements", "การปรับปรุง"), - ("Hardware Codec", "ฮาร์ดแวร์ codec"), - ("Adaptive Bitrate", "บิทเรทผันแปร"), - ("ID Server", "เซิร์ฟเวอร์ ID"), - ("Relay Server", "เซิร์ฟเวอร์ Relay"), - ("API Server", "เซิร์ฟเวอร์ API"), - ("invalid_http", "ต้องขึ้นต้นด้วย http:// หรือ https:// เท่านั้น"), - ("Invalid IP", "IP ไม่ถูกต้อง"), - ("id_change_tip", "อนุญาตเฉพาะตัวอักษร a-z A-Z 0-9 และ _ (ขีดล่าง) เท่านั้น โดยตัวอักษรขึ้นต้นจะต้องเป็น a-z หรือไม่ก็ A-Z และมีความยาวระหว่าง 6 ถึง 16 ตัวอักษร"), - ("Invalid format", "รูปแบบไม่ถูกต้อง"), - ("server_not_support", "ยังไม่รองรับโดยเซิร์ฟเวอร์"), - ("Not available", "ไม่พร้อมใช้งาน"), - ("Too frequent", "ดำเนินการถี่เกินไป"), - ("Cancel", "ยกเลิก"), - ("Skip", "ข้าม"), - ("Close", "ปิด"), - ("Retry", "ลองใหม่อีกครั้ง"), - ("OK", "ตกลง"), - ("Password Required", "ต้องใช้รหัสผ่าน"), - ("Please enter your password", "กรุณาใส่รหัสผ่านของคุณ"), - ("Remember password", "จดจำรหัสผ่าน"), - ("Wrong Password", "รหัสผ่านไม่ถูกต้อง"), - ("Do you want to enter again?", "ต้องการใส่ข้อมูลอีกครั้งหรือไม่?"), - ("Connection Error", "การเชื่อมต่อผิดพลาด"), - ("Error", "ข้อผิดพลาด"), - ("Reset by the peer", "รีเซ็ตโดยอีกฝั่ง"), - ("Connecting...", "กำลังเชื่อมต่อ..."), - ("Connection in progress. Please wait.", "กำลังดำเนินการเชื่อมต่อ กรุณารอซักครู่"), - ("Please try 1 minute later", "กรุณาลองใหม่อีกครั้งใน 1 นาที"), - ("Login Error", "การเข้าสู่ระบบผิดพลาด"), - ("Successful", "สำเร็จ"), - ("Connected, waiting for image...", "เชื่อมต่อสำเร็จ กำลังรับข้อมูลภาพ..."), - ("Name", "ชื่อ"), - ("Type", "ประเภท"), - ("Modified", "แก้ไขล่าสุด"), - ("Size", "ขนาด"), - ("Show Hidden Files", "แสดงไฟล์ที่ถูกซ่อน"), - ("Receive", "รับ"), - ("Send", "ส่ง"), - ("Refresh File", "รีเฟรชไฟล์"), - ("Local", "ต้นทาง"), - ("Remote", "ปลายทาง"), - ("Remote Computer", "คอมพิวเตอร์ปลายทาง"), - ("Local Computer", "คอมพิวเตอร์ต้นทาง"), - ("Confirm Delete", "ยืนยันการลบ"), - ("Delete", "ลบ"), - ("Properties", "ข้อมูล"), - ("Multi Select", "เลือกหลายรายการ"), - ("Select All", "เลือกทั้งหมด"), - ("Unselect All", "ยกเลิกการเลือกทั้งหมด"), - ("Empty Directory", "ไดเรกทอรีว่างเปล่า"), - ("Not an empty directory", "ไม่ใช่ไดเรกทอรีว่างเปล่า"), - ("Are you sure you want to delete this file?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์นี้?"), - ("Are you sure you want to delete this empty directory?", "คุณแน่ใจหรือไม่ที่จะลบไดเรอทอรีว่างเปล่านี้?"), - ("Are you sure you want to delete the file of this directory?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์ของไดเรกทอรีนี้?"), - ("Do this for all conflicts", "ดำเนินการแบบเดียวกันสำหรับรายการทั้งหมด"), - ("This is irreversible!", "การดำเนินการนี้ไม่สามารถย้อนกลับได้!"), - ("Deleting", "กำลังลบ"), - ("files", "ไฟล์"), - ("Waiting", "กำลังรอ"), - ("Finished", "เสร็จแล้ว"), - ("Speed", "ความเร็ว"), - ("Custom Image Quality", "คุณภาพของภาพแบบกำหนดเอง"), - ("Privacy mode", "โหมดความเป็นส่วนตัว"), - ("Block user input", "บล็อคอินพุทจากผู้ใช้งาน"), - ("Unblock user input", "ยกเลิกการบล็อคอินพุทจากผู้ใช้งาน"), - ("Adjust Window", "ปรับขนาดหน้าต่าง"), - ("Original", "ต้นฉบับ"), - ("Shrink", "ย่อ"), - ("Stretch", "ยืด"), - ("Scrollbar", "แถบเลื่อน"), - ("ScrollAuto", "เลื่อนอัตโนมัติ"), - ("Good image quality", "ภาพคุณภาพดี"), - ("Balanced", "สมดุล"), - ("Optimize reaction time", "เน้นการตอบสนอง"), - ("Custom", "กำหนดเอง"), - ("Show remote cursor", "แสดงเคอร์เซอร์ปลายทาง"), - ("Show quality monitor", "แสดงคุณภาพหน้าจอ"), - ("Disable clipboard", "ปิดการใช้งานคลิปบอร์ด"), - ("Lock after session end", "ล็อคหลังจากจบเซสชัน"), - ("Insert", "แทรก"), - ("Insert Lock", "แทรกล็อค"), - ("Refresh", "รีเฟรช"), - ("ID does not exist", "ไม่พอข้อมูล ID"), - ("Failed to connect to rendezvous server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์นัดพบล้มเหลว"), - ("Please try later", "กรุณาลองใหม่ในภายหลัง"), - ("Remote desktop is offline", "เดสก์ท็อปปลายทางออฟไลน์"), - ("Key mismatch", "คีย์ไม่ถูกต้อง"), - ("Timeout", "หมดเวลา"), - ("Failed to connect to relay server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์รีเลย์ล้มเหลว"), - ("Failed to connect via rendezvous server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์นัดพบล้มเหลว"), - ("Failed to connect via relay server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์รีเลย์ล้มเหลว"), - ("Failed to make direct connection to remote desktop", "การเชื่อมต่อตรงไปยังเดสก์ท็อปปลายทางล้มเหลว"), - ("Set Password", "ตั้งรหัสผ่าน"), - ("OS Password", "รหัสผ่านระบบปฏิบัติการ"), - ("install_tip", "เนื่องด้วยข้อจำกัดของการใช้งาน UAC ทำให้ RustDesk ไม่สามารถทำงานได้ปกติในฝั่งปลายทางในบางครั้ง เพื่อหลีกเลี่ยงข้อจำกัดของ UAC กรุณากดปุ่มด้านล่างเพื่อติดตั้ง RustDesk ไปยังระบบของคุณ"), - ("Click to upgrade", "คลิกเพื่ออัปเกรด"), - ("Click to download", "คลิกเพื่อดาวน์โหลด"), - ("Click to update", "คลิกเพื่ออัปเดต"), - ("Configure", "ปรับแต่งค่า"), - ("config_acc", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่ RustDesk"), - ("config_screen", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การบันทึกภาพหน้าจอ\" ให้แก่ RustDesk"), - ("Installing ...", "กำลังติดตั้ง ..."), - ("Install", "ติดตั้ง"), - ("Installation", "การติดตั้ง"), - ("Installation Path", "ตำแหน่งที่ติดตั้ง"), - ("Create start menu shortcuts", "สร้างทางลัดไปยัง Start Menu"), - ("Create desktop icon", "สร้างไอคอนบนเดสก์ท็อป"), - ("agreement_tip", "ในการเริ่มต้นการติดตั้ง ถือว่าคุณได้ยอมรับข้อตกลงใบอนุญาตแล้ว"), - ("Accept and Install", "ยอมรับและติดตั้ง"), - ("End-user license agreement", "ข้อตกลงใบอนุญาตผู้ใช้งาน"), - ("Generating ...", "กำลังสร้าง ..."), - ("Your installation is lower version.", "การติดตั้งของคุณเป็นเวอร์ชั่นที่ต่ำกว่า"), - ("not_close_tcp_tip", "อย่าปิดหน้าต่างนี้ในขณะที่คุณกำลังใช้งานอุโมงค์การเชื่อมต่อ"), - ("Listening ...", "กำลังรอรับข้อมูล ..."), - ("Remote Host", "โฮสต์ปลายทาง"), - ("Remote Port", "พอร์ทปลายทาง"), - ("Action", "การดำเนินการ"), - ("Add", "เพิ่ม"), - ("Local Port", "พอร์ทต้นทาง"), - ("Local Address", "ที่อยู่ต้นทาง"), - ("Change Local Port", "เปลี่ยนพอร์ทต้นทาง"), - ("setup_server_tip", "เพื่อการเชื่อมต่อที่เร็วขึ้น กรุณาเซ็ตอัปเซิร์ฟเวอร์ของคุณเอง"), - ("Too short, at least 6 characters.", "สั้นเกินไป ต้องไม่ต่ำกว่า 6 ตัวอักษร"), - ("The confirmation is not identical.", "การยืนยันข้อมูลไม่ถูกต้อง"), - ("Permissions", "สิทธิ์การใช้งาน"), - ("Accept", "ยอมรับ"), - ("Dismiss", "ปิด"), - ("Disconnect", "ยกเลิกการเชื่อมต่อ"), - ("Allow using keyboard and mouse", "อนุญาตให้ใช้งานคีย์บอร์ดและเมาส์"), - ("Allow using clipboard", "อนุญาตให้ใช้คลิปบอร์ด"), - ("Allow hearing sound", "อนุญาตให้ได้ยินเสียง"), - ("Allow file copy and paste", "อนุญาตให้มีการคัดลอกและวางไฟล์"), - ("Connected", "เชื่อมต่อแล้ว"), - ("Direct and encrypted connection", "การเชื่อมต่อตรงที่มีการเข้ารหัส"), - ("Relayed and encrypted connection", "การเชื่อมต่อแบบรีเลย์ที่มีการเข้ารหัส"), - ("Direct and unencrypted connection", "การเชื่อมต่อตรงที่ไม่มีการเข้ารหัส"), - ("Relayed and unencrypted connection", "การเชื่อมต่อแบบรีเลย์ที่ไม่มีการเข้ารหัส"), - ("Enter Remote ID", "กรอก ID ปลายทาง"), - ("Enter your password", "กรอกรหัสผ่าน"), - ("Logging in...", "กำลังเข้าสู่ระบบ..."), - ("Enable RDP session sharing", "เปิดการใช้งานการแชร์เซสชัน RDP"), - ("Auto Login", "เข้าสู่ระบอัตโนมัติ"), - ("Enable Direct IP Access", "เปิดการใช้งาน IP ตรง"), - ("Rename", "ปลายทาง"), - ("Space", "พื้นที่ว่าง"), - ("Create Desktop Shortcut", "สร้างทางลัดบนเดสก์ท็อป"), - ("Change Path", "เปลี่ยนตำแหน่ง"), - ("Create Folder", "สร้างโฟลเดอร์"), - ("Please enter the folder name", "กรุณาใส่ชื่อโฟลเดอร์"), - ("Fix it", "แก้ไข"), - ("Warning", "คำเตือน"), - ("Login screen using Wayland is not supported", "หน้าจอการเข้าสู่ระบบโดยใช้ Wayland ยังไม่ถูกรองรับ"), - ("Reboot required", "จำเป็นต้องเริ่มต้นระบบใหม่"), - ("Unsupported display server ", "เซิร์ฟเวอร์การแสดงผลที่ไม่รองรับ"), - ("x11 expected", "ต้องใช้งาน x11"), - ("Port", "พอร์ท"), - ("Settings", "ตั้งค่า"), - ("Username", "ชื่อผู้ใช้งาน"), - ("Invalid port", "พอร์ทไม่ถูกต้อง"), - ("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งการการเชื่อมต่อ"), - ("Enable remote configuration modification", "เปิดการใช้งานการแก้ไขการตั้งค่าปลายทาง"), - ("Run without install", "ใช้งานโดยไม่ต้องติดตั้ง"), - ("Always connected via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"), - ("Always connect via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"), - ("whitelist_tip", "อนุญาตเฉพาะการเชื่อมต่อจาก IP ที่ไวท์ลิสต์"), - ("Login", "เข้าสู่ระบบ"), - ("Logout", "ออกจากระบบ"), - ("Tags", "แท็ก"), - ("Search ID", "ค้นหา ID"), - ("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"), - ("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"), - ("Add ID", "เพิ่ม ID"), - ("Add Tag", "เพิ่มแท็ก"), - ("Unselect all tags", "ยกเลิกการเลือกแท็กทั้งหมด"), - ("Network error", "ข้อผิดพลาดของเครือข่าย"), - ("Username missed", "ไม่พบข้อมูลผู้ใช้งาน"), - ("Password missed", "ไม่พบรหัสผ่าน"), - ("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"), - ("Edit Tag", "แก้ไขแท็ก"), - ("Unremember Password", "ยกเลิกการจดจำรหัสผ่าน"), - ("Favorites", "รายการโปรด"), - ("Add to Favorites", "เพิ่มไปยังรายการโปรด"), - ("Remove from Favorites", "ลบออกจากรายการโปรด"), - ("Empty", "ว่างเปล่า"), - ("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"), - ("Socks5 Proxy", "พรอกซี Socks5"), - ("Hostname", "ชื่อโฮสต์"), - ("Discovered", "ค้นพบ"), - ("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"), - ("Remote ID", "ID ปลายทาง"), - ("Paste", "วาง"), - ("Paste here?", "วางที่นี่หรือไม่?"), - ("Are you sure to close the connection?", "คุณแน่ใจหรือไม่ที่จะปิดการเชื่อมต่อ?"), - ("Download new version", "ดาวน์โหลดเวอร์ชั่นใหม่"), - ("Touch mode", "โหมดการสัมผัส"), - ("Mouse mode", "โหมดการใช้เมาส์"), - ("One-Finger Tap", "แตะนิ้วเดียว"), - ("Left Mouse", "เมาส์ซ้าย"), - ("One-Long Tap", "แตะยาวหนึ่งครั้ง"), - ("Two-Finger Tap", "แตะสองนิ้ว"), - ("Right Mouse", "เมาส์ขวา"), - ("One-Finger Move", "ลากนิ้วเดียว"), - ("Double Tap & Move", "แตะเบิ้ลและลาก"), - ("Mouse Drag", "ลากเมาส์"), - ("Three-Finger vertically", "สามนิ้วแนวตั้ง"), - ("Mouse Wheel", "ลูกลิ้งเมาส์"), - ("Two-Finger Move", "ลากสองนิ้ว"), - ("Canvas Move", "ลากแคนวาส"), - ("Pinch to Zoom", "ถ่างเพื่อขยาย"), - ("Canvas Zoom", "ขยายแคนวาส"), - ("Reset canvas", "รีเซ็ตแคนวาส"), - ("No permission of file transfer", "ไม่มีสิทธิ์ในการถ่ายโอนไฟล์"), - ("Note", "บันทึกข้อความ"), - ("Connection", "การเชื่อมต่อ"), - ("Share Screen", "แชร์หน้าจอ"), - ("CLOSE", "ปิด"), - ("OPEN", "เปิด"), - ("Chat", "แชท"), - ("Total", "รวม"), - ("items", "รายการ"), - ("Selected", "ถูกเลือก"), - ("Screen Capture", "แคปเจอร์หน้าจอ"), - ("Input Control", "ควบคุมอินพุท"), - ("Audio Capture", "แคปเจอร์เสียง"), - ("File Connection", "การเชื่อมต่อไฟล์"), - ("Screen Connection", "การเชื่อมต่อหน้าจอ"), - ("Do you accept?", "ยอมรับหรือไม่?"), - ("Open System Setting", "เปิดการตั้งค่าระบบ"), - ("How to get Android input permission?", "เปิดสิทธิ์การใช้งานอินพุทของแอนดรอยด์ได้อย่างไร?"), - ("android_input_permission_tip1", "ในการที่จะอนุญาตให้เครื่องปลายทางควบคุมอุปกรณ์แอนดรอยด์ของคุณโดยใช้เมาส์หรือการสัมผัส คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่เซอร์วิสของ RustDesk"), - ("android_input_permission_tip2", "กรุณาไปยังหน้าตั้งค่าถัดไป ค้นหาและเข้าไปยัง [เซอร์วิสที่ถูกติดตั้ง] และเปิดการใช้งานเซอร์วิส [อินพุท RustDesk]"), - ("android_new_connection_tip", "ได้รับคำขอควบคุมใหม่ที่ต้องการควบคุมอุปกรณ์ของคุณ"), - ("android_service_will_start_tip", "การเปิดการใช้งาน \"การบันทึกหน้าจอ\" จะเป็นการเริ่มต้นการทำงานของเซอร์วิสโดยอัตโนมัติ ที่จะอนุญาตให้อุปกรณ์อื่นๆ ส่งคำขอเข้าถึงมายังอุปกรณ์ของคุณได้"), - ("android_stop_service_tip", "การปิดการใช้งานเซอร์วิสจะปิดการเชื่อมต่อทั้งหมดโดยอัตโนมัติ"), - ("android_version_audio_tip", "เวอร์ชั่นแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชั่น 10 หรือสูงกว่า"), - ("android_start_service_tip", "แตะ [เริ่มต้นใช้งานเซอร์วิส] หรือเปิดสิทธิ์ [การบันทึกหน้าจอ] เพื่อเริ่มเซอร์วิสการแชร์หน้าจอ"), - ("Overwrite", "เขียนทับ"), - ("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"), - ("Quit", "ออก"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), - ("Help", "ช่วยเหลือ"), - ("Failed", "ล้มเหลว"), - ("Succeeded", "สำเร็จ"), - ("Someone turns on privacy mode, exit", "มีใครบางคนเปิดใช้งานโหมดความเป็นส่วนตัว กำลังออก"), - ("Unsupported", "ไม่รองรับ"), - ("Peer denied", "ถูกปฏิเสธโดยอีกฝั่ง"), - ("Please install plugins", "กรุณาติดตั้งปลั๊กอิน"), - ("Peer exit", "อีกฝั่งออก"), - ("Failed to turn off", "การปิดล้มเหลว"), - ("Turned off", "ปิด"), - ("In privacy mode", "อยู่ในโหมดความเป็นส่วนตัว"), - ("Out privacy mode", "อยู่นอกโหมดความเป็นส่วนตัว"), - ("Language", "ภาษา"), - ("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"), - ("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"), - ("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"), - ("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"), - ("Legacy mode", ""), - ("Map mode", ""), - ("Translate mode", ""), - ("Use permanent password", "ใช้รหัสผ่านถาวร"), - ("Use both passwords", "ใช้รหัสผ่านทั้งสองแบบ"), - ("Set permanent password", "ตั้งค่ารหัสผ่านถาวร"), - ("Enable Remote Restart", "เปิดการใช้งานการรีสตาร์ทระบบทางไกล"), - ("Allow remote restart", "อนุญาตการรีสตาร์ทระบบทางไกล"), - ("Restart Remote Device", "รีสตาร์ทอุปกรณ์ปลายทาง"), - ("Are you sure you want to restart", "คุณแน่ใจหรือไม่ที่จะรีสตาร์ท"), - ("Restarting Remote Device", "กำลังรีสตาร์ทระบบปลายทาง"), - ("remote_restarting_tip", "ระบบปลายทางกำลังรีสตาร์ท กรุณาปิดกล่องข้อความนี้และดำเนินการเขื่อมต่อใหม่อีกครั้งด้วยรหัสผ่านถาวรหลังจากผ่านไปซักครู่"), - ("Copied", "คัดลอกแล้ว"), - ("Exit Fullscreen", "ออกจากเต็มหน้าจอ"), - ("Fullscreen", "เต็มหน้าจอ"), - ("Mobile Actions", "การดำเนินการบนมือถือ"), - ("Select Monitor", "เลือกหน้าจอ"), - ("Control Actions", "การดำเนินการควบคุม"), - ("Display Settings", "การตั้งค่าแสดงผล"), - ("Ratio", "อัตราส่วน"), - ("Image Quality", "คุณภาพภาพ"), - ("Scroll Style", "ลักษณะการเลื่อน"), - ("Show Menubar", "แสดงแถบเมนู"), - ("Hide Menubar", "ซ่อนแถบเมนู"), - ("Direct Connection", "การเชื่อมต่อตรง"), - ("Relay Connection", "การเชื่อมต่อแบบรีเลย์"), - ("Secure Connection", "การเชื่อมต่อที่ปลอดภัย"), - ("Insecure Connection", "การเชื่อมต่อที่ไม่ปลอดภัย"), - ("Scale original", "ขนาดเดิม"), - ("Scale adaptive", "ขนาดยืดหยุ่น"), - ("General", "ทั่วไป"), - ("Security", "ความปลอดภัย"), - ("Account", "บัญชี"), - ("Theme", "ธีม"), - ("Dark Theme", "ธีมมืด"), - ("Dark", "มืด"), - ("Light", "สว่าง"), - ("Follow System", "ตามระบบ"), - ("Enable hardware codec", "เปิดการใช้งานฮาร์ดแวร์ codec"), - ("Unlock Security Settings", "ปลดล็อคการตั้งค่าความปลอดภัย"), - ("Enable Audio", "เปิดการใช้งานเสียง"), - ("Unlock Network Settings", "ปลดล็อคการตั้งค่าเครือข่าย"), - ("Server", "เซิร์ฟเวอร์"), - ("Direct IP Access", "การเข้าถึง IP ตรง"), - ("Proxy", "พรอกซี"), - ("Apply", "นำไปใช้"), - ("Disconnect all devices?", "ยกเลิกการเชื่อมต่ออุปกรณ์ทั้งหมด?"), - ("Clear", "ล้างข้อมูล"), - ("Audio Input Device", "อุปกรณ์รับอินพุทข้อมูลเสียง"), - ("Deny remote access", "ปฏิเสธการเชื่อมต่อ"), - ("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"), - ("Network", "เครือข่าย"), - ("Enable RDP", "เปิดการใช้งาน RDP"), - ("Pin menubar", "ปักหมุดแถบเมนู"), - ("Unpin menubar", "ยกเลิกการปักหมุดแถบเมนู"), - ("Recording", "การบันทึก"), - ("Directory", "ไดเรกทอรี่"), - ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"), - ("Change", "เปลี่ยน"), - ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"), - ("Stop session recording", "หยุดการบันทึกเซสซัน"), - ("Enable Recording Session", "เปิดใช้งานการบันทึกเซสชัน"), - ("Allow recording session", "อนุญาตการบันทึกเซสชัน"), - ("Enable LAN Discovery", "เปิดการใช้งานการค้นหาในวง LAN"), - ("Deny LAN Discovery", "ปฏิเสธการใช้งานการค้นหาในวง LAN"), - ("Write a message", "เขียนข้อความ"), - ("Prompt", ""), - ("Please wait for confirmation of UAC...", "กรุณารอการยืนยันจาก UAC..."), - ("elevated_foreground_window_tip", "หน้าต่างปัจจุบันของเครื่องปลายทางต้องการสิทธิ์การใช้งานที่สูงขึ้นสำหรับการทำงาน ดังนั้นเมาส์และคีย์บอร์ดจะไม่สามารถใช้งานได้ชั่วคราว คุณสามารถขอผู้ใช้งานปลายทางให้ย่อหน้าต่าง หรือคลิกปุ่มให้สิทธิ์การใช้งานในหน้าต่างการจัดการการเชื่อมต่อ เพื่อหลีกเลี่ยงปัญหานี้เราแนะนำให้ดำเนินการติดตั้งซอฟท์แวร์ในเครื่องปลายทาง"), - ("Disconnected", "ยกเลิกการเชื่อมต่อ"), - ("Other", "อื่นๆ"), - ("Confirm before closing multiple tabs", "ยืนยันการปิดหลายแท็บ"), - ("Keyboard Settings", "การตั้งค่าคีย์บอร์ด"), - ("Full Access", "การเข้าถึงทั้งหมด"), - ("Screen Share", "การแชร์จอ"), - ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland ต้องการ Ubuntu เวอร์ชั่น 21.04 หรือสูงกว่า"), - ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland ต้องการลินุกซ์เวอร์ชันที่สูงกว่านี้ กรุณาเปลี่ยนไปใช้เดสก์ท็อป X11 หรือเปลี่ยนระบบปฏิบัติการของคุณ"), - ("JumpLink", "View"), - ("Please Select the screen to be shared(Operate on the peer side).", "กรุณาเลือกหน้าจอที่ต้องการแชร์ (ใช้งานในอีกฝั่งของการเชื่อมต่อ)"), - ("Show RustDesk", "แสดง RustDesk"), - ("This PC", ""), - ("or", "หรือ"), - ("Continue with", "ทำต่อด้วย"), - ("Elevate", "ยกระดับ"), - ("Zoom cursor", "ขยายเคอร์เซอร์"), - ("Accept sessions via password", "ยอมรับการเชื่อมต่อด้วยรหัสผ่าน"), - ("Accept sessions via click", "ยอมรับการเชื่อมต่อด้วยการคลิก"), - ("Accept sessions via both", "ยอมรับการเชื่อมต่อด้วยทั้งสองวิธิ"), - ("Please wait for the remote side to accept your session request...", "กรุณารอให้อีกฝั่งยอมรับการเชื่อมต่อของคุณ..."), - ("One-time Password", "รหัสผ่านครั้งเดียว"), - ("Use one-time password", "ใช้รหัสผ่านครั้งเดียว"), - ("One-time password length", "ความยาวรหัสผ่านครั้งเดียว"), - ("Request access to your device", "คำขอการเข้าถึงอุปกรณ์ของคุณ"), - ("Hide connection management window", "ซ่อนหน้าต่างการจัดการการเชื่อมต่อ"), - ("hide_cm_tip", "อนุญาตการซ่อนก็ต่อเมื่อยอมรับการเชื่อมต่อด้วยรหัสผ่าน และต้องเป็นรหัสผ่านถาวรเท่านั้น"), - ("wayland_experiment_tip", "การสนับสนุน Wayland ยังอยู่ในขั้นตอนการทดลอง กรุณาใช้ X11 หากคุณต้องการใช้งานการเข้าถึงแบบไม่มีผู้ดูแล"), - ("Right click to select tabs", "คลิกขวาเพื่อเลือกแท็บ"), - ("Skipped", "ข้าม"), - ("Add to Address Book", "เพิ่มไปยังสมุดรายชื่อ"), - ("Group", "กลุ่ม"), - ("Search", "ค้นหา"), - ("Closed manually by the web console", "ถูกปิดโดยเว็บคอนโซล"), - ("Local keyboard type", "ประเภทคีย์บอร์ด"), - ("Select local keyboard type", "เลือกประเภทคีย์บอร์ด"), - ("software_render_tip", ""), - ("Always use software rendering", ""), - ("config_input", ""), - ].iter().cloned().collect(); - } - \ No newline at end of file +pub static ref T: std::collections::HashMap<&'static str, &'static str> = + [ + ("Status", "สถานะ"), + ("Your Desktop", "หน้าจอของคุณ"), + ("desk_tip", "คุณสามารถเข้าถึงเดสก์ท็อปของคุณได้ด้วย ID และรหัสผ่านต่อไปนี้"), + ("Password", "รหัสผ่าน"), + ("Ready", "พร้อม"), + ("Established", "เชื่อมต่อแล้ว"), + ("connecting_status", "กำลังเชื่อมต่อไปยังเครือข่าย RustDesk..."), + ("Enable Service", "เปิดใช้การงานเซอร์วิส"), + ("Start Service", "เริ่มต้นใช้งานเซอร์วิส"), + ("Service is running", "เซอร์วิสกำลังทำงาน"), + ("Service is not running", "เซอร์วิสไม่ทำงาน"), + ("not_ready_status", "ไม่พร้อมใช้งาน กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"), + ("Control Remote Desktop", "การควบคุมเดสก์ท็อปปลายทาง"), + ("Transfer File", "การถ่ายโอนไฟล์"), + ("Connect", "เชื่อมต่อ"), + ("Recent Sessions", "เซสชันล่าสุด"), + ("Address Book", "สมุดรายชื่อ"), + ("Confirmation", "การยืนยัน"), + ("TCP Tunneling", "อุโมงค์การเชื่อมต่อ TCP"), + ("Remove", "ลบ"), + ("Refresh random password", "รีเฟรชรหัสผ่านใหม่แบบสุ่ม"), + ("Set your own password", "ตั้งรหัสผ่านของคุณเอง"), + ("Enable Keyboard/Mouse", "เปิดการใช้งาน คีย์บอร์ด/เมาส์"), + ("Enable Clipboard", "เปิดการใช้งาน คลิปบอร์ด"), + ("Enable File Transfer", "เปิดการใช้งาน การถ่ายโอนไฟล์"), + ("Enable TCP Tunneling", "เปิดการใช้งาน อุโมงค์การเชื่อมต่อ TCP"), + ("IP Whitelisting", "IP ไวท์ลิสต์"), + ("ID/Relay Server", "เซิร์ฟเวอร์ ID/Relay"), + ("Import Server Config", "นำเข้าการตั้งค่าเซิร์ฟเวอร์"), + ("Export Server Config", "ส่งออกการตั้งค่าเซิร์ฟเวอร์"), + ("Import server configuration successfully", "นำเข้าการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"), + ("Export server configuration successfully", "ส่งออกการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"), + ("Invalid server configuration", "การตั้งค่าของเซิร์ฟเวอร์ไม่ถูกต้อง"), + ("Clipboard is empty", "คลิปบอร์ดว่างเปล่า"), + ("Stop service", "หยุดการใช้งานเซอร์วิส"), + ("Change ID", "เปลี่ยน ID"), + ("Website", "เว็บไซต์"), + ("About", "เกี่ยวกับ"), + ("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"), + ("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"), + ("Mute", "ปิดเสียง"), + ("Audio Input", "ออดิโออินพุท"), + ("Enhancements", "การปรับปรุง"), + ("Hardware Codec", "ฮาร์ดแวร์ codec"), + ("Adaptive Bitrate", "บิทเรทผันแปร"), + ("ID Server", "เซิร์ฟเวอร์ ID"), + ("Relay Server", "เซิร์ฟเวอร์ Relay"), + ("API Server", "เซิร์ฟเวอร์ API"), + ("invalid_http", "ต้องขึ้นต้นด้วย http:// หรือ https:// เท่านั้น"), + ("Invalid IP", "IP ไม่ถูกต้อง"), + ("id_change_tip", "อนุญาตเฉพาะตัวอักษร a-z A-Z 0-9 และ _ (ขีดล่าง) เท่านั้น โดยตัวอักษรขึ้นต้นจะต้องเป็น a-z หรือไม่ก็ A-Z และมีความยาวระหว่าง 6 ถึง 16 ตัวอักษร"), + ("Invalid format", "รูปแบบไม่ถูกต้อง"), + ("server_not_support", "ยังไม่รองรับโดยเซิร์ฟเวอร์"), + ("Not available", "ไม่พร้อมใช้งาน"), + ("Too frequent", "ดำเนินการถี่เกินไป"), + ("Cancel", "ยกเลิก"), + ("Skip", "ข้าม"), + ("Close", "ปิด"), + ("Retry", "ลองใหม่อีกครั้ง"), + ("OK", "ตกลง"), + ("Password Required", "ต้องใช้รหัสผ่าน"), + ("Please enter your password", "กรุณาใส่รหัสผ่านของคุณ"), + ("Remember password", "จดจำรหัสผ่าน"), + ("Wrong Password", "รหัสผ่านไม่ถูกต้อง"), + ("Do you want to enter again?", "ต้องการใส่ข้อมูลอีกครั้งหรือไม่?"), + ("Connection Error", "การเชื่อมต่อผิดพลาด"), + ("Error", "ข้อผิดพลาด"), + ("Reset by the peer", "รีเซ็ตโดยอีกฝั่ง"), + ("Connecting...", "กำลังเชื่อมต่อ..."), + ("Connection in progress. Please wait.", "กำลังดำเนินการเชื่อมต่อ กรุณารอซักครู่"), + ("Please try 1 minute later", "กรุณาลองใหม่อีกครั้งใน 1 นาที"), + ("Login Error", "การเข้าสู่ระบบผิดพลาด"), + ("Successful", "สำเร็จ"), + ("Connected, waiting for image...", "เชื่อมต่อสำเร็จ กำลังรับข้อมูลภาพ..."), + ("Name", "ชื่อ"), + ("Type", "ประเภท"), + ("Modified", "แก้ไขล่าสุด"), + ("Size", "ขนาด"), + ("Show Hidden Files", "แสดงไฟล์ที่ถูกซ่อน"), + ("Receive", "รับ"), + ("Send", "ส่ง"), + ("Refresh File", "รีเฟรชไฟล์"), + ("Local", "ต้นทาง"), + ("Remote", "ปลายทาง"), + ("Remote Computer", "คอมพิวเตอร์ปลายทาง"), + ("Local Computer", "คอมพิวเตอร์ต้นทาง"), + ("Confirm Delete", "ยืนยันการลบ"), + ("Delete", "ลบ"), + ("Properties", "ข้อมูล"), + ("Multi Select", "เลือกหลายรายการ"), + ("Select All", "เลือกทั้งหมด"), + ("Unselect All", "ยกเลิกการเลือกทั้งหมด"), + ("Empty Directory", "ไดเรกทอรีว่างเปล่า"), + ("Not an empty directory", "ไม่ใช่ไดเรกทอรีว่างเปล่า"), + ("Are you sure you want to delete this file?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์นี้?"), + ("Are you sure you want to delete this empty directory?", "คุณแน่ใจหรือไม่ที่จะลบไดเรอทอรีว่างเปล่านี้?"), + ("Are you sure you want to delete the file of this directory?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์ของไดเรกทอรีนี้?"), + ("Do this for all conflicts", "ดำเนินการแบบเดียวกันสำหรับรายการทั้งหมด"), + ("This is irreversible!", "การดำเนินการนี้ไม่สามารถย้อนกลับได้!"), + ("Deleting", "กำลังลบ"), + ("files", "ไฟล์"), + ("Waiting", "กำลังรอ"), + ("Finished", "เสร็จแล้ว"), + ("Speed", "ความเร็ว"), + ("Custom Image Quality", "คุณภาพของภาพแบบกำหนดเอง"), + ("Privacy mode", "โหมดความเป็นส่วนตัว"), + ("Block user input", "บล็อคอินพุทจากผู้ใช้งาน"), + ("Unblock user input", "ยกเลิกการบล็อคอินพุทจากผู้ใช้งาน"), + ("Adjust Window", "ปรับขนาดหน้าต่าง"), + ("Original", "ต้นฉบับ"), + ("Shrink", "ย่อ"), + ("Stretch", "ยืด"), + ("Scrollbar", "แถบเลื่อน"), + ("ScrollAuto", "เลื่อนอัตโนมัติ"), + ("Good image quality", "ภาพคุณภาพดี"), + ("Balanced", "สมดุล"), + ("Optimize reaction time", "เน้นการตอบสนอง"), + ("Custom", "กำหนดเอง"), + ("Show remote cursor", "แสดงเคอร์เซอร์ปลายทาง"), + ("Show quality monitor", "แสดงคุณภาพหน้าจอ"), + ("Disable clipboard", "ปิดการใช้งานคลิปบอร์ด"), + ("Lock after session end", "ล็อคหลังจากจบเซสชัน"), + ("Insert", "แทรก"), + ("Insert Lock", "แทรกล็อค"), + ("Refresh", "รีเฟรช"), + ("ID does not exist", "ไม่พอข้อมูล ID"), + ("Failed to connect to rendezvous server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์นัดพบล้มเหลว"), + ("Please try later", "กรุณาลองใหม่ในภายหลัง"), + ("Remote desktop is offline", "เดสก์ท็อปปลายทางออฟไลน์"), + ("Key mismatch", "คีย์ไม่ถูกต้อง"), + ("Timeout", "หมดเวลา"), + ("Failed to connect to relay server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์รีเลย์ล้มเหลว"), + ("Failed to connect via rendezvous server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์นัดพบล้มเหลว"), + ("Failed to connect via relay server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์รีเลย์ล้มเหลว"), + ("Failed to make direct connection to remote desktop", "การเชื่อมต่อตรงไปยังเดสก์ท็อปปลายทางล้มเหลว"), + ("Set Password", "ตั้งรหัสผ่าน"), + ("OS Password", "รหัสผ่านระบบปฏิบัติการ"), + ("install_tip", "เนื่องด้วยข้อจำกัดของการใช้งาน UAC ทำให้ RustDesk ไม่สามารถทำงานได้ปกติในฝั่งปลายทางในบางครั้ง เพื่อหลีกเลี่ยงข้อจำกัดของ UAC กรุณากดปุ่มด้านล่างเพื่อติดตั้ง RustDesk ไปยังระบบของคุณ"), + ("Click to upgrade", "คลิกเพื่ออัปเกรด"), + ("Click to download", "คลิกเพื่อดาวน์โหลด"), + ("Click to update", "คลิกเพื่ออัปเดต"), + ("Configure", "ปรับแต่งค่า"), + ("config_acc", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่ RustDesk"), + ("config_screen", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การบันทึกภาพหน้าจอ\" ให้แก่ RustDesk"), + ("Installing ...", "กำลังติดตั้ง ..."), + ("Install", "ติดตั้ง"), + ("Installation", "การติดตั้ง"), + ("Installation Path", "ตำแหน่งที่ติดตั้ง"), + ("Create start menu shortcuts", "สร้างทางลัดไปยัง Start Menu"), + ("Create desktop icon", "สร้างไอคอนบนเดสก์ท็อป"), + ("agreement_tip", "ในการเริ่มต้นการติดตั้ง ถือว่าคุณได้ยอมรับข้อตกลงใบอนุญาตแล้ว"), + ("Accept and Install", "ยอมรับและติดตั้ง"), + ("End-user license agreement", "ข้อตกลงใบอนุญาตผู้ใช้งาน"), + ("Generating ...", "กำลังสร้าง ..."), + ("Your installation is lower version.", "การติดตั้งของคุณเป็นเวอร์ชั่นที่ต่ำกว่า"), + ("not_close_tcp_tip", "อย่าปิดหน้าต่างนี้ในขณะที่คุณกำลังใช้งานอุโมงค์การเชื่อมต่อ"), + ("Listening ...", "กำลังรอรับข้อมูล ..."), + ("Remote Host", "โฮสต์ปลายทาง"), + ("Remote Port", "พอร์ทปลายทาง"), + ("Action", "การดำเนินการ"), + ("Add", "เพิ่ม"), + ("Local Port", "พอร์ทต้นทาง"), + ("Local Address", "ที่อยู่ต้นทาง"), + ("Change Local Port", "เปลี่ยนพอร์ทต้นทาง"), + ("setup_server_tip", "เพื่อการเชื่อมต่อที่เร็วขึ้น กรุณาเซ็ตอัปเซิร์ฟเวอร์ของคุณเอง"), + ("Too short, at least 6 characters.", "สั้นเกินไป ต้องไม่ต่ำกว่า 6 ตัวอักษร"), + ("The confirmation is not identical.", "การยืนยันข้อมูลไม่ถูกต้อง"), + ("Permissions", "สิทธิ์การใช้งาน"), + ("Accept", "ยอมรับ"), + ("Dismiss", "ปิด"), + ("Disconnect", "ยกเลิกการเชื่อมต่อ"), + ("Allow using keyboard and mouse", "อนุญาตให้ใช้งานคีย์บอร์ดและเมาส์"), + ("Allow using clipboard", "อนุญาตให้ใช้คลิปบอร์ด"), + ("Allow hearing sound", "อนุญาตให้ได้ยินเสียง"), + ("Allow file copy and paste", "อนุญาตให้มีการคัดลอกและวางไฟล์"), + ("Connected", "เชื่อมต่อแล้ว"), + ("Direct and encrypted connection", "การเชื่อมต่อตรงที่มีการเข้ารหัส"), + ("Relayed and encrypted connection", "การเชื่อมต่อแบบรีเลย์ที่มีการเข้ารหัส"), + ("Direct and unencrypted connection", "การเชื่อมต่อตรงที่ไม่มีการเข้ารหัส"), + ("Relayed and unencrypted connection", "การเชื่อมต่อแบบรีเลย์ที่ไม่มีการเข้ารหัส"), + ("Enter Remote ID", "กรอก ID ปลายทาง"), + ("Enter your password", "กรอกรหัสผ่าน"), + ("Logging in...", "กำลังเข้าสู่ระบบ..."), + ("Enable RDP session sharing", "เปิดการใช้งานการแชร์เซสชัน RDP"), + ("Auto Login", "เข้าสู่ระบอัตโนมัติ"), + ("Enable Direct IP Access", "เปิดการใช้งาน IP ตรง"), + ("Rename", "ปลายทาง"), + ("Space", "พื้นที่ว่าง"), + ("Create Desktop Shortcut", "สร้างทางลัดบนเดสก์ท็อป"), + ("Change Path", "เปลี่ยนตำแหน่ง"), + ("Create Folder", "สร้างโฟลเดอร์"), + ("Please enter the folder name", "กรุณาใส่ชื่อโฟลเดอร์"), + ("Fix it", "แก้ไข"), + ("Warning", "คำเตือน"), + ("Login screen using Wayland is not supported", "หน้าจอการเข้าสู่ระบบโดยใช้ Wayland ยังไม่ถูกรองรับ"), + ("Reboot required", "จำเป็นต้องเริ่มต้นระบบใหม่"), + ("Unsupported display server ", "เซิร์ฟเวอร์การแสดงผลที่ไม่รองรับ"), + ("x11 expected", "ต้องใช้งาน x11"), + ("Port", "พอร์ท"), + ("Settings", "ตั้งค่า"), + ("Username", "ชื่อผู้ใช้งาน"), + ("Invalid port", "พอร์ทไม่ถูกต้อง"), + ("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งการการเชื่อมต่อ"), + ("Enable remote configuration modification", "เปิดการใช้งานการแก้ไขการตั้งค่าปลายทาง"), + ("Run without install", "ใช้งานโดยไม่ต้องติดตั้ง"), + ("Always connected via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"), + ("Always connect via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"), + ("whitelist_tip", "อนุญาตเฉพาะการเชื่อมต่อจาก IP ที่ไวท์ลิสต์"), + ("Login", "เข้าสู่ระบบ"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), + ("Logout", "ออกจากระบบ"), + ("Tags", "แท็ก"), + ("Search ID", "ค้นหา ID"), + ("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"), + ("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"), + ("Add ID", "เพิ่ม ID"), + ("Add Tag", "เพิ่มแท็ก"), + ("Unselect all tags", "ยกเลิกการเลือกแท็กทั้งหมด"), + ("Network error", "ข้อผิดพลาดของเครือข่าย"), + ("Username missed", "ไม่พบข้อมูลผู้ใช้งาน"), + ("Password missed", "ไม่พบรหัสผ่าน"), + ("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"), + ("Edit Tag", "แก้ไขแท็ก"), + ("Unremember Password", "ยกเลิกการจดจำรหัสผ่าน"), + ("Favorites", "รายการโปรด"), + ("Add to Favorites", "เพิ่มไปยังรายการโปรด"), + ("Remove from Favorites", "ลบออกจากรายการโปรด"), + ("Empty", "ว่างเปล่า"), + ("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"), + ("Socks5 Proxy", "พรอกซี Socks5"), + ("Hostname", "ชื่อโฮสต์"), + ("Discovered", "ค้นพบ"), + ("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"), + ("Remote ID", "ID ปลายทาง"), + ("Paste", "วาง"), + ("Paste here?", "วางที่นี่หรือไม่?"), + ("Are you sure to close the connection?", "คุณแน่ใจหรือไม่ที่จะปิดการเชื่อมต่อ?"), + ("Download new version", "ดาวน์โหลดเวอร์ชั่นใหม่"), + ("Touch mode", "โหมดการสัมผัส"), + ("Mouse mode", "โหมดการใช้เมาส์"), + ("One-Finger Tap", "แตะนิ้วเดียว"), + ("Left Mouse", "เมาส์ซ้าย"), + ("One-Long Tap", "แตะยาวหนึ่งครั้ง"), + ("Two-Finger Tap", "แตะสองนิ้ว"), + ("Right Mouse", "เมาส์ขวา"), + ("One-Finger Move", "ลากนิ้วเดียว"), + ("Double Tap & Move", "แตะเบิ้ลและลาก"), + ("Mouse Drag", "ลากเมาส์"), + ("Three-Finger vertically", "สามนิ้วแนวตั้ง"), + ("Mouse Wheel", "ลูกลิ้งเมาส์"), + ("Two-Finger Move", "ลากสองนิ้ว"), + ("Canvas Move", "ลากแคนวาส"), + ("Pinch to Zoom", "ถ่างเพื่อขยาย"), + ("Canvas Zoom", "ขยายแคนวาส"), + ("Reset canvas", "รีเซ็ตแคนวาส"), + ("No permission of file transfer", "ไม่มีสิทธิ์ในการถ่ายโอนไฟล์"), + ("Note", "บันทึกข้อความ"), + ("Connection", "การเชื่อมต่อ"), + ("Share Screen", "แชร์หน้าจอ"), + ("CLOSE", "ปิด"), + ("OPEN", "เปิด"), + ("Chat", "แชท"), + ("Total", "รวม"), + ("items", "รายการ"), + ("Selected", "ถูกเลือก"), + ("Screen Capture", "แคปเจอร์หน้าจอ"), + ("Input Control", "ควบคุมอินพุท"), + ("Audio Capture", "แคปเจอร์เสียง"), + ("File Connection", "การเชื่อมต่อไฟล์"), + ("Screen Connection", "การเชื่อมต่อหน้าจอ"), + ("Do you accept?", "ยอมรับหรือไม่?"), + ("Open System Setting", "เปิดการตั้งค่าระบบ"), + ("How to get Android input permission?", "เปิดสิทธิ์การใช้งานอินพุทของแอนดรอยด์ได้อย่างไร?"), + ("android_input_permission_tip1", "ในการที่จะอนุญาตให้เครื่องปลายทางควบคุมอุปกรณ์แอนดรอยด์ของคุณโดยใช้เมาส์หรือการสัมผัส คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่เซอร์วิสของ RustDesk"), + ("android_input_permission_tip2", "กรุณาไปยังหน้าตั้งค่าถัดไป ค้นหาและเข้าไปยัง [เซอร์วิสที่ถูกติดตั้ง] และเปิดการใช้งานเซอร์วิส [อินพุท RustDesk]"), + ("android_new_connection_tip", "ได้รับคำขอควบคุมใหม่ที่ต้องการควบคุมอุปกรณ์ของคุณ"), + ("android_service_will_start_tip", "การเปิดการใช้งาน \"การบันทึกหน้าจอ\" จะเป็นการเริ่มต้นการทำงานของเซอร์วิสโดยอัตโนมัติ ที่จะอนุญาตให้อุปกรณ์อื่นๆ ส่งคำขอเข้าถึงมายังอุปกรณ์ของคุณได้"), + ("android_stop_service_tip", "การปิดการใช้งานเซอร์วิสจะปิดการเชื่อมต่อทั้งหมดโดยอัตโนมัติ"), + ("android_version_audio_tip", "เวอร์ชั่นแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชั่น 10 หรือสูงกว่า"), + ("android_start_service_tip", "แตะ [เริ่มต้นใช้งานเซอร์วิส] หรือเปิดสิทธิ์ [การบันทึกหน้าจอ] เพื่อเริ่มเซอร์วิสการแชร์หน้าจอ"), + ("Account", "บัญชี"), + ("Overwrite", "เขียนทับ"), + ("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"), + ("Quit", "ออก"), + ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), + ("Help", "ช่วยเหลือ"), + ("Failed", "ล้มเหลว"), + ("Succeeded", "สำเร็จ"), + ("Someone turns on privacy mode, exit", "มีใครบางคนเปิดใช้งานโหมดความเป็นส่วนตัว กำลังออก"), + ("Unsupported", "ไม่รองรับ"), + ("Peer denied", "ถูกปฏิเสธโดยอีกฝั่ง"), + ("Please install plugins", "กรุณาติดตั้งปลั๊กอิน"), + ("Peer exit", "อีกฝั่งออก"), + ("Failed to turn off", "การปิดล้มเหลว"), + ("Turned off", "ปิด"), + ("In privacy mode", "อยู่ในโหมดความเป็นส่วนตัว"), + ("Out privacy mode", "อยู่นอกโหมดความเป็นส่วนตัว"), + ("Language", "ภาษา"), + ("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"), + ("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"), + ("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"), + ("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"), + ("Legacy mode", ""), + ("Map mode", ""), + ("Translate mode", ""), + ("Use permanent password", "ใช้รหัสผ่านถาวร"), + ("Use both passwords", "ใช้รหัสผ่านทั้งสองแบบ"), + ("Set permanent password", "ตั้งค่ารหัสผ่านถาวร"), + ("Enable Remote Restart", "เปิดการใช้งานการรีสตาร์ทระบบทางไกล"), + ("Allow remote restart", "อนุญาตการรีสตาร์ทระบบทางไกล"), + ("Restart Remote Device", "รีสตาร์ทอุปกรณ์ปลายทาง"), + ("Are you sure you want to restart", "คุณแน่ใจหรือไม่ที่จะรีสตาร์ท"), + ("Restarting Remote Device", "กำลังรีสตาร์ทระบบปลายทาง"), + ("remote_restarting_tip", "ระบบปลายทางกำลังรีสตาร์ท กรุณาปิดกล่องข้อความนี้และดำเนินการเขื่อมต่อใหม่อีกครั้งด้วยรหัสผ่านถาวรหลังจากผ่านไปซักครู่"), + ("Copied", "คัดลอกแล้ว"), + ("Exit Fullscreen", "ออกจากเต็มหน้าจอ"), + ("Fullscreen", "เต็มหน้าจอ"), + ("Mobile Actions", "การดำเนินการบนมือถือ"), + ("Select Monitor", "เลือกหน้าจอ"), + ("Control Actions", "การดำเนินการควบคุม"), + ("Display Settings", "การตั้งค่าแสดงผล"), + ("Ratio", "อัตราส่วน"), + ("Image Quality", "คุณภาพภาพ"), + ("Scroll Style", "ลักษณะการเลื่อน"), + ("Show Menubar", "แสดงแถบเมนู"), + ("Hide Menubar", "ซ่อนแถบเมนู"), + ("Direct Connection", "การเชื่อมต่อตรง"), + ("Relay Connection", "การเชื่อมต่อแบบรีเลย์"), + ("Secure Connection", "การเชื่อมต่อที่ปลอดภัย"), + ("Insecure Connection", "การเชื่อมต่อที่ไม่ปลอดภัย"), + ("Scale original", "ขนาดเดิม"), + ("Scale adaptive", "ขนาดยืดหยุ่น"), + ("General", "ทั่วไป"), + ("Security", "ความปลอดภัย"), + ("Theme", "ธีม"), + ("Dark Theme", "ธีมมืด"), + ("Dark", "มืด"), + ("Light", "สว่าง"), + ("Follow System", "ตามระบบ"), + ("Enable hardware codec", "เปิดการใช้งานฮาร์ดแวร์ codec"), + ("Unlock Security Settings", "ปลดล็อคการตั้งค่าความปลอดภัย"), + ("Enable Audio", "เปิดการใช้งานเสียง"), + ("Unlock Network Settings", "ปลดล็อคการตั้งค่าเครือข่าย"), + ("Server", "เซิร์ฟเวอร์"), + ("Direct IP Access", "การเข้าถึง IP ตรง"), + ("Proxy", "พรอกซี"), + ("Apply", "นำไปใช้"), + ("Disconnect all devices?", "ยกเลิกการเชื่อมต่ออุปกรณ์ทั้งหมด?"), + ("Clear", "ล้างข้อมูล"), + ("Audio Input Device", "อุปกรณ์รับอินพุทข้อมูลเสียง"), + ("Deny remote access", "ปฏิเสธการเชื่อมต่อ"), + ("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"), + ("Network", "เครือข่าย"), + ("Enable RDP", "เปิดการใช้งาน RDP"), + ("Pin menubar", "ปักหมุดแถบเมนู"), + ("Unpin menubar", "ยกเลิกการปักหมุดแถบเมนู"), + ("Recording", "การบันทึก"), + ("Directory", "ไดเรกทอรี่"), + ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"), + ("Change", "เปลี่ยน"), + ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"), + ("Stop session recording", "หยุดการบันทึกเซสซัน"), + ("Enable Recording Session", "เปิดใช้งานการบันทึกเซสชัน"), + ("Allow recording session", "อนุญาตการบันทึกเซสชัน"), + ("Enable LAN Discovery", "เปิดการใช้งานการค้นหาในวง LAN"), + ("Deny LAN Discovery", "ปฏิเสธการใช้งานการค้นหาในวง LAN"), + ("Write a message", "เขียนข้อความ"), + ("Prompt", ""), + ("Please wait for confirmation of UAC...", "กรุณารอการยืนยันจาก UAC..."), + ("elevated_foreground_window_tip", "หน้าต่างปัจจุบันของเครื่องปลายทางต้องการสิทธิ์การใช้งานที่สูงขึ้นสำหรับการทำงาน ดังนั้นเมาส์และคีย์บอร์ดจะไม่สามารถใช้งานได้ชั่วคราว คุณสามารถขอผู้ใช้งานปลายทางให้ย่อหน้าต่าง หรือคลิกปุ่มให้สิทธิ์การใช้งานในหน้าต่างการจัดการการเชื่อมต่อ เพื่อหลีกเลี่ยงปัญหานี้เราแนะนำให้ดำเนินการติดตั้งซอฟท์แวร์ในเครื่องปลายทาง"), + ("Disconnected", "ยกเลิกการเชื่อมต่อ"), + ("Other", "อื่นๆ"), + ("Confirm before closing multiple tabs", "ยืนยันการปิดหลายแท็บ"), + ("Keyboard Settings", "การตั้งค่าคีย์บอร์ด"), + ("Full Access", "การเข้าถึงทั้งหมด"), + ("Screen Share", "การแชร์จอ"), + ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland ต้องการ Ubuntu เวอร์ชั่น 21.04 หรือสูงกว่า"), + ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland ต้องการลินุกซ์เวอร์ชันที่สูงกว่านี้ กรุณาเปลี่ยนไปใช้เดสก์ท็อป X11 หรือเปลี่ยนระบบปฏิบัติการของคุณ"), + ("JumpLink", "View"), + ("Please Select the screen to be shared(Operate on the peer side).", "กรุณาเลือกหน้าจอที่ต้องการแชร์ (ใช้งานในอีกฝั่งของการเชื่อมต่อ)"), + ("Show RustDesk", "แสดง RustDesk"), + ("This PC", ""), + ("or", "หรือ"), + ("Continue with", "ทำต่อด้วย"), + ("Elevate", "ยกระดับ"), + ("Zoom cursor", "ขยายเคอร์เซอร์"), + ("Accept sessions via password", "ยอมรับการเชื่อมต่อด้วยรหัสผ่าน"), + ("Accept sessions via click", "ยอมรับการเชื่อมต่อด้วยการคลิก"), + ("Accept sessions via both", "ยอมรับการเชื่อมต่อด้วยทั้งสองวิธิ"), + ("Please wait for the remote side to accept your session request...", "กรุณารอให้อีกฝั่งยอมรับการเชื่อมต่อของคุณ..."), + ("One-time Password", "รหัสผ่านครั้งเดียว"), + ("Use one-time password", "ใช้รหัสผ่านครั้งเดียว"), + ("One-time password length", "ความยาวรหัสผ่านครั้งเดียว"), + ("Request access to your device", "คำขอการเข้าถึงอุปกรณ์ของคุณ"), + ("Hide connection management window", "ซ่อนหน้าต่างการจัดการการเชื่อมต่อ"), + ("hide_cm_tip", "อนุญาตการซ่อนก็ต่อเมื่อยอมรับการเชื่อมต่อด้วยรหัสผ่าน และต้องเป็นรหัสผ่านถาวรเท่านั้น"), + ("wayland_experiment_tip", "การสนับสนุน Wayland ยังอยู่ในขั้นตอนการทดลอง กรุณาใช้ X11 หากคุณต้องการใช้งานการเข้าถึงแบบไม่มีผู้ดูแล"), + ("Right click to select tabs", "คลิกขวาเพื่อเลือกแท็บ"), + ("Skipped", "ข้าม"), + ("Add to Address Book", "เพิ่มไปยังสมุดรายชื่อ"), + ("Group", "กลุ่ม"), + ("Search", "ค้นหา"), + ("Closed manually by the web console", "ถูกปิดโดยเว็บคอนโซล"), + ("Local keyboard type", "ประเภทคีย์บอร์ด"), + ("Select local keyboard type", "เลือกประเภทคีย์บอร์ด"), + ("software_render_tip", ""), + ("Always use software rendering", ""), + ("config_input", ""), + ].iter().cloned().collect(); +} diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 931bcec6d..2d0fc8c59 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Always connect via relay"), ("whitelist_tip", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"), ("Login", "Giriş yap"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Çıkış yap"), ("Tags", "Etiketler"), ("Search ID", "ID Arama"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 847edb599..a58665a70 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "一律透過轉送連線"), ("whitelist_tip", "只有白名單中的 IP 可以存取"), ("Login", "登入"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "登出"), ("Tags", "標籤"), ("Search ID", "搜尋 ID"), diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 65ae32d68..fad7a3880 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Завжди підключатися через ретрансляційний сервер"), ("whitelist_tip", "Тільки IP-адреси з білого списку можуть отримати доступ до мене"), ("Login", "Увійти"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Вийти"), ("Tags", "Ключові слова"), ("Search ID", "Пошук за ID"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index eab0b3497..187572c83 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Always connect via relay", "Luôn kết nối qua relay"), ("whitelist_tip", "Chỉ có những IP đựoc cho phép mới có thể truy cập"), ("Login", "Đăng nhập"), + ("Verify", ""), + ("Remember me", ""), + ("Trust this device", ""), + ("Verification code", ""), + ("verification_tip", ""), ("Logout", "Đăng xuất"), ("Tags", "Tags"), ("Search ID", "Tìm ID"),