Merge pull request #2741 from Heap-Hop/master

refactor user login, add two-step verification (email)
This commit is contained in:
RustDesk 2023-01-09 14:04:57 +08:00 committed by GitHub
commit 20a4550cce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1378 additions and 1059 deletions

View File

@ -1367,7 +1367,7 @@ connect(BuildContext context, String id,
} }
} }
Future<Map<String, String>> getHttpHeaders() async { Map<String, String> getHttpHeaders() {
return { return {
'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}' 'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}'
}; };

View File

@ -1,12 +1,22 @@
import 'package:flutter_hbb/models/peer_model.dart'; 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 { class UserPayload {
String name = ''; String name = '';
String email = ''; String email = '';
String note = ''; String note = '';
int? status; int? status;
String grp = ''; String grp = '';
bool is_admin = false; bool isAdmin = false;
UserPayload.fromJson(Map<String, dynamic> json) UserPayload.fromJson(Map<String, dynamic> json)
: name = json['name'] ?? '', : name = json['name'] ?? '',
@ -14,7 +24,7 @@ class UserPayload {
note = json['note'] ?? '', note = json['note'] ?? '',
status = json['status'], status = json['status'],
grp = json['grp'] ?? '', grp = json['grp'] ?? '',
is_admin = json['is_admin'] == true; isAdmin = json['is_admin'] == true;
} }
class PeerPayload { class PeerPayload {
@ -37,3 +47,73 @@ class PeerPayload {
return Peer.fromJson({"id": p.id}); 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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
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<String, dynamic> 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";
}
}

View File

@ -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/peer_card.dart';
import 'package:flutter_hbb/common/widgets/peers_view.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/popup_menu.dart';
import 'package:flutter_hbb/desktop/widgets/login.dart';
import '../../consts.dart'; import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
import '../../desktop/pages/desktop_home_page.dart'; import 'login.dart';
import '../../mobile/pages/settings_page.dart';
class AddressBook extends StatefulWidget { class AddressBook extends StatefulWidget {
final EdgeInsets? menuPadding; final EdgeInsets? menuPadding;
@ -41,21 +39,12 @@ class _AddressBookState extends State<AddressBook> {
} }
}); });
handleLogin() {
// TODO refactor login dialog for desktop and mobile
if (isDesktop) {
loginDialog();
} else {
showLogin(gFFI.dialogManager);
}
}
Future<Widget> buildBody(BuildContext context) async { Future<Widget> buildBody(BuildContext context) async {
return Obx(() { return Obx(() {
if (gFFI.userModel.userName.value.isEmpty) { if (gFFI.userModel.userName.value.isEmpty) {
return Center( return Center(
child: InkWell( child: InkWell(
onTap: handleLogin, onTap: loginDialog,
child: Text( child: Text(
translate("Login"), translate("Login"),
style: const TextStyle(decoration: TextDecoration.underline), style: const TextStyle(decoration: TextDecoration.underline),

View File

@ -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<StatefulWidget> createState() {
return _WidgetOPState();
}
}
class _WidgetOPState extends State<WidgetOP> {
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<ConfigOP> 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<bool?> 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<bool>((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<bool?> 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<bool>((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;
}

View File

@ -8,7 +8,6 @@ import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.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/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/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.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 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
import '../../common/widgets/dialog.dart'; import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart';
const double _kTabWidth = 235; const double _kTabWidth = 235;
const double _kTabHeight = 42; const double _kTabHeight = 42;

View File

@ -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<StatefulWidget> createState() {
return _WidgetOPState();
}
}
class _WidgetOPState extends State<WidgetOP> {
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<ConfigOP> 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<bool> loginDialog() async {
String username = '';
var usernameMsg = '';
String pass = '';
var passMsg = '';
var isInProgress = false;
var completer = Completer<bool>();
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;
}

View File

@ -7,9 +7,8 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../common.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/peer_tab_page.dart';
import '../../common/widgets/peers_view.dart';
import '../../consts.dart'; import '../../consts.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
@ -258,7 +257,7 @@ class _WebMenuState extends State<WebMenu> {
} }
if (value == 'login') { if (value == 'login') {
if (gFFI.userModel.userName.value.isEmpty) { if (gFFI.userModel.userName.value.isEmpty) {
showLogin(gFFI.dialogManager); loginDialog();
} else { } else {
gFFI.userModel.logOut(); gFFI.userModel.logOut();
} }

View File

@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/dialog.dart'; import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../widgets/dialog.dart'; import '../widgets/dialog.dart';
@ -300,7 +301,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
leading: Icon(Icons.person), leading: Icon(Icons.person),
onPressed: (context) { onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) { if (gFFI.userModel.userName.value.isEmpty) {
showLogin(gFFI.dialogManager); loginDialog();
} else { } else {
gFFI.userModel.logOut(); gFFI.userModel.logOut();
} }
@ -397,7 +398,7 @@ void showServerSettings(OverlayDialogManager dialogManager) async {
void showLanguageSettings(OverlayDialogManager dialogManager) async { void showLanguageSettings(OverlayDialogManager dialogManager) async {
try { try {
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>; final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
var lang = await bind.mainGetLocalOption(key: "lang"); var lang = bind.mainGetLocalOption(key: "lang");
dialogManager.show((setState, close) { dialogManager.show((setState, close) {
setLang(v) { setLang(v) {
if (lang != v) { if (lang != v) {
@ -482,77 +483,6 @@ void showAbout(OverlayDialogManager dialogManager) {
}, clickMaskDismiss: true, backDismiss: true); }, 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
? <Widget>[CircularProgressIndicator()]
: (error != ""
? <Widget>[
Text(translate(error),
style: TextStyle(color: Colors.red))
]
: <Widget>[])) +
<Widget>[
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 { class ScanButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -27,8 +27,7 @@ class AbModel {
abError.value = ""; abError.value = "";
final api = "${await bind.mainGetApiServer()}/api/ab/get"; final api = "${await bind.mainGetApiServer()}/api/ab/get";
try { try {
final resp = final resp = await http.post(Uri.parse(api), headers: getHttpHeaders());
await http.post(Uri.parse(api), headers: await getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") { if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body); Map<String, dynamic> json = jsonDecode(resp.body);
if (json.containsKey('error')) { if (json.containsKey('error')) {
@ -102,7 +101,7 @@ class AbModel {
Future<void> pushAb() async { Future<void> pushAb() async {
abLoading.value = true; abLoading.value = true;
final api = "${await bind.mainGetApiServer()}/api/ab"; final api = "${await bind.mainGetApiServer()}/api/ab";
var authHeaders = await getHttpHeaders(); var authHeaders = getHttpHeaders();
authHeaders['Content-Type'] = "application/json"; authHeaders['Content-Type'] = "application/json";
final peersJsonData = peers.map((e) => e.toJson()).toList(); final peersJsonData = peers.map((e) => e.toJson()).toList();
final body = jsonEncode({ final body = jsonEncode({

View File

@ -59,7 +59,7 @@ class GroupModel {
if (gFFI.userModel.isAdmin.isFalse) if (gFFI.userModel.isAdmin.isFalse)
'grp': gFFI.userModel.groupName.value, '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") { if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body); Map<String, dynamic> json = jsonDecode(resp.body);
if (json.containsKey('error')) { if (json.containsKey('error')) {
@ -110,7 +110,7 @@ class GroupModel {
'grp': gFFI.userModel.groupName.value, 'grp': gFFI.userModel.groupName.value,
'target_user': username '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") { if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body); Map<String, dynamic> json = jsonDecode(resp.body);
if (json.containsKey('error')) { if (json.containsKey('error')) {

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
import 'package:flutter_hbb/common/widgets/peer_tab_page.dart'; import 'package:flutter_hbb/common/widgets/peer_tab_page.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -45,7 +46,9 @@ class UserModel {
if (error != null) { if (error != null) {
throw error; throw error;
} }
await _parseUserInfo(data);
final user = UserPayload.fromJson(data);
await _parseAndUpdateUser(user);
} catch (e) { } catch (e) {
print('Failed to refreshCurrentUser: $e'); print('Failed to refreshCurrentUser: $e');
} finally { } finally {
@ -55,7 +58,6 @@ class UserModel {
Future<void> reset() async { Future<void> reset() async {
await bind.mainSetLocalOption(key: 'access_token', value: ''); await bind.mainSetLocalOption(key: 'access_token', value: '');
await bind.mainSetLocalOption(key: 'user_info', value: '');
await gFFI.abModel.reset(); await gFFI.abModel.reset();
await gFFI.groupModel.reset(); await gFFI.groupModel.reset();
userName.value = ''; userName.value = '';
@ -63,11 +65,10 @@ class UserModel {
statePeerTab.check(); statePeerTab.check();
} }
Future<void> _parseUserInfo(dynamic userinfo) async { Future<void> _parseAndUpdateUser(UserPayload user) async {
bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(userinfo)); userName.value = user.name;
userName.value = userinfo['name'] ?? ''; groupName.value = user.grp;
groupName.value = userinfo['grp'] ?? ''; isAdmin.value = user.isAdmin;
isAdmin.value = userinfo['is_admin'] == true;
} }
Future<void> _updateOtherModels() async { Future<void> _updateOtherModels() async {
@ -85,7 +86,7 @@ class UserModel {
'id': await bind.mainGetMyId(), 'id': await bind.mainGetMyId(),
'uuid': await bind.mainGetUuid(), 'uuid': await bind.mainGetUuid(),
}, },
headers: await getHttpHeaders()) headers: getHttpHeaders())
.timeout(Duration(seconds: 2)); .timeout(Duration(seconds: 2));
} catch (e) { } catch (e) {
print("request /api/logout failed: err=$e"); print("request /api/logout failed: err=$e");
@ -95,26 +96,37 @@ class UserModel {
} }
} }
Future<Map<String, dynamic>> login(String userName, String pass) async { /// throw [RequestException]
Future<LoginResponse> login(LoginRequest loginRequest) async {
final url = await bind.mainGetApiServer(); 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<String, dynamic> body;
try { try {
final resp = await http.post(Uri.parse('$url/api/login'), body = jsonDecode(resp.body);
headers: {'Content-Type': 'application/json'}, } catch (e) {
body: jsonEncode({ print("jsonDecode resp body failed: ${e.toString()}");
'username': userName, rethrow;
'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();
} }
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;
} }
} }

View File

@ -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"), ("Always connect via relay", "Connecta sempre a través de relay"),
("whitelist_tip", ""), ("whitelist_tip", ""),
("Login", "Inicia sessió"), ("Login", "Inicia sessió"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Sortir"), ("Logout", "Sortir"),
("Tags", ""), ("Tags", ""),
("Search ID", "Cerca ID"), ("Search ID", "Cerca ID"),

View File

@ -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", "只有白名单里的ip才能访问我"), ("whitelist_tip", "只有白名单里的ip才能访问我"),
("Login", "登录"), ("Login", "登录"),
("Verify", "验证"),
("Remember me", "记住我"),
("Trust this device", "信任此设备"),
("Verification code", "验证码"),
("verification_tip", "检测到新设备登录,已向注册邮箱发送了登录验证码,输入验证码继续登录"),
("Logout", "登出"), ("Logout", "登出"),
("Tags", "标签"), ("Tags", "标签"),
("Search ID", "查找ID"), ("Search ID", "查找ID"),
@ -221,7 +226,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Network error", "网络错误"), ("Network error", "网络错误"),
("Username missed", "用户名没有填写"), ("Username missed", "用户名没有填写"),
("Password missed", "密码没有填写"), ("Password missed", "密码没有填写"),
("Wrong credentials", "用户名或者密码错误"), ("Wrong credentials", "提供的登入信息错误"),
("Edit Tag", "修改标签"), ("Edit Tag", "修改标签"),
("Unremember Password", "忘掉密码"), ("Unremember Password", "忘掉密码"),
("Favorites", "收藏"), ("Favorites", "收藏"),
@ -273,7 +278,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Do you accept?", "是否接受?"), ("Do you accept?", "是否接受?"),
("Open System Setting", "打开系统设置"), ("Open System Setting", "打开系统设置"),
("How to get Android input permission?", "如何获取安卓的输入权限?"), ("How to get Android input permission?", "如何获取安卓的输入权限?"),
("android_input_permission_tip1", "為了讓遠程設備通過鼠標或者觸屏控制您的安卓設備,你需要允許 RustDesk 使用\"無障礙\"服務"), ("android_input_permission_tip1", "为了让远程设备通过鼠标或触屏控制您的安卓设备你需要允許RustDesk使用\"无障碍\"服务"),
("android_input_permission_tip2", "请在接下来的系统设置页面里,找到并进入 [已安装的服务] 页面,将 [RustDesk Input] 服务开启。"), ("android_input_permission_tip2", "请在接下来的系统设置页面里,找到并进入 [已安装的服务] 页面,将 [RustDesk Input] 服务开启。"),
("android_new_connection_tip", "收到新的连接控制请求,对方想要控制你当前的设备。"), ("android_new_connection_tip", "收到新的连接控制请求,对方想要控制你当前的设备。"),
("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"), ("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"),

View File

@ -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)"), ("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"), ("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"), ("Login", "Přihlásit se"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odhlásit se"), ("Logout", "Odhlásit se"),
("Tags", "Štítky"), ("Tags", "Štítky"),
("Search ID", "Hledat identifikátor"), ("Search ID", "Hledat identifikátor"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Forbindelse via relæ-server"), ("Always connect via relay", "Forbindelse via relæ-server"),
("whitelist_tip", "Kun IP'er på udgivelseslisten kan få adgang til mig"), ("whitelist_tip", "Kun IP'er på udgivelseslisten kan få adgang til mig"),
("Login", "Login"), ("Login", "Login"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "logger af"), ("Logout", "logger af"),
("Tags", "Nøgleord"), ("Tags", "Nøgleord"),
("Search ID", "Søg ID"), ("Search ID", "Søg ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Immer über Relay-Server verbinden"), ("Always connect via relay", "Immer über Relay-Server verbinden"),
("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."), ("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."),
("Login", "Anmelden"), ("Login", "Anmelden"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Abmelden"), ("Logout", "Abmelden"),
("Tags", "Schlagworte"), ("Tags", "Schlagworte"),
("Search ID", "Suche ID"), ("Search ID", "Suche ID"),

View File

@ -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"), ("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."), ("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!"), ("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."), ("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."), ("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."),
].iter().cloned().collect(); ].iter().cloned().collect();

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Ĉiam konekti per relajso"), ("Always connect via relay", "Ĉiam konekti per relajso"),
("whitelist_tip", "Nur la IP en la blanka listo povas kontroli mian komputilon"), ("whitelist_tip", "Nur la IP en la blanka listo povas kontroli mian komputilon"),
("Login", "Konekti"), ("Login", "Konekti"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Malkonekti"), ("Logout", "Malkonekti"),
("Tags", "Etikedi"), ("Tags", "Etikedi"),
("Search ID", "Serĉi ID"), ("Search ID", "Serĉi ID"),

View File

@ -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"), ("Always connect via relay", "Conéctese siempre a través de relay"),
("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"), ("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"),
("Login", "Iniciar sesión"), ("Login", "Iniciar sesión"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Salir"), ("Logout", "Salir"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Buscar ID"), ("Search ID", "Buscar ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "برای اتصال استفاده شود Relay از"), ("Always connect via relay", "برای اتصال استفاده شود Relay از"),
("whitelist_tip", "های مجاز می توانند به این دسکتاپ متصل شوند IP فقط"), ("whitelist_tip", "های مجاز می توانند به این دسکتاپ متصل شوند IP فقط"),
("Login", "ورود"), ("Login", "ورود"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "خروج"), ("Logout", "خروج"),
("Tags", "برچسب ها"), ("Tags", "برچسب ها"),
("Search ID", "جستجوی شناسه"), ("Search ID", "جستجوی شناسه"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Forcer la connexion relais"), ("Always connect via relay", "Forcer la connexion relais"),
("whitelist_tip", "Seul l'IP dans la liste blanche peut accéder à mon appareil"), ("whitelist_tip", "Seul l'IP dans la liste blanche peut accéder à mon appareil"),
("Login", "Connexion"), ("Login", "Connexion"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Déconnexion"), ("Logout", "Déconnexion"),
("Tags", "Étiqueter"), ("Tags", "Étiqueter"),
("Search ID", "Rechercher un ID"), ("Search ID", "Rechercher un ID"),

View File

@ -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", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"), ("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"),
("Login", "Σύνδεση"), ("Login", "Σύνδεση"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Αποσύνδεση"), ("Logout", "Αποσύνδεση"),
("Tags", "Ετικέτες"), ("Tags", "Ετικέτες"),
("Search ID", "Αναζήτηση ID"), ("Search ID", "Αναζήτηση ID"),

View File

@ -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"), ("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"), ("whitelist_tip", "Csak az engedélyezési listán szereplő címek csatlakozhatnak"),
("Login", "Belépés"), ("Login", "Belépés"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Kilépés"), ("Logout", "Kilépés"),
("Tags", "Tagok"), ("Tags", "Tagok"),
("Search ID", "Azonosító keresése..."), ("Search ID", "Azonosító keresése..."),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Selalu terhubung melalui relai"), ("Always connect via relay", "Selalu terhubung melalui relai"),
("whitelist_tip", "Hanya whitelisted IP yang dapat mengakses saya"), ("whitelist_tip", "Hanya whitelisted IP yang dapat mengakses saya"),
("Login", "Masuk"), ("Login", "Masuk"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Keluar"), ("Logout", "Keluar"),
("Tags", "Tag"), ("Tags", "Tag"),
("Search ID", "Cari ID"), ("Search ID", "Cari ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Connetti sempre tramite relay"), ("Always connect via relay", "Connetti sempre tramite relay"),
("whitelist_tip", "Solo gli indirizzi IP autorizzati possono connettersi a questo desktop"), ("whitelist_tip", "Solo gli indirizzi IP autorizzati possono connettersi a questo desktop"),
("Login", "Accedi"), ("Login", "Accedi"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Esci"), ("Logout", "Esci"),
("Tags", "Tag"), ("Tags", "Tag"),
("Search ID", "Cerca ID"), ("Search ID", "Cerca ID"),

View File

@ -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", "ホワイトリストに登録されたIPからのみ接続を許可します"), ("whitelist_tip", "ホワイトリストに登録されたIPからのみ接続を許可します"),
("Login", "ログイン"), ("Login", "ログイン"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "ログアウト"), ("Logout", "ログアウト"),
("Tags", "タグ"), ("Tags", "タグ"),
("Search ID", "IDを検索"), ("Search ID", "IDを検索"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "항상 relay를 통해 접속하기"), ("Always connect via relay", "항상 relay를 통해 접속하기"),
("whitelist_tip", "화이트리스트에 있는 IP만 현 데스크탑에 접속 가능합니다"), ("whitelist_tip", "화이트리스트에 있는 IP만 현 데스크탑에 접속 가능합니다"),
("Login", "로그인"), ("Login", "로그인"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "로그아웃"), ("Logout", "로그아웃"),
("Tags", "태그"), ("Tags", "태그"),
("Search ID", "ID 검색"), ("Search ID", "ID 검색"),

View File

@ -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", "Маған тек ақ-тізімделген IP қол жеткізе алады"), ("whitelist_tip", "Маған тек ақ-тізімделген IP қол жеткізе алады"),
("Login", "Кіру"), ("Login", "Кіру"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Шығу"), ("Logout", "Шығу"),
("Tags", "Тақтар"), ("Tags", "Тақтар"),
("Search ID", "ID Іздеу"), ("Search ID", "ID Іздеу"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Zawsze łącz pośrednio"), ("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"), ("whitelist_tip", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
("Login", "Zaloguj"), ("Login", "Zaloguj"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Wyloguj"), ("Logout", "Wyloguj"),
("Tags", "Tagi"), ("Tags", "Tagi"),
("Search ID", "Szukaj ID"), ("Search ID", "Szukaj ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Sempre conectar via relay"), ("Always connect via relay", "Sempre conectar via relay"),
("whitelist_tip", "Somente IPs na whitelist podem me acessar"), ("whitelist_tip", "Somente IPs na whitelist podem me acessar"),
("Login", "Login"), ("Login", "Login"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Sair"), ("Logout", "Sair"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Procurar ID"), ("Search ID", "Procurar ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Sempre conectar via relay"), ("Always connect via relay", "Sempre conectar via relay"),
("whitelist_tip", "Somente IPs confiáveis podem me acessar"), ("whitelist_tip", "Somente IPs confiáveis podem me acessar"),
("Login", "Login"), ("Login", "Login"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Sair"), ("Logout", "Sair"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Pesquisar ID"), ("Search ID", "Pesquisar ID"),

View File

@ -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", "Только IP-адреса из белого списка могут получить доступ ко мне"), ("whitelist_tip", "Только IP-адреса из белого списка могут получить доступ ко мне"),
("Login", "Войти"), ("Login", "Войти"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Выйти"), ("Logout", "Выйти"),
("Tags", "Метки"), ("Tags", "Метки"),
("Search ID", "Поиск по ID"), ("Search ID", "Поиск по ID"),

View File

@ -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"), ("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"), ("whitelist_tip", "Len vymenované IP adresy majú oprávnenie sa pripojiť k vzdialenej správe"),
("Login", "Prihlásenie"), ("Login", "Prihlásenie"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odhlásenie"), ("Logout", "Odhlásenie"),
("Tags", "Štítky"), ("Tags", "Štítky"),
("Search ID", "Hľadať ID"), ("Search ID", "Hľadať ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Vedno poveži preko posrednika"), ("Always connect via relay", "Vedno poveži preko posrednika"),
("whitelist_tip", "Dostop je možen samo iz dovoljenih IPjev"), ("whitelist_tip", "Dostop je možen samo iz dovoljenih IPjev"),
("Login", "Prijavi"), ("Login", "Prijavi"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odjavi"), ("Logout", "Odjavi"),
("Tags", "Oznake"), ("Tags", "Oznake"),
("Search ID", "Išči ID"), ("Search ID", "Išči ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Gjithmonë lidheni me transmetues"), ("Always connect via relay", "Gjithmonë lidheni me transmetues"),
("whitelist_tip", "Vetëm IP e listës së bardhë mund të më aksesoj."), ("whitelist_tip", "Vetëm IP e listës së bardhë mund të më aksesoj."),
("Login", "Hyrje"), ("Login", "Hyrje"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Dalje"), ("Logout", "Dalje"),
("Tags", "Tage"), ("Tags", "Tage"),
("Search ID", "Kerko ID"), ("Search ID", "Kerko ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Uvek se spoj preko posrednika"), ("Always connect via relay", "Uvek se spoj preko posrednika"),
("whitelist_tip", "Samo dozvoljene IP mi mogu pristupiti"), ("whitelist_tip", "Samo dozvoljene IP mi mogu pristupiti"),
("Login", "Prijava"), ("Login", "Prijava"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odjava"), ("Logout", "Odjava"),
("Tags", "Oznake"), ("Tags", "Oznake"),
("Search ID", "Traži ID"), ("Search ID", "Traži ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Anslut alltid via relay"), ("Always connect via relay", "Anslut alltid via relay"),
("whitelist_tip", "Bara vitlistade IPs kan koppla upp till mig"), ("whitelist_tip", "Bara vitlistade IPs kan koppla upp till mig"),
("Login", "Logga in"), ("Login", "Logga in"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Logga ut"), ("Logout", "Logga ut"),
("Tags", "Taggar"), ("Tags", "Taggar"),
("Search ID", "Sök ID"), ("Search ID", "Sök ID"),

View File

@ -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", ""), ("whitelist_tip", ""),
("Login", ""), ("Login", ""),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", ""), ("Logout", ""),
("Tags", ""), ("Tags", ""),
("Search ID", ""), ("Search ID", ""),

View File

@ -1,413 +1,417 @@
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> = pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[ [
("Status", "สถานะ"), ("Status", "สถานะ"),
("Your Desktop", "หน้าจอของคุณ"), ("Your Desktop", "หน้าจอของคุณ"),
("desk_tip", "คุณสามารถเข้าถึงเดสก์ท็อปของคุณได้ด้วย ID และรหัสผ่านต่อไปนี้"), ("desk_tip", "คุณสามารถเข้าถึงเดสก์ท็อปของคุณได้ด้วย ID และรหัสผ่านต่อไปนี้"),
("Password", "รหัสผ่าน"), ("Password", "รหัสผ่าน"),
("Ready", "พร้อม"), ("Ready", "พร้อม"),
("Established", "เชื่อมต่อแล้ว"), ("Established", "เชื่อมต่อแล้ว"),
("connecting_status", "กำลังเชื่อมต่อไปยังเครือข่าย RustDesk..."), ("connecting_status", "กำลังเชื่อมต่อไปยังเครือข่าย RustDesk..."),
("Enable Service", "เปิดใช้การงานเซอร์วิส"), ("Enable Service", "เปิดใช้การงานเซอร์วิส"),
("Start Service", "เริ่มต้นใช้งานเซอร์วิส"), ("Start Service", "เริ่มต้นใช้งานเซอร์วิส"),
("Service is running", "เซอร์วิสกำลังทำงาน"), ("Service is running", "เซอร์วิสกำลังทำงาน"),
("Service is not running", "เซอร์วิสไม่ทำงาน"), ("Service is not running", "เซอร์วิสไม่ทำงาน"),
("not_ready_status", "ไม่พร้อมใช้งาน กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"), ("not_ready_status", "ไม่พร้อมใช้งาน กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ"),
("Control Remote Desktop", "การควบคุมเดสก์ท็อปปลายทาง"), ("Control Remote Desktop", "การควบคุมเดสก์ท็อปปลายทาง"),
("Transfer File", "การถ่ายโอนไฟล์"), ("Transfer File", "การถ่ายโอนไฟล์"),
("Connect", "เชื่อมต่อ"), ("Connect", "เชื่อมต่อ"),
("Recent Sessions", "เซสชันล่าสุด"), ("Recent Sessions", "เซสชันล่าสุด"),
("Address Book", "สมุดรายชื่อ"), ("Address Book", "สมุดรายชื่อ"),
("Confirmation", "การยืนยัน"), ("Confirmation", "การยืนยัน"),
("TCP Tunneling", "อุโมงค์การเชื่อมต่อ TCP"), ("TCP Tunneling", "อุโมงค์การเชื่อมต่อ TCP"),
("Remove", "ลบ"), ("Remove", "ลบ"),
("Refresh random password", "รีเฟรชรหัสผ่านใหม่แบบสุ่ม"), ("Refresh random password", "รีเฟรชรหัสผ่านใหม่แบบสุ่ม"),
("Set your own password", "ตั้งรหัสผ่านของคุณเอง"), ("Set your own password", "ตั้งรหัสผ่านของคุณเอง"),
("Enable Keyboard/Mouse", "เปิดการใช้งาน คีย์บอร์ด/เมาส์"), ("Enable Keyboard/Mouse", "เปิดการใช้งาน คีย์บอร์ด/เมาส์"),
("Enable Clipboard", "เปิดการใช้งาน คลิปบอร์ด"), ("Enable Clipboard", "เปิดการใช้งาน คลิปบอร์ด"),
("Enable File Transfer", "เปิดการใช้งาน การถ่ายโอนไฟล์"), ("Enable File Transfer", "เปิดการใช้งาน การถ่ายโอนไฟล์"),
("Enable TCP Tunneling", "เปิดการใช้งาน อุโมงค์การเชื่อมต่อ TCP"), ("Enable TCP Tunneling", "เปิดการใช้งาน อุโมงค์การเชื่อมต่อ TCP"),
("IP Whitelisting", "IP ไวท์ลิสต์"), ("IP Whitelisting", "IP ไวท์ลิสต์"),
("ID/Relay Server", "เซิร์ฟเวอร์ ID/Relay"), ("ID/Relay Server", "เซิร์ฟเวอร์ ID/Relay"),
("Import Server Config", "นำเข้าการตั้งค่าเซิร์ฟเวอร์"), ("Import Server Config", "นำเข้าการตั้งค่าเซิร์ฟเวอร์"),
("Export Server Config", "ส่งออกการตั้งค่าเซิร์ฟเวอร์"), ("Export Server Config", "ส่งออกการตั้งค่าเซิร์ฟเวอร์"),
("Import server configuration successfully", "นำเข้าการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"), ("Import server configuration successfully", "นำเข้าการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"),
("Export server configuration successfully", "ส่งออกการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"), ("Export server configuration successfully", "ส่งออกการตั้งค่าเซิร์ฟเวอร์เสร็จสมบูรณ์"),
("Invalid server configuration", "การตั้งค่าของเซิร์ฟเวอร์ไม่ถูกต้อง"), ("Invalid server configuration", "การตั้งค่าของเซิร์ฟเวอร์ไม่ถูกต้อง"),
("Clipboard is empty", "คลิปบอร์ดว่างเปล่า"), ("Clipboard is empty", "คลิปบอร์ดว่างเปล่า"),
("Stop service", "หยุดการใช้งานเซอร์วิส"), ("Stop service", "หยุดการใช้งานเซอร์วิส"),
("Change ID", "เปลี่ยน ID"), ("Change ID", "เปลี่ยน ID"),
("Website", "เว็บไซต์"), ("Website", "เว็บไซต์"),
("About", "เกี่ยวกับ"), ("About", "เกี่ยวกับ"),
("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"), ("Slogan_tip", "ทำด้วยใจ ในโลกใบนี้ที่ยุ่งเหยิง!"),
("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"), ("Privacy Statement", "คำแถลงเกี่ยวกับความเป็นส่วนตัว"),
("Mute", "ปิดเสียง"), ("Mute", "ปิดเสียง"),
("Audio Input", "ออดิโออินพุท"), ("Audio Input", "ออดิโออินพุท"),
("Enhancements", "การปรับปรุง"), ("Enhancements", "การปรับปรุง"),
("Hardware Codec", "ฮาร์ดแวร์ codec"), ("Hardware Codec", "ฮาร์ดแวร์ codec"),
("Adaptive Bitrate", "บิทเรทผันแปร"), ("Adaptive Bitrate", "บิทเรทผันแปร"),
("ID Server", "เซิร์ฟเวอร์ ID"), ("ID Server", "เซิร์ฟเวอร์ ID"),
("Relay Server", "เซิร์ฟเวอร์ Relay"), ("Relay Server", "เซิร์ฟเวอร์ Relay"),
("API Server", "เซิร์ฟเวอร์ API"), ("API Server", "เซิร์ฟเวอร์ API"),
("invalid_http", "ต้องขึ้นต้นด้วย http:// หรือ https:// เท่านั้น"), ("invalid_http", "ต้องขึ้นต้นด้วย http:// หรือ https:// เท่านั้น"),
("Invalid IP", "IP ไม่ถูกต้อง"), ("Invalid IP", "IP ไม่ถูกต้อง"),
("id_change_tip", "อนุญาตเฉพาะตัวอักษร a-z A-Z 0-9 และ _ (ขีดล่าง) เท่านั้น โดยตัวอักษรขึ้นต้นจะต้องเป็น a-z หรือไม่ก็ A-Z และมีความยาวระหว่าง 6 ถึง 16 ตัวอักษร"), ("id_change_tip", "อนุญาตเฉพาะตัวอักษร a-z A-Z 0-9 และ _ (ขีดล่าง) เท่านั้น โดยตัวอักษรขึ้นต้นจะต้องเป็น a-z หรือไม่ก็ A-Z และมีความยาวระหว่าง 6 ถึง 16 ตัวอักษร"),
("Invalid format", "รูปแบบไม่ถูกต้อง"), ("Invalid format", "รูปแบบไม่ถูกต้อง"),
("server_not_support", "ยังไม่รองรับโดยเซิร์ฟเวอร์"), ("server_not_support", "ยังไม่รองรับโดยเซิร์ฟเวอร์"),
("Not available", "ไม่พร้อมใช้งาน"), ("Not available", "ไม่พร้อมใช้งาน"),
("Too frequent", "ดำเนินการถี่เกินไป"), ("Too frequent", "ดำเนินการถี่เกินไป"),
("Cancel", "ยกเลิก"), ("Cancel", "ยกเลิก"),
("Skip", "ข้าม"), ("Skip", "ข้าม"),
("Close", "ปิด"), ("Close", "ปิด"),
("Retry", "ลองใหม่อีกครั้ง"), ("Retry", "ลองใหม่อีกครั้ง"),
("OK", "ตกลง"), ("OK", "ตกลง"),
("Password Required", "ต้องใช้รหัสผ่าน"), ("Password Required", "ต้องใช้รหัสผ่าน"),
("Please enter your password", "กรุณาใส่รหัสผ่านของคุณ"), ("Please enter your password", "กรุณาใส่รหัสผ่านของคุณ"),
("Remember password", "จดจำรหัสผ่าน"), ("Remember password", "จดจำรหัสผ่าน"),
("Wrong Password", "รหัสผ่านไม่ถูกต้อง"), ("Wrong Password", "รหัสผ่านไม่ถูกต้อง"),
("Do you want to enter again?", "ต้องการใส่ข้อมูลอีกครั้งหรือไม่?"), ("Do you want to enter again?", "ต้องการใส่ข้อมูลอีกครั้งหรือไม่?"),
("Connection Error", "การเชื่อมต่อผิดพลาด"), ("Connection Error", "การเชื่อมต่อผิดพลาด"),
("Error", "ข้อผิดพลาด"), ("Error", "ข้อผิดพลาด"),
("Reset by the peer", "รีเซ็ตโดยอีกฝั่ง"), ("Reset by the peer", "รีเซ็ตโดยอีกฝั่ง"),
("Connecting...", "กำลังเชื่อมต่อ..."), ("Connecting...", "กำลังเชื่อมต่อ..."),
("Connection in progress. Please wait.", "กำลังดำเนินการเชื่อมต่อ กรุณารอซักครู่"), ("Connection in progress. Please wait.", "กำลังดำเนินการเชื่อมต่อ กรุณารอซักครู่"),
("Please try 1 minute later", "กรุณาลองใหม่อีกครั้งใน 1 นาที"), ("Please try 1 minute later", "กรุณาลองใหม่อีกครั้งใน 1 นาที"),
("Login Error", "การเข้าสู่ระบบผิดพลาด"), ("Login Error", "การเข้าสู่ระบบผิดพลาด"),
("Successful", "สำเร็จ"), ("Successful", "สำเร็จ"),
("Connected, waiting for image...", "เชื่อมต่อสำเร็จ กำลังรับข้อมูลภาพ..."), ("Connected, waiting for image...", "เชื่อมต่อสำเร็จ กำลังรับข้อมูลภาพ..."),
("Name", "ชื่อ"), ("Name", "ชื่อ"),
("Type", "ประเภท"), ("Type", "ประเภท"),
("Modified", "แก้ไขล่าสุด"), ("Modified", "แก้ไขล่าสุด"),
("Size", "ขนาด"), ("Size", "ขนาด"),
("Show Hidden Files", "แสดงไฟล์ที่ถูกซ่อน"), ("Show Hidden Files", "แสดงไฟล์ที่ถูกซ่อน"),
("Receive", "รับ"), ("Receive", "รับ"),
("Send", "ส่ง"), ("Send", "ส่ง"),
("Refresh File", "รีเฟรชไฟล์"), ("Refresh File", "รีเฟรชไฟล์"),
("Local", "ต้นทาง"), ("Local", "ต้นทาง"),
("Remote", "ปลายทาง"), ("Remote", "ปลายทาง"),
("Remote Computer", "คอมพิวเตอร์ปลายทาง"), ("Remote Computer", "คอมพิวเตอร์ปลายทาง"),
("Local Computer", "คอมพิวเตอร์ต้นทาง"), ("Local Computer", "คอมพิวเตอร์ต้นทาง"),
("Confirm Delete", "ยืนยันการลบ"), ("Confirm Delete", "ยืนยันการลบ"),
("Delete", "ลบ"), ("Delete", "ลบ"),
("Properties", "ข้อมูล"), ("Properties", "ข้อมูล"),
("Multi Select", "เลือกหลายรายการ"), ("Multi Select", "เลือกหลายรายการ"),
("Select All", "เลือกทั้งหมด"), ("Select All", "เลือกทั้งหมด"),
("Unselect All", "ยกเลิกการเลือกทั้งหมด"), ("Unselect All", "ยกเลิกการเลือกทั้งหมด"),
("Empty Directory", "ไดเรกทอรีว่างเปล่า"), ("Empty Directory", "ไดเรกทอรีว่างเปล่า"),
("Not an empty directory", "ไม่ใช่ไดเรกทอรีว่างเปล่า"), ("Not an empty directory", "ไม่ใช่ไดเรกทอรีว่างเปล่า"),
("Are you sure you want to delete this file?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์นี้?"), ("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 this empty directory?", "คุณแน่ใจหรือไม่ที่จะลบไดเรอทอรีว่างเปล่านี้?"),
("Are you sure you want to delete the file of this directory?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์ของไดเรกทอรีนี้?"), ("Are you sure you want to delete the file of this directory?", "คุณแน่ใจหรือไม่ที่จะลบไฟล์ของไดเรกทอรีนี้?"),
("Do this for all conflicts", "ดำเนินการแบบเดียวกันสำหรับรายการทั้งหมด"), ("Do this for all conflicts", "ดำเนินการแบบเดียวกันสำหรับรายการทั้งหมด"),
("This is irreversible!", "การดำเนินการนี้ไม่สามารถย้อนกลับได้!"), ("This is irreversible!", "การดำเนินการนี้ไม่สามารถย้อนกลับได้!"),
("Deleting", "กำลังลบ"), ("Deleting", "กำลังลบ"),
("files", "ไฟล์"), ("files", "ไฟล์"),
("Waiting", "กำลังรอ"), ("Waiting", "กำลังรอ"),
("Finished", "เสร็จแล้ว"), ("Finished", "เสร็จแล้ว"),
("Speed", "ความเร็ว"), ("Speed", "ความเร็ว"),
("Custom Image Quality", "คุณภาพของภาพแบบกำหนดเอง"), ("Custom Image Quality", "คุณภาพของภาพแบบกำหนดเอง"),
("Privacy mode", "โหมดความเป็นส่วนตัว"), ("Privacy mode", "โหมดความเป็นส่วนตัว"),
("Block user input", "บล็อคอินพุทจากผู้ใช้งาน"), ("Block user input", "บล็อคอินพุทจากผู้ใช้งาน"),
("Unblock user input", "ยกเลิกการบล็อคอินพุทจากผู้ใช้งาน"), ("Unblock user input", "ยกเลิกการบล็อคอินพุทจากผู้ใช้งาน"),
("Adjust Window", "ปรับขนาดหน้าต่าง"), ("Adjust Window", "ปรับขนาดหน้าต่าง"),
("Original", "ต้นฉบับ"), ("Original", "ต้นฉบับ"),
("Shrink", "ย่อ"), ("Shrink", "ย่อ"),
("Stretch", "ยืด"), ("Stretch", "ยืด"),
("Scrollbar", "แถบเลื่อน"), ("Scrollbar", "แถบเลื่อน"),
("ScrollAuto", "เลื่อนอัตโนมัติ"), ("ScrollAuto", "เลื่อนอัตโนมัติ"),
("Good image quality", "ภาพคุณภาพดี"), ("Good image quality", "ภาพคุณภาพดี"),
("Balanced", "สมดุล"), ("Balanced", "สมดุล"),
("Optimize reaction time", "เน้นการตอบสนอง"), ("Optimize reaction time", "เน้นการตอบสนอง"),
("Custom", "กำหนดเอง"), ("Custom", "กำหนดเอง"),
("Show remote cursor", "แสดงเคอร์เซอร์ปลายทาง"), ("Show remote cursor", "แสดงเคอร์เซอร์ปลายทาง"),
("Show quality monitor", "แสดงคุณภาพหน้าจอ"), ("Show quality monitor", "แสดงคุณภาพหน้าจอ"),
("Disable clipboard", "ปิดการใช้งานคลิปบอร์ด"), ("Disable clipboard", "ปิดการใช้งานคลิปบอร์ด"),
("Lock after session end", "ล็อคหลังจากจบเซสชัน"), ("Lock after session end", "ล็อคหลังจากจบเซสชัน"),
("Insert", "แทรก"), ("Insert", "แทรก"),
("Insert Lock", "แทรกล็อค"), ("Insert Lock", "แทรกล็อค"),
("Refresh", "รีเฟรช"), ("Refresh", "รีเฟรช"),
("ID does not exist", "ไม่พอข้อมูล ID"), ("ID does not exist", "ไม่พอข้อมูล ID"),
("Failed to connect to rendezvous server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์นัดพบล้มเหลว"), ("Failed to connect to rendezvous server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์นัดพบล้มเหลว"),
("Please try later", "กรุณาลองใหม่ในภายหลัง"), ("Please try later", "กรุณาลองใหม่ในภายหลัง"),
("Remote desktop is offline", "เดสก์ท็อปปลายทางออฟไลน์"), ("Remote desktop is offline", "เดสก์ท็อปปลายทางออฟไลน์"),
("Key mismatch", "คีย์ไม่ถูกต้อง"), ("Key mismatch", "คีย์ไม่ถูกต้อง"),
("Timeout", "หมดเวลา"), ("Timeout", "หมดเวลา"),
("Failed to connect to relay server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์รีเลย์ล้มเหลว"), ("Failed to connect to relay server", "การเชื่อมต่อไปยังเซิร์ฟเวอร์รีเลย์ล้มเหลว"),
("Failed to connect via rendezvous server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์นัดพบล้มเหลว"), ("Failed to connect via rendezvous server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์นัดพบล้มเหลว"),
("Failed to connect via relay server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์รีเลย์ล้มเหลว"), ("Failed to connect via relay server", "การเชื่อมต่อผ่านเซิร์ฟเวอร์รีเลย์ล้มเหลว"),
("Failed to make direct connection to remote desktop", "การเชื่อมต่อตรงไปยังเดสก์ท็อปปลายทางล้มเหลว"), ("Failed to make direct connection to remote desktop", "การเชื่อมต่อตรงไปยังเดสก์ท็อปปลายทางล้มเหลว"),
("Set Password", "ตั้งรหัสผ่าน"), ("Set Password", "ตั้งรหัสผ่าน"),
("OS Password", "รหัสผ่านระบบปฏิบัติการ"), ("OS Password", "รหัสผ่านระบบปฏิบัติการ"),
("install_tip", "เนื่องด้วยข้อจำกัดของการใช้งาน UAC ทำให้ RustDesk ไม่สามารถทำงานได้ปกติในฝั่งปลายทางในบางครั้ง เพื่อหลีกเลี่ยงข้อจำกัดของ UAC กรุณากดปุ่มด้านล่างเพื่อติดตั้ง RustDesk ไปยังระบบของคุณ"), ("install_tip", "เนื่องด้วยข้อจำกัดของการใช้งาน UAC ทำให้ RustDesk ไม่สามารถทำงานได้ปกติในฝั่งปลายทางในบางครั้ง เพื่อหลีกเลี่ยงข้อจำกัดของ UAC กรุณากดปุ่มด้านล่างเพื่อติดตั้ง RustDesk ไปยังระบบของคุณ"),
("Click to upgrade", "คลิกเพื่ออัปเกรด"), ("Click to upgrade", "คลิกเพื่ออัปเกรด"),
("Click to download", "คลิกเพื่อดาวน์โหลด"), ("Click to download", "คลิกเพื่อดาวน์โหลด"),
("Click to update", "คลิกเพื่ออัปเดต"), ("Click to update", "คลิกเพื่ออัปเดต"),
("Configure", "ปรับแต่งค่า"), ("Configure", "ปรับแต่งค่า"),
("config_acc", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่ RustDesk"), ("config_acc", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่ RustDesk"),
("config_screen", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การบันทึกภาพหน้าจอ\" ให้แก่ RustDesk"), ("config_screen", "เพื่อที่จะควบคุมเดสก์ท็อปปลายทางของคุณ คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การบันทึกภาพหน้าจอ\" ให้แก่ RustDesk"),
("Installing ...", "กำลังติดตั้ง ..."), ("Installing ...", "กำลังติดตั้ง ..."),
("Install", "ติดตั้ง"), ("Install", "ติดตั้ง"),
("Installation", "การติดตั้ง"), ("Installation", "การติดตั้ง"),
("Installation Path", "ตำแหน่งที่ติดตั้ง"), ("Installation Path", "ตำแหน่งที่ติดตั้ง"),
("Create start menu shortcuts", "สร้างทางลัดไปยัง Start Menu"), ("Create start menu shortcuts", "สร้างทางลัดไปยัง Start Menu"),
("Create desktop icon", "สร้างไอคอนบนเดสก์ท็อป"), ("Create desktop icon", "สร้างไอคอนบนเดสก์ท็อป"),
("agreement_tip", "ในการเริ่มต้นการติดตั้ง ถือว่าคุณได้ยอมรับข้อตกลงใบอนุญาตแล้ว"), ("agreement_tip", "ในการเริ่มต้นการติดตั้ง ถือว่าคุณได้ยอมรับข้อตกลงใบอนุญาตแล้ว"),
("Accept and Install", "ยอมรับและติดตั้ง"), ("Accept and Install", "ยอมรับและติดตั้ง"),
("End-user license agreement", "ข้อตกลงใบอนุญาตผู้ใช้งาน"), ("End-user license agreement", "ข้อตกลงใบอนุญาตผู้ใช้งาน"),
("Generating ...", "กำลังสร้าง ..."), ("Generating ...", "กำลังสร้าง ..."),
("Your installation is lower version.", "การติดตั้งของคุณเป็นเวอร์ชั่นที่ต่ำกว่า"), ("Your installation is lower version.", "การติดตั้งของคุณเป็นเวอร์ชั่นที่ต่ำกว่า"),
("not_close_tcp_tip", "อย่าปิดหน้าต่างนี้ในขณะที่คุณกำลังใช้งานอุโมงค์การเชื่อมต่อ"), ("not_close_tcp_tip", "อย่าปิดหน้าต่างนี้ในขณะที่คุณกำลังใช้งานอุโมงค์การเชื่อมต่อ"),
("Listening ...", "กำลังรอรับข้อมูล ..."), ("Listening ...", "กำลังรอรับข้อมูล ..."),
("Remote Host", "โฮสต์ปลายทาง"), ("Remote Host", "โฮสต์ปลายทาง"),
("Remote Port", "พอร์ทปลายทาง"), ("Remote Port", "พอร์ทปลายทาง"),
("Action", "การดำเนินการ"), ("Action", "การดำเนินการ"),
("Add", "เพิ่ม"), ("Add", "เพิ่ม"),
("Local Port", "พอร์ทต้นทาง"), ("Local Port", "พอร์ทต้นทาง"),
("Local Address", "ที่อยู่ต้นทาง"), ("Local Address", "ที่อยู่ต้นทาง"),
("Change Local Port", "เปลี่ยนพอร์ทต้นทาง"), ("Change Local Port", "เปลี่ยนพอร์ทต้นทาง"),
("setup_server_tip", "เพื่อการเชื่อมต่อที่เร็วขึ้น กรุณาเซ็ตอัปเซิร์ฟเวอร์ของคุณเอง"), ("setup_server_tip", "เพื่อการเชื่อมต่อที่เร็วขึ้น กรุณาเซ็ตอัปเซิร์ฟเวอร์ของคุณเอง"),
("Too short, at least 6 characters.", "สั้นเกินไป ต้องไม่ต่ำกว่า 6 ตัวอักษร"), ("Too short, at least 6 characters.", "สั้นเกินไป ต้องไม่ต่ำกว่า 6 ตัวอักษร"),
("The confirmation is not identical.", "การยืนยันข้อมูลไม่ถูกต้อง"), ("The confirmation is not identical.", "การยืนยันข้อมูลไม่ถูกต้อง"),
("Permissions", "สิทธิ์การใช้งาน"), ("Permissions", "สิทธิ์การใช้งาน"),
("Accept", "ยอมรับ"), ("Accept", "ยอมรับ"),
("Dismiss", "ปิด"), ("Dismiss", "ปิด"),
("Disconnect", "ยกเลิกการเชื่อมต่อ"), ("Disconnect", "ยกเลิกการเชื่อมต่อ"),
("Allow using keyboard and mouse", "อนุญาตให้ใช้งานคีย์บอร์ดและเมาส์"), ("Allow using keyboard and mouse", "อนุญาตให้ใช้งานคีย์บอร์ดและเมาส์"),
("Allow using clipboard", "อนุญาตให้ใช้คลิปบอร์ด"), ("Allow using clipboard", "อนุญาตให้ใช้คลิปบอร์ด"),
("Allow hearing sound", "อนุญาตให้ได้ยินเสียง"), ("Allow hearing sound", "อนุญาตให้ได้ยินเสียง"),
("Allow file copy and paste", "อนุญาตให้มีการคัดลอกและวางไฟล์"), ("Allow file copy and paste", "อนุญาตให้มีการคัดลอกและวางไฟล์"),
("Connected", "เชื่อมต่อแล้ว"), ("Connected", "เชื่อมต่อแล้ว"),
("Direct and encrypted connection", "การเชื่อมต่อตรงที่มีการเข้ารหัส"), ("Direct and encrypted connection", "การเชื่อมต่อตรงที่มีการเข้ารหัส"),
("Relayed and encrypted connection", "การเชื่อมต่อแบบรีเลย์ที่มีการเข้ารหัส"), ("Relayed and encrypted connection", "การเชื่อมต่อแบบรีเลย์ที่มีการเข้ารหัส"),
("Direct and unencrypted connection", "การเชื่อมต่อตรงที่ไม่มีการเข้ารหัส"), ("Direct and unencrypted connection", "การเชื่อมต่อตรงที่ไม่มีการเข้ารหัส"),
("Relayed and unencrypted connection", "การเชื่อมต่อแบบรีเลย์ที่ไม่มีการเข้ารหัส"), ("Relayed and unencrypted connection", "การเชื่อมต่อแบบรีเลย์ที่ไม่มีการเข้ารหัส"),
("Enter Remote ID", "กรอก ID ปลายทาง"), ("Enter Remote ID", "กรอก ID ปลายทาง"),
("Enter your password", "กรอกรหัสผ่าน"), ("Enter your password", "กรอกรหัสผ่าน"),
("Logging in...", "กำลังเข้าสู่ระบบ..."), ("Logging in...", "กำลังเข้าสู่ระบบ..."),
("Enable RDP session sharing", "เปิดการใช้งานการแชร์เซสชัน RDP"), ("Enable RDP session sharing", "เปิดการใช้งานการแชร์เซสชัน RDP"),
("Auto Login", "เข้าสู่ระบอัตโนมัติ"), ("Auto Login", "เข้าสู่ระบอัตโนมัติ"),
("Enable Direct IP Access", "เปิดการใช้งาน IP ตรง"), ("Enable Direct IP Access", "เปิดการใช้งาน IP ตรง"),
("Rename", "ปลายทาง"), ("Rename", "ปลายทาง"),
("Space", "พื้นที่ว่าง"), ("Space", "พื้นที่ว่าง"),
("Create Desktop Shortcut", "สร้างทางลัดบนเดสก์ท็อป"), ("Create Desktop Shortcut", "สร้างทางลัดบนเดสก์ท็อป"),
("Change Path", "เปลี่ยนตำแหน่ง"), ("Change Path", "เปลี่ยนตำแหน่ง"),
("Create Folder", "สร้างโฟลเดอร์"), ("Create Folder", "สร้างโฟลเดอร์"),
("Please enter the folder name", "กรุณาใส่ชื่อโฟลเดอร์"), ("Please enter the folder name", "กรุณาใส่ชื่อโฟลเดอร์"),
("Fix it", "แก้ไข"), ("Fix it", "แก้ไข"),
("Warning", "คำเตือน"), ("Warning", "คำเตือน"),
("Login screen using Wayland is not supported", "หน้าจอการเข้าสู่ระบบโดยใช้ Wayland ยังไม่ถูกรองรับ"), ("Login screen using Wayland is not supported", "หน้าจอการเข้าสู่ระบบโดยใช้ Wayland ยังไม่ถูกรองรับ"),
("Reboot required", "จำเป็นต้องเริ่มต้นระบบใหม่"), ("Reboot required", "จำเป็นต้องเริ่มต้นระบบใหม่"),
("Unsupported display server ", "เซิร์ฟเวอร์การแสดงผลที่ไม่รองรับ"), ("Unsupported display server ", "เซิร์ฟเวอร์การแสดงผลที่ไม่รองรับ"),
("x11 expected", "ต้องใช้งาน x11"), ("x11 expected", "ต้องใช้งาน x11"),
("Port", "พอร์ท"), ("Port", "พอร์ท"),
("Settings", "ตั้งค่า"), ("Settings", "ตั้งค่า"),
("Username", "ชื่อผู้ใช้งาน"), ("Username", "ชื่อผู้ใช้งาน"),
("Invalid port", "พอร์ทไม่ถูกต้อง"), ("Invalid port", "พอร์ทไม่ถูกต้อง"),
("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งการการเชื่อมต่อ"), ("Closed manually by the peer", "ถูกปิดโดยอีกฝั่งการการเชื่อมต่อ"),
("Enable remote configuration modification", "เปิดการใช้งานการแก้ไขการตั้งค่าปลายทาง"), ("Enable remote configuration modification", "เปิดการใช้งานการแก้ไขการตั้งค่าปลายทาง"),
("Run without install", "ใช้งานโดยไม่ต้องติดตั้ง"), ("Run without install", "ใช้งานโดยไม่ต้องติดตั้ง"),
("Always connected via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"), ("Always connected via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"),
("Always connect via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"), ("Always connect via relay", "เชื่อมต่อผ่านรีเลย์เสมอ"),
("whitelist_tip", "อนุญาตเฉพาะการเชื่อมต่อจาก IP ที่ไวท์ลิสต์"), ("whitelist_tip", "อนุญาตเฉพาะการเชื่อมต่อจาก IP ที่ไวท์ลิสต์"),
("Login", "เข้าสู่ระบบ"), ("Login", "เข้าสู่ระบบ"),
("Logout", "ออกจากระบบ"), ("Verify", ""),
("Tags", "แท็ก"), ("Remember me", ""),
("Search ID", "ค้นหา ID"), ("Trust this device", ""),
("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"), ("Verification code", ""),
("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"), ("verification_tip", ""),
("Add ID", "เพิ่ม ID"), ("Logout", "ออกจากระบบ"),
("Add Tag", "เพิ่มแท็ก"), ("Tags", "แท็ก"),
("Unselect all tags", "ยกเลิกการเลือกแท็กทั้งหมด"), ("Search ID", "ค้นหา ID"),
("Network error", "ข้อผิดพลาดของเครือข่าย"), ("Current Wayland display server is not supported", "เซิร์ฟเวอร์การแสดงผล Wayland ปัจจุบันไม่รองรับ"),
("Username missed", "ไม่พบข้อมูลผู้ใช้งาน"), ("whitelist_sep", "คั่นโดยเครื่องหมาย comma semicolon เว้นวรรค หรือ ขึ้นบรรทัดใหม่"),
("Password missed", "ไม่พบรหัสผ่าน"), ("Add ID", "เพิ่ม ID"),
("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"), ("Add Tag", "เพิ่มแท็ก"),
("Edit Tag", "แก้ไขแท็ก"), ("Unselect all tags", "ยกเลิกการเลือกแท็กทั้งหมด"),
("Unremember Password", "ยกเลิกการจดจำรหัสผ่าน"), ("Network error", "ข้อผิดพลาดของเครือข่าย"),
("Favorites", "รายการโปรด"), ("Username missed", "ไม่พบข้อมูลผู้ใช้งาน"),
("Add to Favorites", "เพิ่มไปยังรายการโปรด"), ("Password missed", "ไม่พบรหัสผ่าน"),
("Remove from Favorites", "ลบออกจากรายการโปรด"), ("Wrong credentials", "ข้อมูลสำหรับเข้าสู่ระบบไม่ถูกต้อง"),
("Empty", "ว่างเปล่า"), ("Edit Tag", "แก้ไขแท็ก"),
("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"), ("Unremember Password", "ยกเลิกการจดจำรหัสผ่าน"),
("Socks5 Proxy", "พรอกซี Socks5"), ("Favorites", "รายการโปรด"),
("Hostname", "ชื่อโฮสต์"), ("Add to Favorites", "เพิ่มไปยังรายการโปรด"),
("Discovered", "ค้นพบ"), ("Remove from Favorites", "ลบออกจากรายการโปรด"),
("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"), ("Empty", "ว่างเปล่า"),
("Remote ID", "ID ปลายทาง"), ("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"),
("Paste", "วาง"), ("Socks5 Proxy", "พรอกซี Socks5"),
("Paste here?", "วางที่นี่หรือไม่?"), ("Hostname", "ชื่อโฮสต์"),
("Are you sure to close the connection?", "คุณแน่ใจหรือไม่ที่จะปิดการเชื่อมต่อ?"), ("Discovered", "ค้นพบ"),
("Download new version", "ดาวน์โหลดเวอร์ชั่นใหม่"), ("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"),
("Touch mode", "โหมดการสัมผัส"), ("Remote ID", "ID ปลายทาง"),
("Mouse mode", "โหมดการใช้เมาส์"), ("Paste", "วาง"),
("One-Finger Tap", "แตะนิ้วเดียว"), ("Paste here?", "วางที่นี่หรือไม่?"),
("Left Mouse", "เมาส์ซ้าย"), ("Are you sure to close the connection?", "คุณแน่ใจหรือไม่ที่จะปิดการเชื่อมต่อ?"),
("One-Long Tap", "แตะยาวหนึ่งครั้ง"), ("Download new version", "ดาวน์โหลดเวอร์ชั่นใหม่"),
("Two-Finger Tap", "แตะสองนิ้ว"), ("Touch mode", "โหมดการสัมผัส"),
("Right Mouse", "เมาส์ขวา"), ("Mouse mode", "โหมดการใช้เมาส์"),
("One-Finger Move", "ลากนิ้วเดียว"), ("One-Finger Tap", "แตะนิ้วเดียว"),
("Double Tap & Move", "แตะเบิ้ลและลาก"), ("Left Mouse", "เมาส์ซ้าย"),
("Mouse Drag", "ลากเมาส์"), ("One-Long Tap", "แตะยาวหนึ่งครั้ง"),
("Three-Finger vertically", "สามนิ้วแนวตั้ง"), ("Two-Finger Tap", "แตะสองนิ้ว"),
("Mouse Wheel", "ลูกลิ้งเมาส์"), ("Right Mouse", "เมาส์ขวา"),
("Two-Finger Move", "ลากสองนิ้ว"), ("One-Finger Move", "ลากนิ้วเดียว"),
("Canvas Move", "ลากแคนวาส"), ("Double Tap & Move", "แตะเบิ้ลและลาก"),
("Pinch to Zoom", "ถ่างเพื่อขยาย"), ("Mouse Drag", "ลากเมาส์"),
("Canvas Zoom", "ขยายแคนวาส"), ("Three-Finger vertically", "สามนิ้วแนวตั้ง"),
("Reset canvas", "รีเซ็ตแคนวาส"), ("Mouse Wheel", "ลูกลิ้งเมาส์"),
("No permission of file transfer", "ไม่มีสิทธิ์ในการถ่ายโอนไฟล์"), ("Two-Finger Move", "ลากสองนิ้ว"),
("Note", "บันทึกข้อความ"), ("Canvas Move", "ลากแคนวาส"),
("Connection", "การเชื่อมต่อ"), ("Pinch to Zoom", "ถ่างเพื่อขยาย"),
("Share Screen", "แชร์หน้าจอ"), ("Canvas Zoom", "ขยายแคนวาส"),
("CLOSE", "ปิด"), ("Reset canvas", "รีเซ็ตแคนวาส"),
("OPEN", "เปิด"), ("No permission of file transfer", "ไม่มีสิทธิ์ในการถ่ายโอนไฟล์"),
("Chat", "แชท"), ("Note", "บันทึกข้อความ"),
("Total", "รวม"), ("Connection", "การเชื่อมต่อ"),
("items", "รายการ"), ("Share Screen", "แชร์หน้าจอ"),
("Selected", "ถูกเลือก"), ("CLOSE", "ปิด"),
("Screen Capture", "แคปเจอร์หน้าจอ"), ("OPEN", "เปิด"),
("Input Control", "ควบคุมอินพุท"), ("Chat", "แชท"),
("Audio Capture", "แคปเจอร์เสียง"), ("Total", "รวม"),
("File Connection", "การเชื่อมต่อไฟล์"), ("items", "รายการ"),
("Screen Connection", "การเชื่อมต่อหน้าจอ"), ("Selected", "ถูกเลือก"),
("Do you accept?", "ยอมรับหรือไม่?"), ("Screen Capture", "แคปเจอร์หน้าจอ"),
("Open System Setting", "เปิดการตั้งค่าระบบ"), ("Input Control", "ควบคุมอินพุท"),
("How to get Android input permission?", "เปิดสิทธิ์การใช้งานอินพุทของแอนดรอยด์ได้อย่างไร?"), ("Audio Capture", "แคปเจอร์เสียง"),
("android_input_permission_tip1", "ในการที่จะอนุญาตให้เครื่องปลายทางควบคุมอุปกรณ์แอนดรอยด์ของคุณโดยใช้เมาส์หรือการสัมผัส คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่เซอร์วิสของ RustDesk"), ("File Connection", "การเชื่อมต่อไฟล์"),
("android_input_permission_tip2", "กรุณาไปยังหน้าตั้งค่าถัดไป ค้นหาและเข้าไปยัง [เซอร์วิสที่ถูกติดตั้ง] และเปิดการใช้งานเซอร์วิส [อินพุท RustDesk]"), ("Screen Connection", "การเชื่อมต่อหน้าจอ"),
("android_new_connection_tip", "ได้รับคำขอควบคุมใหม่ที่ต้องการควบคุมอุปกรณ์ของคุณ"), ("Do you accept?", "ยอมรับหรือไม่?"),
("android_service_will_start_tip", "การเปิดการใช้งาน \"การบันทึกหน้าจอ\" จะเป็นการเริ่มต้นการทำงานของเซอร์วิสโดยอัตโนมัติ ที่จะอนุญาตให้อุปกรณ์อื่นๆ ส่งคำขอเข้าถึงมายังอุปกรณ์ของคุณได้"), ("Open System Setting", "เปิดการตั้งค่าระบบ"),
("android_stop_service_tip", "การปิดการใช้งานเซอร์วิสจะปิดการเชื่อมต่อทั้งหมดโดยอัตโนมัติ"), ("How to get Android input permission?", "เปิดสิทธิ์การใช้งานอินพุทของแอนดรอยด์ได้อย่างไร?"),
("android_version_audio_tip", "เวอร์ชั่นแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชั่น 10 หรือสูงกว่า"), ("android_input_permission_tip1", "ในการที่จะอนุญาตให้เครื่องปลายทางควบคุมอุปกรณ์แอนดรอยด์ของคุณโดยใช้เมาส์หรือการสัมผัส คุณจำเป็นจะต้องอนุญาตสิทธิ์ \"การเข้าถึง\" ให้แก่เซอร์วิสของ RustDesk"),
("android_start_service_tip", "แตะ [เริ่มต้นใช้งานเซอร์วิส] หรือเปิดสิทธิ์ [การบันทึกหน้าจอ] เพื่อเริ่มเซอร์วิสการแชร์หน้าจอ"), ("android_input_permission_tip2", "กรุณาไปยังหน้าตั้งค่าถัดไป ค้นหาและเข้าไปยัง [เซอร์วิสที่ถูกติดตั้ง] และเปิดการใช้งานเซอร์วิส [อินพุท RustDesk]"),
("Overwrite", "เขียนทับ"), ("android_new_connection_tip", "ได้รับคำขอควบคุมใหม่ที่ต้องการควบคุมอุปกรณ์ของคุณ"),
("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"), ("android_service_will_start_tip", "การเปิดการใช้งาน \"การบันทึกหน้าจอ\" จะเป็นการเริ่มต้นการทำงานของเซอร์วิสโดยอัตโนมัติ ที่จะอนุญาตให้อุปกรณ์อื่นๆ ส่งคำขอเข้าถึงมายังอุปกรณ์ของคุณได้"),
("Quit", "ออก"), ("android_stop_service_tip", "การปิดการใช้งานเซอร์วิสจะปิดการเชื่อมต่อทั้งหมดโดยอัตโนมัติ"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("android_version_audio_tip", "เวอร์ชั่นแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชั่น 10 หรือสูงกว่า"),
("Help", "ช่วยเหลือ"), ("android_start_service_tip", "แตะ [เริ่มต้นใช้งานเซอร์วิส] หรือเปิดสิทธิ์ [การบันทึกหน้าจอ] เพื่อเริ่มเซอร์วิสการแชร์หน้าจอ"),
("Failed", "ล้มเหลว"), ("Account", "บัญชี"),
("Succeeded", "สำเร็จ"), ("Overwrite", "เขียนทับ"),
("Someone turns on privacy mode, exit", "มีใครบางคนเปิดใช้งานโหมดความเป็นส่วนตัว กำลังออก"), ("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"),
("Unsupported", "ไม่รองรับ"), ("Quit", "ออก"),
("Peer denied", "ถูกปฏิเสธโดยอีกฝั่ง"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Please install plugins", "กรุณาติดตั้งปลั๊กอิน"), ("Help", "ช่วยเหลือ"),
("Peer exit", "อีกฝั่งออก"), ("Failed", "ล้มเหลว"),
("Failed to turn off", "การปิดล้มเหลว"), ("Succeeded", "สำเร็จ"),
("Turned off", "ปิด"), ("Someone turns on privacy mode, exit", "มีใครบางคนเปิดใช้งานโหมดความเป็นส่วนตัว กำลังออก"),
("In privacy mode", "อยู่ในโหมดความเป็นส่วนตัว"), ("Unsupported", "ไม่รองรับ"),
("Out privacy mode", "อยู่นอกโหมดความเป็นส่วนตัว"), ("Peer denied", "ถูกปฏิเสธโดยอีกฝั่ง"),
("Language", "ภาษา"), ("Please install plugins", "กรุณาติดตั้งปลั๊กอิน"),
("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"), ("Peer exit", "อีกฝั่งออก"),
("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"), ("Failed to turn off", "การปิดล้มเหลว"),
("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"), ("Turned off", "ปิด"),
("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"), ("In privacy mode", "อยู่ในโหมดความเป็นส่วนตัว"),
("Legacy mode", ""), ("Out privacy mode", "อยู่นอกโหมดความเป็นส่วนตัว"),
("Map mode", ""), ("Language", "ภาษา"),
("Translate mode", ""), ("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"),
("Use permanent password", "ใช้รหัสผ่านถาวร"), ("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"),
("Use both passwords", "ใช้รหัสผ่านทั้งสองแบบ"), ("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"),
("Set permanent password", "ตั้งค่ารหัสผ่านถาวร"), ("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"),
("Enable Remote Restart", "เปิดการใช้งานการรีสตาร์ทระบบทางไกล"), ("Legacy mode", ""),
("Allow remote restart", "อนุญาตการรีสตาร์ทระบบทางไกล"), ("Map mode", ""),
("Restart Remote Device", "รีสตาร์ทอุปกรณ์ปลายทาง"), ("Translate mode", ""),
("Are you sure you want to restart", "คุณแน่ใจหรือไม่ที่จะรีสตาร์ท"), ("Use permanent password", "ใช้รหัสผ่านถาวร"),
("Restarting Remote Device", "กำลังรีสตาร์ทระบบปลายทาง"), ("Use both passwords", "ใช้รหัสผ่านทั้งสองแบบ"),
("remote_restarting_tip", "ระบบปลายทางกำลังรีสตาร์ท กรุณาปิดกล่องข้อความนี้และดำเนินการเขื่อมต่อใหม่อีกครั้งด้วยรหัสผ่านถาวรหลังจากผ่านไปซักครู่"), ("Set permanent password", "ตั้งค่ารหัสผ่านถาวร"),
("Copied", "คัดลอกแล้ว"), ("Enable Remote Restart", "เปิดการใช้งานการรีสตาร์ทระบบทางไกล"),
("Exit Fullscreen", "ออกจากเต็มหน้าจอ"), ("Allow remote restart", "อนุญาตการรีสตาร์ทระบบทางไกล"),
("Fullscreen", "เต็มหน้าจอ"), ("Restart Remote Device", "รีสตาร์ทอุปกรณ์ปลายทาง"),
("Mobile Actions", "การดำเนินการบนมือถือ"), ("Are you sure you want to restart", "คุณแน่ใจหรือไม่ที่จะรีสตาร์ท"),
("Select Monitor", "เลือกหน้าจอ"), ("Restarting Remote Device", "กำลังรีสตาร์ทระบบปลายทาง"),
("Control Actions", "การดำเนินการควบคุม"), ("remote_restarting_tip", "ระบบปลายทางกำลังรีสตาร์ท กรุณาปิดกล่องข้อความนี้และดำเนินการเขื่อมต่อใหม่อีกครั้งด้วยรหัสผ่านถาวรหลังจากผ่านไปซักครู่"),
("Display Settings", "การตั้งค่าแสดงผล"), ("Copied", "คัดลอกแล้ว"),
("Ratio", "อัตราส่วน"), ("Exit Fullscreen", "ออกจากเต็มหน้าจอ"),
("Image Quality", "คุณภาพภาพ"), ("Fullscreen", "เต็มหน้าจอ"),
("Scroll Style", "ลักษณะการเลื่อน"), ("Mobile Actions", "การดำเนินการบนมือถือ"),
("Show Menubar", "แสดงแถบเมนู"), ("Select Monitor", "เลือกหน้าจอ"),
("Hide Menubar", "ซ่อนแถบเมนู"), ("Control Actions", "การดำเนินการควบคุม"),
("Direct Connection", "การเชื่อมต่อตรง"), ("Display Settings", "การตั้งค่าแสดงผล"),
("Relay Connection", "การเชื่อมต่อแบบรีเลย์"), ("Ratio", "อัตราส่วน"),
("Secure Connection", "การเชื่อมต่อที่ปลอดภัย"), ("Image Quality", "คุณภาพภาพ"),
("Insecure Connection", "การเชื่อมต่อที่ไม่ปลอดภัย"), ("Scroll Style", "ลักษณะการเลื่อน"),
("Scale original", "ขนาดเดิม"), ("Show Menubar", "แสดงแถบเมนู"),
("Scale adaptive", "ขนาดยืดหยุ่น"), ("Hide Menubar", "ซ่อนแถบเมนู"),
("General", "ทั่วไป"), ("Direct Connection", "การเชื่อมต่อตรง"),
("Security", "ความปลอดภัย"), ("Relay Connection", "การเชื่อมต่อแบบรีเลย์"),
("Account", "บัญชี"), ("Secure Connection", "การเชื่อมต่อที่ปลอดภัย"),
("Theme", "ธีม"), ("Insecure Connection", "การเชื่อมต่อที่ไม่ปลอดภัย"),
("Dark Theme", "ธีมมืด"), ("Scale original", "ขนาดเดิม"),
("Dark", "มืด"), ("Scale adaptive", "ขนาดยืดหยุ่น"),
("Light", "สว่าง"), ("General", "ทั่วไป"),
("Follow System", "ตามระบบ"), ("Security", "ความปลอดภัย"),
("Enable hardware codec", "เปิดการใช้งานฮาร์ดแวร์ codec"), ("Theme", "ธีม"),
("Unlock Security Settings", "ปลดล็อคการตั้งค่าความปลอดภัย"), ("Dark Theme", "ธีมมืด"),
("Enable Audio", "เปิดการใช้งานเสียง"), ("Dark", "มืด"),
("Unlock Network Settings", "ปลดล็อคการตั้งค่าเครือข่าย"), ("Light", "สว่าง"),
("Server", "เซิร์ฟเวอร์"), ("Follow System", "ตามระบบ"),
("Direct IP Access", "การเข้าถึง IP ตรง"), ("Enable hardware codec", "เปิดการใช้งานฮาร์ดแวร์ codec"),
("Proxy", "พรอกซี"), ("Unlock Security Settings", "ปลดล็อคการตั้งค่าความปลอดภัย"),
("Apply", "นำไปใช้"), ("Enable Audio", "เปิดการใช้งานเสียง"),
("Disconnect all devices?", "ยกเลิกการเชื่อมต่ออุปกรณ์ทั้งหมด?"), ("Unlock Network Settings", "ปลดล็อคการตั้งค่าเครือข่าย"),
("Clear", "ล้างข้อมูล"), ("Server", "เซิร์ฟเวอร์"),
("Audio Input Device", "อุปกรณ์รับอินพุทข้อมูลเสียง"), ("Direct IP Access", "การเข้าถึง IP ตรง"),
("Deny remote access", "ปฏิเสธการเชื่อมต่อ"), ("Proxy", "พรอกซี"),
("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"), ("Apply", "นำไปใช้"),
("Network", "เครือข่าย"), ("Disconnect all devices?", "ยกเลิกการเชื่อมต่ออุปกรณ์ทั้งหมด?"),
("Enable RDP", "เปิดการใช้งาน RDP"), ("Clear", "ล้างข้อมูล"),
("Pin menubar", "ปักหมุดแถบเมนู"), ("Audio Input Device", "อุปกรณ์รับอินพุทข้อมูลเสียง"),
("Unpin menubar", "ยกเลิกการปักหมุดแถบเมนู"), ("Deny remote access", "ปฏิเสธการเชื่อมต่อ"),
("Recording", "การบันทึก"), ("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"),
("Directory", "ไดเรกทอรี่"), ("Network", "เครือข่าย"),
("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"), ("Enable RDP", "เปิดการใช้งาน RDP"),
("Change", "เปลี่ยน"), ("Pin menubar", "ปักหมุดแถบเมนู"),
("Start session recording", "เริ่มต้นการบันทึกเซสชัน"), ("Unpin menubar", "ยกเลิกการปักหมุดแถบเมนู"),
("Stop session recording", "หยุดการบันทึกเซสซัน"), ("Recording", "การบันทึก"),
("Enable Recording Session", "เปิดใช้งานการบันทึกเซสชัน"), ("Directory", "ไดเรกทอรี่"),
("Allow recording session", "อนุญาตการบันทึกเซสชัน"), ("Automatically record incoming sessions", "บันทึกเซสชันขาเข้าโดยอัตโนมัติ"),
("Enable LAN Discovery", "เปิดการใช้งานการค้นหาในวง LAN"), ("Change", "เปลี่ยน"),
("Deny LAN Discovery", "ปฏิเสธการใช้งานการค้นหาในวง LAN"), ("Start session recording", "เริ่มต้นการบันทึกเซสชัน"),
("Write a message", "เขียนข้อความ"), ("Stop session recording", "หยุดการบันทึกเซสซัน"),
("Prompt", ""), ("Enable Recording Session", "เปิดใช้งานการบันทึกเซสชัน"),
("Please wait for confirmation of UAC...", "กรุณารอการยืนยันจาก UAC..."), ("Allow recording session", "อนุญาตการบันทึกเซสชัน"),
("elevated_foreground_window_tip", "หน้าต่างปัจจุบันของเครื่องปลายทางต้องการสิทธิ์การใช้งานที่สูงขึ้นสำหรับการทำงาน ดังนั้นเมาส์และคีย์บอร์ดจะไม่สามารถใช้งานได้ชั่วคราว คุณสามารถขอผู้ใช้งานปลายทางให้ย่อหน้าต่าง หรือคลิกปุ่มให้สิทธิ์การใช้งานในหน้าต่างการจัดการการเชื่อมต่อ เพื่อหลีกเลี่ยงปัญหานี้เราแนะนำให้ดำเนินการติดตั้งซอฟท์แวร์ในเครื่องปลายทาง"), ("Enable LAN Discovery", "เปิดการใช้งานการค้นหาในวง LAN"),
("Disconnected", "ยกเลิกการเชื่อมต่อ"), ("Deny LAN Discovery", "ปฏิเสธการใช้งานการค้นหาในวง LAN"),
("Other", "อื่นๆ"), ("Write a message", "เขียนข้อความ"),
("Confirm before closing multiple tabs", "ยืนยันการปิดหลายแท็บ"), ("Prompt", ""),
("Keyboard Settings", "การตั้งค่าคีย์บอร์ด"), ("Please wait for confirmation of UAC...", "กรุณารอการยืนยันจาก UAC..."),
("Full Access", "การเข้าถึงทั้งหมด"), ("elevated_foreground_window_tip", "หน้าต่างปัจจุบันของเครื่องปลายทางต้องการสิทธิ์การใช้งานที่สูงขึ้นสำหรับการทำงาน ดังนั้นเมาส์และคีย์บอร์ดจะไม่สามารถใช้งานได้ชั่วคราว คุณสามารถขอผู้ใช้งานปลายทางให้ย่อหน้าต่าง หรือคลิกปุ่มให้สิทธิ์การใช้งานในหน้าต่างการจัดการการเชื่อมต่อ เพื่อหลีกเลี่ยงปัญหานี้เราแนะนำให้ดำเนินการติดตั้งซอฟท์แวร์ในเครื่องปลายทาง"),
("Screen Share", "การแชร์จอ"), ("Disconnected", "ยกเลิกการเชื่อมต่อ"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland ต้องการ Ubuntu เวอร์ชั่น 21.04 หรือสูงกว่า"), ("Other", "อื่นๆ"),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland ต้องการลินุกซ์เวอร์ชันที่สูงกว่านี้ กรุณาเปลี่ยนไปใช้เดสก์ท็อป X11 หรือเปลี่ยนระบบปฏิบัติการของคุณ"), ("Confirm before closing multiple tabs", "ยืนยันการปิดหลายแท็บ"),
("JumpLink", "View"), ("Keyboard Settings", "การตั้งค่าคีย์บอร์ด"),
("Please Select the screen to be shared(Operate on the peer side).", "กรุณาเลือกหน้าจอที่ต้องการแชร์ (ใช้งานในอีกฝั่งของการเชื่อมต่อ)"), ("Full Access", "การเข้าถึงทั้งหมด"),
("Show RustDesk", "แสดง RustDesk"), ("Screen Share", "การแชร์จอ"),
("This PC", ""), ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland ต้องการ Ubuntu เวอร์ชั่น 21.04 หรือสูงกว่า"),
("or", "หรือ"), ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland ต้องการลินุกซ์เวอร์ชันที่สูงกว่านี้ กรุณาเปลี่ยนไปใช้เดสก์ท็อป X11 หรือเปลี่ยนระบบปฏิบัติการของคุณ"),
("Continue with", "ทำต่อด้วย"), ("JumpLink", "View"),
("Elevate", "ยกระดับ"), ("Please Select the screen to be shared(Operate on the peer side).", "กรุณาเลือกหน้าจอที่ต้องการแชร์ (ใช้งานในอีกฝั่งของการเชื่อมต่อ)"),
("Zoom cursor", "ขยายเคอร์เซอร์"), ("Show RustDesk", "แสดง RustDesk"),
("Accept sessions via password", "ยอมรับการเชื่อมต่อด้วยรหัสผ่าน"), ("This PC", ""),
("Accept sessions via click", "ยอมรับการเชื่อมต่อด้วยการคลิก"), ("or", "หรือ"),
("Accept sessions via both", "ยอมรับการเชื่อมต่อด้วยทั้งสองวิธิ"), ("Continue with", "ทำต่อด้วย"),
("Please wait for the remote side to accept your session request...", "กรุณารอให้อีกฝั่งยอมรับการเชื่อมต่อของคุณ..."), ("Elevate", "ยกระดับ"),
("One-time Password", "รหัสผ่านครั้งเดียว"), ("Zoom cursor", "ขยายเคอร์เซอร์"),
("Use one-time password", "ใช้รหัสผ่านครั้งเดียว"), ("Accept sessions via password", "ยอมรับการเชื่อมต่อด้วยรหัสผ่าน"),
("One-time password length", "ความยาวรหัสผ่านครั้งเดียว"), ("Accept sessions via click", "ยอมรับการเชื่อมต่อด้วยการคลิก"),
("Request access to your device", "คำขอการเข้าถึงอุปกรณ์ของคุณ"), ("Accept sessions via both", "ยอมรับการเชื่อมต่อด้วยทั้งสองวิธิ"),
("Hide connection management window", "ซ่อนหน้าต่างการจัดการการเชื่อมต่อ"), ("Please wait for the remote side to accept your session request...", "กรุณารอให้อีกฝั่งยอมรับการเชื่อมต่อของคุณ..."),
("hide_cm_tip", "อนุญาตการซ่อนก็ต่อเมื่อยอมรับการเชื่อมต่อด้วยรหัสผ่าน และต้องเป็นรหัสผ่านถาวรเท่านั้น"), ("One-time Password", "รหัสผ่านครั้งเดียว"),
("wayland_experiment_tip", "การสนับสนุน Wayland ยังอยู่ในขั้นตอนการทดลอง กรุณาใช้ X11 หากคุณต้องการใช้งานการเข้าถึงแบบไม่มีผู้ดูแล"), ("Use one-time password", "ใช้รหัสผ่านครั้งเดียว"),
("Right click to select tabs", "คลิกขวาเพื่อเลือกแท็บ"), ("One-time password length", "ความยาวรหัสผ่านครั้งเดียว"),
("Skipped", "ข้าม"), ("Request access to your device", "คำขอการเข้าถึงอุปกรณ์ของคุณ"),
("Add to Address Book", "เพิ่มไปยังสมุดรายชื่อ"), ("Hide connection management window", "ซ่อนหน้าต่างการจัดการการเชื่อมต่อ"),
("Group", "กลุ่ม"), ("hide_cm_tip", "อนุญาตการซ่อนก็ต่อเมื่อยอมรับการเชื่อมต่อด้วยรหัสผ่าน และต้องเป็นรหัสผ่านถาวรเท่านั้น"),
("Search", "ค้นหา"), ("wayland_experiment_tip", "การสนับสนุน Wayland ยังอยู่ในขั้นตอนการทดลอง กรุณาใช้ X11 หากคุณต้องการใช้งานการเข้าถึงแบบไม่มีผู้ดูแล"),
("Closed manually by the web console", "ถูกปิดโดยเว็บคอนโซล"), ("Right click to select tabs", "คลิกขวาเพื่อเลือกแท็บ"),
("Local keyboard type", "ประเภทคีย์บอร์ด"), ("Skipped", "ข้าม"),
("Select local keyboard type", "เลือกประเภทคีย์บอร์ด"), ("Add to Address Book", "เพิ่มไปยังสมุดรายชื่อ"),
("software_render_tip", ""), ("Group", "กลุ่ม"),
("Always use software rendering", ""), ("Search", "ค้นหา"),
("config_input", ""), ("Closed manually by the web console", "ถูกปิดโดยเว็บคอนโซล"),
].iter().cloned().collect(); ("Local keyboard type", "ประเภทคีย์บอร์ด"),
} ("Select local keyboard type", "เลือกประเภทคีย์บอร์ด"),
("software_render_tip", ""),
("Always use software rendering", ""),
("config_input", ""),
].iter().cloned().collect();
}

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Always connect via relay"), ("Always connect via relay", "Always connect via relay"),
("whitelist_tip", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"), ("whitelist_tip", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"),
("Login", "Giriş yap"), ("Login", "Giriş yap"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Çıkış yap"), ("Logout", "Çıkış yap"),
("Tags", "Etiketler"), ("Tags", "Etiketler"),
("Search ID", "ID Arama"), ("Search ID", "ID Arama"),

View File

@ -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", "只有白名單中的 IP 可以存取"), ("whitelist_tip", "只有白名單中的 IP 可以存取"),
("Login", "登入"), ("Login", "登入"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "登出"), ("Logout", "登出"),
("Tags", "標籤"), ("Tags", "標籤"),
("Search ID", "搜尋 ID"), ("Search ID", "搜尋 ID"),

View File

@ -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", "Тільки IP-адреси з білого списку можуть отримати доступ до мене"), ("whitelist_tip", "Тільки IP-адреси з білого списку можуть отримати доступ до мене"),
("Login", "Увійти"), ("Login", "Увійти"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Вийти"), ("Logout", "Вийти"),
("Tags", "Ключові слова"), ("Tags", "Ключові слова"),
("Search ID", "Пошук за ID"), ("Search ID", "Пошук за ID"),

View File

@ -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"), ("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"), ("whitelist_tip", "Chỉ có những IP đựoc cho phép mới có thể truy cập"),
("Login", "Đăng nhập"), ("Login", "Đăng nhập"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Đăng xuất"), ("Logout", "Đăng xuất"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Tìm ID"), ("Search ID", "Tìm ID"),