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 {
'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}'
};

View File

@ -1,12 +1,22 @@
import 'package:flutter_hbb/models/peer_model.dart';
class HttpType {
static const kAuthReqTypeAccount = "account";
static const kAuthReqTypeMobile = "mobile";
static const kAuthReqTypeSMSCode = "sms_code";
static const kAuthReqTypeEmailCode = "email_code";
static const kAuthResTypeToken = "access_token";
static const kAuthResTypeEmailCheck = "email_check";
}
class UserPayload {
String name = '';
String email = '';
String note = '';
int? status;
String grp = '';
bool is_admin = false;
bool isAdmin = false;
UserPayload.fromJson(Map<String, dynamic> json)
: name = json['name'] ?? '',
@ -14,7 +24,7 @@ class UserPayload {
note = json['note'] ?? '',
status = json['status'],
grp = json['grp'] ?? '',
is_admin = json['is_admin'] == true;
isAdmin = json['is_admin'] == true;
}
class PeerPayload {
@ -37,3 +47,73 @@ class PeerPayload {
return Peer.fromJson({"id": p.id});
}
}
class LoginRequest {
String? username;
String? password;
String? id;
String? uuid;
bool? autoLogin;
String? type;
String? verificationCode;
String? deviceInfo;
LoginRequest(
{this.username,
this.password,
this.id,
this.uuid,
this.autoLogin,
this.type,
this.verificationCode,
this.deviceInfo});
LoginRequest.fromJson(Map<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/peers_view.dart';
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
import 'package:flutter_hbb/desktop/widgets/login.dart';
import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import 'package:get/get.dart';
import '../../common.dart';
import '../../desktop/pages/desktop_home_page.dart';
import '../../mobile/pages/settings_page.dart';
import 'login.dart';
class AddressBook extends StatefulWidget {
final EdgeInsets? menuPadding;
@ -41,21 +39,12 @@ class _AddressBookState extends State<AddressBook> {
}
});
handleLogin() {
// TODO refactor login dialog for desktop and mobile
if (isDesktop) {
loginDialog();
} else {
showLogin(gFFI.dialogManager);
}
}
Future<Widget> buildBody(BuildContext context) async {
return Obx(() {
if (gFFI.userModel.userName.value.isEmpty) {
return Center(
child: InkWell(
onTap: handleLogin,
onTap: loginDialog,
child: Text(
translate("Login"),
style: const TextStyle(decoration: TextDecoration.underline),

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

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

View File

@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart';
@ -300,7 +301,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
leading: Icon(Icons.person),
onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) {
showLogin(gFFI.dialogManager);
loginDialog();
} else {
gFFI.userModel.logOut();
}
@ -397,7 +398,7 @@ void showServerSettings(OverlayDialogManager dialogManager) async {
void showLanguageSettings(OverlayDialogManager dialogManager) async {
try {
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
var lang = await bind.mainGetLocalOption(key: "lang");
var lang = bind.mainGetLocalOption(key: "lang");
dialogManager.show((setState, close) {
setLang(v) {
if (lang != v) {
@ -482,77 +483,6 @@ void showAbout(OverlayDialogManager dialogManager) {
}, clickMaskDismiss: true, backDismiss: true);
}
void showLogin(OverlayDialogManager dialogManager) {
final passwordController = TextEditingController();
final nameController = TextEditingController();
var loading = false;
var error = '';
dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate('Login')),
content: Column(mainAxisSize: MainAxisSize.min, children: [
TextField(
autofocus: true,
autocorrect: false,
enableSuggestions: false,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
labelText: translate('Username'),
),
controller: nameController,
),
PasswordWidget(controller: passwordController, autoFocus: false),
]),
actions: (loading
? <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 {
@override
Widget build(BuildContext context) {

View File

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

View File

@ -59,7 +59,7 @@ class GroupModel {
if (gFFI.userModel.isAdmin.isFalse)
'grp': gFFI.userModel.groupName.value,
});
final resp = await http.get(uri, headers: await getHttpHeaders());
final resp = await http.get(uri, headers: getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body);
if (json.containsKey('error')) {
@ -110,7 +110,7 @@ class GroupModel {
'grp': gFFI.userModel.groupName.value,
'target_user': username
});
final resp = await http.get(uri, headers: await getHttpHeaders());
final resp = await http.get(uri, headers: getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(resp.body);
if (json.containsKey('error')) {

View File

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

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

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)"),
("whitelist_tip", "Přístup je umožněn pouze z IP adres, nacházejících se na seznamu povolených"),
("Login", "Přihlásit se"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odhlásit se"),
("Tags", "Štítky"),
("Search ID", "Hledat identifikátor"),

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"),
("whitelist_tip", "Kun IP'er på udgivelseslisten kan få adgang til mig"),
("Login", "Login"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "logger af"),
("Tags", "Nøgleord"),
("Search ID", "Søg ID"),

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"),
("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."),
("Login", "Anmelden"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Abmelden"),
("Tags", "Schlagworte"),
("Search ID", "Suche ID"),

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"),
("wayland_experiment_tip", "Wayland support is in experimental stage, please use X11 if you require unattended access."),
("Slogan_tip", "Made with heart in this chaotic world!"),
("verification_tip", "A new device has been detected, and a verification code has been sent to the registered email address, enter the verification code to continue logging in."),
("software_render_tip", "If you have an Nvidia graphics card and the remote window closes immediately after connecting, installing the nouveau driver and choosing to use software rendering may help. A software restart is required."),
("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."),
].iter().cloned().collect();

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"),
("whitelist_tip", "Nur la IP en la blanka listo povas kontroli mian komputilon"),
("Login", "Konekti"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Malkonekti"),
("Tags", "Etikedi"),
("Search ID", "Serĉi ID"),

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"),
("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"),
("Login", "Iniciar sesión"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Salir"),
("Tags", "Tags"),
("Search ID", "Buscar ID"),

View File

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

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"),
("whitelist_tip", "Seul l'IP dans la liste blanche peut accéder à mon appareil"),
("Login", "Connexion"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Déconnexion"),
("Tags", "Étiqueter"),
("Search ID", "Rechercher un ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Σύνδεση πάντα μέσω αναμετάδοσης"),
("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"),
("Login", "Σύνδεση"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Αποσύνδεση"),
("Tags", "Ετικέτες"),
("Search ID", "Αναζήτηση ID"),

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

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"),
("whitelist_tip", "Hanya whitelisted IP yang dapat mengakses saya"),
("Login", "Masuk"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Keluar"),
("Tags", "Tag"),
("Search ID", "Cari ID"),

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"),
("whitelist_tip", "Solo gli indirizzi IP autorizzati possono connettersi a questo desktop"),
("Login", "Accedi"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Esci"),
("Tags", "Tag"),
("Search ID", "Cerca ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "常に中継サーバー経由で接続"),
("whitelist_tip", "ホワイトリストに登録されたIPからのみ接続を許可します"),
("Login", "ログイン"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "ログアウト"),
("Tags", "タグ"),
("Search ID", "IDを検索"),

View File

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

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Әрқашан да релай сербері арқылы қосылу"),
("whitelist_tip", "Маған тек ақ-тізімделген IP қол жеткізе алады"),
("Login", "Кіру"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Шығу"),
("Tags", "Тақтар"),
("Search ID", "ID Іздеу"),

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"),
("whitelist_tip", "Zezwlaj na łączenie z tym komputerem tylko z adresów IP znajdujących się na białej liście"),
("Login", "Zaloguj"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Wyloguj"),
("Tags", "Tagi"),
("Search ID", "Szukaj ID"),

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"),
("whitelist_tip", "Somente IPs na whitelist podem me acessar"),
("Login", "Login"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Sair"),
("Tags", "Tags"),
("Search ID", "Procurar ID"),

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"),
("whitelist_tip", "Somente IPs confiáveis podem me acessar"),
("Login", "Login"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Sair"),
("Tags", "Tags"),
("Search ID", "Pesquisar ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Всегда подключаться через ретрансляционный сервер"),
("whitelist_tip", "Только IP-адреса из белого списка могут получить доступ ко мне"),
("Login", "Войти"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Выйти"),
("Tags", "Метки"),
("Search ID", "Поиск по ID"),

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

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"),
("whitelist_tip", "Dostop je možen samo iz dovoljenih IPjev"),
("Login", "Prijavi"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odjavi"),
("Tags", "Oznake"),
("Search ID", "Išči ID"),

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"),
("whitelist_tip", "Vetëm IP e listës së bardhë mund të më aksesoj."),
("Login", "Hyrje"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Dalje"),
("Tags", "Tage"),
("Search ID", "Kerko ID"),

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"),
("whitelist_tip", "Samo dozvoljene IP mi mogu pristupiti"),
("Login", "Prijava"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Odjava"),
("Tags", "Oznake"),
("Search ID", "Traži ID"),

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"),
("whitelist_tip", "Bara vitlistade IPs kan koppla upp till mig"),
("Login", "Logga in"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Logga ut"),
("Tags", "Taggar"),
("Search ID", "Sök ID"),

View File

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

View File

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

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", "Bu masaüstüne yalnızca yetkili IP adresleri bağlanabilir"),
("Login", "Giriş yap"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Çıkış yap"),
("Tags", "Etiketler"),
("Search ID", "ID Arama"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "一律透過轉送連線"),
("whitelist_tip", "只有白名單中的 IP 可以存取"),
("Login", "登入"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "登出"),
("Tags", "標籤"),
("Search ID", "搜尋 ID"),

View File

@ -210,6 +210,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Always connect via relay", "Завжди підключатися через ретрансляційний сервер"),
("whitelist_tip", "Тільки IP-адреси з білого списку можуть отримати доступ до мене"),
("Login", "Увійти"),
("Verify", ""),
("Remember me", ""),
("Trust this device", ""),
("Verification code", ""),
("verification_tip", ""),
("Logout", "Вийти"),
("Tags", "Ключові слова"),
("Search ID", "Пошук за ID"),

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