diff --git a/lib/content_views/accepted_jobs_page.dart b/lib/content_views/accepted_jobs_page.dart index 51a8600..22d6190 100644 --- a/lib/content_views/accepted_jobs_page.dart +++ b/lib/content_views/accepted_jobs_page.dart @@ -9,6 +9,7 @@ import '../main.dart'; import '../model/job.dart'; import '../objectbox.g.dart'; import '../popups/files_popup.dart'; +import '../tools/app_localizations.dart'; import '../tools/constants.dart'; import '../tools/util.dart'; @@ -237,11 +238,11 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ProducerMixin { SizedBox( height: 100, width: cellWidth * 2, - child: const Center( + child: Center( child: Text( textAlign: TextAlign.center, - "Angenommene Aufträge", - style: TextStyle( + 'accepted_jobs'.tr, + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white), diff --git a/lib/content_views/approach_area_page.dart b/lib/content_views/approach_area_page.dart index 7ed42a4..c9255dc 100644 --- a/lib/content_views/approach_area_page.dart +++ b/lib/content_views/approach_area_page.dart @@ -10,6 +10,7 @@ import 'package:stadtbote/tools/constants.dart'; import 'package:stadtbote/tools/messages/publish_payload_message.dart'; import 'package:badges/badges.dart' as badges; +import '../tools/app_localizations.dart'; import '../tools/util.dart'; class ApproachAreaPage extends ContentViewWidget { @@ -101,8 +102,8 @@ class _ApproachAreaPageState extends State { showZipPopup( context, - 'ACHTUNG: Wenn Sie Ihre PLZ ändern, verlieren Sie Ihr Ranking!\n', - 'Freimelden', + 'zip_change_warning'.tr, + 'release'.tr, (zip) => {_sendZip(rank, zip)}), } }, diff --git a/lib/content_views/assigned_jobs_page.dart b/lib/content_views/assigned_jobs_page.dart index 29e7bb0..4b33a29 100644 --- a/lib/content_views/assigned_jobs_page.dart +++ b/lib/content_views/assigned_jobs_page.dart @@ -18,6 +18,7 @@ import 'package:stadtbote/tools/messages/remove_state_0_job_from_map.dart'; import '../main.dart'; import '../model/job.dart'; import '../popups/files_popup.dart'; +import '../tools/app_localizations.dart'; import '../tools/constants.dart'; import '../tools/util.dart'; @@ -124,9 +125,9 @@ class _AssignedJobsPageState extends State with ProducerMixin, child: badges.Badge( position: badges.BadgePosition.topEnd(top: -13, end: -5), showBadge: true, - badgeContent: const Text( - 'Neu', - style: TextStyle( + badgeContent: Text( + 'new'.tr, + style: const TextStyle( color: Colors.white, fontSize: 18.0, ), @@ -159,7 +160,7 @@ class _AssignedJobsPageState extends State with ProducerMixin, ), ], ), - Text('Auftragsnummer: ${job.externalId}'), + Text('${'order_number'.tr}: ${job.externalId}'), Text(job.tours[0].comp), Text(job.tours[0].street), Text(job.tours[0].city), @@ -173,11 +174,11 @@ class _AssignedJobsPageState extends State with ProducerMixin, headerAlignment: ExpandablePanelHeaderAlignment.center, tapBodyToCollapse: true, ), - header: const Padding( - padding: EdgeInsets.all(0), + header: Padding( + padding: const EdgeInsets.all(0), child: Text( - "Weitere Infos....", - style: TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold), + 'more_info'.tr, + style: const TextStyle(fontSize: 15, color: Colors.black, fontWeight: FontWeight.bold), )), collapsed: const SizedBox(), expanded: Column( @@ -202,7 +203,7 @@ class _AssignedJobsPageState extends State with ProducerMixin, const SizedBox(height: 20), Row(children: [ Expanded( - child: getDefaultButton(!job.global ? 'Ablehnen' : 'Ignorieren', () { + child: getDefaultButton(!job.global ? 'decline'.tr : 'ignore'.tr, () { sendMessage( routingKey: Constants.messageTopicRemoveJob, payload: RemoveJobMessage(id: job.externalId), @@ -217,7 +218,7 @@ class _AssignedJobsPageState extends State with ProducerMixin, ), const SizedBox(width: 20), Expanded( - child: getDefaultButton('Annehmen', () { + child: getDefaultButton('accept'.tr, () { sendMessage( routingKey: Constants.messageTopicRemoveJob, payload: RemoveJobMessage(id: job.externalId), @@ -380,11 +381,11 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ProducerMixin { SizedBox( height: 100, width: cellWidth * 2, - child: const Center( + child: Center( child: Text( textAlign: TextAlign.center, - "Zugewiesene Aufträge", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white), + 'assigned_jobs'.tr, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white), ), ), ), diff --git a/lib/content_views/finished_jobs_page.dart b/lib/content_views/finished_jobs_page.dart index cc2bcc4..0a4ce3a 100644 --- a/lib/content_views/finished_jobs_page.dart +++ b/lib/content_views/finished_jobs_page.dart @@ -9,6 +9,7 @@ import 'package:stadtbote/messages/pop_content_view_message.dart'; import '../main.dart'; import '../model/job.dart'; import '../popups/files_popup.dart'; +import '../tools/app_localizations.dart'; import '../tools/constants.dart'; import '../tools/util.dart'; @@ -226,11 +227,11 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ProducerMixin { SizedBox( height: 100, width: cellWidth * 2, - child: const Center( + child: Center( child: Text( textAlign: TextAlign.center, - "Erledigte Aufträge", - style: TextStyle( + 'finished_jobs'.tr, + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white), diff --git a/lib/content_views/main_page.dart b/lib/content_views/main_page.dart index 8e9b4ea..e83ecfb 100644 --- a/lib/content_views/main_page.dart +++ b/lib/content_views/main_page.dart @@ -15,6 +15,7 @@ import 'package:stadtbote/model/chat_message.dart'; import 'package:stadtbote/model/session_status.dart'; import 'package:stadtbote/network_messages/logout_message.dart'; import 'package:stadtbote/popups/infos_popup.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/constants.dart'; import 'package:stadtbote/tools/messages/job_received_message.dart'; import 'package:stadtbote/tools/messages/publish_payload_message.dart'; @@ -104,7 +105,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { elevation: 0, ), child: getDefaultButton( - 'Zugewiesene Aufträge', + 'assigned_jobs'.tr, image: Image.asset('assets/zugewiesene_auftraege.png', width: 30, height: 30), () async => { @@ -144,7 +145,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { elevation: 0, ), child: getDefaultButton( - 'Angenommene Aufträge', + 'accepted_jobs'.tr, image: Image.asset( 'assets/angenommene_auftraege.png', width: 30, @@ -163,7 +164,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { Visibility( visible: !_loggedOut, child: getDefaultButton( - 'Erledigte Aufträge', + 'finished_jobs'.tr, image: Image.asset('assets/erledigte_auftraege.png', width: 30, height: 30), () async => { @@ -199,7 +200,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { elevation: 0, ), child: getDefaultButton( - 'Nachrichten', + 'messages'.tr, image: Image.asset('assets/nachrichten.png', width: 30, height: 30), () async => { @@ -216,7 +217,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { Visibility( visible: !_loggedOut, child: getDefaultButton( - 'Anfahrtsbereich', + 'approach_area'.tr, image: Image.asset('assets/anfahrtsbereich.png', width: 30, height: 30), () async => { @@ -230,7 +231,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { Visibility( visible: !_loggedOut && _showBWVButton, child: getDefaultButton( - 'Wunschziel für heute', + 'desired_zip'.tr, image: Image.asset('assets/wunsch_plz.png', width: 30, height: 30), () async => { @@ -241,7 +242,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { }), ), SizedBox(height: _loggedOut == true ? 0 : 20), - getDefaultButton(_loggedOut ? 'Anmelden' : 'Abmelden', + getDefaultButton(_loggedOut ? 'login'.tr : 'logout'.tr, image: Image.asset( _loggedOut ? 'assets/anmelden.png' @@ -360,12 +361,12 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { showLogoutAlertDialog(BuildContext context) async { Widget yesButton = TextButton( - child: const Text("Ja"), + child: Text('yes'.tr), onPressed: () async { Navigator.of(context).pop(); showMessageDialog( - context, "Information", "Sie werden abgemeldet.\nBitte warten..."); + context, 'information'.tr, 'logging_out'.tr); _sendLogoutMessage(); @@ -394,7 +395,7 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { ); Widget noButton = TextButton( - child: const Text("Nein"), + child: Text('no'.tr), onPressed: () { Navigator.of(context).pop(); }, @@ -402,8 +403,8 @@ class _MainPageState extends State with ConsumerMixin, ProducerMixin { // set up the AlertDialog AlertDialog alert = AlertDialog( - title: const Text("Warnung"), - content: const Text("Wollen Sie sich wirklich abmelden?"), + title: Text('warning'.tr), + content: Text('logout_confirm'.tr), actions: [ yesButton, noButton, @@ -547,18 +548,18 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ConsumerMixin { children: [ SizedBox( width: cellWidth, - child: const Text( - 'Meldung', + child: Text( + 'message'.tr, textAlign: TextAlign.center, - style: TextStyle(fontSize: 11, color: Colors.white), + style: const TextStyle(fontSize: 11, color: Colors.white), ), ), SizedBox( width: cellWidth, - child: const Text( - 'Ortung', + child: Text( + 'location'.tr, textAlign: TextAlign.center, - style: TextStyle(fontSize: 11, color: Colors.white), + style: const TextStyle(fontSize: 11, color: Colors.white), ), ), ], @@ -602,10 +603,10 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ConsumerMixin { ), Column( children: [ - const Text( - 'Kurierkennung', + Text( + 'courier_id'.tr, textAlign: TextAlign.center, - style: TextStyle(fontSize: 11, color: Colors.white), + style: const TextStyle(fontSize: 11, color: Colors.white), ), ValueListenableBuilder( valueListenable: _sidValueNotifier, diff --git a/lib/content_views/messages_page.dart b/lib/content_views/messages_page.dart index d75ee65..c6a7659 100644 --- a/lib/content_views/messages_page.dart +++ b/lib/content_views/messages_page.dart @@ -12,6 +12,7 @@ import 'package:stadtbote/network_messages/messages_showed.dart'; import 'package:stadtbote/tools/constants.dart'; import 'package:stadtbote/tools/messages/publish_payload_message.dart'; +import '../tools/app_localizations.dart'; import '../tools/util.dart'; class MessagesPage extends ContentViewWidget { @@ -51,7 +52,7 @@ class _MessagesPageState extends State with ProducerMixin { final box = MyApp.objectBoxStore.box(); final sessions = box.getAll(); var status = sessions.isNotEmpty ? sessions.first : null; - _sid = status != null ? status.sid : 'Unbekannt'; + _sid = status != null ? status.sid : 'unknown'.tr; _sendMessagesReadMessage(); @@ -165,17 +166,17 @@ class _MessagesPageState extends State with ProducerMixin { padding: const EdgeInsets.all(5), child: Column( children: [ - const Text('Soll die Nachricht gelöscht werden?'), + Text('delete_message_confirm'.tr), const SizedBox(height: 20), Row(children: [ Expanded( - child: getDefaultButton('Nein', + child: getDefaultButton('no'.tr, () => {Navigator.of(context).pop()}), ), const SizedBox(width: 20), Expanded( child: getDefaultButton( - 'Ja', + 'yes'.tr, () async => { MyApp.objectBoxStore.box() .remove(int.parse(message.id)), diff --git a/lib/content_views/set_desired_zip_page.dart b/lib/content_views/set_desired_zip_page.dart index c697a4f..aac784a 100644 --- a/lib/content_views/set_desired_zip_page.dart +++ b/lib/content_views/set_desired_zip_page.dart @@ -6,6 +6,7 @@ import 'package:stadtbote/messages/pop_content_view_message.dart'; import 'package:stadtbote/network_messages/set_desired_zip_message.dart'; import 'package:stadtbote/tools/messages/publish_payload_message.dart'; +import '../tools/app_localizations.dart'; import '../tools/constants.dart'; import '../tools/util.dart'; @@ -43,7 +44,7 @@ class _SetDesiredZipPageState extends State return Padding( padding: const EdgeInsets.all(8.0), child: Column(children: [ - const Text("Bitte geben Sie Ihr Wunschziel für heute ein"), + Text('enter_desired_zip'.tr), const SizedBox(height: 20), Row( mainAxisAlignment: @@ -67,7 +68,7 @@ class _SetDesiredZipPageState extends State ), const SizedBox(height: 20), getDefaultButton( - 'Übernehmen', + 'apply'.tr, () => { if (textEditController.text.isEmpty || textEditController.text.length == 5) @@ -82,8 +83,8 @@ class _SetDesiredZipPageState extends State } else { - showAlertDialog("Fehler", - "Das Feld muss leer sein, oder es müssen fünf Zahlen eingegeben werden") + showAlertDialog('error'.tr, + 'zip_validation_error'.tr) } }) ]), diff --git a/lib/content_views/task_list_page.dart b/lib/content_views/task_list_page.dart index 2ab4b6b..0ef1fd2 100644 --- a/lib/content_views/task_list_page.dart +++ b/lib/content_views/task_list_page.dart @@ -26,6 +26,7 @@ import 'package:stadtbote/tools/messages/publish_payload_message.dart'; import '../main.dart'; import '../model/task_info.dart'; +import '../tools/app_localizations.dart'; import '../tools/util.dart'; class TaskListPage extends ContentViewWidget { @@ -93,7 +94,7 @@ class _TaskListPageState extends State with ProducerMixin { visible: _commisionNumber.isNotEmpty, child: Row( children: [ - Text('Komm-#: $_commisionNumber', style: const TextStyle(fontSize: 20), textAlign: TextAlign.left), + Text('${'commission_number_short'.tr}: $_commisionNumber', style: const TextStyle(fontSize: 20), textAlign: TextAlign.left), ], ), ), @@ -314,7 +315,7 @@ class _TaskListPageState extends State with ProducerMixin { } taskInfo.answer = jsonEncode(answer.toJson()); - taskInfo.additionalText = "Barcodes gescannt"; + taskInfo.additionalText = 'barcodes_scanned'.tr; taskInfo.completed = true; setState(() {}); @@ -349,7 +350,7 @@ class _TaskListPageState extends State with ProducerMixin { box.put(picture); } - taskInfo.additionalText = "Fotos aufgenommen"; + taskInfo.additionalText = 'photos_taken'.tr; taskInfo.completed = true; if (mounted) { @@ -446,7 +447,7 @@ class _TaskListPageState extends State with ProducerMixin { answer.sign = signature; taskInfo.answer = jsonEncode(answer.toJson()); - taskInfo.additionalText = "Unterschrift erfasst"; + taskInfo.additionalText = 'signature_captured'.tr; taskInfo.completed = true; _sendFinTourMessage(); diff --git a/lib/content_views/tour_details_page.dart b/lib/content_views/tour_details_page.dart index 2a880c1..f338d6b 100644 --- a/lib/content_views/tour_details_page.dart +++ b/lib/content_views/tour_details_page.dart @@ -8,6 +8,7 @@ import 'package:stadtbote/model/job_tour.dart'; import '../model/job.dart'; import '../popups/files_popup.dart'; import '../popups/map_popup.dart'; +import '../tools/app_localizations.dart'; import '../tools/constants.dart'; import '../tools/util.dart'; @@ -59,7 +60,7 @@ class _TourDetailsPageState extends State with ProducerMixin { if (widget.tour.rem.isNotEmpty) { remark += '${widget.tour.rem}\n'; } else { - remark = "* Keine Bemerkung vorhanden *"; + remark = 'no_remark'.tr; } return Padding( @@ -77,7 +78,7 @@ class _TourDetailsPageState extends State with ProducerMixin { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - "${"Station ${widget.tour.sort}"} ${mapTourMode(widget.tour.mode)}", + "${'station'.tr} ${widget.tour.sort} ${mapTourMode(widget.tour.mode)}", textAlign: TextAlign.left, ), Text( @@ -97,11 +98,11 @@ class _TourDetailsPageState extends State with ProducerMixin { textAlign: TextAlign.left, ), Text( - "Tel.: ${widget.tour.phone}", + "${'phone'.tr} ${widget.tour.phone}", textAlign: TextAlign.left, ), Text( - "Komm-#: ${widget.tour.com}", + "${'commission_number'.tr}: ${widget.tour.com}", textAlign: TextAlign.left, ), if (widget.tour.files != null && widget.tour.files!.isNotEmpty) ...[ @@ -110,9 +111,9 @@ class _TourDetailsPageState extends State with ProducerMixin { children: [ FilesBadge(files: widget.tour.files), const SizedBox(width: 8), - const Text( - 'Dateien verfügbar', - style: TextStyle( + Text( + 'files_available'.tr, + style: const TextStyle( fontSize: 12, color: Colors.grey, ), @@ -122,7 +123,7 @@ class _TourDetailsPageState extends State with ProducerMixin { ], const SizedBox(height: 10), getDefaultButton( - 'Karte', + 'map'.tr, () => { if (widget.tour.pos != null) { @@ -131,8 +132,8 @@ class _TourDetailsPageState extends State with ProducerMixin { } else { - showAlertDialog('Information', - 'Es liegt keine Position für diese Station vor') + showAlertDialog('information'.tr, + 'no_position'.tr) } }), ], @@ -158,7 +159,7 @@ class _TourDetailsPageState extends State with ProducerMixin { ), const SizedBox(height: 10), getDefaultButton( - 'Weiter', + 'next'.tr, () async => { sendMessage( routingKey: Constants.messageTopicPushContentView, diff --git a/lib/content_views/tour_list_page.dart b/lib/content_views/tour_list_page.dart index 5192d70..12f8041 100644 --- a/lib/content_views/tour_list_page.dart +++ b/lib/content_views/tour_list_page.dart @@ -8,6 +8,7 @@ import 'package:stadtbote/popups/map_popup.dart'; import '../model/job.dart'; import '../popups/files_popup.dart'; +import '../tools/app_localizations.dart'; import '../tools/constants.dart'; import '../tools/util.dart'; @@ -102,7 +103,7 @@ class _TourListPageState extends State with ProducerMixin { foregroundColor: WidgetStateProperty.all(Colors.black), ), onPressed: () => {showMapPopup(context, widget.job.tours[0], widget.job.tours)}, - child: const Text("Karte")), + child: Text('map'.tr)), )) ], ), @@ -157,7 +158,7 @@ class _TourListPageState extends State with ProducerMixin { children: [ Expanded( child: Text( - "${"Station ${tour.sort}"} ${mapTourMode(tour.mode)}", + "${'station'.tr} ${tour.sort} ${mapTourMode(tour.mode)}", textAlign: TextAlign.left, ), ), diff --git a/lib/main.dart b/lib/main.dart index 54a7851..7076fc7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,6 +24,7 @@ import 'content_views/utils/content_view_widget.dart'; import 'content_views/utils/widget_stack.dart'; import 'model/job.dart'; import 'objectbox.g.dart'; +import 'tools/app_localizations.dart'; import 'tools/constants.dart'; import 'tools/util.dart'; @@ -32,6 +33,9 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Util.init(); + + // Lade die gespeicherte Sprache + AppLocalizations().loadSavedLanguage(); MQClient.initialize(); MQClient.instance.declareQueue(Constants.messageTopicRemoveJob); diff --git a/lib/popups/accept_jobs_popup.dart b/lib/popups/accept_jobs_popup.dart index 859f6e8..91b5e49 100644 --- a/lib/popups/accept_jobs_popup.dart +++ b/lib/popups/accept_jobs_popup.dart @@ -16,6 +16,7 @@ import 'package:stadtbote/tools/messages/remove_job_message.dart'; import 'package:stadtbote/tools/mqtt_core.dart'; import 'package:stadtbote/tools/util.dart'; +import '../tools/app_localizations.dart'; import '../tools/messages/publish_payload_message.dart'; final ScrollController _scrollController = ScrollController(); @@ -109,7 +110,7 @@ Future showAcceptJobsPopup(SharedMapReference mapReference, Function() onF ), ), ), - getDefaultButton("Liste schliessen", () { + getDefaultButton('close_list'.tr, () { isActive = false; if (builderCreated) { extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobReceived); @@ -191,7 +192,7 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent), ), ), - SizedBox(width: double.infinity, child: Text('Auftragsnummer: ${job.id}', textAlign: TextAlign.left)), + SizedBox(width: double.infinity, child: Text('${'order_number'.tr}: ${job.id}', textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].comp, textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].street, textAlign: TextAlign.left)), SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)), @@ -212,7 +213,7 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini const SizedBox(height: 10), Row(children: [ Expanded( - child: getDefaultButton(!job.global ? 'Ablehnen' : 'Ignorieren', () { + child: getDefaultButton(!job.global ? 'decline'.tr : 'ignore'.tr, () { dialogState(() { _sendAnswer(mapReference, job, !job.global ? 2 : 3); @@ -229,7 +230,7 @@ Future> _fetchJobWidgets(StateSetter dialogState, Function() onFini ), const SizedBox(width: 20), Expanded( - child: getDefaultButton('Annehmen', enabledTimerActive: false, () { + child: getDefaultButton('accept'.tr, enabledTimerActive: false, () { dialogState(() { _sendAnswer(mapReference, job, 1); diff --git a/lib/popups/infos_popup.dart b/lib/popups/infos_popup.dart index a40931b..d76855f 100644 --- a/lib/popups/infos_popup.dart +++ b/lib/popups/infos_popup.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stadtbote/main.dart'; import 'package:stadtbote/model/session_status.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/mqtt_core.dart'; import 'package:stadtbote/tools/util.dart'; @@ -10,7 +11,9 @@ void showInfosPopup(SessionStatus? status) async { await showDialog( context: MyApp.appNavigatorKey.currentContext!, builder: (BuildContext context) { - late String connectedString = MqttCore.instance.getConnectionStatus() == ConnectionStateEnum.connected ? "Verbunden" : "NICHT verbunden"; + late String connectedString = MqttCore.instance.getConnectionStatus() == ConnectionStateEnum.connected + ? 'connected'.tr + : 'not_connected'.tr; return AlertDialog( contentPadding: EdgeInsets.zero, @@ -18,31 +21,42 @@ void showInfosPopup(SessionStatus? status) async { backgroundColor: Colors.white, content: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { Future getGpsStatustext() async { - String gpsStatus = "nicht verfügbar"; + String gpsStatus = 'gps_not_available'.tr; var currentPosition = await MyApp.getCurrentPosition(); if (currentPosition != null) { var nowUtc = DateTime.now().toUtc(); var minutesDelta = nowUtc.difference(currentPosition.timestamp); if (minutesDelta >= const Duration(minutes: 15)) { - gpsStatus = "veraltet"; + gpsStatus = 'gps_outdated'.tr; } else { - gpsStatus = "aktuell"; + gpsStatus = 'gps_available'.tr; } } return gpsStatus; } + void onLanguageChanged(AppLanguage? newLanguage) { + if (newLanguage != null) { + AppLocalizations().setLanguage(newLanguage); + setState(() {}); + + // Sende Nachricht an MainPage, um UI zu aktualisieren + // Wir verwenden einen einfachen Ansatz: Die App wird neu geladen + // In einer echten App würde man hier einen State-Management-Mechanismus verwenden + } + } + return FutureBuilder( future: getGpsStatustext(), builder: (context, snapshot) { - String gpsStatus = snapshot.data ?? "wird geladen..."; + String gpsStatus = snapshot.data ?? 'loading'.tr; - var text = "Versionsnummer: a$version\n"; - text += "Verbindungsstatus: $connectedString\n"; - text += "GPS-Status: $gpsStatus\n"; + var text = "${'version'.tr}: a$version\n"; + text += "${'connection_status'.tr}: $connectedString\n"; + text += "${'gps_status'.tr}: $gpsStatus\n"; text += "\n"; - text += "User Status:\n"; + text += "${'user_status'.tr}:\n"; if (status != null) { text += "cr_id: ${status.crId}\n"; text += "cust_id: ${status.custId}\n"; @@ -51,12 +65,12 @@ void showInfosPopup(SessionStatus? status) async { text += "server_version: ${status.serverVersion}\n"; text += "server_instance: ${status.serverInstance}\n"; } else { - text += "Keine Informationen gefunden"; + text += 'no_info_found'.tr; } return SizedBox( key: GlobalKey(), - height: 350, + height: 450, width: MediaQuery.of(context).size.width * 0.8, child: Container( height: 100, @@ -72,9 +86,44 @@ void showInfosPopup(SessionStatus? status) async { padding: const EdgeInsets.all(12.0), child: Column( children: [ - Expanded(child: Text(text, textAlign: TextAlign.left)), + Expanded(child: SingleChildScrollView(child: Text(text, textAlign: TextAlign.left))), + const Divider(), + // Sprachauswahl + Row( + children: [ + Text('${'language'.tr}: ', style: const TextStyle(fontWeight: FontWeight.bold)), + Expanded( + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: AppLocalizations().currentLanguage, + items: AppLanguage.values.map((AppLanguage language) { + return DropdownMenuItem( + value: language, + child: Row( + children: [ + Text(language.flag, style: const TextStyle(fontSize: 20)), + const SizedBox(width: 8), + Flexible( + child: Text( + language.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14), + ), + ), + ], + ), + ); + }).toList(), + onChanged: onLanguageChanged, + ), + ), + ), + ], + ), + const SizedBox(height: 12), getDefaultButton( - 'OK', + 'ok'.tr, () { Navigator.pop(context); }, @@ -88,5 +137,3 @@ void showInfosPopup(SessionStatus? status) async { })); }); } - - diff --git a/lib/popups/map_popup.dart b/lib/popups/map_popup.dart index 7434378..7158abc 100644 --- a/lib/popups/map_popup.dart +++ b/lib/popups/map_popup.dart @@ -4,6 +4,7 @@ import 'package:animated_toggle_switch/animated_toggle_switch.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; import '../model/job_tour.dart'; @@ -41,7 +42,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List tourList) currentTour = tour; streamControllers[tour.sort]!.sink.add(true); } else { - showAlertDialog('Information', 'Es liegt keine Position für diese Station vor'); + showAlertDialog('information'.tr, 'no_position'.tr); } }, child: SizedBox( @@ -64,7 +65,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List tourList) height: 12.0, width: 70, child: Text( - "Station ${tour.sort}", + "${'station'.tr} ${tour.sort}", softWrap: true, textAlign: TextAlign.center, style: const TextStyle( @@ -164,7 +165,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List tourList) mapController!.move(mapController!.camera.center, b ? 14.0 : 16.0); }, styleBuilder: (i) => ToggleStyle(indicatorColor: i == true ? _colors[0] : _colors[1]), - textBuilder: (value) => value ? const Center(child: Text('Fern')) : const Center(child: Text('Nah')), + textBuilder: (value) => value ? Center(child: Text('far'.tr)) : Center(child: Text('near'.tr)), ); }, ), diff --git a/lib/popups/tasks_popups/commision_number_popup.dart b/lib/popups/tasks_popups/commision_number_popup.dart index 3d01b68..8792576 100644 --- a/lib/popups/tasks_popups/commision_number_popup.dart +++ b/lib/popups/tasks_popups/commision_number_popup.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stadtbote/model/task_info.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; showComissionNumberPopup(BuildContext context, TaskInfo taskInfo, Function(String) onFinished) async { @@ -42,15 +43,15 @@ showComissionNumberPopup(BuildContext context, TaskInfo taskInfo, Function(Strin padding: const EdgeInsets.all(35.0), child: Column( children: [ - const Text('Bitte geben Sie die Kommisionsnummer ein'), + Text('enter_commission_number'.tr), const SizedBox(height: 20), - Util.getDefaultTextField('Kommisionsnummer', + Util.getDefaultTextField('commission_number_hint'.tr, textInputType: TextInputType.text, formatter: [FilteringTextInputFormatter.singleLineFormatter], controller: textEditingController, autofocus: true), const SizedBox(height: 20), - getDefaultButton('Übernehmen', () { + getDefaultButton('apply'.tr, () { final result = textEditingController.text; textEditingController.dispose(); Navigator.of(context).pop(); diff --git a/lib/popups/tasks_popups/pickup_delivery_status_popup.dart b/lib/popups/tasks_popups/pickup_delivery_status_popup.dart index 3cea51c..a147251 100644 --- a/lib/popups/tasks_popups/pickup_delivery_status_popup.dart +++ b/lib/popups/tasks_popups/pickup_delivery_status_popup.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stadtbote/model/task_info.dart'; import 'package:stadtbote/model/tour_task.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(TourEntry?, String? remark) onFinished) async { @@ -58,7 +59,7 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function( DropdownMenu( expandedInsets: const EdgeInsets.all(0), controller: controller, - hintText: 'Status', + hintText: 'status'.tr, onSelected: (TourEntry? value) { selectedTourEntry = value; @@ -68,9 +69,9 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function( dropdownMenuEntries: entries, ), const SizedBox(height: 20), - Visibility(visible: selectedTourEntry != null ? selectedTourEntry!.queryRemark : false, child: Util.getDefaultTextField(controller: remController, "Bemerkung")), + Visibility(visible: selectedTourEntry != null ? selectedTourEntry!.queryRemark : false, child: Util.getDefaultTextField(controller: remController, 'remark'.tr)), const SizedBox(height: 20), - getDefaultButton('Übernehmen', buttonHeight: 50, () => { + getDefaultButton('apply'.tr, buttonHeight: 50, () => { if (selectedTourEntry!.queryRemark) { if (remController.text.isNotEmpty) { Navigator.of(context).pop(), @@ -80,7 +81,7 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function( onFinished(selectedTourEntry, remController.text) } else { - showAlertDialog("Fehler", "Bitte geben Sie eine Bemerkung ein") + showAlertDialog('error'.tr, 'enter_remark'.tr) } } else { Navigator.of(context).pop(), diff --git a/lib/popups/tasks_popups/receipt_giver_popup.dart b/lib/popups/tasks_popups/receipt_giver_popup.dart index a8eeac6..b8bbcc1 100644 --- a/lib/popups/tasks_popups/receipt_giver_popup.dart +++ b/lib/popups/tasks_popups/receipt_giver_popup.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stadtbote/model/task_info.dart'; import 'package:stadtbote/model/tour_task.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String, TourEntry) onFinished) async { @@ -57,7 +58,7 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String, DropdownMenu( expandedInsets: const EdgeInsets.all(0), controller: controller, - hintText: 'Geschlecht', + hintText: 'gender'.tr, onSelected: (TourEntry? value) { selectedTourEntry = value; @@ -73,7 +74,7 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String, maxLength: 40, autofocus: true), const SizedBox(height: 20), - getDefaultButton('Übernehmen', () { + getDefaultButton('apply'.tr, () { if (textEditingController.text.isNotEmpty && selectedTourEntry != null) { Navigator.of(context).pop(); @@ -83,7 +84,7 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String, onFinished(result, selectedTourEntry!); } else { - showAlertDialog("Fehler", "Sie müssen einen Text eingeben und ein Geschlecht auswählen"); + showAlertDialog('error'.tr, 'enter_text_and_gender'.tr); } }) ], diff --git a/lib/popups/tasks_popups/signature_popup.dart b/lib/popups/tasks_popups/signature_popup.dart index ff83d03..671e238 100644 --- a/lib/popups/tasks_popups/signature_popup.dart +++ b/lib/popups/tasks_popups/signature_popup.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:signature/signature.dart'; import 'package:stadtbote/model/task_info.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; showSignaturePopup(BuildContext context, TaskInfo taskInfo, Function(String?) onFinished) async { @@ -78,13 +79,13 @@ showSignaturePopup(BuildContext context, TaskInfo taskInfo, Function(String?) on ), ), getDefaultButton( - 'Übernehmen', + 'apply'.tr, buttonHeight: 50, () => { if (controller.points.length > 50) {Navigator.of(context).pop(), onFinished(convertPoints(controller.points))} else - {showAlertDialog("Fehler", "Die Unterschrift ist zu kurz.")} + {showAlertDialog('error'.tr, 'signature_too_short'.tr)} }) ], ), diff --git a/lib/popups/tasks_popups/station_notice_popup.dart b/lib/popups/tasks_popups/station_notice_popup.dart index f350946..32c9f31 100644 --- a/lib/popups/tasks_popups/station_notice_popup.dart +++ b/lib/popups/tasks_popups/station_notice_popup.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stadtbote/model/task_info.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; showStationNoticePopup(BuildContext context, TaskInfo taskInfo, Function(String) onFinished) async { @@ -54,7 +55,7 @@ showStationNoticePopup(BuildContext context, TaskInfo taskInfo, Function(String) autofocus: true), ), const SizedBox(height: 20), - getDefaultButton('Übernehmen', buttonHeight: 50, () { + getDefaultButton('apply'.tr, buttonHeight: 50, () { final result = textEditingController.text; textEditingController.dispose(); Navigator.of(context).pop(); diff --git a/lib/popups/tasks_popups/take_photos_popup.dart b/lib/popups/tasks_popups/take_photos_popup.dart index ac2b848..92b358f 100644 --- a/lib/popups/tasks_popups/take_photos_popup.dart +++ b/lib/popups/tasks_popups/take_photos_popup.dart @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image/image.dart' as image_lib; import 'package:stadtbote/model/task_info.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; Future showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Function(List) onFinished) async { @@ -53,7 +54,7 @@ Future showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi onTap: () {}, child: GestureDetector( onTap: () async { - await showQuestionDialog(context, "Achtung", "Wollen Sie das Bild wirklich löschen?", [ + await showQuestionDialog(context, 'attention'.tr, 'delete_photo_confirm'.tr, [ TextButton(onPressed: () => {Navigator.of(context).pop(), imageDataList.removeAt(currentCarousselSliderIndex), imageWidgetsList.removeAt(currentCarousselSliderIndex), @@ -63,8 +64,8 @@ Future showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi currentCarousselSliderIndex = 0, setState(() {}) - }, child: const Text('Ja')), - TextButton(onPressed: () => {Navigator.of(context).pop()}, child: const Text('Nein')), + }, child: Text('yes'.tr)), + TextButton(onPressed: () => {Navigator.of(context).pop()}, child: Text('no'.tr)), ]); }, child: Image.memory(resizedImageData), @@ -99,7 +100,7 @@ Future showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi padding: const EdgeInsets.all(35.0), child: Column( children: [ - const Text('Aufgenommene Fotos'), + Text('photos'.tr), const SizedBox(height: 20), Container( decoration: BoxDecoration( @@ -129,16 +130,16 @@ Future showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi const SizedBox(height: 20), Visibility( visible: imageDataList.length < (taskInfo.task?.max ?? 999), - child: getDefaultButton('Hinzufügen', buttonHeight: 50, () => {capturePhoto()})), + child: getDefaultButton('add'.tr, buttonHeight: 50, () => {capturePhoto()})), const SizedBox(height: 12), getDefaultButton( - 'Übernehmen', + 'apply'.tr, buttonHeight: 50, () => { if (imageDataList.length >= (taskInfo.task?.min ?? 0) && imageDataList.length <= (taskInfo.task?.max ?? 999)) {Navigator.of(context).pop(), onFinished(imageDataList)} else - {showAlertDialog('Fehler', 'Es müssen mindestens ${taskInfo.task?.min ?? 0} Foto(s) aufgenommen werden')} + {showAlertDialog('error'.tr, 'min_photos_error'.trParams({'min': taskInfo.task?.min ?? 0}))} }) ], ), diff --git a/lib/popups/tasks_popups/zip_popup.dart b/lib/popups/tasks_popups/zip_popup.dart index 894a20d..1e65912 100644 --- a/lib/popups/tasks_popups/zip_popup.dart +++ b/lib/popups/tasks_popups/zip_popup.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/util.dart'; showZipPopup(BuildContext context, String text, String buttonText, @@ -55,7 +56,7 @@ showZipPopup(BuildContext context, String text, String buttonText, style: const TextStyle( fontWeight: FontWeight.bold)), const SizedBox(height: 20), - Util.getDefaultTextField('Postleitzahl', + Util.getDefaultTextField('zip'.tr, formatter: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(5), @@ -89,11 +90,11 @@ bool _checkInput(BuildContext context, String input) { String message = ''; if (input.length != 5) { - message += 'Bitte geben Sie eine fünfstellige Postleitzahl ein'; + message += 'enter_zip'.tr; } if (message.isNotEmpty) { - showAlertDialog('Fehler', message); + showAlertDialog('error'.tr, message); } return message.isEmpty; diff --git a/lib/tools/app_localizations.dart b/lib/tools/app_localizations.dart new file mode 100644 index 0000000..eee638a --- /dev/null +++ b/lib/tools/app_localizations.dart @@ -0,0 +1,1306 @@ +import 'package:stadtbote/tools/util.dart'; + +enum AppLanguage { + german('de', 'Deutsch', '🇩🇪'), + english('en', 'English', '🇬🇧'), + spanish('es', 'Español', '🇪🇸'), + french('fr', 'Français', '🇫🇷'), + polish('pl', 'Polski', '🇵🇱'), + russian('ru', 'Русский', '🇷🇺'), + turkish('tr', 'Türkçe', '🇹🇷'), + estonian('et', 'Eesti', '🇪🇪'), + latvian('lv', 'Latviešu', '🇱🇻'), + lithuanian('lt', 'Lietuvių', '🇱🇹'); + + final String code; + final String name; + final String flag; + + const AppLanguage(this.code, this.name, this.flag); + + static AppLanguage fromCode(String code) { + return AppLanguage.values.firstWhere( + (lang) => lang.code == code, + orElse: () => AppLanguage.german, + ); + } +} + +class AppLocalizations { + static final AppLocalizations _instance = AppLocalizations._internal(); + factory AppLocalizations() => _instance; + AppLocalizations._internal(); + + static const String _prefsLanguageKey = 'app_language'; + + AppLanguage _currentLanguage = AppLanguage.german; + + AppLanguage get currentLanguage => _currentLanguage; + + void setLanguage(AppLanguage language) { + _currentLanguage = language; + Util.preferencesSaveString(_prefsLanguageKey, language.code); + } + + void loadSavedLanguage() { + final savedCode = Util.preferencesGetString(_prefsLanguageKey, null); + if (savedCode != null) { + _currentLanguage = AppLanguage.fromCode(savedCode); + } + } + + // Hilfsmethode für einfachen Zugriff + static String tr(String key) { + return _instance._translations[_instance._currentLanguage.code]?[key] ?? + _instance._translations['de']?[key] ?? + key; + } + + // Alle Übersetzungen + final Map> _translations = { + // Deutsch + 'de': { + // Hauptseite + 'assigned_jobs': 'Zugewiesene Aufträge', + 'accepted_jobs': 'Angenommene Aufträge', + 'finished_jobs': 'Erledigte Aufträge', + 'messages': 'Nachrichten', + 'approach_area': 'Anfahrtsbereich', + 'desired_zip': 'Wunschziel für heute', + 'login': 'Anmelden', + 'logout': 'Abmelden', + 'courier_id': 'Kurierkennung', + 'message': 'Meldung', + 'location': 'Ortung', + + // Dialoge + 'warning': 'Warnung', + 'information': 'Information', + 'error': 'Fehler', + 'yes': 'Ja', + 'no': 'Nein', + 'ok': 'OK', + 'close': 'Schließen', + 'back': 'Zurück', + 'confirm': 'Bestätigen', + 'cancel': 'Abbrechen', + + // Logout Dialog + 'logout_confirm': 'Wollen Sie sich wirklich abmelden?', + 'logging_out': 'Sie werden abgemeldet.\nBitte warten...', + + // Info Dialog + 'version': 'Versionsnummer', + 'connection_status': 'Verbindungsstatus', + 'connected': 'Verbunden', + 'not_connected': 'NICHT verbunden', + 'gps_status': 'GPS-Status', + 'gps_available': 'aktuell', + 'gps_outdated': 'veraltet', + 'gps_not_available': 'nicht verfügbar', + 'loading': 'wird geladen...', + 'user_status': 'User Status', + 'no_info_found': 'Keine Informationen gefunden', + 'language': 'Sprache', + + // Job-Seiten + 'new': 'Neu', + 'accept': 'Annehmen', + 'decline': 'Ablehnen', + 'ignore': 'Ignorieren', + 'order_number': 'Auftragsnummer', + 'more_info': 'Weitere Infos....', + 'list_of_stations': 'Liste der Stationen', + 'station': 'Station', + 'map': 'Karte', + + // Tour Details + 'station_details': 'Details der Station', + 'phone': 'Tel.', + 'commission_number': 'Komm-#', + 'next': 'Weiter', + 'files_available': 'Dateien verfügbar', + 'no_remark': '* Keine Bemerkung vorhanden *', + + // Tour Modus + 'pickup': 'Abholung', + 'delivery': 'Auslieferung', + 'pickup_delivery': 'Abholung/Auslieferung', + + // Fahrzeugtypen + 'bicycle': 'Fahrrad', + 'car': 'Pkw', + 'station_wagon': 'Kombi', + 'van': 'Kasten', + 'bus': 'Bus', + 'large_bus': 'GrBus', + 'truck': 'Lkw', + 'service': 'Service', + 'unknown': 'Unbekannt', + + // Aufgabenliste + 'task_list': 'Liste der Aufgaben', + 'commission_number_short': 'Komm-#', + 'photos_taken': 'Fotos aufgenommen', + 'signature_captured': 'Unterschrift erfasst', + 'barcodes_scanned': 'Barcodes gescannt', + + // Popup Dialoge + 'apply': 'Übernehmen', + 'add': 'Hinzufügen', + 'delete': 'Löschen', + 'signature': 'Unterschrift', + 'signature_too_short': 'Die Unterschrift ist zu kurz.', + 'photos': 'Aufgenommene Fotos', + 'min_photos_error': 'Es müssen mindestens {min} Foto(s) aufgenommen werden', + 'delete_photo_confirm': 'Wollen Sie das Bild wirklich löschen?', + 'delete_message_confirm': 'Soll die Nachricht gelöscht werden?', + 'status': 'Status', + 'remark': 'Bemerkung', + 'enter_remark': 'Bitte geben Sie eine Bemerkung ein', + 'gender': 'Geschlecht', + 'enter_text_and_gender': 'Sie müssen einen Text eingeben und ein Geschlecht auswählen', + 'attention': 'Achtung', + 'close_dialog_warning': 'Wenn Sie den Dialog jetzt schließen gehen erfasste Daten verloren und die Aufgabe oder Station bleibt unerledigt.', + 'zip_change_warning': 'ACHTUNG: Wenn Sie Ihre PLZ ändern, verlieren Sie Ihr Ranking!', + 'release': 'Freimelden', + 'zip': 'Postleitzahl', + 'enter_zip': 'Bitte geben Sie eine fünfstellige Postleitzahl ein', + 'enter_desired_zip': 'Bitte geben Sie Ihr Wunschziel für heute ein', + 'zip_hint': 'PLZ', + 'zip_validation_error': 'Das Feld muss leer sein, oder es müssen fünf Zahlen eingegeben werden', + 'enter_commission_number': 'Bitte geben Sie die Kommisionsnummer ein', + 'commission_number_hint': 'Kommisionsnummer', + + // Karte + 'far': 'Fern', + 'near': 'Nah', + 'no_position': 'Es liegt keine Position für diese Station vor', + + // Liste schließen + 'close_list': 'Liste schliessen', + }, + + // English + 'en': { + // Main Page + 'assigned_jobs': 'Assigned Jobs', + 'accepted_jobs': 'Accepted Jobs', + 'finished_jobs': 'Finished Jobs', + 'messages': 'Messages', + 'approach_area': 'Approach Area', + 'desired_zip': 'Desired Location for Today', + 'login': 'Login', + 'logout': 'Logout', + 'courier_id': 'Courier ID', + 'message': 'Message', + 'location': 'Location', + + // Dialogs + 'warning': 'Warning', + 'information': 'Information', + 'error': 'Error', + 'yes': 'Yes', + 'no': 'No', + 'ok': 'OK', + 'close': 'Close', + 'back': 'Back', + 'confirm': 'Confirm', + 'cancel': 'Cancel', + + // Logout Dialog + 'logout_confirm': 'Do you really want to logout?', + 'logging_out': 'You are being logged out.\nPlease wait...', + + // Info Dialog + 'version': 'Version Number', + 'connection_status': 'Connection Status', + 'connected': 'Connected', + 'not_connected': 'NOT connected', + 'gps_status': 'GPS Status', + 'gps_available': 'current', + 'gps_outdated': 'outdated', + 'gps_not_available': 'not available', + 'loading': 'loading...', + 'user_status': 'User Status', + 'no_info_found': 'No information found', + 'language': 'Language', + + // Job Pages + 'new': 'New', + 'accept': 'Accept', + 'decline': 'Decline', + 'ignore': 'Ignore', + 'order_number': 'Order Number', + 'more_info': 'More Info....', + 'list_of_stations': 'List of Stations', + 'station': 'Station', + 'map': 'Map', + + // Tour Details + 'station_details': 'Station Details', + 'phone': 'Tel.', + 'commission_number': 'Comm-#', + 'next': 'Next', + 'files_available': 'Files Available', + 'no_remark': '* No remark available *', + + // Tour Mode + 'pickup': 'Pickup', + 'delivery': 'Delivery', + 'pickup_delivery': 'Pickup/Delivery', + + // Vehicle Types + 'bicycle': 'Bicycle', + 'car': 'Car', + 'station_wagon': 'Station Wagon', + 'van': 'Van', + 'bus': 'Bus', + 'large_bus': 'Large Bus', + 'truck': 'Truck', + 'service': 'Service', + 'unknown': 'Unknown', + + // Task List + 'task_list': 'Task List', + 'commission_number_short': 'Comm-#', + 'photos_taken': 'Photos taken', + 'signature_captured': 'Signature captured', + 'barcodes_scanned': 'Barcodes scanned', + + // Popup Dialogs + 'apply': 'Apply', + 'add': 'Add', + 'delete': 'Delete', + 'signature': 'Signature', + 'signature_too_short': 'The signature is too short.', + 'photos': 'Taken Photos', + 'min_photos_error': 'At least {min} photo(s) must be taken', + 'delete_photo_confirm': 'Do you really want to delete this image?', + 'delete_message_confirm': 'Should the message be deleted?', + 'status': 'Status', + 'remark': 'Remark', + 'enter_remark': 'Please enter a remark', + 'gender': 'Gender', + 'enter_text_and_gender': 'You must enter text and select a gender', + 'attention': 'Attention', + 'close_dialog_warning': 'If you close this dialog now, captured data will be lost and the task or station will remain incomplete.', + 'zip_change_warning': 'WARNING: If you change your ZIP code, you will lose your ranking!', + 'release': 'Release', + 'zip': 'ZIP Code', + 'enter_zip': 'Please enter a five-digit ZIP code', + 'enter_desired_zip': 'Please enter your desired location for today', + 'zip_hint': 'ZIP', + 'zip_validation_error': 'The field must be empty or five digits must be entered', + 'enter_commission_number': 'Please enter the commission number', + 'commission_number_hint': 'Commission Number', + + // Map + 'far': 'Far', + 'near': 'Near', + 'no_position': 'No position is available for this station', + + // Close List + 'close_list': 'Close List', + }, + + // Spanish + 'es': { + // Main Page + 'assigned_jobs': 'Trabajos Asignados', + 'accepted_jobs': 'Trabajos Aceptados', + 'finished_jobs': 'Trabajos Terminados', + 'messages': 'Mensajes', + 'approach_area': 'Área de Aproximación', + 'desired_zip': 'Ubicación Deseada para Hoy', + 'login': 'Iniciar Sesión', + 'logout': 'Cerrar Sesión', + 'courier_id': 'ID de Mensajero', + 'message': 'Mensaje', + 'location': 'Ubicación', + + // Dialogs + 'warning': 'Advertencia', + 'information': 'Información', + 'error': 'Error', + 'yes': 'Sí', + 'no': 'No', + 'ok': 'OK', + 'close': 'Cerrar', + 'back': 'Atrás', + 'confirm': 'Confirmar', + 'cancel': 'Cancelar', + + // Logout Dialog + 'logout_confirm': '¿Realmente desea cerrar sesión?', + 'logging_out': 'Se está cerrando su sesión.\nPor favor espere...', + + // Info Dialog + 'version': 'Número de Versión', + 'connection_status': 'Estado de Conexión', + 'connected': 'Conectado', + 'not_connected': 'NO conectado', + 'gps_status': 'Estado del GPS', + 'gps_available': 'actual', + 'gps_outdated': 'desactualizado', + 'gps_not_available': 'no disponible', + 'loading': 'cargando...', + 'user_status': 'Estado del Usuario', + 'no_info_found': 'No se encontró información', + 'language': 'Idioma', + + // Job Pages + 'new': 'Nuevo', + 'accept': 'Aceptar', + 'decline': 'Rechazar', + 'ignore': 'Ignorar', + 'order_number': 'Número de Orden', + 'more_info': 'Más Información....', + 'list_of_stations': 'Lista de Estaciones', + 'station': 'Estación', + 'map': 'Mapa', + + // Tour Details + 'station_details': 'Detalles de la Estación', + 'phone': 'Tel.', + 'commission_number': 'Núm. Com.', + 'next': 'Siguiente', + 'files_available': 'Archivos Disponibles', + 'no_remark': '* No hay observaciones disponibles *', + + // Tour Mode + 'pickup': 'Recogida', + 'delivery': 'Entrega', + 'pickup_delivery': 'Recogida/Entrega', + + // Vehicle Types + 'bicycle': 'Bicicleta', + 'car': 'Coche', + 'station_wagon': 'Familiar', + 'van': 'Furgoneta', + 'bus': 'Autobús', + 'large_bus': 'Autobús Grande', + 'truck': 'Camión', + 'service': 'Servicio', + 'unknown': 'Desconocido', + + // Task List + 'task_list': 'Lista de Tareas', + 'commission_number_short': 'Núm. Com.', + 'photos_taken': 'Fotos tomadas', + 'signature_captured': 'Firma capturada', + 'barcodes_scanned': 'Códigos escaneados', + + // Popup Dialogs + 'apply': 'Aplicar', + 'add': 'Añadir', + 'delete': 'Eliminar', + 'signature': 'Firma', + 'signature_too_short': 'La firma es demasiado corta.', + 'photos': 'Fotos Tomadas', + 'min_photos_error': 'Se deben tomar al menos {min} foto(s)', + 'delete_photo_confirm': '¿Realmente desea eliminar esta imagen?', + 'delete_message_confirm': '¿Se debe eliminar el mensaje?', + 'status': 'Estado', + 'remark': 'Observación', + 'enter_remark': 'Por favor ingrese una observación', + 'gender': 'Género', + 'enter_text_and_gender': 'Debe ingresar texto y seleccionar un género', + 'attention': 'Atención', + 'close_dialog_warning': 'Si cierra este diálogo ahora, los datos capturados se perderán y la tarea o estación permanecerá incompleta.', + 'zip_change_warning': '¡ATENCIÓN: Si cambia su código postal, perderá su clasificación!', + 'release': 'Liberar', + 'zip': 'Código Postal', + 'enter_zip': 'Por favor ingrese un código postal de cinco dígitos', + 'enter_desired_zip': 'Por favor ingrese su ubicación deseada para hoy', + 'zip_hint': 'CP', + 'zip_validation_error': 'El campo debe estar vacío o deben ingresarse cinco dígitos', + 'enter_commission_number': 'Por favor ingrese el número de comisión', + 'commission_number_hint': 'Número de Comisión', + + // Map + 'far': 'Lejos', + 'near': 'Cerca', + 'no_position': 'No hay posición disponible para esta estación', + + // Close List + 'close_list': 'Cerrar Lista', + }, + + // French + 'fr': { + // Main Page + 'assigned_jobs': 'Tâches Assignées', + 'accepted_jobs': 'Tâches Acceptées', + 'finished_jobs': 'Tâches Terminées', + 'messages': 'Messages', + 'approach_area': "Zone d'Approche", + 'desired_zip': 'Lieu Souhaité pour Aujourd\'hui', + 'login': 'Connexion', + 'logout': 'Déconnexion', + 'courier_id': 'ID Coursier', + 'message': 'Message', + 'location': 'Localisation', + + // Dialogs + 'warning': 'Avertissement', + 'information': 'Information', + 'error': 'Erreur', + 'yes': 'Oui', + 'no': 'Non', + 'ok': 'OK', + 'close': 'Fermer', + 'back': 'Retour', + 'confirm': 'Confirmer', + 'cancel': 'Annuler', + + // Logout Dialog + 'logout_confirm': 'Voulez-vous vraiment vous déconnecter?', + 'logging_out': 'Vous êtes déconnecté.\nVeuillez patienter...', + + // Info Dialog + 'version': 'Numéro de Version', + 'connection_status': 'État de la Connexion', + 'connected': 'Connecté', + 'not_connected': 'NON connecté', + 'gps_status': 'État du GPS', + 'gps_available': 'actuel', + 'gps_outdated': 'périmé', + 'gps_not_available': 'non disponible', + 'loading': 'chargement...', + 'user_status': 'État de l\'Utilisateur', + 'no_info_found': 'Aucune information trouvée', + 'language': 'Langue', + + // Job Pages + 'new': 'Nouveau', + 'accept': 'Accepter', + 'decline': 'Refuser', + 'ignore': 'Ignorer', + 'order_number': 'Numéro de Commande', + 'more_info': 'Plus d\'Infos....', + 'list_of_stations': 'Liste des Stations', + 'station': 'Station', + 'map': 'Carte', + + // Tour Details + 'station_details': 'Détails de la Station', + 'phone': 'Tél.', + 'commission_number': 'N° Comm.', + 'next': 'Suivant', + 'files_available': 'Fichiers Disponibles', + 'no_remark': '* Aucune remarque disponible *', + + // Tour Mode + 'pickup': 'Collecte', + 'delivery': 'Livraison', + 'pickup_delivery': 'Collecte/Livraison', + + // Vehicle Types + 'bicycle': 'Vélo', + 'car': 'Voiture', + 'station_wagon': 'Break', + 'van': 'Camionnette', + 'bus': 'Bus', + 'large_bus': 'Grand Bus', + 'truck': 'Camion', + 'service': 'Service', + 'unknown': 'Inconnu', + + // Task List + 'task_list': 'Liste des Tâches', + 'commission_number_short': 'N° Comm.', + 'photos_taken': 'Photos prises', + 'signature_captured': 'Signature capturée', + 'barcodes_scanned': 'Codes-barres scannés', + + // Popup Dialogs + 'apply': 'Appliquer', + 'add': 'Ajouter', + 'delete': 'Supprimer', + 'signature': 'Signature', + 'signature_too_short': 'La signature est trop courte.', + 'photos': 'Photos Prises', + 'min_photos_error': 'Au moins {min} photo(s) doit être prise', + 'delete_photo_confirm': 'Voulez-vous vraiment supprimer cette image?', + 'delete_message_confirm': 'Le message doit-il être supprimé?', + 'status': 'État', + 'remark': 'Remarque', + 'enter_remark': 'Veuillez saisir une remarque', + 'gender': 'Genre', + 'enter_text_and_gender': 'Vous devez saisir du texte et sélectionner un genre', + 'attention': 'Attention', + 'close_dialog_warning': 'Si vous fermez cette boîte de dialogue maintenant, les données capturées seront perdues et la tâche ou la station restera incomplète.', + 'zip_change_warning': 'ATTENTION: Si vous changez votre code postal, vous perdrez votre classement!', + 'release': 'Libérer', + 'zip': 'Code Postal', + 'enter_zip': 'Veuillez saisir un code postal à cinq chiffres', + 'enter_desired_zip': 'Veuillez saisir votre lieu souhaité pour aujourd\'hui', + 'zip_hint': 'CP', + 'zip_validation_error': 'Le champ doit être vide ou cinq chiffres doivent être saisis', + 'enter_commission_number': 'Veuillez saisir le numéro de commission', + 'commission_number_hint': 'Numéro de Commission', + + // Map + 'far': 'Loin', + 'near': 'Près', + 'no_position': 'Aucune position n\'est disponible pour cette station', + + // Close List + 'close_list': 'Fermer la Liste', + }, + + // Polish + 'pl': { + // Main Page + 'assigned_jobs': 'Przypisane Zadania', + 'accepted_jobs': 'Przyjęte Zadania', + 'finished_jobs': 'Zakończone Zadania', + 'messages': 'Wiadomości', + 'approach_area': 'Strefa Dojazdu', + 'desired_zip': 'Pożądana Lokalizacja na Dziś', + 'login': 'Zaloguj', + 'logout': 'Wyloguj', + 'courier_id': 'ID Kuriera', + 'message': 'Wiadomość', + 'location': 'Lokalizacja', + + // Dialogs + 'warning': 'Ostrzeżenie', + 'information': 'Informacja', + 'error': 'Błąd', + 'yes': 'Tak', + 'no': 'Nie', + 'ok': 'OK', + 'close': 'Zamknij', + 'back': 'Wstecz', + 'confirm': 'Potwierdź', + 'cancel': 'Anuluj', + + // Logout Dialog + 'logout_confirm': 'Czy na pewno chcesz się wylogować?', + 'logging_out': 'Trwa wylogowywanie.\nProszę czekać...', + + // Info Dialog + 'version': 'Numer Wersji', + 'connection_status': 'Status Połączenia', + 'connected': 'Połączony', + 'not_connected': 'NIE połączony', + 'gps_status': 'Status GPS', + 'gps_available': 'aktualny', + 'gps_outdated': 'przestarzały', + 'gps_not_available': 'niedostępny', + 'loading': 'ładowanie...', + 'user_status': 'Status Użytkownika', + 'no_info_found': 'Nie znaleziono informacji', + 'language': 'Język', + + // Job Pages + 'new': 'Nowy', + 'accept': 'Przyjmij', + 'decline': 'Odrzuć', + 'ignore': 'Ignoruj', + 'order_number': 'Numer Zamówienia', + 'more_info': 'Więcej Informacji....', + 'list_of_stations': 'Lista Stacji', + 'station': 'Stacja', + 'map': 'Mapa', + + // Tour Details + 'station_details': 'Szczegóły Stacji', + 'phone': 'Tel.', + 'commission_number': 'Nr Zlec.', + 'next': 'Dalej', + 'files_available': 'Dostępne Pliki', + 'no_remark': '* Brak dostępnych uwag *', + + // Tour Mode + 'pickup': 'Odbiór', + 'delivery': 'Dostawa', + 'pickup_delivery': 'Odbiór/Dostawa', + + // Vehicle Types + 'bicycle': 'Rower', + 'car': 'Samochód', + 'station_wagon': 'Kombi', + 'van': 'Van', + 'bus': 'Autobus', + 'large_bus': 'Duży Autobus', + 'truck': 'Ciężarówka', + 'service': 'Serwis', + 'unknown': 'Nieznany', + + // Task List + 'task_list': 'Lista Zadań', + 'commission_number_short': 'Nr Zlec.', + 'photos_taken': 'Zrobione zdjęcia', + 'signature_captured': 'Podpis zapisany', + 'barcodes_scanned': 'Zeskanowane kody', + + // Popup Dialogs + 'apply': 'Zastosuj', + 'add': 'Dodaj', + 'delete': 'Usuń', + 'signature': 'Podpis', + 'signature_too_short': 'Podpis jest za krótki.', + 'photos': 'Zrobione Zdjęcia', + 'min_photos_error': 'Musi zostać wykonane co najmniej {min} zdjęcie', + 'delete_photo_confirm': 'Czy na pewno chcesz usunąć to zdjęcie?', + 'delete_message_confirm': 'Czy wiadomość powinna zostać usunięta?', + 'status': 'Status', + 'remark': 'Uwaga', + 'enter_remark': 'Proszę wprowadzić uwagę', + 'gender': 'Płeć', + 'enter_text_and_gender': 'Musisz wprowadzić tekst i wybrać płeć', + 'attention': 'Uwaga', + 'close_dialog_warning': 'Jeśli zamkniesz to okno teraz, zebrane dane zostaną utracone, a zadanie lub stacja pozostanie niezakończone.', + 'zip_change_warning': 'UWAGA: Jeśli zmienisz kod pocztowy, stracisz swoją pozycję w rankingu!', + 'release': 'Zwolnij', + 'zip': 'Kod Pocztowy', + 'enter_zip': 'Proszę wprowadzić pięciocyfrowy kod pocztowy', + 'enter_desired_zip': 'Proszę wprowadzić pożądaną lokalizację na dziś', + 'zip_hint': 'Kod', + 'zip_validation_error': 'Pole musi być puste lub muszą zostać wprowadzone pięć cyfr', + 'enter_commission_number': 'Proszę wprowadzić numer zlecenia', + 'commission_number_hint': 'Numer Zlecenia', + + // Map + 'far': 'Daleko', + 'near': 'Blisko', + 'no_position': 'Brak dostępnej pozycji dla tej stacji', + + // Close List + 'close_list': 'Zamknij Listę', + }, + + // Russian + 'ru': { + // Main Page + 'assigned_jobs': 'Назначенные Задания', + 'accepted_jobs': 'Принятые Задания', + 'finished_jobs': 'Завершенные Задания', + 'messages': 'Сообщения', + 'approach_area': 'Зона Доступа', + 'desired_zip': 'Желаемое Местоположение на Сегодня', + 'login': 'Войти', + 'logout': 'Выйти', + 'courier_id': 'ID Курьера', + 'message': 'Сообщение', + 'location': 'Местоположение', + + // Dialogs + 'warning': 'Предупреждение', + 'information': 'Информация', + 'error': 'Ошибка', + 'yes': 'Да', + 'no': 'Нет', + 'ok': 'OK', + 'close': 'Закрыть', + 'back': 'Назад', + 'confirm': 'Подтвердить', + 'cancel': 'Отмена', + + // Logout Dialog + 'logout_confirm': 'Вы действительно хотите выйти?', + 'logging_out': 'Выход из системы.\nПожалуйста, подождите...', + + // Info Dialog + 'version': 'Номер Версии', + 'connection_status': 'Статус Подключения', + 'connected': 'Подключено', + 'not_connected': 'НЕ подключено', + 'gps_status': 'Статус GPS', + 'gps_available': 'текущий', + 'gps_outdated': 'устаревший', + 'gps_not_available': 'недоступен', + 'loading': 'загрузка...', + 'user_status': 'Статус Пользователя', + 'no_info_found': 'Информация не найдена', + 'language': 'Язык', + + // Job Pages + 'new': 'Новое', + 'accept': 'Принять', + 'decline': 'Отклонить', + 'ignore': 'Игнорировать', + 'order_number': 'Номер Заказа', + 'more_info': 'Подробнее....', + 'list_of_stations': 'Список Станций', + 'station': 'Станция', + 'map': 'Карта', + + // Tour Details + 'station_details': 'Детали Станции', + 'phone': 'Тел.', + 'commission_number': '№ Комм.', + 'next': 'Далее', + 'files_available': 'Доступные Файлы', + 'no_remark': '* Нет доступных примечаний *', + + // Tour Mode + 'pickup': 'Забор', + 'delivery': 'Доставка', + 'pickup_delivery': 'Забор/Доставка', + + // Vehicle Types + 'bicycle': 'Велосипед', + 'car': 'Автомобиль', + 'station_wagon': 'Универсал', + 'van': 'Фургон', + 'bus': 'Автобус', + 'large_bus': 'Большой Автобус', + 'truck': 'Грузовик', + 'service': 'Сервис', + 'unknown': 'Неизвестно', + + // Task List + 'task_list': 'Список Задач', + 'commission_number_short': '№ Комм.', + 'photos_taken': 'Фотографии сделаны', + 'signature_captured': 'Подпись сохранена', + 'barcodes_scanned': 'Штрих-коды отсканированы', + + // Popup Dialogs + 'apply': 'Применить', + 'add': 'Добавить', + 'delete': 'Удалить', + 'signature': 'Подпись', + 'signature_too_short': 'Подпись слишком короткая.', + 'photos': 'Сделанные Фотографии', + 'min_photos_error': 'Должно быть сделано не менее {min} фото', + 'delete_photo_confirm': 'Вы действительно хотите удалить это изображение?', + 'delete_message_confirm': 'Сообщение должно быть удалено?', + 'status': 'Статус', + 'remark': 'Примечание', + 'enter_remark': 'Пожалуйста, введите примечание', + 'gender': 'Пол', + 'enter_text_and_gender': 'Вы должны ввести текст и выбрать пол', + 'attention': 'Внимание', + 'close_dialog_warning': 'Если вы закроете этот диалог сейчас, собранные данные будут потеряны, и задача или станция останутся незавершенными.', + 'zip_change_warning': 'ВНИМАНИЕ: Если вы измените почтовый индекс, вы потеряете свой рейтинг!', + 'release': 'Освободить', + 'zip': 'Почтовый Индекс', + 'enter_zip': 'Пожалуйста, введите пятизначный почтовый индекс', + 'enter_desired_zip': 'Пожалуйста, введите желаемое местоположение на сегодня', + 'zip_hint': 'Индекс', + 'zip_validation_error': 'Поле должно быть пустым или должны быть введены пять цифр', + 'enter_commission_number': 'Пожалуйста, введите номер комиссии', + 'commission_number_hint': 'Номер Комиссии', + + // Map + 'far': 'Далеко', + 'near': 'Близко', + 'no_position': 'Для этой станции нет доступной позиции', + + // Close List + 'close_list': 'Закрыть Список', + }, + + // Turkish + 'tr': { + // Main Page + 'assigned_jobs': 'Atanan Görevler', + 'accepted_jobs': 'Kabul Edilen Görevler', + 'finished_jobs': 'Tamamlanan Görevler', + 'messages': 'Mesajlar', + 'approach_area': 'Yaklaşım Bölgesi', + 'desired_zip': 'Bugün İçin İstenen Konum', + 'login': 'Giriş', + 'logout': 'Çıkış', + 'courier_id': 'Kurye ID', + 'message': 'Mesaj', + 'location': 'Konum', + + // Dialogs + 'warning': 'Uyarı', + 'information': 'Bilgi', + 'error': 'Hata', + 'yes': 'Evet', + 'no': 'Hayır', + 'ok': 'OK', + 'close': 'Kapat', + 'back': 'Geri', + 'confirm': 'Onayla', + 'cancel': 'İptal', + + // Logout Dialog + 'logout_confirm': 'Gerçekten çıkış yapmak istiyor musunuz?', + 'logging_out': 'Çıkış yapılıyor.\nLütfen bekleyin...', + + // Info Dialog + 'version': 'Sürüm Numarası', + 'connection_status': 'Bağlantı Durumu', + 'connected': 'Bağlı', + 'not_connected': 'BAĞLI DEĞİL', + 'gps_status': 'GPS Durumu', + 'gps_available': 'güncel', + 'gps_outdated': 'eski', + 'gps_not_available': 'mevcut değil', + 'loading': 'yükleniyor...', + 'user_status': 'Kullanıcı Durumu', + 'no_info_found': 'Bilgi bulunamadı', + 'language': 'Dil', + + // Job Pages + 'new': 'Yeni', + 'accept': 'Kabul Et', + 'decline': 'Reddet', + 'ignore': 'Yoksay', + 'order_number': 'Sipariş Numarası', + 'more_info': 'Daha Fazla Bilgi....', + 'list_of_stations': 'İstasyon Listesi', + 'station': 'İstasyon', + 'map': 'Harita', + + // Tour Details + 'station_details': 'İstasyon Detayları', + 'phone': 'Tel.', + 'commission_number': 'Kom. No.', + 'next': 'Sonraki', + 'files_available': 'Dosyalar Mevcut', + 'no_remark': '* Mevcut not yok *', + + // Tour Mode + 'pickup': 'Alım', + 'delivery': 'Teslimat', + 'pickup_delivery': 'Alım/Teslimat', + + // Vehicle Types + 'bicycle': 'Bisiklet', + 'car': 'Araba', + 'station_wagon': 'Station Wagon', + 'van': 'Van', + 'bus': 'Otobüs', + 'large_bus': 'Büyük Otobüs', + 'truck': 'Kamyon', + 'service': 'Servis', + 'unknown': 'Bilinmiyor', + + // Task List + 'task_list': 'Görev Listesi', + 'commission_number_short': 'Kom. No.', + 'photos_taken': 'Fotoğraflar çekildi', + 'signature_captured': 'İmza kaydedildi', + 'barcodes_scanned': 'Barkodlar tarandı', + + // Popup Dialogs + 'apply': 'Uygula', + 'add': 'Ekle', + 'delete': 'Sil', + 'signature': 'İmza', + 'signature_too_short': 'İmza çok kısa.', + 'photos': 'Çekilen Fotoğraflar', + 'min_photos_error': 'En az {min} fotoğraf çekilmelidir', + 'delete_photo_confirm': 'Bu görüntüyü gerçekten silmek istiyor musunuz?', + 'delete_message_confirm': 'Mesaj silinsin mi?', + 'status': 'Durum', + 'remark': 'Not', + 'enter_remark': 'Lütfen bir not girin', + 'gender': 'Cinsiyet', + 'enter_text_and_gender': 'Metin girmeli ve cinsiyet seçmelisiniz', + 'attention': 'Dikkat', + 'close_dialog_warning': 'Bu iletişim kutusunu şimdi kapatırsanız, yakalanan veriler kaybolacak ve görev veya istasyon tamamlanmamış kalacaktır.', + 'zip_change_warning': 'DİKKAT: Posta kodunuzu değiştirirseniz, sıralamanızı kaybedersiniz!', + 'release': 'Serbest Bırak', + 'zip': 'Posta Kodu', + 'enter_zip': 'Lütfen beş haneli bir posta kodu girin', + 'enter_desired_zip': 'Lütfen bugün için istediğiniz konumu girin', + 'zip_hint': 'PK', + 'zip_validation_error': 'Alan boş olmalı veya beş rakam girilmelidir', + 'enter_commission_number': 'Lütfen komisyon numarasını girin', + 'commission_number_hint': 'Komisyon Numarası', + + // Map + 'far': 'Uzak', + 'near': 'Yakın', + 'no_position': 'Bu istasyon için mevcut bir konum yok', + + // Close List + 'close_list': 'Listeyi Kapat', + }, + + // Estonian + 'et': { + // Main Page + 'assigned_jobs': 'Määratud Ülesanded', + 'accepted_jobs': 'Vastu Võetud Ülesanded', + 'finished_jobs': 'Lõpetatud Ülesanded', + 'messages': 'Sõnumid', + 'approach_area': 'Ligipääsu Piirkond', + 'desired_zip': 'Tänane Soovitud Asukoht', + 'login': 'Logi Sisse', + 'logout': 'Logi Välja', + 'courier_id': 'Kulleri ID', + 'message': 'Sõnum', + 'location': 'Asukoht', + + // Dialogs + 'warning': 'Hoiatus', + 'information': 'Teave', + 'error': 'Viga', + 'yes': 'Jah', + 'no': 'Ei', + 'ok': 'OK', + 'close': 'Sulge', + 'back': 'Tagasi', + 'confirm': 'Kinnita', + 'cancel': 'Tühista', + + // Logout Dialog + 'logout_confirm': 'Kas soovite tõesti välja logida?', + 'logging_out': 'Logitakse välja.\nPalun oodake...', + + // Info Dialog + 'version': 'Versiooni Number', + 'connection_status': 'Ühenduse Olek', + 'connected': 'Ühendatud', + 'not_connected': 'EI ühendatud', + 'gps_status': 'GPS Olek', + 'gps_available': 'kehtiv', + 'gps_outdated': 'aegunud', + 'gps_not_available': 'pole saadaval', + 'loading': 'laadimine...', + 'user_status': 'Kasutaja Olek', + 'no_info_found': 'Teavet ei leitud', + 'language': 'Keel', + + // Job Pages + 'new': 'Uus', + 'accept': 'Võta vastu', + 'decline': 'Keeldu', + 'ignore': 'Ignoreeri', + 'order_number': 'Tellimuse Number', + 'more_info': 'Rohkem Infot....', + 'list_of_stations': 'Jaamade Nimekiri', + 'station': 'Jaam', + 'map': 'Kaart', + + // Tour Details + 'station_details': 'Jaama Detailid', + 'phone': 'Tel.', + 'commission_number': 'Kom. Nr.', + 'next': 'Edasi', + 'files_available': 'Failid Saadaval', + 'no_remark': '* Märkused puuduvad *', + + // Tour Mode + 'pickup': 'Pealevõtt', + 'delivery': 'Kohaletoimetamine', + 'pickup_delivery': 'Pealevõtt/Kohaletoimetamine', + + // Vehicle Types + 'bicycle': 'Jalgratas', + 'car': 'Auto', + 'station_wagon': 'Universaal', + 'van': 'Kaubik', + 'bus': 'Buss', + 'large_bus': 'Suur Buss', + 'truck': 'Veoauto', + 'service': 'Teenindus', + 'unknown': 'Teadmata', + + // Task List + 'task_list': 'Ülesannete Nimekiri', + 'commission_number_short': 'Kom. Nr.', + 'photos_taken': 'Fotod tehtud', + 'signature_captured': 'Allkiri salvestatud', + 'barcodes_scanned': 'Triipkoodid skaneeritud', + + // Popup Dialogs + 'apply': 'Rakenda', + 'add': 'Lisa', + 'delete': 'Kustuta', + 'signature': 'Allkiri', + 'signature_too_short': 'Allkiri on liiga lühike.', + 'photos': 'Tehtud Fotod', + 'min_photos_error': 'Peab tegema vähemalt {min} foto(t)', + 'delete_photo_confirm': 'Kas soovite tõesti selle pildi kustutada?', + 'delete_message_confirm': 'Kas sõnum tuleks kustutada?', + 'status': 'Olek', + 'remark': 'Märkus', + 'enter_remark': 'Palun sisestage märkus', + 'gender': 'Sugu', + 'enter_text_and_gender': 'Peate sisestama teksti ja valima soo', + 'attention': 'Tähelepanu', + 'close_dialog_warning': 'Kui sulgete selle dialoogi praegu, lähevad kogutud andmed kaotsi ja ülesanne või jaam jääb lõpetamata.', + 'zip_change_warning': 'TÄHELEPANU: Kui muudate oma postiindeksit, kaotate oma reitingu!', + 'release': 'Vabasta', + 'zip': 'Postiindeks', + 'enter_zip': 'Palun sisestage viiekohaline postiindeks', + 'enter_desired_zip': 'Palun sisestage tänane soovitud asukoht', + 'zip_hint': 'Indeks', + 'zip_validation_error': 'Väli peab olema tühi või tuleb sisestada viis numbrit', + 'enter_commission_number': 'Palun sisestage komisjoni number', + 'commission_number_hint': 'Komisjoni Number', + + // Map + 'far': 'Kaugel', + 'near': 'Lähedal', + 'no_position': 'Selle jaama jaoks pole asukohta saadaval', + + // Close List + 'close_list': 'Sulge Nimekiri', + }, + + // Latvian + 'lv': { + // Main Page + 'assigned_jobs': 'Piešķirtie Uzdevumi', + 'accepted_jobs': 'Pieņemtie Uzdevumi', + 'finished_jobs': 'Pabeigtie Uzdevumi', + 'messages': 'Ziņojumi', + 'approach_area': 'Piekļuves Zona', + 'desired_zip': 'Vēlamā Atrašanās Vieta Šodien', + 'login': 'Pieteikties', + 'logout': 'Iziet', + 'courier_id': 'Kurjera ID', + 'message': 'Ziņojums', + 'location': 'Atrašanās Vieta', + + // Dialogs + 'warning': 'Brīdinājums', + 'information': 'Informācija', + 'error': 'Kļūda', + 'yes': 'Jā', + 'no': 'Nē', + 'ok': 'OK', + 'close': 'Aizvērt', + 'back': 'Atpakaļ', + 'confirm': 'Apstiprināt', + 'cancel': 'Atcelt', + + // Logout Dialog + 'logout_confirm': 'Vai tiešām vēlaties iziet?', + 'logging_out': 'Notiek iziešana.\nLūdzu, uzgaidiet...', + + // Info Dialog + 'version': 'Versijas Numurs', + 'connection_status': 'Savienojuma Statuss', + 'connected': 'Savienots', + 'not_connected': 'NAV savienots', + 'gps_status': 'GPS Statuss', + 'gps_available': 'aktuāls', + 'gps_outdated': 'novecojis', + 'gps_not_available': 'nav pieejams', + 'loading': 'ielādē...', + 'user_status': 'Lietotāja Statuss', + 'no_info_found': 'Informācija nav atrasta', + 'language': 'Valoda', + + // Job Pages + 'new': 'Jauns', + 'accept': 'Pieņemt', + 'decline': 'Noraidīt', + 'ignore': 'Ignorēt', + 'order_number': 'Pasūtījuma Numurs', + 'more_info': 'Vairāk Informācijas....', + 'list_of_stations': 'Staciju Saraksts', + 'station': 'Stacija', + 'map': 'Karte', + + // Tour Details + 'station_details': 'Stacijas Detaļas', + 'phone': 'Tālrunis', + 'commission_number': 'Kom. Nr.', + 'next': 'Tālāk', + 'files_available': 'Pieejamie Faili', + 'no_remark': '* Nav piezīmju *', + + // Tour Mode + 'pickup': 'Saņemšana', + 'delivery': 'Piegāde', + 'pickup_delivery': 'Saņemšana/Piegāde', + + // Vehicle Types + 'bicycle': 'Velosipēds', + 'car': 'Automašīna', + 'station_wagon': 'Universālis', + 'van': 'Furgons', + 'bus': 'Autobuss', + 'large_bus': 'Liels Autobuss', + 'truck': 'Kravas Automobilis', + 'service': 'Serviss', + 'unknown': 'Nezināms', + + // Task List + 'task_list': 'Uzdevumu Saraksts', + 'commission_number_short': 'Kom. Nr.', + 'photos_taken': 'Fotogrāfijas uzņemtas', + 'signature_captured': 'Paraksts saglabāts', + 'barcodes_scanned': 'Svītrkodi skenēti', + + // Popup Dialogs + 'apply': 'Lietot', + 'add': 'Pievienot', + 'delete': 'Dzēst', + 'signature': 'Paraksts', + 'signature_too_short': 'Paraksts ir pārāk īss.', + 'photos': 'Uzņemtās Fotogrāfijas', + 'min_photos_error': 'Jāuzņem vismaz {min} fotogrāfija(s)', + 'delete_photo_confirm': 'Vai tiešām vēlaties dzēst šo attēlu?', + 'delete_message_confirm': 'Vai ziņojums jāizdzēš?', + 'status': 'Statuss', + 'remark': 'Piezīme', + 'enter_remark': 'Lūdzu, ievadiet piezīmi', + 'gender': 'Dzimums', + 'enter_text_and_gender': 'Jums jāievada teksts un jāizvēlas dzimums', + 'attention': 'Uzmanību', + 'close_dialog_warning': 'Ja tagad aizvērsiet šo dialoglodziņu, ievadītie dati tiks zaudēti, un uzdevums vai stacija paliks nepabeigta.', + 'zip_change_warning': 'UZMANĪBU: Ja mainīsiet pasta indeksu, zaudēsiet savu reitingu!', + 'release': 'Atbrīvot', + 'zip': 'Pasta Indekss', + 'enter_zip': 'Lūdzu, ievadiet piecciparu pasta indeksu', + 'enter_desired_zip': 'Lūdzu, ievadiet vēlamo atrašanās vietu šodien', + 'zip_hint': 'Indekss', + 'zip_validation_error': 'Lauks jābūt tukšam vai jāievada pieci cipari', + 'enter_commission_number': 'Lūdzu, ievadiet komisijas numuru', + 'commission_number_hint': 'Komisijas Numurs', + + // Map + 'far': 'Tālu', + 'near': 'Tuvu', + 'no_position': 'Šai stacijai nav pieejama atrašanās vieta', + + // Close List + 'close_list': 'Aizvērt Sarakstu', + }, + + // Lithuanian + 'lt': { + // Main Page + 'assigned_jobs': 'Paskirtos Užduotys', + 'accepted_jobs': 'Priimtos Užduotys', + 'finished_jobs': 'Baigtos Užduotys', + 'messages': 'Žinutės', + 'approach_area': 'Prieigos Zona', + 'desired_zip': 'Pageidaujama Vieta Šiandien', + 'login': 'Prisijungti', + 'logout': 'Atsijungti', + 'courier_id': 'Kurjerio ID', + 'message': 'Žinutė', + 'location': 'Vieta', + + // Dialogs + 'warning': 'Įspėjimas', + 'information': 'Informacija', + 'error': 'Klaida', + 'yes': 'Taip', + 'no': 'Ne', + 'ok': 'OK', + 'close': 'Uždaryti', + 'back': 'Atgal', + 'confirm': 'Patvirtinti', + 'cancel': 'Atšaukti', + + // Logout Dialog + 'logout_confirm': 'Ar tikrai norite atsijungti?', + 'logging_out': 'Atsijungiama.\nPrašome palaukti...', + + // Info Dialog + 'version': 'Versijos Numeris', + 'connection_status': 'Ryšio Statusas', + 'connected': 'Prisijungta', + 'not_connected': 'Neprisijungta', + 'gps_status': 'GPS Statusas', + 'gps_available': 'esamas', + 'gps_outdated': 'pasenęs', + 'gps_not_available': 'nepasiekiamas', + 'loading': 'įkeliama...', + 'user_status': 'Vartotojo Statusas', + 'no_info_found': 'Informacija nerasta', + 'language': 'Kalba', + + // Job Pages + 'new': 'Naujas', + 'accept': 'Priimti', + 'decline': 'Atmesti', + 'ignore': 'Ignoruoti', + 'order_number': 'Užsakymo Numeris', + 'more_info': 'Daugiau Informacijos....', + 'list_of_stations': 'Stotelių Sąrašas', + 'station': 'Stotelė', + 'map': 'Žemėlapis', + + // Tour Details + 'station_details': 'Stotelės Detalės', + 'phone': 'Tel.', + 'commission_number': 'Kom. Nr.', + 'next': 'Toliau', + 'files_available': 'Failai Prieinami', + 'no_remark': '* Pastabų nėra *', + + // Tour Mode + 'pickup': 'Paėmimas', + 'delivery': 'Pristatymas', + 'pickup_delivery': 'Paėmimas/Pristatymas', + + // Vehicle Types + 'bicycle': 'Dviratis', + 'car': 'Automobilis', + 'station_wagon': 'Universalas', + 'van': 'Furgonas', + 'bus': 'Autobusas', + 'large_bus': 'Didelis Autobusas', + 'truck': 'Sunkvežimis', + 'service': 'Servisas', + 'unknown': 'Nežinoma', + + // Task List + 'task_list': 'Užduočių Sąrašas', + 'commission_number_short': 'Kom. Nr.', + 'photos_taken': 'Nuotraukos padarytos', + 'signature_captured': 'Parašas užfiksuotas', + 'barcodes_scanned': 'Brūkšniai kodai nuskaityti', + + // Popup Dialogs + 'apply': 'Taikyti', + 'add': 'Pridėti', + 'delete': 'Ištrinti', + 'signature': 'Parašas', + 'signature_too_short': 'Parašas per trumpas.', + 'photos': 'Padarytos Nuotraukos', + 'min_photos_error': 'Turi būti padaryta bent {min} nuotrauka(-os)', + 'delete_photo_confirm': 'Ar tikrai norite ištrinti šį paveikslėlį?', + 'delete_message_confirm': 'Ar žinutė turėtų būti ištrinta?', + 'status': 'Statusas', + 'remark': 'Pastaba', + 'enter_remark': 'Prašome įvesti pastabą', + 'gender': 'Lytis', + 'enter_text_and_gender': 'Turite įvesti tekstą ir pasirinkti lytį', + 'attention': 'Dėmesio', + 'close_dialog_warning': 'Jei dabar uždarysite šį dialogą, surinkti duomenys bus prarasti, o užduotis ar stotelė liks nebaigta.', + 'zip_change_warning': 'DĖMESIO: Jei pakeisite pašto kodą, prarasite savo reitingą!', + 'release': 'Atleisti', + 'zip': 'Pašto Kodas', + 'enter_zip': 'Prašome įvesti penkiaženklį pašto kodą', + 'enter_desired_zip': 'Prašome įvesti pageidaujamą vietą šiandien', + 'zip_hint': 'Kodas', + 'zip_validation_error': 'Laukas turi būti tuščias arba turi būti įvesti penki skaitmenys', + 'enter_commission_number': 'Prašome įvesti komisijos numerį', + 'commission_number_hint': 'Komisijos Numeris', + + // Map + 'far': 'Toli', + 'near': 'Arti', + 'no_position': 'Šiai stoelei nėra prieinamos vietos', + + // Close List + 'close_list': 'Uždaryti Sąrašą', + }, + }; + + // Hilfsmethode für dynamische Werte + static String trWithParams(String key, Map params) { + String text = tr(key); + params.forEach((key, value) { + text = text.replaceAll('{$key}', value.toString()); + }); + return text; + } +} + +// Extension für einfachen Zugriff im Code +extension StringLocalization on String { + String get tr => AppLocalizations.tr(this); + String trParams(Map params) => AppLocalizations.trWithParams(this, params); +} diff --git a/lib/tools/constants.dart b/lib/tools/constants.dart index 7d25e73..f067c09 100644 --- a/lib/tools/constants.dart +++ b/lib/tools/constants.dart @@ -31,6 +31,7 @@ class Constants { static const String prefsPreviousMqttSubscription = "previous_mqtt_subscription"; static const String prefsBWVEnabled = "bwv_enabled"; static const String prefsDesiredZip = "desired_zip"; + static const String prefsAppLanguage = "app_language"; static const int stateAssigned = 0; static const int stateAccepted = 1; diff --git a/lib/tools/mqtt_core.dart b/lib/tools/mqtt_core.dart index ac4c59d..5bd37c7 100644 --- a/lib/tools/mqtt_core.dart +++ b/lib/tools/mqtt_core.dart @@ -6,7 +6,6 @@ import 'dart:math'; import 'package:archive/archive.dart'; import 'package:dart_mq/dart_mq.dart'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:mqtt5_client/mqtt5_client.dart'; import 'package:mqtt5_client/mqtt5_server_client.dart'; diff --git a/lib/tools/util.dart b/lib/tools/util.dart index 1c89aaa..44b0a52 100644 --- a/lib/tools/util.dart +++ b/lib/tools/util.dart @@ -10,6 +10,7 @@ import 'package:stadtbote/main.dart'; import 'package:stadtbote/model/job.dart'; import 'package:stadtbote/model/log_entry.dart'; import 'package:stadtbote/objectbox.g.dart'; +import 'package:stadtbote/tools/app_localizations.dart'; import 'package:stadtbote/tools/constants.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; @@ -355,22 +356,22 @@ String mapVehicleTypeToImageName(int vehType) String mapVehicleTypeToText(int vehType) { switch (vehType) { case 1: - return "Fahrrad"; + return 'bicycle'.tr; case 2: - return "Pkw"; + return 'car'.tr; case 3: - return "Kombi"; + return 'station_wagon'.tr; case 4: - return "Kasten"; + return 'van'.tr; case 5: - return "Bus"; + return 'bus'.tr; case 6: - return "GrBus"; + return 'large_bus'.tr; case 7: case 8: @@ -379,10 +380,10 @@ String mapVehicleTypeToText(int vehType) { case 12: case 13: case 14: - return "Lkw"; + return 'truck'.tr; case 999: - return "Service"; + return 'service'.tr; } return "!?????!"; @@ -392,15 +393,15 @@ String mapTourMode(String mode) { var text = ""; switch (mode) { case "pu": - text += "/ Abholung"; + text += "/ ${'pickup'.tr}"; break; case "del": - text += "/ Auslieferung"; + text += "/ ${'delivery'.tr}"; break; case "pu/del": - text += "/ Abholung/Auslieferung"; + text += "/ ${'pickup_delivery'.tr}"; break; } @@ -490,16 +491,16 @@ Future taskDialogSecurityException(BuildContext context) async { barrierDismissible: false, builder: (dialogContext) { return AlertDialog( - title: const Text('Achtung'), - content: const Text('Wenn Sie den Dialog jetzt schließen gehen erfasste Daten verloren und die Aufgabe oder Station bleibt unerledigt.'), + title: Text('attention'.tr), + content: Text('close_dialog_warning'.tr), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(true), - child: const Text('Schließen'), + child: Text('close'.tr), ), TextButton( onPressed: () => Navigator.of(dialogContext).pop(false), - child: const Text('Zurück'), + child: Text('back'.tr), ), ], );