diff --git a/README.md b/README.md index 45f09e20..18812b8a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![pub package](https://img.shields.io/pub/v/alice_chopper.svg)](https://pub.dartlang.org/packages/alice_chopper) [![pub package](https://img.shields.io/pub/v/alice_http.svg)](https://pub.dartlang.org/packages/alice_http) [![pub package](https://img.shields.io/pub/v/alice_http_client.svg)](https://pub.dartlang.org/packages/alice_http_client) +[![pub package](https://img.shields.io/pub/v/alice_objectbox.svg)](https://pub.dartlang.org/packages/alice_objectbox) [![pub package](https://img.shields.io/badge/platform-flutter-blue.svg)](https://github.com/jhomlala/alice) [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) diff --git a/docs/install.md b/docs/install.md index f177b3d3..a691d6a3 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,7 +4,7 @@ ```yaml dependencies: - alice: ^1.0.0-dev7 + alice: ^1.0.0-dev8 ``` 2. Choose adapter based on your HTTP client. **pubspec.yaml** file: diff --git a/packages/alice/CHANGELOG.md b/packages/alice/CHANGELOG.md index 29529b2d..d777bef6 100644 --- a/packages/alice/CHANGELOG.md +++ b/packages/alice/CHANGELOG.md @@ -1,4 +1,9 @@ -# 1.0.0-dev-7 +# 1.0.0-dev.8 +* Added storage abstractions (by Klemen Tusar https://github.com/techouse). +* Added in memory storage implementation (by Klemen Tusar https://github.com/techouse). +* Added translations. + +# 1.0.0-dev.7 * Refactored UI code. # 1.0.0-dev.6 diff --git a/packages/alice/lib/core/alice_core.dart b/packages/alice/lib/core/alice_core.dart index f7ac926e..ae191238 100644 --- a/packages/alice/lib/core/alice_core.dart +++ b/packages/alice/lib/core/alice_core.dart @@ -9,6 +9,8 @@ import 'package:alice/model/alice_http_call.dart'; import 'package:alice/model/alice_http_error.dart'; import 'package:alice/model/alice_http_response.dart'; import 'package:alice/model/alice_log.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/ui/common/alice_navigation.dart'; import 'package:alice/utils/shake_detector.dart'; import 'package:flutter/material.dart'; @@ -139,7 +141,6 @@ class AliceCore { Future _onDidReceiveNotificationResponse( NotificationResponse response, ) async { - assert(response.payload != null, "payload can't be null"); navigateToCallListScreen(); } @@ -165,10 +166,14 @@ class AliceCore { BuildContext? getContext() => navigatorKey?.currentState?.overlay?.context; String _getNotificationMessage(AliceStats stats) => [ - if (stats.loading > 0) 'Loading: ${stats.loading}', - if (stats.successes > 0) 'Success: ${stats.successes}', - if (stats.redirects > 0) 'Redirect: ${stats.redirects}', - if (stats.errors > 0) 'Error: ${stats.errors}', + if (stats.loading > 0) + '${getContext()?.i18n(AliceTranslationKey.notificationLoading)} ${stats.loading}', + if (stats.successes > 0) + '${getContext()?.i18n(AliceTranslationKey.notificationSuccess)} ${stats.successes}', + if (stats.redirects > 0) + '${getContext()?.i18n(AliceTranslationKey.notificationRedirect)} ${stats.redirects}', + if (stats.errors > 0) + '${getContext()?.i18n(AliceTranslationKey.notificationError)} ${stats.errors}', ].join(' | '); Future _requestNotificationPermissions() async { @@ -207,7 +212,9 @@ class AliceCore { await _flutterLocalNotificationsPlugin?.show( 0, - 'Alice (total: ${stats.total} requests)', + getContext() + ?.i18n(AliceTranslationKey.notificationTotalRequests) + .replaceAll("[requestCount]", stats.total.toString()), message, _notificationDetails, payload: '', diff --git a/packages/alice/lib/core/alice_translations.dart b/packages/alice/lib/core/alice_translations.dart new file mode 100644 index 00000000..9b585146 --- /dev/null +++ b/packages/alice/lib/core/alice_translations.dart @@ -0,0 +1,356 @@ +import 'package:alice/model/alice_translation.dart'; + +class AliceTranslations { + static final List _translations = _initialise(); + + static List _initialise() { + List translations = []; + translations.add(_buildEnTranslations()); + translations.add(_buildPlTranslations()); + return translations; + } + + static AliceTranslationData _buildEnTranslations() { + return AliceTranslationData(languageCode: "en", values: { + AliceTranslationKey.alice: "Alice", + AliceTranslationKey.callDetails: "HTTP Call Details", + AliceTranslationKey.emailSubject: "Alice report", + AliceTranslationKey.callDetailsRequest: "Request", + AliceTranslationKey.callDetailsResponse: "Response", + AliceTranslationKey.callDetailsOverview: "Overview", + AliceTranslationKey.callDetailsError: "Error", + AliceTranslationKey.callDetailsEmpty: "Loading data failed", + AliceTranslationKey.callErrorScreenErrorEmpty: "Error is empty", + AliceTranslationKey.callErrorScreenError: "Error:", + AliceTranslationKey.callErrorScreenStacktrace: "Stack trace:", + AliceTranslationKey.callErrorScreenEmpty: "Nothing to display here", + AliceTranslationKey.callOverviewMethod: "Method:", + AliceTranslationKey.callOverviewServer: "Server:", + AliceTranslationKey.callOverviewEndpoint: "Endpoint:", + AliceTranslationKey.callOverviewStarted: "Started:", + AliceTranslationKey.callOverviewFinished: "Finished:", + AliceTranslationKey.callOverviewDuration: "Duration:", + AliceTranslationKey.callOverviewBytesSent: "Bytes sent:", + AliceTranslationKey.callOverviewBytesReceived: "Bytes received:", + AliceTranslationKey.callOverviewClient: "Client:", + AliceTranslationKey.callOverviewSecure: "Secure:", + AliceTranslationKey.callRequestStarted: "Started:", + AliceTranslationKey.callRequestBytesSent: "Bytes sent:", + AliceTranslationKey.callRequestContentType: "Content type:", + AliceTranslationKey.callRequestBody: "Body:", + AliceTranslationKey.callRequestBodyEmpty: "Body is empty", + AliceTranslationKey.callRequestFormDataFields: "Form data fields:", + AliceTranslationKey.callRequestFormDataFiles: "Form files:", + AliceTranslationKey.callRequestHeaders: "Headers:", + AliceTranslationKey.callRequestHeadersEmpty: "Headers are empty", + AliceTranslationKey.callRequestQueryParameters: "Query parameters", + AliceTranslationKey.callRequestQueryParametersEmpty: + "Query parameters are empty", + AliceTranslationKey.callResponseWaitingForResponse: + "Awaiting response...", + AliceTranslationKey.callResponseError: "Error", + AliceTranslationKey.callResponseReceived: "Received:", + AliceTranslationKey.callResponseBytesReceived: "Bytes received:", + AliceTranslationKey.callResponseStatus: "Status:", + AliceTranslationKey.callResponseHeaders: "Headers:", + AliceTranslationKey.callResponseHeadersEmpty: "Headers are empty", + AliceTranslationKey.callResponseBodyImage: "Body: Image", + AliceTranslationKey.callResponseBody: "Body:", + AliceTranslationKey.callResponseTooLargeToShow: "Too large to show", + AliceTranslationKey.callResponseBodyShow: "Show body", + AliceTranslationKey.callResponseLargeBodyShowWarning: + 'Warning! It will take some time to render output.', + AliceTranslationKey.callResponseBodyVideo: 'Body: Video', + AliceTranslationKey.callResponseBodyVideoWebBrowser: + 'Open video in web browser', + AliceTranslationKey.callResponseHeadersUnknown: "Unknown", + AliceTranslationKey.callResponseBodyUnknown: 'Unsupported body. Alice' + ' can render video/image/text body. Response has Content-Type: ' + "[contentType] which can't be handled. If you're feeling lucky you " + "can try button below to try render body as text, but it may fail.", + AliceTranslationKey.callResponseBodyUnknownShow: "Show unsupported body", + AliceTranslationKey.callsListInspector: "Inspector", + AliceTranslationKey.callsListLogger: "Logger", + AliceTranslationKey.callsListDeleteLogsDialogTitle: "Delete logs", + AliceTranslationKey.callsListDeleteLogsDialogDescription: + "Do you want to clear logs?", + AliceTranslationKey.callsListYes: "Yes", + AliceTranslationKey.callsListNo: "No", + AliceTranslationKey.callsListDeleteCallsDialogTitle: "Delete calls", + AliceTranslationKey.callsListDeleteCallsDialogDescription: + "Do you want to delete HTTP calls?", + AliceTranslationKey.callsListSearchHint: "Search HTTP call...", + AliceTranslationKey.callsListSort: "Sort", + AliceTranslationKey.callsListDelete: "Delete", + AliceTranslationKey.callsListStats: "Stats", + AliceTranslationKey.callsListSave: "Save", + AliceTranslationKey.logsEmpty: "There are no logs to show", + AliceTranslationKey.logsItemError: "Error:", + AliceTranslationKey.logsItemStackTrace: "Stack trace:", + AliceTranslationKey.logsCopied: "Copied to clipboard.", + AliceTranslationKey.sortDialogTitle: "Select filter", + AliceTranslationKey.sortDialogAscending: 'Ascending', + AliceTranslationKey.sortDialogDescending: "Descending", + AliceTranslationKey.sortDialogAccept: "Accept", + AliceTranslationKey.sortDialogCancel: "Cancel", + AliceTranslationKey.sortDialogTime: "Create time (default)", + AliceTranslationKey.sortDialogResponseTime: "Response time", + AliceTranslationKey.sortDialogResponseCode: "Response code", + AliceTranslationKey.sortDialogResponseSize: "Response size", + AliceTranslationKey.sortDialogEndpoint: "Endpoint", + AliceTranslationKey.statsTitle: "Stats", + AliceTranslationKey.statsTotalRequests: "Total requests:", + AliceTranslationKey.statsPendingRequests: "Pending requests:", + AliceTranslationKey.statsSuccessRequests: "Success requests:", + AliceTranslationKey.statsRedirectionRequests: "Redirection requests:", + AliceTranslationKey.statsErrorRequests: "Error requests:", + AliceTranslationKey.statsBytesSent: "Bytes sent:", + AliceTranslationKey.statsBytesReceived: "Bytes received:", + AliceTranslationKey.statsAverageRequestTime: "Average request time:", + AliceTranslationKey.statsMaxRequestTime: "Max request time:", + AliceTranslationKey.statsMinRequestTime: "Min request time:", + AliceTranslationKey.statsGetRequests: "GET requests:", + AliceTranslationKey.statsPostRequests: "POST requests:", + AliceTranslationKey.statsDeleteRequests: "DELETE requests:", + AliceTranslationKey.statsPutRequests: "PUT requests:", + AliceTranslationKey.statsPatchRequests: "PATCH requests:", + AliceTranslationKey.statsSecuredRequests: "Secured requests:", + AliceTranslationKey.statsUnsecuredRequests: "Unsecured requests:", + AliceTranslationKey.notificationLoading: "Loading:", + AliceTranslationKey.notificationSuccess: "Success:", + AliceTranslationKey.notificationRedirect: "Redirect:", + AliceTranslationKey.notificationError: "Error:", + AliceTranslationKey.notificationTotalRequests: + "Alice (total [requestCount] requests)", + AliceTranslationKey.saveDialogPermissionErrorTitle: "Permission error", + AliceTranslationKey.saveDialogPermissionErrorDescription: + "Permission not granted. Couldn't save logs.", + AliceTranslationKey.saveDialogEmptyErrorTitle: "Call history empty", + AliceTranslationKey.saveDialogEmptyErrorDescription: + "There are no calls to save.", + AliceTranslationKey.saveDialogFileSaveErrorTitle: "Save error", + AliceTranslationKey.saveDialogFileSaveErrorDescription: + "Failed to save http calls to file.", + AliceTranslationKey.saveSuccessTitle: "Logs saved", + AliceTranslationKey.saveSuccessDescription: + "Successfully saved logs in [path].", + AliceTranslationKey.saveSuccessView: "View file", + AliceTranslationKey.saveHeaderTitle: "Alice - HTTP Inspector", + AliceTranslationKey.saveHeaderAppName: "App name:", + AliceTranslationKey.saveHeaderPackage: "Package:", + AliceTranslationKey.saveHeaderVersion: "Version:", + AliceTranslationKey.saveHeaderBuildNumber: "Build number:", + AliceTranslationKey.saveHeaderGenerated: "Generated:", + AliceTranslationKey.saveLogId: "Id:", + AliceTranslationKey.saveLogGeneralData: "General data", + AliceTranslationKey.saveLogServer: "Server:", + AliceTranslationKey.saveLogMethod: "Method:", + AliceTranslationKey.saveLogEndpoint: "Endpoint:", + AliceTranslationKey.saveLogClient: "Client:", + AliceTranslationKey.saveLogDuration: "Duration:", + AliceTranslationKey.saveLogSecured: "Secured connection:", + AliceTranslationKey.saveLogCompleted: "Completed:", + AliceTranslationKey.saveLogRequest: "Request", + AliceTranslationKey.saveLogRequestTime: "Request time:", + AliceTranslationKey.saveLogRequestContentType: "Request content type:", + AliceTranslationKey.saveLogRequestCookies: "Request cookies:", + AliceTranslationKey.saveLogRequestHeaders: "Request headers:", + AliceTranslationKey.saveLogRequestQueryParams: "Request query params:", + AliceTranslationKey.saveLogRequestSize: "Request size:", + AliceTranslationKey.saveLogRequestBody: "Request body:", + AliceTranslationKey.saveLogResponse: "Response", + AliceTranslationKey.saveLogResponseTime: "Response time:", + AliceTranslationKey.saveLogResponseStatus: "Response status:", + AliceTranslationKey.saveLogResponseSize: "Response size:", + AliceTranslationKey.saveLogResponseHeaders: "Response headers:", + AliceTranslationKey.saveLogResponseBody: "Response body:", + AliceTranslationKey.saveLogError: "Error", + AliceTranslationKey.saveLogStackTrace: "Stack trace", + AliceTranslationKey.saveLogCurl: "Curl", + AliceTranslationKey.accept: "Accept", + AliceTranslationKey.parserFailed: "Failed to parse: ", + AliceTranslationKey.unknown: "Unknown", + }); + } + + static AliceTranslationData _buildPlTranslations() { + return AliceTranslationData(languageCode: "pl", values: { + AliceTranslationKey.alice: "Alice", + AliceTranslationKey.callDetails: "Połączenie HTTP - detale", + AliceTranslationKey.emailSubject: "Raport ALice", + AliceTranslationKey.callDetailsRequest: "Żądanie", + AliceTranslationKey.callDetailsResponse: "Odpowiedź", + AliceTranslationKey.callDetailsOverview: "Przegląd", + AliceTranslationKey.callDetailsError: "Błąd", + AliceTranslationKey.callDetailsEmpty: "Błąd ładowania danych", + AliceTranslationKey.callErrorScreenErrorEmpty: "Brak błędów", + AliceTranslationKey.callErrorScreenError: "Błąd:", + AliceTranslationKey.callErrorScreenStacktrace: "Ślad stosu:", + AliceTranslationKey.callErrorScreenEmpty: "Brak danych do wyświetlenia", + AliceTranslationKey.callOverviewMethod: "Metoda:", + AliceTranslationKey.callOverviewServer: "Serwer:", + AliceTranslationKey.callOverviewEndpoint: "Endpoint:", + AliceTranslationKey.callOverviewStarted: "Rozpoczęto:", + AliceTranslationKey.callOverviewFinished: "Zakończono:", + AliceTranslationKey.callOverviewDuration: "Czas trwania:", + AliceTranslationKey.callOverviewBytesSent: "Bajty wysłane:", + AliceTranslationKey.callOverviewBytesReceived: "Bajty odebrane:", + AliceTranslationKey.callOverviewClient: "Klient:", + AliceTranslationKey.callOverviewSecure: "Połączenie zabezpieczone:", + AliceTranslationKey.callRequestStarted: "Ropoczęto:", + AliceTranslationKey.callRequestBytesSent: "Bajty wysłane:", + AliceTranslationKey.callRequestContentType: "Typ zawartości:", + AliceTranslationKey.callRequestBody: "Body:", + AliceTranslationKey.callRequestBodyEmpty: "Body jest puste", + AliceTranslationKey.callRequestFormDataFields: "Pola forumlarza:", + AliceTranslationKey.callRequestFormDataFiles: "Pliki formularza:", + AliceTranslationKey.callRequestHeaders: "Headery:", + AliceTranslationKey.callRequestHeadersEmpty: "Headery są puste", + AliceTranslationKey.callRequestQueryParameters: "Parametry query", + AliceTranslationKey.callRequestQueryParametersEmpty: + "Parametry query są puste", + AliceTranslationKey.callResponseWaitingForResponse: + "Oczekiwanie na odpowiedź...", + AliceTranslationKey.callResponseError: "Błąd", + AliceTranslationKey.callResponseReceived: "Otrzymano:", + AliceTranslationKey.callResponseBytesReceived: "Bajty odebrane:", + AliceTranslationKey.callResponseStatus: "Status:", + AliceTranslationKey.callResponseHeaders: "Headery:", + AliceTranslationKey.callResponseHeadersEmpty: "Headery są puste", + AliceTranslationKey.callResponseBodyImage: "Body: Obraz", + AliceTranslationKey.callResponseBody: "Body:", + AliceTranslationKey.callResponseTooLargeToShow: "Za duże aby pokazać", + AliceTranslationKey.callResponseBodyShow: "Pokaż body", + AliceTranslationKey.callResponseLargeBodyShowWarning: + 'Uwaga! Może zająć trochę czasu, zanim uda się wyrenderować output.', + AliceTranslationKey.callResponseBodyVideo: 'Body: Video', + AliceTranslationKey.callResponseBodyVideoWebBrowser: + 'Otwórz video w przeglądarce', + AliceTranslationKey.callResponseHeadersUnknown: "Nieznane", + AliceTranslationKey.callResponseBodyUnknown: 'Nieznane body. Alice' + ' może renderować video/image/text. Odpowiedź ma typ zawartości:' + "[contentType], który nie może być obsłużony.Jeżeli chcesz, możesz " + "spróbować wyrenderować body jako tekst, ale może to się nie udać.", + AliceTranslationKey.callResponseBodyUnknownShow: + "Pokaż nieobsługiwane body", + AliceTranslationKey.callsListInspector: "Inspektor", + AliceTranslationKey.callsListLogger: "Logger", + AliceTranslationKey.callsListDeleteLogsDialogTitle: "Usuń logi", + AliceTranslationKey.callsListDeleteLogsDialogDescription: + "Czy chcesz usunąc logi?", + AliceTranslationKey.callsListYes: "Tak", + AliceTranslationKey.callsListNo: "Nie", + AliceTranslationKey.callsListDeleteCallsDialogTitle: "Usuń połączenia", + AliceTranslationKey.callsListDeleteCallsDialogDescription: + "Czy chcesz usunąć zapisane połaczenia HTTP?", + AliceTranslationKey.callsListSearchHint: "Szukaj połączenia HTTP...", + AliceTranslationKey.callsListSort: "Sortuj", + AliceTranslationKey.callsListDelete: "Usuń", + AliceTranslationKey.callsListStats: "Statystyki", + AliceTranslationKey.callsListSave: "Zapis", + AliceTranslationKey.logsEmpty: "Brak rezultatów", + AliceTranslationKey.logsItemError: "Błąd:", + AliceTranslationKey.logsItemStackTrace: "Ślad stosu:", + AliceTranslationKey.logsCopied: "Skopiowano do schowka.", + AliceTranslationKey.sortDialogTitle: "Wybierz filtr", + AliceTranslationKey.sortDialogAscending: 'Rosnąco', + AliceTranslationKey.sortDialogDescending: "Malejąco", + AliceTranslationKey.sortDialogAccept: "Akceptuj", + AliceTranslationKey.sortDialogCancel: "Anuluj", + AliceTranslationKey.sortDialogTime: "Czas utworzenia (domyślnie)", + AliceTranslationKey.sortDialogResponseTime: "Czas odpowiedzi", + AliceTranslationKey.sortDialogResponseCode: "Status odpowiedzi", + AliceTranslationKey.sortDialogResponseSize: "Rozmiar odpowiedzi", + AliceTranslationKey.sortDialogEndpoint: "Endpoint", + AliceTranslationKey.statsTitle: "Statystyki", + AliceTranslationKey.statsTotalRequests: "Razem żądań:", + AliceTranslationKey.statsPendingRequests: "Oczekujące żądania:", + AliceTranslationKey.statsSuccessRequests: "Poprawne żądania:", + AliceTranslationKey.statsRedirectionRequests: "Żądania przekierowania:", + AliceTranslationKey.statsErrorRequests: "Błędne żądania:", + AliceTranslationKey.statsBytesSent: "Bajty wysłane:", + AliceTranslationKey.statsBytesReceived: "Bajty otrzymane:", + AliceTranslationKey.statsAverageRequestTime: "Średni czas żądania:", + AliceTranslationKey.statsMaxRequestTime: "Maksymalny czas żądania:", + AliceTranslationKey.statsMinRequestTime: "Minimalny czas żądania:", + AliceTranslationKey.statsGetRequests: "Żądania GET:", + AliceTranslationKey.statsPostRequests: "Żądania POST:", + AliceTranslationKey.statsDeleteRequests: "Żądania DELETE:", + AliceTranslationKey.statsPutRequests: "Żądania PUT:", + AliceTranslationKey.statsPatchRequests: "Żądania PATCH:", + AliceTranslationKey.statsSecuredRequests: "Żądania zabezpieczone:", + AliceTranslationKey.statsUnsecuredRequests: "Żądania niezabezpieczone:", + AliceTranslationKey.notificationLoading: "Oczekujące:", + AliceTranslationKey.notificationSuccess: "Poprawne:", + AliceTranslationKey.notificationRedirect: "Przekierowanie:", + AliceTranslationKey.notificationError: "Błąd:", + AliceTranslationKey.notificationTotalRequests: + "Alice (razem [requestCount] żądań)", + AliceTranslationKey.saveDialogPermissionErrorTitle: "Błąd pozwolenia", + AliceTranslationKey.saveDialogPermissionErrorDescription: + "Pozwolenie nieprzyznane. Nie można zapisać logów.", + AliceTranslationKey.saveDialogEmptyErrorTitle: "Pusta historia połaczeń", + AliceTranslationKey.saveDialogEmptyErrorDescription: + "Nie ma połączeń do zapisania.", + AliceTranslationKey.saveDialogFileSaveErrorTitle: "Błąd zapisu", + AliceTranslationKey.saveDialogFileSaveErrorDescription: + "Nie można zapisać danych do pliku.", + AliceTranslationKey.saveSuccessTitle: "Logi zapisane", + AliceTranslationKey.saveSuccessDescription: "Zapisano logi w [path].", + AliceTranslationKey.saveSuccessView: "Otwórz plik", + AliceTranslationKey.saveHeaderTitle: "Alice - Inspektor HTTP", + AliceTranslationKey.saveHeaderAppName: "Nazwa aplikacji:", + AliceTranslationKey.saveHeaderPackage: "Paczka:", + AliceTranslationKey.saveHeaderVersion: "Wersja:", + AliceTranslationKey.saveHeaderBuildNumber: "Numer buildu:", + AliceTranslationKey.saveHeaderGenerated: "Wygenerowano:", + AliceTranslationKey.saveLogId: "Id:", + AliceTranslationKey.saveLogGeneralData: "Ogólne informacje", + AliceTranslationKey.saveLogServer: "Serwer:", + AliceTranslationKey.saveLogMethod: "Metoda:", + AliceTranslationKey.saveLogEndpoint: "Endpoint:", + AliceTranslationKey.saveLogClient: "Klient:", + AliceTranslationKey.saveLogDuration: "Czas trwania:", + AliceTranslationKey.saveLogSecured: "Połączenie zabezpieczone:", + AliceTranslationKey.saveLogCompleted: "Zakończono:", + AliceTranslationKey.saveLogRequest: "Żądanie", + AliceTranslationKey.saveLogRequestTime: "Czas żądania:", + AliceTranslationKey.saveLogRequestContentType: "Typ zawartości żądania:", + AliceTranslationKey.saveLogRequestCookies: "Ciasteczka żądania:", + AliceTranslationKey.saveLogRequestHeaders: "Heady żądania", + AliceTranslationKey.saveLogRequestQueryParams: "Parametry query żądania", + AliceTranslationKey.saveLogRequestSize: "Rozmiar żądania:", + AliceTranslationKey.saveLogRequestBody: "Body żądania:", + AliceTranslationKey.saveLogResponse: "Odpowiedź", + AliceTranslationKey.saveLogResponseTime: "Czas odpowiedzi:", + AliceTranslationKey.saveLogResponseStatus: "Status odpowiedzi:", + AliceTranslationKey.saveLogResponseSize: "Rozmiar odpowiedzi:", + AliceTranslationKey.saveLogResponseHeaders: "Headery odpowiedzi:", + AliceTranslationKey.saveLogResponseBody: "Body odpowiedzi:", + AliceTranslationKey.saveLogError: "Błąd", + AliceTranslationKey.saveLogStackTrace: "Ślad stosu", + AliceTranslationKey.saveLogCurl: "Curl", + AliceTranslationKey.accept: "Akceptuj", + AliceTranslationKey.parserFailed: "Problem z parsowaniem: ", + AliceTranslationKey.unknown: "Nieznane" + }); + } + + /// Returns localized value for specific [languageCode] and [key]. If value + /// can't be selected then [key] will be returned. + static String get({ + required String languageCode, + required AliceTranslationKey key, + }) { + try { + final data = _translations.firstWhere( + (element) => element.languageCode == languageCode, + orElse: () => _translations.first, + ); + final value = data.values[key] ?? key.toString(); + return value; + } catch (error) { + return key.toString(); + } + } +} diff --git a/packages/alice/lib/helper/alice_save_helper.dart b/packages/alice/lib/helper/alice_save_helper.dart index 5239d8f2..35aaa9b5 100644 --- a/packages/alice/lib/helper/alice_save_helper.dart +++ b/packages/alice/lib/helper/alice_save_helper.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use_build_context_synchronously + import 'dart:convert' show JsonEncoder; import 'dart:io' show Directory, File, FileMode, IOSink, Platform; @@ -5,9 +7,12 @@ import 'package:alice/core/alice_utils.dart'; import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/helper/operating_system.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/ui/common/alice_dialog.dart'; import 'package:alice/utils/alice_parser.dart'; import 'package:alice/utils/curl.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:open_filex/open_filex.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -61,8 +66,10 @@ class AliceSaveHelper { } else { AliceGeneralDialog.show( context: context, - title: 'Permission error', - description: "Permission not granted. Couldn't save logs.", + title: + context.i18n(AliceTranslationKey.saveDialogPermissionErrorTitle), + description: context + .i18n(AliceTranslationKey.saveDialogPermissionErrorDescription), ); } } @@ -76,8 +83,9 @@ class AliceSaveHelper { if (calls.isEmpty) { AliceGeneralDialog.show( context: context, - title: 'Error', - description: 'There are no logs to save', + title: context.i18n(AliceTranslationKey.saveDialogEmptyErrorTitle), + description: + context.i18n(AliceTranslationKey.saveDialogEmptyErrorDescription), ); return ''; } @@ -93,9 +101,9 @@ class AliceSaveHelper { 'alice_log_${DateTime.now().millisecondsSinceEpoch}.txt'; final File file = File('${externalDir.path}/$fileName')..createSync(); final IOSink sink = file.openWrite(mode: FileMode.append) - ..write(await _buildAliceLog()); + ..write(await _buildAliceLog(context: context)); for (final AliceHttpCall call in calls) { - sink.write(_buildCallLog(call)); + sink.write(_buildCallLog(context: context, call: call)); } await sink.flush(); await sink.close(); @@ -103,9 +111,13 @@ class AliceSaveHelper { if (context.mounted) { AliceGeneralDialog.show( context: context, - title: 'Success', - description: 'Successfully saved logs in ${file.path}', - secondButtonTitle: Platform.isAndroid ? 'View file' : null, + title: context.i18n(AliceTranslationKey.saveSuccessTitle), + description: context + .i18n(AliceTranslationKey.saveSuccessDescription) + .replaceAll("[path]", file.path), + secondButtonTitle: Platform.isAndroid + ? context.i18n(AliceTranslationKey.saveSuccessView) + : null, secondButtonAction: () => Platform.isAndroid ? OpenFilex.open(file.path) : null, ); @@ -116,8 +128,10 @@ class AliceSaveHelper { if (context.mounted) { AliceGeneralDialog.show( context: context, - title: 'Error', - description: 'Failed to save http calls to file', + title: + context.i18n(AliceTranslationKey.saveDialogFileSaveErrorTitle), + description: context + .i18n(AliceTranslationKey.saveDialogFileSaveErrorDescription), ); } } @@ -125,8 +139,9 @@ class AliceSaveHelper { if (context.mounted) { AliceGeneralDialog.show( context: context, - title: 'Error', - description: 'Failed to save http calls to file', + title: context.i18n(AliceTranslationKey.saveDialogFileSaveErrorTitle), + description: context + .i18n(AliceTranslationKey.saveDialogFileSaveErrorDescription), ); AliceUtils.log(exception.toString()); } @@ -135,78 +150,80 @@ class AliceSaveHelper { return ''; } - static Future _buildAliceLog() async { + static Future _buildAliceLog({required BuildContext context}) async { final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - return 'Alice - HTTP Inspector\n' - 'App name: ${packageInfo.appName}\n' - 'Package: ${packageInfo.packageName}\n' - 'Version: ${packageInfo.version}\n' - 'Build number: ${packageInfo.buildNumber}\n' - 'Generated: ${DateTime.now().toIso8601String()}\n' + return '${context.i18n(AliceTranslationKey.saveHeaderTitle)}\n' + '${context.i18n(AliceTranslationKey.saveHeaderAppName)} ${packageInfo.appName}\n' + '${context.i18n(AliceTranslationKey.saveHeaderPackage)} ${packageInfo.packageName}\n' + '${context.i18n(AliceTranslationKey.saveHeaderTitle)} ${packageInfo.version}\n' + '${context.i18n(AliceTranslationKey.saveHeaderBuildNumber)} ${packageInfo.buildNumber}\n' + '${context.i18n(AliceTranslationKey.saveHeaderGenerated)} ${DateTime.now().toIso8601String()}\n' '\n'; } - static String _buildCallLog(AliceHttpCall call) { + static String _buildCallLog( + {required BuildContext context, required AliceHttpCall call}) { final StringBuffer stringBuffer = StringBuffer() ..writeAll([ '===========================================\n', - 'Id: ${call.id}\n', + '${context.i18n(AliceTranslationKey.saveLogId)} ${call.id}\n', '============================================\n', '--------------------------------------------\n', - 'General data\n', + '${context.i18n(AliceTranslationKey.saveLogGeneralData)}\n', '--------------------------------------------\n', - 'Server: ${call.server} \n', - 'Method: ${call.method} \n', - 'Endpoint: ${call.endpoint} \n', - 'Client: ${call.client} \n', - 'Duration ${AliceConversionHelper.formatTime(call.duration)}\n', - 'Secured connection: ${call.secure}\n', - 'Completed: ${!call.loading} \n', + '${context.i18n(AliceTranslationKey.saveLogServer)} ${call.server} \n', + '${context.i18n(AliceTranslationKey.saveLogMethod)} ${call.method} \n', + '${context.i18n(AliceTranslationKey.saveLogEndpoint)} ${call.endpoint} \n', + '${context.i18n(AliceTranslationKey.saveLogClient)} ${call.client} \n', + '${context.i18n(AliceTranslationKey.saveLogDuration)} ${AliceConversionHelper.formatTime(call.duration)}\n', + '${context.i18n(AliceTranslationKey.saveLogSecured)} ${call.secure}\n', + '${context.i18n(AliceTranslationKey.saveLogCompleted)}: ${!call.loading} \n', '--------------------------------------------\n', - 'Request\n', + '${context.i18n(AliceTranslationKey.saveLogRequest)}\n', '--------------------------------------------\n', - 'Request time: ${call.request?.time}\n', - 'Request content type: ${call.request?.contentType}\n', - 'Request cookies: ${_encoder.convert(call.request?.cookies)}\n', - 'Request headers: ${_encoder.convert(call.request?.headers)}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestTime)} ${call.request?.time}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestContentType)}: ${call.request?.contentType}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestCookies)} ${_encoder.convert(call.request?.cookies)}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestHeaders)} ${_encoder.convert(call.request?.headers)}\n', ]); if (call.request?.queryParameters.isNotEmpty ?? false) { stringBuffer.write( - 'Request query params: ${_encoder.convert(call.request?.queryParameters)}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestQueryParams)} ${_encoder.convert(call.request?.queryParameters)}\n', ); } stringBuffer.writeAll([ - 'Request size: ${AliceConversionHelper.formatBytes(call.request?.size ?? 0)}\n', - 'Request body: ${AliceBodyParser.formatBody(call.request?.body, call.request?.contentType)}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestSize)} ${AliceConversionHelper.formatBytes(call.request?.size ?? 0)}\n', + '${context.i18n(AliceTranslationKey.saveLogRequestBody)} ${AliceBodyParser.formatBody(context: context, body: call.request?.body, contentType: call.request?.contentType)}\n', '--------------------------------------------\n', - 'Response\n', + '${context.i18n(AliceTranslationKey.saveLogResponse)}\n', '--------------------------------------------\n', - 'Response time: ${call.response?.time}\n', - 'Response status: ${call.response?.status}\n', - 'Response size: ${AliceConversionHelper.formatBytes(call.response?.size ?? 0)}\n', - 'Response headers: ${_encoder.convert(call.response?.headers)}\n', - 'Response body: ${AliceBodyParser.formatBody(call.response?.body, AliceBodyParser.getContentType(call.response?.headers))}\n', + '${context.i18n(AliceTranslationKey.saveLogResponseTime)} ${call.response?.time}\n', + '${context.i18n(AliceTranslationKey.saveLogResponseStatus)} ${call.response?.status}\n', + '${context.i18n(AliceTranslationKey.saveLogResponseSize)} ${AliceConversionHelper.formatBytes(call.response?.size ?? 0)}\n', + '${context.i18n(AliceTranslationKey.saveLogResponseHeaders)} ${_encoder.convert(call.response?.headers)}\n', + '${context.i18n(AliceTranslationKey.saveLogResponseBody)} ${AliceBodyParser.formatBody(context: context, body: call.response?.body, contentType: AliceBodyParser.getContentType(context: context, headers: call.response?.headers))}\n', ]); if (call.error != null) { stringBuffer.writeAll([ '--------------------------------------------\n', - 'Error\n', + '${context.i18n(AliceTranslationKey.saveLogError)}\n', '--------------------------------------------\n', - 'Error: ${call.error?.error}\n', + '${context.i18n(AliceTranslationKey.saveLogError)}: ${call.error?.error}\n', ]); if (call.error?.stackTrace != null) { - stringBuffer.write('Error stacktrace: ${call.error?.stackTrace}\n'); + stringBuffer.write( + '${context.i18n(AliceTranslationKey.saveLogStackTrace)}: ${call.error?.stackTrace}\n'); } } stringBuffer.writeAll([ '--------------------------------------------\n', - 'Curl\n', + '${context.i18n(AliceTranslationKey.saveLogCurl)}\n', '--------------------------------------------\n', getCurlCommand(call), '\n', @@ -217,9 +234,14 @@ class AliceSaveHelper { return stringBuffer.toString(); } - static Future buildCallLog(AliceHttpCall call) async { + static Future buildCallLog( + {required BuildContext context, required AliceHttpCall call}) async { try { - return await _buildAliceLog() + _buildCallLog(call); + return await _buildAliceLog(context: context) + + _buildCallLog( + call: call, + context: context, + ); } catch (exception) { return 'Failed to generate call log'; } diff --git a/packages/alice/lib/model/alice_translation.dart b/packages/alice/lib/model/alice_translation.dart new file mode 100644 index 00000000..477ecf7f --- /dev/null +++ b/packages/alice/lib/model/alice_translation.dart @@ -0,0 +1,161 @@ +/// Definition of translations for specific locale +class AliceTranslationData { + /// Language code of locale, i.e (en_US) => en. + final String languageCode; + + /// Translation values for language + final Map values; + + AliceTranslationData({ + required this.languageCode, + required this.values, + }); +} + +/// Definition of all available translation keys. +enum AliceTranslationKey { + alice, + callDetails, + emailSubject, + callDetailsOverview, + callDetailsRequest, + callDetailsResponse, + callDetailsError, + callDetailsEmpty, + callErrorScreenErrorEmpty, + callErrorScreenError, + callErrorScreenStacktrace, + callErrorScreenEmpty, + callOverviewMethod, + callOverviewServer, + callOverviewEndpoint, + callOverviewStarted, + callOverviewFinished, + callOverviewDuration, + callOverviewBytesSent, + callOverviewBytesReceived, + callOverviewClient, + callOverviewSecure, + callRequestStarted, + callRequestBytesSent, + callRequestContentType, + callRequestBody, + callRequestBodyEmpty, + callRequestFormDataFields, + callRequestFormDataFiles, + callRequestHeaders, + callRequestHeadersEmpty, + callRequestQueryParameters, + callRequestQueryParametersEmpty, + callResponseWaitingForResponse, + callResponseError, + callResponseReceived, + callResponseBytesReceived, + callResponseStatus, + callResponseHeaders, + callResponseHeadersEmpty, + callResponseBodyImage, + callResponseBody, + callResponseTooLargeToShow, + callResponseBodyShow, + callResponseLargeBodyShowWarning, + callResponseBodyVideo, + callResponseBodyVideoWebBrowser, + callResponseHeadersUnknown, + callResponseBodyUnknown, + callResponseBodyUnknownShow, + callsListInspector, + callsListLogger, + callsListDeleteLogsDialogTitle, + callsListDeleteLogsDialogDescription, + callsListYes, + callsListNo, + callsListDeleteCallsDialogTitle, + callsListDeleteCallsDialogDescription, + callsListSearchHint, + callsListSort, + callsListDelete, + callsListStats, + callsListSave, + logsEmpty, + logsItemError, + logsItemStackTrace, + logsCopied, + sortDialogTitle, + sortDialogAscending, + sortDialogDescending, + sortDialogAccept, + sortDialogCancel, + sortDialogTime, + sortDialogResponseTime, + sortDialogResponseCode, + sortDialogResponseSize, + sortDialogEndpoint, + statsTitle, + statsTotalRequests, + statsPendingRequests, + statsSuccessRequests, + statsRedirectionRequests, + statsErrorRequests, + statsBytesSent, + statsBytesReceived, + statsAverageRequestTime, + statsMaxRequestTime, + statsMinRequestTime, + statsGetRequests, + statsPostRequests, + statsDeleteRequests, + statsPutRequests, + statsPatchRequests, + statsSecuredRequests, + statsUnsecuredRequests, + notificationLoading, + notificationSuccess, + notificationRedirect, + notificationError, + notificationTotalRequests, + saveDialogPermissionErrorTitle, + saveDialogPermissionErrorDescription, + saveDialogEmptyErrorTitle, + saveDialogEmptyErrorDescription, + saveDialogFileSaveErrorTitle, + saveDialogFileSaveErrorDescription, + saveSuccessTitle, + saveSuccessDescription, + saveSuccessView, + saveHeaderTitle, + saveHeaderAppName, + saveHeaderPackage, + saveHeaderVersion, + saveHeaderBuildNumber, + saveHeaderGenerated, + saveLogId, + saveLogGeneralData, + saveLogServer, + saveLogMethod, + saveLogEndpoint, + saveLogClient, + saveLogDuration, + saveLogSecured, + saveLogCompleted, + saveLogRequest, + saveLogRequestTime, + saveLogRequestContentType, + saveLogRequestCookies, + saveLogRequestHeaders, + saveLogRequestQueryParams, + saveLogRequestSize, + saveLogRequestBody, + saveLogResponse, + saveLogResponseTime, + saveLogResponseStatus, + saveLogResponseSize, + saveLogResponseHeaders, + saveLogResponseBody, + saveLogError, + saveLogStackTrace, + saveLogCurl, + accept, + parserFailed, + unknown +} diff --git a/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart b/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart index 077931cf..c8b049f1 100644 --- a/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart +++ b/packages/alice/lib/ui/call_details/page/alice_call_details_page.dart @@ -1,13 +1,17 @@ +// ignore_for_file: use_build_context_synchronously + import 'package:alice/core/alice_core.dart'; import 'package:alice/helper/alice_save_helper.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/call_details/model/alice_call_details_tab.dart'; import 'package:alice/ui/call_details/widget/alice_call_error_screen.dart'; import 'package:alice/ui/call_details/widget/alice_call_overview_screen.dart'; import 'package:alice/ui/call_details/widget/alice_call_request_screen.dart'; import 'package:alice/ui/call_details/widget/alice_call_response_screen.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/ui/common/alice_page.dart'; -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:share_plus/share_plus.dart'; @@ -60,7 +64,8 @@ class _AliceCallDetailsPageState extends State ); }).toList(), ), - title: const Text('Alice - HTTP Call Details'), + title: Text('${context.i18n(AliceTranslationKey.alice)} -' + ' ${context.i18n(AliceTranslationKey.callDetails)}'), ), body: TabBarView( children: [ @@ -74,10 +79,7 @@ class _AliceCallDetailsPageState extends State ? FloatingActionButton( backgroundColor: AliceTheme.lightRed, key: const Key('share_key'), - onPressed: () async => await Share.share( - await AliceSaveHelper.buildCallLog(widget.call), - subject: 'Request Details', - ), + onPressed: () async => _saveCallsToFile(), child: const Icon( Icons.share, color: AliceTheme.white, @@ -89,23 +91,36 @@ class _AliceCallDetailsPageState extends State } } - return const Center(child: Text('Failed to load data')); + return Center( + child: Text( + context.i18n( + AliceTranslationKey.callDetailsEmpty, + ), + ), + ); }, ), ); } + void _saveCallsToFile() async { + await Share.share( + await AliceSaveHelper.buildCallLog(call: widget.call, context: context), + subject: context.i18n(AliceTranslationKey.emailSubject), + ); + } + /// Get tab name based on [item] type. String _getTabName({required AliceCallDetailsTabItem item}) { switch (item) { case AliceCallDetailsTabItem.overview: - return "Overview"; + return context.i18n(AliceTranslationKey.callDetailsOverview); case AliceCallDetailsTabItem.request: - return "Request"; + return context.i18n(AliceTranslationKey.callDetailsRequest); case AliceCallDetailsTabItem.response: - return "Response"; + return context.i18n(AliceTranslationKey.callDetailsResponse); case AliceCallDetailsTabItem.error: - return "Error"; + return context.i18n(AliceTranslationKey.callDetailsError); } } diff --git a/packages/alice/lib/ui/call_details/widget/alice_call_error_screen.dart b/packages/alice/lib/ui/call_details/widget/alice_call_error_screen.dart index be744bb2..4e2fa401 100644 --- a/packages/alice/lib/ui/call_details/widget/alice_call_error_screen.dart +++ b/packages/alice/lib/ui/call_details/widget/alice_call_error_screen.dart @@ -1,7 +1,9 @@ import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/call_details/widget/alice_call_expandable_list_row.dart'; import 'package:alice/ui/call_details/widget/alice_call_list_row.dart'; -import 'package:alice/utils/alice_scroll_behavior.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:alice/ui/common/alice_scroll_behavior.dart'; import 'package:flutter/material.dart'; /// Call error screen which displays info on HTTP call error. @@ -15,8 +17,9 @@ class AliceCallErrorScreen extends StatelessWidget { if (call.error != null) { final dynamic error = call.error?.error; final StackTrace? stackTrace = call.error?.stackTrace; - final String errorText = - error != null ? error.toString() : 'Error is empty'; + final String errorText = error != null + ? error.toString() + : context.i18n(AliceTranslationKey.callErrorScreenErrorEmpty); return Container( padding: const EdgeInsets.all(6), @@ -24,10 +27,13 @@ class AliceCallErrorScreen extends StatelessWidget { behavior: AliceScrollBehavior(), child: ListView( children: [ - AliceCallListRow(name: 'Error:', value: errorText), + AliceCallListRow( + name: context.i18n(AliceTranslationKey.callErrorScreenError), + value: errorText), if (stackTrace != null) AliceCallExpandableListRow( - name: 'Stack trace:', + name: context + .i18n(AliceTranslationKey.callErrorScreenStacktrace), value: stackTrace.toString(), ), ], @@ -35,8 +41,12 @@ class AliceCallErrorScreen extends StatelessWidget { ), ); } else { - return const Center( - child: Text('Nothing to display here'), + return Center( + child: Text( + context.i18n( + AliceTranslationKey.callErrorScreenEmpty, + ), + ), ); } } diff --git a/packages/alice/lib/ui/call_details/widget/alice_call_overview_screen.dart b/packages/alice/lib/ui/call_details/widget/alice_call_overview_screen.dart index 507320de..a1f8f08d 100644 --- a/packages/alice/lib/ui/call_details/widget/alice_call_overview_screen.dart +++ b/packages/alice/lib/ui/call_details/widget/alice_call_overview_screen.dart @@ -1,12 +1,15 @@ import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/call_details/widget/alice_call_list_row.dart'; -import 'package:alice/utils/alice_scroll_behavior.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:alice/ui/common/alice_scroll_behavior.dart'; import 'package:flutter/material.dart'; /// Screen which displays call overview data, for example method, server. class AliceCallOverviewScreen extends StatelessWidget { final AliceHttpCall call; + const AliceCallOverviewScreen({super.key, required this.call}); @override @@ -18,44 +21,46 @@ class AliceCallOverviewScreen extends StatelessWidget { child: ListView( children: [ AliceCallListRow( - name: 'Method: ', + name: context.i18n(AliceTranslationKey.callOverviewMethod), value: call.method, ), AliceCallListRow( - name: 'Server: ', + name: context.i18n(AliceTranslationKey.callOverviewServer), value: call.server, ), AliceCallListRow( - name: 'Endpoint: ', + name: context.i18n(AliceTranslationKey.callOverviewEndpoint), value: call.endpoint, ), AliceCallListRow( - name: 'Started:', + name: context.i18n(AliceTranslationKey.callOverviewStarted), value: call.request?.time.toString(), ), AliceCallListRow( - name: 'Finished:', + name: context.i18n(AliceTranslationKey.callOverviewFinished), value: call.response?.time.toString(), ), AliceCallListRow( - name: 'Duration:', + name: context.i18n(AliceTranslationKey.callOverviewDuration), value: AliceConversionHelper.formatTime(call.duration), ), AliceCallListRow( - name: 'Bytes sent:', + name: context.i18n(AliceTranslationKey.callOverviewBytesSent), value: AliceConversionHelper.formatBytes( call.request?.size ?? 0, ), ), AliceCallListRow( - name: 'Bytes received:', + name: context.i18n(AliceTranslationKey.callOverviewBytesReceived), value: AliceConversionHelper.formatBytes( call.response?.size ?? 0, ), ), - AliceCallListRow(name: 'Client:', value: call.client), AliceCallListRow( - name: 'Secure:', + name: context.i18n(AliceTranslationKey.callOverviewClient), + value: call.client), + AliceCallListRow( + name: context.i18n(AliceTranslationKey.callOverviewSecure), value: call.secure.toString(), ), ], diff --git a/packages/alice/lib/ui/call_details/widget/alice_call_request_screen.dart b/packages/alice/lib/ui/call_details/widget/alice_call_request_screen.dart index 017adbc6..2e5416d0 100644 --- a/packages/alice/lib/ui/call_details/widget/alice_call_request_screen.dart +++ b/packages/alice/lib/ui/call_details/widget/alice_call_request_screen.dart @@ -2,9 +2,12 @@ import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/model/alice_form_data_file.dart'; import 'package:alice/model/alice_from_data_field.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/call_details/widget/alice_call_list_row.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/utils/alice_parser.dart'; -import 'package:alice/utils/alice_scroll_behavior.dart'; +import 'package:alice/ui/common/alice_scroll_behavior.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; /// Screen which displays information about call request: content, transfer, @@ -17,21 +20,31 @@ class AliceCallRequestScreen extends StatelessWidget { @override Widget build(BuildContext context) { final List rows = [ - AliceCallListRow(name: 'Started:', value: call.request?.time.toString()), AliceCallListRow( - name: 'Bytes sent:', + name: context.i18n(AliceTranslationKey.callRequestStarted), + value: call.request?.time.toString()), + AliceCallListRow( + name: context.i18n(AliceTranslationKey.callRequestBytesSent), value: AliceConversionHelper.formatBytes(call.request?.size ?? 0)), AliceCallListRow( - name: 'Content type:', - value: AliceBodyParser.getContentType(call.request?.headers)), + name: context.i18n(AliceTranslationKey.callRequestContentType), + value: AliceBodyParser.getContentType( + context: context, headers: call.request?.headers)), ]; - rows.add(AliceCallListRow(name: 'Body:', value: _getBodyContent())); + rows.add(AliceCallListRow( + name: context.i18n(AliceTranslationKey.callRequestBody), + value: _getBodyContent( + context: context, + ), + )); final List? formDataFields = call.request?.formDataFields; if (formDataFields?.isNotEmpty ?? false) { - rows.add(const AliceCallListRow(name: 'Form data fields: ', value: '')); + rows.add(AliceCallListRow( + name: context.i18n(AliceTranslationKey.callRequestFormDataFields), + value: '')); rows.addAll([ for (final AliceFormDataField field in formDataFields!) AliceCallListRow(name: ' • ${field.name}:', value: field.value) @@ -40,7 +53,9 @@ class AliceCallRequestScreen extends StatelessWidget { final List? formDataFiles = call.request!.formDataFiles; if (formDataFiles?.isNotEmpty ?? false) { - rows.add(const AliceCallListRow(name: 'Form data files: ', value: '')); + rows.add(AliceCallListRow( + name: context.i18n(AliceTranslationKey.callRequestFormDataFiles), + value: '')); rows.addAll([ for (final AliceFormDataFile file in formDataFiles!) AliceCallListRow( @@ -51,9 +66,12 @@ class AliceCallRequestScreen extends StatelessWidget { } final Map? headers = call.request?.headers; - final String headersContent = - headers?.isEmpty ?? true ? 'Headers are empty' : ''; - rows.add(AliceCallListRow(name: 'Headers: ', value: headersContent)); + final String headersContent = headers?.isEmpty ?? true + ? context.i18n(AliceTranslationKey.callRequestHeadersEmpty) + : ''; + rows.add(AliceCallListRow( + name: context.i18n(AliceTranslationKey.callRequestHeaders), + value: headersContent)); rows.addAll([ for (final MapEntry header in headers?.entries ?? []) AliceCallListRow( @@ -61,10 +79,12 @@ class AliceCallRequestScreen extends StatelessWidget { ]); final Map? queryParameters = call.request?.queryParameters; - final String queryParametersContent = - queryParameters?.isEmpty ?? true ? 'Query parameters are empty' : ''; + final String queryParametersContent = queryParameters?.isEmpty ?? true + ? context.i18n(AliceTranslationKey.callRequestQueryParametersEmpty) + : ''; rows.add(AliceCallListRow( - name: 'Query Parameters: ', value: queryParametersContent)); + name: context.i18n(AliceTranslationKey.callRequestQueryParameters), + value: queryParametersContent)); rows.addAll([ for (final MapEntry queryParam in queryParameters?.entries ?? []) @@ -81,11 +101,16 @@ class AliceCallRequestScreen extends StatelessWidget { ); } - String _getBodyContent() { + /// Returns body content formatted. + String _getBodyContent({required BuildContext context}) { final dynamic body = call.request?.body; return body != null ? AliceBodyParser.formatBody( - body, AliceBodyParser.getContentType(call.request?.headers)) - : 'Body is empty'; + context: context, + body: body, + contentType: AliceBodyParser.getContentType( + context: context, headers: call.request?.headers), + ) + : context.i18n(AliceTranslationKey.callRequestBodyEmpty); } } diff --git a/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart b/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart index d9d9906b..e8790dc9 100644 --- a/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart +++ b/packages/alice/lib/ui/call_details/widget/alice_call_response_screen.dart @@ -1,8 +1,10 @@ import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/call_details/widget/alice_call_list_row.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/utils/alice_parser.dart'; -import 'package:alice/utils/alice_scroll_behavior.dart'; +import 'package:alice/ui/common/alice_scroll_behavior.dart'; import 'package:alice/utils/num_comparison.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -48,18 +50,21 @@ class _GeneralDataColumn extends StatelessWidget { @override Widget build(BuildContext context) { final int? status = call.response?.status; - final String statusText = status == -1 ? 'Error' : '$status'; + final String statusText = status == -1 + ? context.i18n(AliceTranslationKey.callResponseError) + : '$status'; return Column( children: [ AliceCallListRow( - name: 'Received:', value: call.response?.time.toString()), + name: context.i18n(AliceTranslationKey.callResponseReceived), + value: call.response?.time.toString()), AliceCallListRow( - name: 'Bytes received:', + name: context.i18n(AliceTranslationKey.callResponseBytesReceived), value: AliceConversionHelper.formatBytes(call.response?.size ?? 0), ), AliceCallListRow( - name: 'Status:', + name: context.i18n(AliceTranslationKey.callResponseStatus), value: statusText, ), ], @@ -75,12 +80,15 @@ class _HeaderDataColumn extends StatelessWidget { @override Widget build(BuildContext context) { final Map? headers = call.response?.headers; - final String headersContent = - headers?.isEmpty ?? true ? 'Headers are empty' : ''; + final String headersContent = headers?.isEmpty ?? true + ? context.i18n(AliceTranslationKey.callResponseHeadersEmpty) + : ''; return Column( children: [ - AliceCallListRow(name: 'Headers: ', value: headersContent), + AliceCallListRow( + name: context.i18n(AliceTranslationKey.callResponseHeaders), + value: headersContent), for (final MapEntry header in headers?.entries ?? []) AliceCallListRow( name: ' • ${header.key}:', @@ -162,7 +170,8 @@ class _BodyDataColumnState extends State<_BodyDataColumn> { } String? _getContentTypeOfResponse() { - return AliceBodyParser.getContentType(call.response?.headers); + return AliceBodyParser.getContentType( + context: context, headers: call.response?.headers); } bool _isLargeResponseBody() => @@ -192,11 +201,11 @@ class _ImageBody extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - const Row( + Row( children: [ Text( - 'Body: Image', - style: TextStyle(fontWeight: FontWeight.bold), + context.i18n(AliceTranslationKey.callResponseBodyImage), + style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), @@ -260,16 +269,25 @@ class _LargeTextBody extends StatelessWidget { } else { return Column(children: [ AliceCallListRow( - name: 'Body:', - value: 'Too large to show ' - '(${call.response?.body.toString().length ?? 0} Bytes)', + name: context.i18n(AliceTranslationKey.callResponseBody), + value: + '${context.i18n(AliceTranslationKey.callResponseTooLargeToShow)}' + '(${call.response?.body.toString().length ?? 0} B)', ), const SizedBox(height: 8), TextButton( onPressed: onShowLargeBodyPressed, - child: const Text('Show body'), + child: Text( + context.i18n( + AliceTranslationKey.callResponseBodyShow, + ), + ), + ), + Text( + context.i18n( + AliceTranslationKey.callResponseLargeBodyShowWarning, + ), ), - const Text('Warning! It will take some time to render output.') ]); } } @@ -284,8 +302,14 @@ class _TextBody extends StatelessWidget { Widget build(BuildContext context) { final Map? headers = call.response?.headers; final String bodyContent = AliceBodyParser.formatBody( - call.response?.body, AliceBodyParser.getContentType(headers)); - return AliceCallListRow(name: 'Body:', value: bodyContent); + context: context, + body: call.response?.body, + contentType: + AliceBodyParser.getContentType(context: context, headers: headers), + ); + return AliceCallListRow( + name: context.i18n(AliceTranslationKey.callResponseBody), + value: bodyContent); } } @@ -298,17 +322,18 @@ class _VideoBody extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - const Row( + Row( children: [ Text( - 'Body: Video', - style: TextStyle(fontWeight: FontWeight.bold), + context.i18n(AliceTranslationKey.callResponseBodyVideo), + style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 8), TextButton( - child: const Text('Open video in web browser'), + child: Text(context + .i18n(AliceTranslationKey.callResponseBodyVideoWebBrowser)), onPressed: () async { await launchUrl(Uri.parse(call.uri)); }, @@ -333,25 +358,38 @@ class _UnknownBody extends StatelessWidget { Widget build(BuildContext context) { final Map? headers = call.response?.headers; final String contentType = - AliceBodyParser.getContentType(headers) ?? ''; + AliceBodyParser.getContentType(context: context, headers: headers) ?? + context.i18n(AliceTranslationKey.callResponseHeadersUnknown); if (showUnsupportedBody) { final bodyContent = AliceBodyParser.formatBody( - call.response?.body, AliceBodyParser.getContentType(headers)); - return AliceCallListRow(name: 'Body:', value: bodyContent); + context: context, + body: call.response?.body, + contentType: + AliceBodyParser.getContentType(context: context, headers: headers), + ); + return AliceCallListRow( + name: context.i18n(AliceTranslationKey.callResponseBody), + value: bodyContent); } else { return Column( children: [ AliceCallListRow( - name: 'Body:', - value: - 'Unsupported body. Alice can render video/image/text body. ' - "Response has Content-Type: $contentType which can't be " - "handled. If you're feeling lucky you can try button below " - 'to try render body as text, but it may fail.'), + name: context.i18n(AliceTranslationKey.callResponseBody), + value: context + .i18n(AliceTranslationKey.callResponseBodyUnknown) + .replaceAll( + "[contentType]", + contentType, + ), + ), TextButton( onPressed: onShowUnsupportedBodyPressed, - child: const Text('Show unsupported body'), + child: Text( + context.i18n( + AliceTranslationKey.callResponseBodyUnknownShow, + ), + ), ), ], ); diff --git a/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart b/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart index c9fd7aa2..455d3631 100644 --- a/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart +++ b/packages/alice/lib/ui/calls_list/page/alice_calls_list_page.dart @@ -1,16 +1,18 @@ import 'package:alice/core/alice_core.dart'; import 'package:alice/core/alice_logger.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/call_details/model/alice_menu_item.dart'; import 'package:alice/ui/calls_list/model/alice_calls_list_sort_option.dart'; import 'package:alice/ui/calls_list/model/alice_calls_list_tab_item.dart'; import 'package:alice/ui/calls_list/widget/alice_inspector_screen.dart'; import 'package:alice/ui/calls_list/widget/alice_sort_dialog.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/ui/common/alice_dialog.dart'; import 'package:alice/ui/common/alice_navigation.dart'; import 'package:alice/ui/common/alice_page.dart'; import 'package:alice/ui/calls_list/widget/alice_logs_screen.dart'; -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:flutter/material.dart'; /// Page which displays list of calls caught by Alice. It displays tab view @@ -90,7 +92,11 @@ class _AliceCallsListPageState extends State textEditingController: _queryTextEditingController, onChanged: _updateSearchQuery, ) - : const Text('Alice'), + : Text( + context.i18n( + AliceTranslationKey.alice, + ), + ), actions: isLoggerTab ? [ IconButton( @@ -149,9 +155,9 @@ class _AliceCallsListPageState extends State String _getTabName({required AliceCallsListTabItem item}) { switch (item) { case AliceCallsListTabItem.inspector: - return "Inspector"; + return context.i18n(AliceTranslationKey.callsListInspector); case AliceCallsListTabItem.logger: - return "Logger"; + return context.i18n(AliceTranslationKey.callsListLogger); } } @@ -165,10 +171,11 @@ class _AliceCallsListPageState extends State /// user confirmation. void _onClearLogsPressed() => AliceGeneralDialog.show( context: context, - title: 'Delete logs', - description: 'Do you want to clear logs?', - firstButtonTitle: 'No', - secondButtonTitle: 'Yes', + title: context.i18n(AliceTranslationKey.callsListDeleteLogsDialogTitle), + description: context + .i18n(AliceTranslationKey.callsListDeleteLogsDialogDescription), + firstButtonTitle: context.i18n(AliceTranslationKey.callsListNo), + secondButtonTitle: context.i18n(AliceTranslationKey.callsListYes), secondButtonAction: _onLogsClearPressed, ); @@ -186,7 +193,7 @@ class _AliceCallsListPageState extends State } }); - /// Called when search button. It displays search textfield. + /// Called when search button. It displays search text field. void _onSearchPressed() => setState(() { _searchEnabled = !_searchEnabled; if (!_searchEnabled) { @@ -226,11 +233,13 @@ class _AliceCallsListPageState extends State /// Called when remove all calls button has been pressed. void _onRemovePressed() => AliceGeneralDialog.show( context: context, - title: 'Delete calls', - description: 'Do you want to delete http calls?', - firstButtonTitle: 'No', + title: + context.i18n(AliceTranslationKey.callsListDeleteCallsDialogTitle), + description: context + .i18n(AliceTranslationKey.callsListDeleteCallsDialogDescription), + firstButtonTitle: context.i18n(AliceTranslationKey.callsListNo), firstButtonAction: () => {}, - secondButtonTitle: 'Yes', + secondButtonTitle: context.i18n(AliceTranslationKey.callsListYes), secondButtonAction: _removeCalls, ); @@ -307,9 +316,9 @@ class _SearchTextField extends StatelessWidget { return TextField( controller: textEditingController, autofocus: true, - decoration: const InputDecoration( - hintText: 'Search http request...', - hintStyle: TextStyle(fontSize: 16, color: AliceTheme.grey), + decoration: InputDecoration( + hintText: context.i18n(AliceTranslationKey.callsListSearchHint), + hintStyle: const TextStyle(fontSize: 16, color: AliceTheme.grey), border: InputBorder.none, ), style: const TextStyle(fontSize: 16), @@ -318,7 +327,7 @@ class _SearchTextField extends StatelessWidget { } } -/// Menu button displayed in app bar. It displays overflow menu with addtional +/// Menu button displayed in app bar. It displays overflow menu with additional /// actions. class _ContextMenuButton extends StatelessWidget { const _ContextMenuButton({required this.onMenuItemSelected}); @@ -343,7 +352,10 @@ class _ContextMenuButton extends StatelessWidget { const Padding( padding: EdgeInsets.only(left: 10), ), - Text(_getTitle(itemType: item)), + Text(_getTitle( + context: context, + itemType: item, + )), ], ), ), @@ -352,16 +364,19 @@ class _ContextMenuButton extends StatelessWidget { } /// Get title of the menu item based on [itemType]. - String _getTitle({required AliceCallDetailsMenuItemType itemType}) { + String _getTitle({ + required BuildContext context, + required AliceCallDetailsMenuItemType itemType, + }) { switch (itemType) { case AliceCallDetailsMenuItemType.sort: - return "Sort"; + return context.i18n(AliceTranslationKey.callsListSort); case AliceCallDetailsMenuItemType.delete: - return "Delete"; + return context.i18n(AliceTranslationKey.callsListDelete); case AliceCallDetailsMenuItemType.stats: - return "Stats"; + return context.i18n(AliceTranslationKey.callsListStats); case AliceCallDetailsMenuItemType.save: - return "Save"; + return context.i18n(AliceTranslationKey.callsListSave); } } diff --git a/packages/alice/lib/ui/calls_list/widget/alice_call_list_item_widget.dart b/packages/alice/lib/ui/calls_list/widget/alice_call_list_item_widget.dart index c36ad100..3a780c8a 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_call_list_item_widget.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_call_list_item_widget.dart @@ -1,7 +1,7 @@ import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/model/alice_http_call.dart'; import 'package:alice/model/alice_http_response.dart'; -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:flutter/material.dart'; const int _endpointMaxLines = 10; diff --git a/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart b/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart index 3dfd2151..9ed7097e 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_calls_list_screen.dart @@ -1,7 +1,7 @@ import 'package:alice/model/alice_http_call.dart'; import 'package:alice/ui/calls_list/model/alice_calls_list_sort_option.dart'; import 'package:alice/ui/calls_list/widget/alice_call_list_item_widget.dart'; -import 'package:alice/utils/alice_scroll_behavior.dart'; +import 'package:alice/ui/common/alice_scroll_behavior.dart'; import 'package:flutter/material.dart'; /// Widget which displays calls list. It's hosted in tab in calls list page. diff --git a/packages/alice/lib/ui/calls_list/widget/alice_empty_logs_widget.dart b/packages/alice/lib/ui/calls_list/widget/alice_empty_logs_widget.dart index 282a5561..17283445 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_empty_logs_widget.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_empty_logs_widget.dart @@ -1,4 +1,6 @@ -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:flutter/material.dart'; /// Widget which renders empty text for calls list. @@ -11,18 +13,18 @@ class AliceEmptyLogsWidget extends StatelessWidget { Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 32), - child: const Center( + child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.error_outline, color: AliceTheme.orange, ), - SizedBox(height: 6), + const SizedBox(height: 6), Text( - 'There are no logs to show', - style: TextStyle(fontSize: 18), + context.i18n(AliceTranslationKey.logsEmpty), + style: const TextStyle(fontSize: 18), ), ], ), diff --git a/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart b/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart index 4058768d..2c83abce 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_log_list_widget.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'package:alice/model/alice_log.dart'; -import 'package:alice/utils/alice_scroll_behavior.dart'; -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:alice/ui/common/alice_scroll_behavior.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -83,10 +85,11 @@ class _AliceLogEntryWidget extends StatelessWidget { ), ), TextSpan(text: ' ${log.message}'), - ..._toText(context, 'Error', log.error), + ..._toText(context, context.i18n(AliceTranslationKey.logsItemError), + log.error), ..._toText( context, - 'Stack Trace', + context.i18n(AliceTranslationKey.logsItemStackTrace), log.stackTrace, addLineBreakAfterTitle: true, ), @@ -159,15 +162,23 @@ class _AliceLogEntryWidget extends StatelessWidget { final StringBuffer text = StringBuffer() ..writeAll([ '${log.timestamp}: ${log.message}\n', - if (error != null) 'Error: $error\n', - if (stackTrace != null) 'Stack Trace: $stackTrace\n', + if (error != null) + '${context.i18n(AliceTranslationKey.logsItemError)} $error\n', + if (stackTrace != null) + '${context.i18n(AliceTranslationKey.logsItemStackTrace)}: $stackTrace\n', ]); await Clipboard.setData(ClipboardData(text: text.toString())); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Copied!')), + SnackBar( + content: Text( + context.i18n( + AliceTranslationKey.logsCopied, + ), + ), + ), ); } } diff --git a/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart b/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart index 2ab1a20a..58537d7f 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_raw_log_list_widger.dart @@ -1,3 +1,5 @@ +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -52,8 +54,12 @@ class AliceRawLogListWidget extends StatelessWidget { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Copied!'), + SnackBar( + content: Text( + context.i18n( + AliceTranslationKey.logsCopied, + ), + ), ), ); } diff --git a/packages/alice/lib/ui/calls_list/widget/alice_sort_dialog.dart b/packages/alice/lib/ui/calls_list/widget/alice_sort_dialog.dart index 6cba9274..9614dc3a 100644 --- a/packages/alice/lib/ui/calls_list/widget/alice_sort_dialog.dart +++ b/packages/alice/lib/ui/calls_list/widget/alice_sort_dialog.dart @@ -1,4 +1,6 @@ +import 'package:alice/model/alice_translation.dart'; import 'package:alice/ui/calls_list/model/alice_calls_list_sort_option.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:flutter/material.dart'; /// Dialog which can be used to sort alice calls. @@ -23,15 +25,22 @@ class AliceSortDialog extends StatelessWidget { child: StatefulBuilder( builder: (context, setState) { return AlertDialog( - title: const Text('Select filter'), + title: Text( + context.i18n( + AliceTranslationKey.sortDialogTitle, + ), + ), content: Wrap( children: [ for (final AliceCallsListSortOption sortOption in AliceCallsListSortOption.values) RadioListTile( - title: Text(_getName( - option: sortOption, - )), + title: Text( + _getName( + context: context, + option: sortOption, + ), + ), value: sortOption, groupValue: currentSortOption, onChanged: (AliceCallsListSortOption? value) { @@ -45,7 +54,9 @@ class AliceSortDialog extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text('Descending'), + Text( + context.i18n(AliceTranslationKey.sortDialogDescending), + ), Switch( value: currentSortAscending, onChanged: (value) { @@ -56,7 +67,9 @@ class AliceSortDialog extends StatelessWidget { activeTrackColor: Colors.grey, activeColor: Colors.white, ), - const Text('Ascending'), + Text( + context.i18n(AliceTranslationKey.sortDialogAscending), + ), ], ), ], @@ -64,7 +77,9 @@ class AliceSortDialog extends StatelessWidget { actions: [ TextButton( onPressed: Navigator.of(context).pop, - child: const Text('Cancel'), + child: Text( + context.i18n(AliceTranslationKey.sortDialogCancel), + ), ), TextButton( onPressed: () { @@ -75,7 +90,9 @@ class AliceSortDialog extends StatelessWidget { ), ); }, - child: const Text('Use filter'), + child: Text( + context.i18n(AliceTranslationKey.sortDialogAccept), + ), ), ], ); @@ -85,13 +102,21 @@ class AliceSortDialog extends StatelessWidget { } /// Get sort option name based on [option]. - String _getName({required AliceCallsListSortOption option}) { + String _getName({ + required BuildContext context, + required AliceCallsListSortOption option, + }) { return switch (option) { - AliceCallsListSortOption.time => 'Create time (default)', - AliceCallsListSortOption.responseTime => 'Response time', - AliceCallsListSortOption.responseCode => 'Response code', - AliceCallsListSortOption.responseSize => 'Response size', - AliceCallsListSortOption.endpoint => 'Endpoint', + AliceCallsListSortOption.time => + context.i18n(AliceTranslationKey.sortDialogTime), + AliceCallsListSortOption.responseTime => + context.i18n(AliceTranslationKey.sortDialogResponseTime), + AliceCallsListSortOption.responseCode => + context.i18n(AliceTranslationKey.sortDialogResponseCode), + AliceCallsListSortOption.responseSize => + context.i18n(AliceTranslationKey.sortDialogResponseSize), + AliceCallsListSortOption.endpoint => + context.i18n(AliceTranslationKey.sortDialogEndpoint), }; } } diff --git a/packages/alice/lib/ui/common/alice_context_ext.dart b/packages/alice/lib/ui/common/alice_context_ext.dart new file mode 100644 index 00000000..30022cd6 --- /dev/null +++ b/packages/alice/lib/ui/common/alice_context_ext.dart @@ -0,0 +1,14 @@ +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/core/alice_translations.dart'; +import 'package:flutter/material.dart'; + +extension AliceContextExt on BuildContext { + String i18n(AliceTranslationKey key) { + try { + final locale = Localizations.localeOf(this); + return AliceTranslations.get(languageCode: locale.languageCode, key: key); + } catch (error) { + return key.toString(); + } + } +} diff --git a/packages/alice/lib/ui/common/alice_dialog.dart b/packages/alice/lib/ui/common/alice_dialog.dart index 02ae6473..3c0be6c6 100644 --- a/packages/alice/lib/ui/common/alice_dialog.dart +++ b/packages/alice/lib/ui/common/alice_dialog.dart @@ -1,4 +1,6 @@ -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:flutter/material.dart'; /// General dialogs used in Alice. @@ -8,7 +10,7 @@ class AliceGeneralDialog { required BuildContext context, required String title, required String description, - String firstButtonTitle = 'Accept', + String? firstButtonTitle, String? secondButtonTitle, Function? firstButtonAction, Function? secondButtonAction, @@ -28,7 +30,10 @@ class AliceGeneralDialog { firstButtonAction?.call(); Navigator.of(context).pop(); }, - child: Text(firstButtonTitle), + child: Text( + firstButtonTitle ?? + context.i18n(AliceTranslationKey.accept), + ), ), if (secondButtonTitle != null) TextButton( diff --git a/packages/alice/lib/ui/common/alice_page.dart b/packages/alice/lib/ui/common/alice_page.dart index 1f4167e1..748815f6 100644 --- a/packages/alice/lib/ui/common/alice_page.dart +++ b/packages/alice/lib/ui/common/alice_page.dart @@ -1,5 +1,5 @@ import 'package:alice/core/alice_core.dart'; -import 'package:alice/utils/alice_theme.dart'; +import 'package:alice/ui/common/alice_theme.dart'; import 'package:flutter/material.dart'; /// Common page widget which is used across Alice pages. diff --git a/packages/alice/lib/utils/alice_scroll_behavior.dart b/packages/alice/lib/ui/common/alice_scroll_behavior.dart similarity index 100% rename from packages/alice/lib/utils/alice_scroll_behavior.dart rename to packages/alice/lib/ui/common/alice_scroll_behavior.dart diff --git a/packages/alice/lib/utils/alice_theme.dart b/packages/alice/lib/ui/common/alice_theme.dart similarity index 100% rename from packages/alice/lib/utils/alice_theme.dart rename to packages/alice/lib/ui/common/alice_theme.dart diff --git a/packages/alice/lib/ui/stats/alice_stats_page.dart b/packages/alice/lib/ui/stats/alice_stats_page.dart index d969a5d3..7222498c 100644 --- a/packages/alice/lib/ui/stats/alice_stats_page.dart +++ b/packages/alice/lib/ui/stats/alice_stats_page.dart @@ -1,6 +1,8 @@ import 'package:alice/core/alice_core.dart'; import 'package:alice/helper/alice_conversion_helper.dart'; import 'package:alice/model/alice_http_call.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; import 'package:alice/ui/common/alice_page.dart'; import 'package:alice/ui/widget/alice_stats_row.dart'; import 'package:alice/utils/num_comparison.dart'; @@ -21,48 +23,67 @@ class AliceStatsPage extends StatelessWidget { core: aliceCore, child: Scaffold( appBar: AppBar( - title: const Text('Alice - HTTP Inspector - Stats'), + title: Text('${context.i18n(AliceTranslationKey.alice)} - ' + '${context.i18n(AliceTranslationKey.statsTitle)}'), ), body: Container( padding: const EdgeInsets.all(8), child: ListView( children: [ - AliceStatsRow('Total requests:', '${_getTotalRequests()}'), - AliceStatsRow('Pending requests:', '${_getPendingRequests()}'), - AliceStatsRow('Success requests:', '${_getSuccessRequests()}'), AliceStatsRow( - 'Redirection requests:', + context.i18n(AliceTranslationKey.statsTotalRequests), + '${_getTotalRequests()}'), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsPendingRequests), + '${_getPendingRequests()}'), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsSuccessRequests), + '${_getSuccessRequests()}'), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsRedirectionRequests), '${_getRedirectionRequests()}', ), - AliceStatsRow('Error requests:', '${_getErrorRequests()}'), AliceStatsRow( - 'Bytes send:', + context.i18n(AliceTranslationKey.statsErrorRequests), + '${_getErrorRequests()}'), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsBytesSent), AliceConversionHelper.formatBytes(_getBytesSent()), ), AliceStatsRow( - 'Bytes received:', + context.i18n(AliceTranslationKey.statsBytesReceived), AliceConversionHelper.formatBytes(_getBytesReceived()), ), AliceStatsRow( - 'Average request time:', + context.i18n(AliceTranslationKey.statsAverageRequestTime), AliceConversionHelper.formatTime(_getAverageRequestTime()), ), AliceStatsRow( - 'Max request time:', + context.i18n(AliceTranslationKey.statsMaxRequestTime), AliceConversionHelper.formatTime(_getMaxRequestTime()), ), AliceStatsRow( - 'Min request time:', + context.i18n(AliceTranslationKey.statsMinRequestTime), AliceConversionHelper.formatTime(_getMinRequestTime()), ), - AliceStatsRow('Get requests:', '${_getRequests('GET')} '), - AliceStatsRow('Post requests:', '${_getRequests('POST')} '), - AliceStatsRow('Delete requests:', '${_getRequests('DELETE')} '), - AliceStatsRow('Put requests:', '${_getRequests('PUT')} '), - AliceStatsRow('Patch requests:', '${_getRequests('PATCH')} '), - AliceStatsRow('Secured requests:', '${_getSecuredRequests()}'), + AliceStatsRow(context.i18n(AliceTranslationKey.statsGetRequests), + '${_getRequests('GET')} '), + AliceStatsRow(context.i18n(AliceTranslationKey.statsPostRequests), + '${_getRequests('POST')} '), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsDeleteRequests), + '${_getRequests('DELETE')} '), + AliceStatsRow(context.i18n(AliceTranslationKey.statsPutRequests), + '${_getRequests('PUT')} '), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsPatchRequests), + '${_getRequests('PATCH')} '), + AliceStatsRow( + context.i18n(AliceTranslationKey.statsSecuredRequests), + '${_getSecuredRequests()}'), AliceStatsRow( - 'Unsecured requests:', '${_getUnsecuredRequests()}'), + context.i18n(AliceTranslationKey.statsUnsecuredRequests), + '${_getUnsecuredRequests()}'), ], ), ), diff --git a/packages/alice/lib/utils/alice_parser.dart b/packages/alice/lib/utils/alice_parser.dart index 08e2ab15..f7432c20 100644 --- a/packages/alice/lib/utils/alice_parser.dart +++ b/packages/alice/lib/utils/alice_parser.dart @@ -1,14 +1,15 @@ import 'dart:convert'; +import 'package:alice/model/alice_translation.dart'; +import 'package:alice/ui/common/alice_context_ext.dart'; +import 'package:flutter/material.dart'; + /// Body parser helper used to parsing body data. class AliceBodyParser { - static const String _emptyBody = 'Body is empty'; - static const String _unknownContentType = 'Unknown'; static const String _jsonContentTypeSmall = 'content-type'; static const String _jsonContentTypeBig = 'Content-Type'; static const String _stream = 'Stream'; static const String _applicationJson = 'application/json'; - static const String _parseFailedText = 'Failed to parse '; static const JsonEncoder encoder = JsonEncoder.withIndent(' '); /// Tries to parse json. If it fails, it will return the json itself. @@ -32,13 +33,18 @@ class AliceBodyParser { /// Formats body based on [contentType]. If body is null it will return /// [_emptyBody]. Otherwise if body type is json - it will try to format it. /// - static String formatBody(dynamic body, String? contentType) { + static String formatBody({ + required BuildContext context, + required dynamic body, + String? contentType, + }) { try { if (body == null) { - return _emptyBody; + return context.i18n(AliceTranslationKey.callRequestBodyEmpty); } - String bodyContent = _emptyBody; + String bodyContent = + context.i18n(AliceTranslationKey.callRequestBodyEmpty); if (contentType == null || !contentType.toLowerCase().contains(_applicationJson)) { @@ -67,13 +73,14 @@ class AliceBodyParser { return bodyContent; } catch (_) { - return _parseFailedText + body.toString(); + return context.i18n(AliceTranslationKey.parserFailed) + body.toString(); } } /// Get content type from [headers]. It looks for json and if it can't find /// it, it will return unknown content type. - static String? getContentType(Map? headers) { + static String? getContentType( + {required BuildContext context, Map? headers}) { if (headers != null) { if (headers.containsKey(_jsonContentTypeSmall)) { return headers[_jsonContentTypeSmall] as String?; @@ -82,6 +89,6 @@ class AliceBodyParser { return headers[_jsonContentTypeBig] as String?; } } - return _unknownContentType; + return context.i18n(AliceTranslationKey.unknown); } } diff --git a/packages/alice/pubspec.yaml b/packages/alice/pubspec.yaml index 377d8122..6c82b106 100644 --- a/packages/alice/pubspec.yaml +++ b/packages/alice/pubspec.yaml @@ -1,6 +1,6 @@ name: alice description: Alice is an HTTP Inspector tool which helps debugging http requests. It catches and stores http requests and responses, which can be viewed via simple UI. -version: 1.0.0-dev.7 +version: 1.0.0-dev.8 homepage: https://github.com/jhomlala/alice repository: https://github.com/jhomlala/alice @@ -26,3 +26,4 @@ dependencies: dev_dependencies: flutter_lints: ^4.0.0 lints: ^4.0.0 + test: ^1.25.2 diff --git a/packages/alice/test/core/alice_translations_test.dart b/packages/alice/test/core/alice_translations_test.dart new file mode 100644 index 00000000..4561a78c --- /dev/null +++ b/packages/alice/test/core/alice_translations_test.dart @@ -0,0 +1,62 @@ +import 'package:alice/core/alice_translations.dart'; +import 'package:alice/model/alice_translation.dart'; +import 'package:test/test.dart'; + +void main() { + group("AliceTranslations", () { + test("should return translated value", () { + expect( + AliceTranslations.get( + languageCode: "en", + key: AliceTranslationKey.saveLogId, + ), + "Id:", + ); + + expect( + AliceTranslations.get( + languageCode: "en", + key: AliceTranslationKey.logsEmpty, + ), + "There are no logs to show", + ); + }); + + test("should return english translation when there's no translation found", + () { + expect( + AliceTranslations.get( + languageCode: "xx", + key: AliceTranslationKey.saveLogId, + ), + "Id:", + ); + + expect( + AliceTranslations.get( + languageCode: "xx", + key: AliceTranslationKey.logsEmpty, + ), + "There are no logs to show", + ); + }); + + test("should return translated key for other languages", () { + expect( + AliceTranslations.get( + languageCode: "pl", + key: AliceTranslationKey.logsEmpty, + ), + "Brak rezultatów", + ); + + expect( + AliceTranslations.get( + languageCode: "pl", + key: AliceTranslationKey.saveLogRequest, + ), + "Żądanie", + ); + }); + }); +}