in the midst of it

This commit is contained in:
William Jordan-Cooley 2024-01-20 07:32:45 -05:00 committed by William Jordan-Cooley
parent 695374ee46
commit 361690935d
16 changed files with 759 additions and 77 deletions

View file

@ -3956,5 +3956,6 @@
"inNoSpaces": "You are not a member of any classes or exchanges",
"successfullySubscribed": "You have successfully subscribed!",
"clickToManageSubscription": "Click here to manage your subscription.",
"emptyInviteWarning": "Add this chat to a class or exchange to invite other users."
"emptyInviteWarning": "Add this chat to a class or exchange to invite other users.",
"errorGettingAudio": "Error getting audio. Please refresh and try again."
}

View file

@ -1,17 +1,17 @@
import 'package:animations/animations.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
import 'package:fluffychat/pangea/widgets/chat/message_actions.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../config/themes.dart';
import 'chat.dart';
import 'input_bar.dart';
@ -58,18 +58,21 @@ class ChatInputRow extends StatelessWidget {
),
)
else
SizedBox(
height: 56,
child: TextButton(
onPressed: controller.forwardEventsAction,
child: Row(
children: <Widget>[
const Icon(Icons.keyboard_arrow_left_outlined),
Text(L10n.of(context)!.forward),
],
),
),
),
// #Pangea
PangeaMessageActions(chatController: controller),
// SizedBox(
// height: 56,
// child: TextButton(
// onPressed: controller.forwardEventsAction,
// child: Row(
// children: <Widget>[
// const Icon(Icons.keyboard_arrow_left_outlined),
// Text(L10n.of(context)!.forward),
// ],
// ),
// ),
// ),
// Pangea#
controller.selectedEvents.length == 1
? controller.selectedEvents.first
.getDisplayEvent(controller.timeline!)

View file

@ -29,7 +29,10 @@ import '../../utils/stream_extension.dart';
import 'chat_emoji_picker.dart';
import 'chat_input_row.dart';
enum _EventContextAction { info, report }
//#Pangea
// enum _EventContextAction { info, report }
enum _EventContextAction { info, forward, report }
//Pangea#
class ChatView extends StatelessWidget {
final ChatController controller;
@ -85,18 +88,34 @@ class ChatView extends StatelessWidget {
case _EventContextAction.report:
controller.reportEventAction();
break;
// #Pangea
case _EventContextAction.forward:
controller.forwardEventsAction();
break;
// Pangea#
}
},
itemBuilder: (context) => [
// #Pangea
// PopupMenuItem(
// value: _EventContextAction.info,
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Icons.info_outlined),
// const SizedBox(width: 12),
// Text(L10n.of(context)!.messageInfo),
// ],
// ),
// ),
PopupMenuItem(
value: _EventContextAction.info,
value: _EventContextAction.forward,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outlined),
const Icon(Icons.forward),
const SizedBox(width: 12),
Text(L10n.of(context)!.messageInfo),
Text(L10n.of(context)!.forward),
],
),
),

View file

@ -1,5 +1,5 @@
import 'package:fluffychat/pangea/widgets/flag.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../../../config/app_config.dart';
@ -55,3 +55,70 @@ class LanguageDisplayToggle extends StatelessWidget {
// );
}
}
class LanguageToggleSwitch extends StatefulWidget {
final ChatController controller;
const LanguageToggleSwitch({super.key, required this.controller});
@override
_LanguageToggleSwitchState createState() => _LanguageToggleSwitchState();
}
class _LanguageToggleSwitchState extends State<LanguageToggleSwitch> {
@override
Widget build(BuildContext context) {
final borderRadius =
BorderRadius.circular(20.0); // Use the same radius as your LanguageFlag
return Tooltip(
message: L10n.of(context)!.toggleLanguages,
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Colors.transparent, // No background color
shape: RoundedRectangleBorder(borderRadius: borderRadius),
padding: EdgeInsets.zero, // Aligns with your custom padding
),
onPressed: _toggleLanguage, // Use the onTap logic for onPressed
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.background, // Adapt to your app theme or custom color
borderRadius: borderRadius,
),
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
//trranslatte icon
Opacity(
opacity: isL1Selected ? 1.0 : 0.6,
child: LanguageFlag(
language: widget.controller.choreographer.l1Lang,
),
),
const SizedBox(width: 8.0), // Spacing between flags
Opacity(
opacity: isL1Selected ? 0.6 : 1.0,
child: LanguageFlag(
language: widget.controller.choreographer.l2Lang,
),
),
],
),
),
),
);
}
bool get isL1Selected =>
widget.controller.choreographer.messageOptions.isTranslationOn;
void _toggleLanguage() {
setState(() {
widget.controller.choreographer.messageOptions
.toggleSelectedDisplayLang();
});
}
}

View file

@ -51,7 +51,7 @@ class ModelKey {
static const String word = "word";
static const String lang = "lang";
static const String deepL = "deepl";
static const String langCode = 'langCode';
static const String langCode = 'lang_code';
static const String wordLang = "word_lang";
static const String lemma = "lemma";
static const String saveVocab = "save_vocab";
@ -87,4 +87,6 @@ class ModelKey {
static const String currentText = "current";
static const String bestContinuance = "best_continuance";
static const String feedbackLang = "feedback_lang";
static const String transcription = "transcription";
}

View file

@ -13,4 +13,6 @@ class PangeaEventTypes {
static const vocab = "p.vocab";
static const roomInfo = "pangea.roomtopic";
static const audio = "p.audio";
}

View file

@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/controllers/message_data_controller.dart';
import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart';
import 'package:fluffychat/pangea/controllers/permissions_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/controllers/user_controller.dart';
import 'package:fluffychat/pangea/controllers/word_net_controller.dart';
import 'package:fluffychat/pangea/guard/p_vguard.dart';
@ -44,6 +45,7 @@ class PangeaController {
late ITFeedbackController itFeedback;
late InstructionsController instructions;
late SubscriptionController subscriptionController;
late TextToSpeechController textToSpeech;
///store Services
late PLocalStore pStoreService;
@ -89,6 +91,7 @@ class PangeaController {
instructions = InstructionsController(this);
subscriptionController = SubscriptionController(this);
itFeedback = ITFeedbackController(this);
textToSpeech = TextToSpeechController(this);
PAuthGaurd.pController = this;
}

View file

@ -0,0 +1,125 @@
import 'dart:async';
import 'dart:convert';
import 'package:fluffychat/pangea/config/environment.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/network/urls.dart';
import 'package:http/http.dart';
import '../network/requests.dart';
class TextToSpeechRequest {
String text;
String langCode;
TextToSpeechRequest({required this.text, required this.langCode});
Map<String, dynamic> toJson() => {
ModelKey.text: text,
ModelKey.langCode: langCode,
};
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TextToSpeechRequest &&
other.text == text &&
other.langCode == langCode;
}
@override
int get hashCode => text.hashCode ^ langCode.hashCode;
}
class TextToSpeechResponse {
String audioContent;
String mediaType;
int durationMillis;
List<int> waveform;
TextToSpeechResponse({
required this.audioContent,
required this.mediaType,
required this.durationMillis,
required this.waveform,
});
factory TextToSpeechResponse.fromJson(
Map<String, dynamic> json,
) =>
TextToSpeechResponse(
audioContent: json["audio_content"],
mediaType: json["media_type"],
durationMillis: json["duration_millis"],
waveform: List<int>.from(json["wave_form"]),
);
}
class _TextToSpeechCacheItem {
Future<TextToSpeechResponse> data;
_TextToSpeechCacheItem({
required this.data,
});
}
class TextToSpeechController {
static final Map<TextToSpeechRequest, _TextToSpeechCacheItem> _cache = {};
late final PangeaController _pangeaController;
Timer? _cacheClearTimer;
TextToSpeechController(PangeaController pangeaController) {
_pangeaController = pangeaController;
_initializeCacheClearing();
}
void _initializeCacheClearing() {
const duration = Duration(minutes: 15); // Adjust the duration as needed
_cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
}
void _clearCache() {
_cache.clear();
}
void dispose() {
_cacheClearTimer?.cancel();
}
Future<TextToSpeechResponse> get(
TextToSpeechRequest params,
) async {
if (_cache.containsKey(params)) {
return _cache[params]!.data;
} else {
final Future<TextToSpeechResponse> response = _fetchResponse(
await _pangeaController.userController.accessToken,
params,
);
_cache[params] = _TextToSpeechCacheItem(data: response);
return response;
}
}
static Future<TextToSpeechResponse> _fetchResponse(
String accessToken,
TextToSpeechRequest params,
) async {
final Requests request = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: accessToken,
);
final Response res = await request.post(
url: PApiUrls.textToSpeech,
body: params.toJson(),
);
final Map<String, dynamic> json = jsonDecode(res.body);
return TextToSpeechResponse.fromJson(json);
}
}

View file

@ -0,0 +1,9 @@
// relates to a pangea representation event
// the matrix even fits the form of a regular matrix audio event
// but with something to distinguish it as a pangea audio event
import 'package:matrix/matrix.dart';
class PangeaAudioEvent {
Event? _event;
}

View file

@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/constants/pangea_message_types.dart';
import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/models/choreo_record.dart';
import 'package:fluffychat/pangea/models/message_data_models.dart';
@ -81,6 +83,116 @@ class PangeaMessageEvent {
return true;
}
//get audio for text and language
//if no audio exists, create it
//if audio exists, return it
Future<String?> getAudioGlobal(String langCode) async {
// try {
final String text = representationByLanguage(langCode)?.text ?? body;
final local = getAudioLocal(langCode, text);
if (local != null) return Future.value(local.eventId);
final TextToSpeechRequest params = TextToSpeechRequest(
text: text,
langCode: langCode,
);
final TextToSpeechResponse response =
await MatrixState.pangeaController.textToSpeech.get(
params,
);
if (response.mediaType != 'audio/ogg') {
throw Exception('Unexpected media type: ${response.mediaType}');
}
final audioBytes = base64.decode(response.audioContent);
// from text, trim whitespace, remove special characters, and limit to 20 characters
// final fileName =
// text.trim().replaceAll(RegExp('[^A-Za-z0-9]'), '').substring(0, 20);
final fileName = "audio_for_${eventId}_$langCode";
final file = MatrixAudioFile(
bytes: audioBytes,
name: fileName,
);
return room.sendFileEvent(
file,
inReplyTo: _event,
extraContent: {
'info': {
...file.info,
'duration': response.durationMillis,
},
'org.matrix.msc3245.voice': {},
'org.matrix.msc1767.audio': {
'duration': response.durationMillis,
'waveform': null,
// 'waveform': response.waveform,
},
'transcription': {
ModelKey.text: text,
ModelKey.langCode: langCode,
},
},
).timeout(
Durations.long4,
onTimeout: () {
debugPrint("timeout in getAudioGlobal");
return null;
},
).then((eventId) {
debugPrint("eventId in getAudioGlobal $eventId");
return eventId;
}).catchError((err, s) {
debugPrint("error in getAudioGlobal");
debugPrint(err);
debugPrint(s);
debugger(when: kDebugMode);
return null;
});
// } catch (err, s) {
// debugger(when: kDebugMode);
// ErrorHandler.logError(
// e: err,
// s: s,
// );
// return Future.value(null);
// }
}
Event? getAudioLocal(String langCode, String text) {
return allAudio.firstWhereOrNull(
(element) {
// Safely access the transcription map
final transcription =
element.content.tryGet<Map<String, String>>(ModelKey.transcription);
if (transcription == null) {
// If transcription is null, this element does not match.
return false;
}
// Safely get language code and text from the transcription
final elementLangCode = transcription.tryGet(ModelKey.langCode);
final elementText = transcription.tryGet(ModelKey.text);
// Check if both language code and text match
return elementLangCode == langCode && elementText == text;
},
);
}
// get audio events that are related to this event
Set<Event> get allAudio => _latestEdit.aggregatedEvents(
timeline,
EventTypes.Message,
);
List<RepresentationEvent>? _representations;
List<RepresentationEvent> get representations {
if (_representations != null) return _representations!;
@ -188,11 +300,6 @@ class PangeaMessageEvent {
RepresentationEvent? rep = representationByLanguage(langCode);
//if event is less than 1 minute old, then print new event
if (isNew) {
debugger(when: kDebugMode);
}
while ((isNew || eventId.contains("web")) && tries < 20) {
if (rep != null) return rep;
await Future.delayed(const Duration(milliseconds: 500));

View file

@ -51,6 +51,8 @@ class PApiUrls {
static String firstStep = "/it_initialstep";
static String subseqStep = "/it_step";
static String textToSpeech = "$choreoBaseApi/text_to_speech";
///-------------------------------- revenue cat --------------------------
static String rcApiV1 = "https://api.revenuecat.com/v1";
static String rcApiV2 =

View file

@ -0,0 +1,72 @@
import 'dart:convert';
import 'dart:developer';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import '../config/environment.dart';
import '../network/requests.dart';
class GenerateImageeResponse {
final String imageUrl;
final String prompt;
GenerateImageeResponse({
required this.imageUrl,
required this.prompt,
});
factory GenerateImageeResponse.fromJson(Map<String, dynamic> json) {
return GenerateImageeResponse(
imageUrl: json['image_url'],
prompt: json['prompt'],
);
}
factory GenerateImageeResponse.error() {
return GenerateImageeResponse(
imageUrl: 'https://i.imgur.com/2L2JYqk.png',
prompt: 'Error',
);
}
}
class GenerateImageRequest {
String prompt;
GenerateImageRequest({required this.prompt});
Map<String, dynamic> toJson() => {
'prompt': prompt,
};
}
class ImageRepo {
static Future<GenerateImageeResponse> fetchImage(
GenerateImageRequest request) async {
final Requests req =
Requests(baseUrl: Environment.choreoApi); // Set your API base URL
final requestBody = request.toJson();
try {
final Response res = await req.post(
url: '/generate-image/', // Endpoint in your FastAPI server
body: requestBody,
);
if (res.statusCode == 200) {
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
return GenerateImageeResponse.fromJson(
decodedBody); // Convert response to ImageModel
} else {
throw Exception('Failed to load image');
}
} catch (err, stack) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: err, s: stack, data: requestBody);
return GenerateImageeResponse
.error(); // Return an error model or handle accordingly
}
}
}

View file

@ -0,0 +1,66 @@
// import 'dart:async';
// import 'dart:convert';
// import 'package:fluffychat/pangea/config/environment.dart';
// import 'package:fluffychat/pangea/constants/model_keys.dart';
// import 'package:fluffychat/pangea/network/urls.dart';
// import 'package:http/http.dart';
// import '../network/requests.dart';
// class TextToSpeechRequest {
// String text;
// String langCode;
// TextToSpeechRequest({required this.text, required this.langCode});
// Map<String, dynamic> toJson() => {
// ModelKey.text: text,
// ModelKey.langCode: langCode,
// };
// }
// class TextToSpeechResponse {
// String audioContent;
// String mediaType;
// int durationMillis;
// List<int> waveform;
// TextToSpeechResponse({
// required this.audioContent,
// required this.mediaType,
// required this.durationMillis,
// required this.waveform,
// });
// factory TextToSpeechResponse.fromJson(
// Map<String, dynamic> json,
// ) =>
// TextToSpeechResponse(
// audioContent: json["audio_content"],
// mediaType: json["media_type"],
// durationMillis: json["duration_millis"],
// waveform: List<int>.from(json["wave_form"]),
// );
// }
// class TextToSpeechService {
// static Future<TextToSpeechResponse> get({
// required String accessToken,
// required TextToSpeechRequest params,
// }) async {
// final Requests request = Requests(
// choreoApiKey: Environment.choreoApiKey,
// accessToken: accessToken,
// );
// final Response res = await request.post(
// url: PApiUrls.textToSpeech,
// body: params.toJson(),
// );
// final Map<String, dynamic> json = jsonDecode(res.body);
// return TextToSpeechResponse.fromJson(json);
// }
// }

View file

@ -0,0 +1,27 @@
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/choreographer/widgets/language_display_toggle.dart';
import 'package:fluffychat/pangea/widgets/chat/text_to_speech_button.dart';
import 'package:flutter/material.dart';
class PangeaMessageActions extends StatelessWidget {
final ChatController chatController;
const PangeaMessageActions({super.key, required this.chatController});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
LanguageToggleSwitch(controller: chatController),
TextToSpeechButton(
controller: chatController,
),
// IconButton(
// icon: Icon(Icons.mic),
// onPressed: chatController.onMicTap,
// ),
// Add more IconButton widgets here
],
);
}
}

View file

@ -0,0 +1,128 @@
import 'dart:developer';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/constants/language_keys.dart';
import 'package:fluffychat/pangea/models/pangea_message_event.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:just_audio/just_audio.dart';
import 'package:matrix/matrix.dart';
class TextToSpeechButton extends StatefulWidget {
final ChatController controller;
const TextToSpeechButton({
super.key,
required this.controller,
});
@override
_TextToSpeechButtonState createState() => _TextToSpeechButtonState();
}
class _TextToSpeechButtonState extends State<TextToSpeechButton> {
final AudioPlayer _audioPlayer = AudioPlayer();
@override
void dispose() {
_audioPlayer.dispose();
super.dispose();
}
void _playSpeech() {
try {
final String langCode = widget.controller.choreographer.messageOptions
.selectedDisplayLang?.langCode ??
widget.controller.choreographer.l2LangCode ??
'en';
final Event event = widget.controller.selectedEvents.first;
PangeaMessageEvent(
event: event,
timeline: widget.controller.timeline!,
ownMessage: event.senderId == Matrix.of(context).client.userID,
selected: true,
).getAudioGlobal(langCode);
// final String? text = PangeaMessageEvent(
// event: event,
// timeline: widget.controller.timeline!,
// ownMessage: event.senderId == Matrix.of(context).client.userID,
// selected: true,
// ).representationByLanguage(langCode)?.text;
// if (text == null || text.isEmpty) {
// throw Exception("text is null or empty in text_to_speech_button.dart");
// }
// final TextToSpeechRequest params = TextToSpeechRequest(
// text: text,
// langCode: widget.controller.choreographer.messageOptions
// .selectedDisplayLang?.langCode ??
// widget.controller.choreographer.l2LangCode ??
// LanguageKeys.unknownLanguage,
// );
// final TextToSpeechResponse response = await TextToSpeechService.get(
// accessToken:
// await MatrixState.pangeaController.userController.accessToken,
// params: params,
// );
// if (response.mediaType != 'audio/ogg') {
// throw Exception('Unexpected media type: ${response.mediaType}');
// }
// // Decode the base64 audio content to bytes
// final audioBytes = base64.decode(response.audioContent);
// final encoding = Uri.dataFromBytes(audioBytes);
// final uri = AudioSource.uri(encoding);
// // gets here without problems
// await _audioPlayer.setAudioSource(uri);
// await _audioPlayer.play();
// final audioBytes = base64.decode(response.audioContent);
// final tempDir = await getTemporaryDirectory();
// final file = File('${tempDir.path}/speech.ogg');
// await file.writeAsBytes(audioBytes);
// await _audioPlayer.setFilePath(file.path);
// await _audioPlayer.play();
} catch (e) {
debugger(when: kDebugMode);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
L10n.of(context)!.errorGettingAudio,
),
),
);
ErrorHandler.logError(
e: Exception(),
s: StackTrace.current,
m: 'text is null or empty in text_to_speech_button.dart',
data: {
'event': widget.controller.selectedEvents.first,
'langCode': widget.controller.choreographer.messageOptions
.selectedDisplayLang?.langCode ??
widget.controller.choreographer.l2LangCode ??
LanguageKeys.unknownLanguage,
},
);
}
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _playSpeech,
child: const Text('Convert to Speech'),
);
}
}

View file

@ -765,7 +765,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"bn": [
@ -1539,7 +1540,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"bo": [
@ -2313,7 +2315,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ca": [
@ -3082,7 +3085,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"cs": [
@ -3851,7 +3855,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"de": [
@ -4620,7 +4625,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"el": [
@ -5394,7 +5400,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"eo": [
@ -6163,7 +6170,12 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"es": [
"errorGettingAudio"
],
"et": [
@ -6932,7 +6944,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"eu": [
@ -7701,7 +7714,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"fa": [
@ -8470,7 +8484,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"fi": [
@ -9239,7 +9254,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"fr": [
@ -10008,7 +10024,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ga": [
@ -10777,7 +10794,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"gl": [
@ -11546,7 +11564,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"he": [
@ -12315,7 +12334,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"hi": [
@ -13089,7 +13109,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"hr": [
@ -13858,7 +13879,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"hu": [
@ -14627,7 +14649,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"id": [
@ -15396,7 +15419,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ie": [
@ -16167,7 +16191,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"it": [
@ -16936,7 +16961,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ja": [
@ -17705,7 +17731,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ko": [
@ -18474,7 +18501,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"lt": [
@ -19243,7 +19271,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"lv": [
@ -20017,7 +20046,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"nb": [
@ -20786,7 +20816,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"nl": [
@ -21555,7 +21586,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"pl": [
@ -22324,7 +22356,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"pt": [
@ -23098,7 +23131,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"pt_BR": [
@ -23867,7 +23901,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"pt_PT": [
@ -24636,7 +24671,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ro": [
@ -25405,7 +25441,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ru": [
@ -26174,7 +26211,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"sk": [
@ -26944,7 +26982,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"sl": [
@ -27716,7 +27755,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"sr": [
@ -28485,7 +28525,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"sv": [
@ -29254,7 +29295,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"ta": [
@ -30028,7 +30070,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"th": [
@ -30802,7 +30845,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"tr": [
@ -31571,7 +31615,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"uk": [
@ -32340,7 +32385,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"vi": [
@ -33112,7 +33158,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"zh": [
@ -33881,7 +33928,8 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
],
"zh_Hant": [
@ -34650,6 +34698,7 @@
"activateTrial",
"successfullySubscribed",
"clickToManageSubscription",
"emptyInviteWarning"
"emptyInviteWarning",
"errorGettingAudio"
]
}