From a2b842fb67f42462dc18e2f70384368a18d5e467 Mon Sep 17 00:00:00 2001 From: stopflock Date: Fri, 26 Sep 2025 14:43:57 -0500 Subject: [PATCH] offline areas localization --- lib/localizations/de.json | 22 +++++ lib/localizations/en.json | 22 +++++ lib/localizations/es.json | 22 +++++ lib/localizations/fr.json | 22 +++++ .../offline_areas_section.dart | 90 ++++++++++--------- 5 files changed, 138 insertions(+), 40 deletions(-) diff --git a/lib/localizations/de.json b/lib/localizations/de.json index 16d8e54..66bfc73 100644 --- a/lib/localizations/de.json +++ b/lib/localizations/de.json @@ -211,5 +211,27 @@ "deleteOperatorProfile": "Betreiber-Profil Löschen", "deleteOperatorProfileConfirm": "Sind Sie sicher, dass Sie \"{}\" löschen möchten?", "operatorProfileDeleted": "Betreiber-Profil gelöscht" + }, + "offlineAreas": { + "noAreasTitle": "Keine Offline-Bereiche", + "noAreasSubtitle": "Laden Sie einen Kartenbereich für die Offline-Nutzung herunter.", + "provider": "Anbieter", + "zoomLevels": "Z{}-{}", + "latitude": "Breite", + "longitude": "Länge", + "tiles": "Kacheln", + "size": "Größe", + "cameras": "Kameras", + "areaIdFallback": "Bereich {}...", + "renameArea": "Bereich umbenennen", + "refreshWorldTiles": "Welt-Kacheln aktualisieren/neu herunterladen", + "deleteOfflineArea": "Offline-Bereich löschen", + "cancelDownload": "Download abbrechen", + "renameAreaDialogTitle": "Offline-Bereich Umbenennen", + "areaNameLabel": "Bereichsname", + "renameButton": "Umbenennen", + "megabytes": "MB", + "kilobytes": "KB", + "progress": "{}%" } } \ No newline at end of file diff --git a/lib/localizations/en.json b/lib/localizations/en.json index 0ef7594..3dc0a8e 100644 --- a/lib/localizations/en.json +++ b/lib/localizations/en.json @@ -211,5 +211,27 @@ "deleteOperatorProfile": "Delete Operator Profile", "deleteOperatorProfileConfirm": "Are you sure you want to delete \"{}\"?", "operatorProfileDeleted": "Operator profile deleted" + }, + "offlineAreas": { + "noAreasTitle": "No offline areas", + "noAreasSubtitle": "Download a map area for offline use.", + "provider": "Provider", + "zoomLevels": "Z{}-{}", + "latitude": "Lat", + "longitude": "Lon", + "tiles": "Tiles", + "size": "Size", + "cameras": "Cameras", + "areaIdFallback": "Area {}...", + "renameArea": "Rename area", + "refreshWorldTiles": "Refresh/re-download world tiles", + "deleteOfflineArea": "Delete offline area", + "cancelDownload": "Cancel download", + "renameAreaDialogTitle": "Rename Offline Area", + "areaNameLabel": "Area Name", + "renameButton": "Rename", + "megabytes": "MB", + "kilobytes": "KB", + "progress": "{}%" } } \ No newline at end of file diff --git a/lib/localizations/es.json b/lib/localizations/es.json index eb981f1..f5dbed0 100644 --- a/lib/localizations/es.json +++ b/lib/localizations/es.json @@ -211,5 +211,27 @@ "deleteOperatorProfile": "Eliminar Perfil de Operador", "deleteOperatorProfileConfirm": "¿Está seguro de que desea eliminar \"{}\"?", "operatorProfileDeleted": "Perfil de operador eliminado" + }, + "offlineAreas": { + "noAreasTitle": "Sin áreas sin conexión", + "noAreasSubtitle": "Descarga un área del mapa para uso sin conexión.", + "provider": "Proveedor", + "zoomLevels": "Z{}-{}", + "latitude": "Lat", + "longitude": "Lon", + "tiles": "Teselas", + "size": "Tamaño", + "cameras": "Cámaras", + "areaIdFallback": "Área {}...", + "renameArea": "Renombrar área", + "refreshWorldTiles": "Actualizar/re-descargar teselas mundiales", + "deleteOfflineArea": "Eliminar área sin conexión", + "cancelDownload": "Cancelar descarga", + "renameAreaDialogTitle": "Renombrar Área Sin Conexión", + "areaNameLabel": "Nombre del Área", + "renameButton": "Renombrar", + "megabytes": "MB", + "kilobytes": "KB", + "progress": "{}%" } } \ No newline at end of file diff --git a/lib/localizations/fr.json b/lib/localizations/fr.json index 08c2843..eb4bbe8 100644 --- a/lib/localizations/fr.json +++ b/lib/localizations/fr.json @@ -211,5 +211,27 @@ "deleteOperatorProfile": "Supprimer Profil d'Opérateur", "deleteOperatorProfileConfirm": "Êtes-vous sûr de vouloir supprimer \"{}\"?", "operatorProfileDeleted": "Profil d'opérateur supprimé" + }, + "offlineAreas": { + "noAreasTitle": "Aucune zone hors ligne", + "noAreasSubtitle": "Téléchargez une zone de carte pour utilisation hors ligne.", + "provider": "Fournisseur", + "zoomLevels": "Z{}-{}", + "latitude": "Lat", + "longitude": "Lon", + "tiles": "Tuiles", + "size": "Taille", + "cameras": "Caméras", + "areaIdFallback": "Zone {}...", + "renameArea": "Renommer la zone", + "refreshWorldTiles": "Actualiser/re-télécharger les tuiles mondiales", + "deleteOfflineArea": "Supprimer la zone hors ligne", + "cancelDownload": "Annuler le téléchargement", + "renameAreaDialogTitle": "Renommer la Zone Hors Ligne", + "areaNameLabel": "Nom de la Zone", + "renameButton": "Renommer", + "megabytes": "Mo", + "kilobytes": "Ko", + "progress": "{}%" } } \ No newline at end of file diff --git a/lib/screens/settings_screen_sections/offline_areas_section.dart b/lib/screens/settings_screen_sections/offline_areas_section.dart index b46defe..5e15350 100644 --- a/lib/screens/settings_screen_sections/offline_areas_section.dart +++ b/lib/screens/settings_screen_sections/offline_areas_section.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../../services/offline_area_service.dart'; import '../../services/offline_areas/offline_area_models.dart'; +import '../../services/localization_service.dart'; class OfflineAreasSection extends StatefulWidget { const OfflineAreasSection({super.key}); @@ -25,35 +26,42 @@ class _OfflineAreasSectionState extends State { @override Widget build(BuildContext context) { - final areas = service.offlineAreas; - if (areas.isEmpty) { - return const ListTile( - leading: Icon(Icons.download_for_offline), - title: Text('No offline areas'), - subtitle: Text('Download a map area for offline use.'), - ); - } - return Column( - children: areas.map((area) { - String diskStr = area.sizeBytes > 0 - ? area.sizeBytes > 1024 * 1024 - ? "${(area.sizeBytes / (1024 * 1024)).toStringAsFixed(2)} MB" - : "${(area.sizeBytes / 1024).toStringAsFixed(1)} KB" - : '--'; - String subtitle = - 'Provider: ${area.tileProviderDisplay}\n' + - 'Z${area.minZoom}-${area.maxZoom}\n' + - 'Lat: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' + - 'Lat: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}'; - if (area.status == OfflineAreaStatus.downloading) { - subtitle += '\nTiles: ${area.tilesDownloaded} / ${area.tilesTotal}'; - } else { - subtitle += '\nTiles: ${area.tilesTotal}'; - } - subtitle += '\nSize: $diskStr'; - if (!area.isPermanent) { - subtitle += '\nCameras: ${area.cameras.length}'; + return AnimatedBuilder( + animation: LocalizationService.instance, + builder: (context, child) { + final locService = LocalizationService.instance; + final areas = service.offlineAreas; + + if (areas.isEmpty) { + return ListTile( + leading: const Icon(Icons.download_for_offline), + title: Text(locService.t('offlineAreas.noAreasTitle')), + subtitle: Text(locService.t('offlineAreas.noAreasSubtitle')), + ); } + + return Column( + children: areas.map((area) { + String diskStr = area.sizeBytes > 0 + ? area.sizeBytes > 1024 * 1024 + ? "${(area.sizeBytes / (1024 * 1024)).toStringAsFixed(2)} ${locService.t('offlineAreas.megabytes')}" + : "${(area.sizeBytes / 1024).toStringAsFixed(1)} ${locService.t('offlineAreas.kilobytes')}" + : '--'; + + String subtitle = '${locService.t('offlineAreas.provider')}: ${area.tileProviderDisplay}\n' + + locService.t('offlineAreas.zoomLevels', params: [area.minZoom.toString(), area.maxZoom.toString()]) + '\n' + + '${locService.t('offlineAreas.latitude')}: ${area.bounds.southWest.latitude.toStringAsFixed(3)}, ${area.bounds.southWest.longitude.toStringAsFixed(3)}\n' + + '${locService.t('offlineAreas.latitude')}: ${area.bounds.northEast.latitude.toStringAsFixed(3)}, ${area.bounds.northEast.longitude.toStringAsFixed(3)}'; + + if (area.status == OfflineAreaStatus.downloading) { + subtitle += '\n${locService.t('offlineAreas.tiles')}: ${area.tilesDownloaded} / ${area.tilesTotal}'; + } else { + subtitle += '\n${locService.t('offlineAreas.tiles')}: ${area.tilesTotal}'; + } + subtitle += '\n${locService.t('offlineAreas.size')}: $diskStr'; + if (!area.isPermanent) { + subtitle += '\n${locService.t('offlineAreas.cameras')}: ${area.cameras.length}'; + } return Card( child: ListTile( leading: Icon(area.status == OfflineAreaStatus.complete @@ -66,35 +74,35 @@ class _OfflineAreasSectionState extends State { Expanded( child: Text(area.name.isNotEmpty ? area.name - : 'Area ${area.id.substring(0, 6)}...'), + : locService.t('offlineAreas.areaIdFallback', params: [area.id.substring(0, 6)])), ), if (!area.isPermanent) IconButton( icon: const Icon(Icons.edit, size: 20), - tooltip: 'Rename area', + tooltip: locService.t('offlineAreas.renameArea'), onPressed: () async { String? newName = await showDialog( context: context, builder: (ctx) { final ctrl = TextEditingController(text: area.name); return AlertDialog( - title: const Text('Rename Offline Area'), + title: Text(locService.t('offlineAreas.renameAreaDialogTitle')), content: TextField( controller: ctrl, maxLength: 40, - decoration: const InputDecoration(labelText: 'Area Name'), + decoration: InputDecoration(labelText: locService.t('offlineAreas.areaNameLabel')), autofocus: true, ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), - child: const Text('Cancel'), + child: Text(locService.t('actions.cancel')), ), ElevatedButton( onPressed: () { Navigator.pop(ctx, ctrl.text.trim()); }, - child: const Text('Rename'), + child: Text(locService.t('offlineAreas.renameButton')), ), ], ); @@ -111,7 +119,7 @@ class _OfflineAreasSectionState extends State { if (area.isPermanent && area.status != OfflineAreaStatus.downloading) IconButton( icon: const Icon(Icons.refresh, color: Colors.blue), - tooltip: 'Refresh/re-download world tiles', + tooltip: locService.t('offlineAreas.refreshWorldTiles'), onPressed: () async { await service.downloadArea( id: area.id, @@ -133,7 +141,7 @@ class _OfflineAreasSectionState extends State { else if (!area.isPermanent && area.status != OfflineAreaStatus.downloading) IconButton( icon: const Icon(Icons.delete, color: Colors.red), - tooltip: 'Delete offline area', + tooltip: locService.t('offlineAreas.deleteOfflineArea'), onPressed: () async { service.deleteArea(area.id); setState(() {}); @@ -154,7 +162,7 @@ class _OfflineAreasSectionState extends State { children: [ LinearProgressIndicator(value: area.progress), Text( - '${(area.progress * 100).toStringAsFixed(0)}%', + locService.t('offlineAreas.progress', params: [(area.progress * 100).toStringAsFixed(0)]), style: const TextStyle(fontSize: 12), ) ], @@ -162,7 +170,7 @@ class _OfflineAreasSectionState extends State { ), IconButton( icon: const Icon(Icons.cancel, color: Colors.orange), - tooltip: 'Cancel download', + tooltip: locService.t('offlineAreas.cancelDownload'), onPressed: () { service.cancelDownload(area.id); setState(() {}); @@ -178,8 +186,10 @@ class _OfflineAreasSectionState extends State { } : null, ), - ); - }).toList(), + ); + }).toList(), + ); + }, ); } }