add translate message feature, fix Russian locale
This commit is contained in:
parent
4964c7f621
commit
b89ad6598e
|
|
@ -1780,6 +1780,21 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"translateMessage": "Translate message",
|
||||||
|
"@translateMessage": {
|
||||||
|
"type": "String",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"translatedMessage": "Translated message",
|
||||||
|
"@translatedMessage": {
|
||||||
|
"type": "String",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"errorTranslatingMessage": "An error has occured while translating the message.",
|
||||||
|
"@errorTranslatingMessage": {
|
||||||
|
"type": "String",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"recoverMessage": "Recover message",
|
"recoverMessage": "Recover message",
|
||||||
"@recoverMessage": {
|
"@recoverMessage": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
@ -1790,7 +1805,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"errorRecoveringMessage": "An error has occured while recovering a message.",
|
"errorRecoveringMessage": "An error has occured while recovering the message.",
|
||||||
"@errorRecoveringMessage": {
|
"@errorRecoveringMessage": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -2654,7 +2669,7 @@
|
||||||
"@foregroundServiceRunning": {},
|
"@foregroundServiceRunning": {},
|
||||||
"screenSharingTitle": "screen sharing",
|
"screenSharingTitle": "screen sharing",
|
||||||
"@screenSharingTitle": {},
|
"@screenSharingTitle": {},
|
||||||
"screenSharingDetail": "You are sharing your screen in FuffyChat",
|
"screenSharingDetail": "You are sharing your screen in Extera",
|
||||||
"@screenSharingDetail": {},
|
"@screenSharingDetail": {},
|
||||||
"callingPermissions": "Calling permissions",
|
"callingPermissions": "Calling permissions",
|
||||||
"@callingPermissions": {},
|
"@callingPermissions": {},
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,7 +2,10 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fluffychat/pages/chat/recovered_event_dialog.dart';
|
import 'package:fluffychat/pages/chat/recovered_event_dialog.dart';
|
||||||
|
import 'package:fluffychat/pages/chat/translated_event_dialog.dart';
|
||||||
|
import 'package:fluffychat/utils/file_description.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/synapse_admin_extension.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/synapse_admin_extension.dart';
|
||||||
|
import 'package:fluffychat/utils/translator.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -752,6 +755,38 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
fullscreenDialog: true));
|
fullscreenDialog: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void translateEventAction() async {
|
||||||
|
final event = selectedEvents.single;
|
||||||
|
var text = event.isRichMessage ? event.formattedText : event.text;
|
||||||
|
if (text == null) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(L10n.of(context).errorTranslatingMessage)),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var content = {...event.content};
|
||||||
|
try {
|
||||||
|
text = await Translator.translate(text, PlatformDispatcher.instance.locale.languageCode);
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(L10n.of(context).errorTranslatingMessage)),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.isRichMessage) {
|
||||||
|
content['formatted_body'] = text;
|
||||||
|
} else {
|
||||||
|
content['body'] = text;
|
||||||
|
}
|
||||||
|
content['xyz.extera.translated'] = true;
|
||||||
|
Navigator.of(context).push(new MaterialPageRoute(
|
||||||
|
builder: (BuildContext ctx) {
|
||||||
|
return TranslatedEventDialog(
|
||||||
|
event: new Event(content: content, type: 'm.room.message', eventId: event.eventId, senderId: event.senderId, originServerTs: event.originServerTs, room: room), timeline: timeline!);
|
||||||
|
},
|
||||||
|
fullscreenDialog: true));
|
||||||
|
}
|
||||||
|
|
||||||
void reportEventAction() async {
|
void reportEventAction() async {
|
||||||
final event = selectedEvents.single;
|
final event = selectedEvents.single;
|
||||||
final score = await showModalActionPopup<int>(
|
final score = await showModalActionPopup<int>(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import '../../utils/stream_extension.dart';
|
||||||
import 'chat_emoji_picker.dart';
|
import 'chat_emoji_picker.dart';
|
||||||
import 'chat_input_row.dart';
|
import 'chat_input_row.dart';
|
||||||
|
|
||||||
enum _EventContextAction { info, recover, report }
|
enum _EventContextAction { info, recover, translate, report }
|
||||||
|
|
||||||
class ChatView extends StatelessWidget {
|
class ChatView extends StatelessWidget {
|
||||||
final ChatController controller;
|
final ChatController controller;
|
||||||
|
|
@ -49,6 +49,12 @@ class ChatView extends StatelessWidget {
|
||||||
tooltip: L10n.of(context).copy,
|
tooltip: L10n.of(context).copy,
|
||||||
onPressed: controller.copyEventsAction,
|
onPressed: controller.copyEventsAction,
|
||||||
),
|
),
|
||||||
|
if (controller.selectedEvents.length == 1 && controller.selectedEvents.single.content['xyz.extera.translated'] == null)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.translate_outlined),
|
||||||
|
tooltip: L10n.of(context).translateMessage,
|
||||||
|
onPressed: controller.translateEventAction,
|
||||||
|
),
|
||||||
if (controller.canSaveSelectedEvent)
|
if (controller.canSaveSelectedEvent)
|
||||||
// Use builder context to correctly position the share dialog on iPad
|
// Use builder context to correctly position the share dialog on iPad
|
||||||
Builder(
|
Builder(
|
||||||
|
|
@ -84,6 +90,9 @@ class ChatView extends StatelessWidget {
|
||||||
case _EventContextAction.recover:
|
case _EventContextAction.recover:
|
||||||
controller.recoverEventAction();
|
controller.recoverEventAction();
|
||||||
break;
|
break;
|
||||||
|
case _EventContextAction.translate:
|
||||||
|
controller.translateEventAction();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:fluffychat/config/themes.dart';
|
||||||
|
import 'package:fluffychat/pages/chat/events/message.dart';
|
||||||
|
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
|
class TranslatedEventDialog extends StatefulWidget {
|
||||||
|
final Event event;
|
||||||
|
final Timeline timeline;
|
||||||
|
|
||||||
|
const TranslatedEventDialog({
|
||||||
|
required this.event,
|
||||||
|
required this.timeline,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
TranslatedEventDialogState createState() =>
|
||||||
|
TranslatedEventDialogState(event, timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TranslatedEventDialogState extends State<TranslatedEventDialog> {
|
||||||
|
final Event event;
|
||||||
|
final Timeline timeline;
|
||||||
|
TranslatedEventDialogState(this.event, this.timeline);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
final colors = [
|
||||||
|
theme.secondaryBubbleColor,
|
||||||
|
theme.bubbleColor,
|
||||||
|
];
|
||||||
|
|
||||||
|
final message = Message(
|
||||||
|
event,
|
||||||
|
colors: colors,
|
||||||
|
onInfoTab: (Event ev) => {},
|
||||||
|
onMention: () => {},
|
||||||
|
onSelect: (Event ev) => {},
|
||||||
|
onSwipe: () => {},
|
||||||
|
scrollToEventId: (String p0) => {},
|
||||||
|
timeline: timeline,
|
||||||
|
animateIn: false,
|
||||||
|
displayReadMarker: false,
|
||||||
|
highlightMarker: false,
|
||||||
|
longPressSelect: false,
|
||||||
|
selected: false,
|
||||||
|
wallpaperMode: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(L10n.of(context).translatedMessage)
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
message,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
class Translator {
|
||||||
|
static Future<String> translate(String str, String targetLanguage) async {
|
||||||
|
final url = Uri.parse('https://ecs.extera.xyz/translate/anything/auto/$targetLanguage');
|
||||||
|
final response = await http.post(
|
||||||
|
url,
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode({'text': str}),
|
||||||
|
);
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseData = jsonDecode(response.body);
|
||||||
|
return responseData['text'] ?? '';
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to translate text: ${response.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue