i18n: Add 247 missing translation keys across all 13 languages

Replace hardcoded English strings in 32 Dart source files with l10n
references. Covers settings labels, UI actions, map/SAR features,
device config, notifications, profiles, spectrum scan, and status
messages for sl, de, hr, pl, es, fr, it, el, ru, tr, uk, zh, pt.
This commit is contained in:
Janez T
2026-03-17 10:36:58 +01:00
parent bbd5d108cb
commit fa2b586ab7
70 changed files with 16366 additions and 742 deletions

View File

@@ -221,7 +221,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
Icons.info_outline,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
const SizedBox(width: 12),
SizedBox(width: 12),
Expanded(
child: Text(
AppLocalizations.of(context)!.defaultPinInfo,
@@ -260,7 +260,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
context,
).colorScheme.onSurfaceVariant.withValues(alpha: 0.5),
),
const SizedBox(height: 16),
SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.noDevicesFound,
style: TextStyle(
@@ -271,7 +271,7 @@ class _ConnectionDialogState extends State<ConnectionDialog>
const SizedBox(height: 8),
TextButton.icon(
onPressed: _refreshBleDevices,
icon: const Icon(Icons.refresh),
icon: Icon(Icons.refresh),
label: Text(AppLocalizations.of(context)!.scanAgain),
),
],

View File

@@ -133,7 +133,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
onPressed: _isCreating
? null
: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
icon: Icon(Icons.close),
tooltip: l10n.close,
),
],
@@ -158,7 +158,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
size: 20,
color: theme.colorScheme.primary,
),
const SizedBox(width: 8),
SizedBox(width: 8),
Expanded(
child: Text(
l10n.channelTypesInfo,
@@ -170,7 +170,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
],
),
),
const SizedBox(height: 16),
SizedBox(height: 16),
TextFormField(
controller: _nameController,
decoration: InputDecoration(
@@ -196,7 +196,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
},
),
if (!isHashChannel) ...[
const SizedBox(height: 16),
SizedBox(height: 16),
TextFormField(
controller: _secretController,
decoration: InputDecoration(
@@ -211,7 +211,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => _handleCreate(),
),
const SizedBox(height: 8),
SizedBox(height: 8),
Text(
l10n.channelSecretHelp,
style: theme.textTheme.bodySmall?.copyWith(
@@ -236,7 +236,7 @@ class _AddChannelSheetState extends State<AddChannelSheet> {
size: 20,
color: Colors.blue,
),
const SizedBox(width: 8),
SizedBox(width: 8),
Expanded(
child: Text(
l10n.hashChannelInfo,

View File

@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../../models/contact.dart';
import '../../models/path_history.dart';
import '../../l10n/app_localizations.dart';
import '../../providers/app_provider.dart';
import '../../providers/connection_provider.dart';
import '../../services/contact_route_resolver.dart';
@@ -213,6 +214,16 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
);
}
ParsedContactRoute? get _effectiveRoute {
if (_parsedRoute != null) {
return _parsedRoute;
}
if (_errorText != null || _controller.text.trim().isNotEmpty) {
return null;
}
return ContactRouteCodec.direct(hashSize: _selectedHashSize);
}
void _toggleHop(Contact contact) {
setState(() {
if (_selectedMapHops.any(
@@ -415,13 +426,14 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
),
trailing: FilledButton.tonal(
onPressed: () => _applyHistoryRecord(record),
child: const Text('Use'),
child: Text(AppLocalizations.of(context)!.use),
),
),
);
}
Widget _buildPreviewSection() {
final previewRoute = _effectiveRoute;
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: double.infinity,
@@ -435,20 +447,21 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_parsedRoute == null ? 'Route preview' : _parsedRoute!.summary,
previewRoute == null ? 'Route preview' : previewRoute.summary,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 4),
Text(
_parsedRoute == null
previewRoute == null
? 'Pick relays from the list below or open manual edit if you need exact hop tokens.'
: '${_parsedRoute!.byteLength} bytes • descriptor 0x${_parsedRoute!.encodedPathLen.toRadixString(16).padLeft(2, '0').toUpperCase()}',
: '${previewRoute.byteLength} bytes • descriptor 0x${previewRoute.encodedPathLen.toRadixString(16).padLeft(2, '0').toUpperCase()}',
style: Theme.of(context).textTheme.bodySmall,
),
if (_parsedRoute != null) ...[
if (previewRoute != null &&
previewRoute.canonicalText.isNotEmpty) ...[
const SizedBox(height: 10),
SelectableText(
_parsedRoute!.canonicalText,
previewRoute.canonicalText,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(fontFamily: 'monospace'),
@@ -469,7 +482,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
border: Border.all(color: Theme.of(context).dividerColor),
),
child: Text(
'No relays selected yet. Add repeaters below or let auto resolve build a route.',
'No relays selected. Save now to use a direct path, or add repeaters below.',
style: Theme.of(context).textTheme.bodyMedium,
),
);
@@ -590,7 +603,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
_showManualEditor = expanded;
});
},
title: const Text('Manual route edit'),
title: Text(AppLocalizations.of(context)!.manualRouteEdit),
subtitle: const Text(
'Use this when you need to paste or tweak hop tokens directly.',
),
@@ -728,8 +741,8 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
children: [
FilledButton.tonalIcon(
onPressed: _resolvePathAutomatically,
icon: const Icon(Icons.auto_fix_high),
label: const Text('Auto resolve'),
icon: Icon(Icons.auto_fix_high),
label: Text(AppLocalizations.of(context)!.autoResolve),
),
OutlinedButton.icon(
onPressed: _selectedMapHops.isEmpty
@@ -740,8 +753,8 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
_syncControllerFromSelectedHops();
});
},
icon: const Icon(Icons.clear_all),
label: const Text('Clear relays'),
icon: Icon(Icons.clear_all),
label: Text(AppLocalizations.of(context)!.clearRelays),
),
],
),
@@ -796,7 +809,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (observedRecord != null) ...[
_buildHistoryRecordTile(observedRecord, title: 'Observed mesh route'),
_buildHistoryRecordTile(observedRecord, title: AppLocalizations.of(context)!.observedMeshRoute),
const SizedBox(height: 16),
],
if (remainingRecords.isEmpty)
@@ -822,6 +835,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
@override
Widget build(BuildContext context) {
final effectiveRoute = _effectiveRoute;
final appProvider = context.watch<AppProvider>();
final connectionProvider = context.watch<ConnectionProvider>();
final selfPoint =
@@ -939,22 +953,24 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
TextButton(
onPressed: () => Navigator.of(
context,
).pop(const ContactRouteDialogResult.clear()),
child: const Text('Clear Route'),
).pop(ContactRouteDialogResult.clear()),
child: Text(
'${AppLocalizations.of(context)!.clearRoute} (${AppLocalizations.of(context)!.flood})',
),
)
else
const SizedBox.shrink(),
FilledButton(
onPressed: _parsedRoute == null
onPressed: effectiveRoute == null
? null
: () => Navigator.of(context).pop(
ContactRouteDialogResult.setWithFallback(
_parsedRoute!,
effectiveRoute,
inferredFallbackLocation:
_buildSyntheticFallbackLocation(),
),
),
child: const Text('Save Path'),
child: Text(AppLocalizations.of(context)!.savePath),
),
],
),

View File

@@ -159,7 +159,7 @@ class ContactTile extends StatelessWidget {
Row(children: [_buildRoutePill(context, contact)]),
] else
Padding(
padding: const EdgeInsets.only(top: 4),
padding: EdgeInsets.only(top: 4),
child: Text(
AppLocalizations.of(context)!.noGpsData,
style: Theme.of(
@@ -361,8 +361,8 @@ class ContactTile extends StatelessWidget {
onTap: () async {
Navigator.pop(sheetContext);
final toggled = contact.toggleFavourite();
final connectionProvider =
context.read<ConnectionProvider>();
final connectionProvider = context
.read<ConnectionProvider>();
await connectionProvider.addOrUpdateContact(toggled);
if (context.mounted) {
// Refresh contact from device so the local cache is updated
@@ -372,12 +372,12 @@ class ContactTile extends StatelessWidget {
),
if (!contact.isChannel)
ListTile(
leading: const Icon(Icons.share_outlined),
title: const Text('Share Contact'),
leading: Icon(Icons.share_outlined),
title: Text(l10n.shareContact),
onTap: () async {
Navigator.pop(sheetContext);
final connectionProvider =
context.read<ConnectionProvider>();
final connectionProvider = context
.read<ConnectionProvider>();
final url = await connectionProvider.exportContactUrl(
contact.publicKey,
);
@@ -385,22 +385,20 @@ class ContactTile extends StatelessWidget {
await Clipboard.setData(ClipboardData(text: url));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Contact link copied to clipboard'),
SnackBar(
content: Text(l10n.contactLinkCopiedToClipboard),
),
);
}
} else if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to export contact'),
),
SnackBar(content: Text(l10n.failedToExportContact)),
);
}
},
),
ListTile(
leading: const Icon(Icons.message_outlined),
leading: Icon(Icons.message_outlined),
title: Text(l10n.messages),
enabled: canMessage,
onTap: !canMessage
@@ -412,7 +410,7 @@ class ContactTile extends StatelessWidget {
),
if (contact.displayLocation != null)
ListTile(
leading: const Icon(Icons.map_outlined),
leading: Icon(Icons.map_outlined),
title: Text(l10n.viewOnMap),
onTap: () {
Navigator.pop(sheetContext);
@@ -438,8 +436,8 @@ class ContactTile extends StatelessWidget {
),
if (canPreviewSensor)
ListTile(
leading: const Icon(Icons.visibility_outlined),
title: const Text('Preview'),
leading: Icon(Icons.visibility_outlined),
title: Text(l10n.preview),
onTap: () async {
Navigator.pop(sheetContext);
await Future<void>.delayed(Duration.zero);
@@ -467,7 +465,7 @@ class ContactTile extends StatelessWidget {
),
if (canSetPath)
ListTile(
leading: const Icon(Icons.alt_route),
leading: Icon(Icons.alt_route),
title: Text(l10n.contactSetPath),
onTap: () {
Navigator.pop(sheetContext);
@@ -476,8 +474,8 @@ class ContactTile extends StatelessWidget {
),
if (!contact.isChannel)
ListTile(
leading: const Icon(Icons.route),
title: const Text('Trace'),
leading: Icon(Icons.route),
title: Text(l10n.trace),
onTap: () {
Navigator.pop(sheetContext);
_showTraceSheet(context, contact);
@@ -494,8 +492,8 @@ class ContactTile extends StatelessWidget {
),
if (!contact.isPublicChannel)
ListTile(
leading: const Icon(Icons.edit_outlined),
title: const Text('Edit name'),
leading: Icon(Icons.edit_outlined),
title: Text(l10n.editName),
onTap: () {
Navigator.pop(sheetContext);
_showNameOverrideDialog(context, contact);
@@ -503,7 +501,7 @@ class ContactTile extends StatelessWidget {
),
if (!contact.isPublicChannel)
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
leading: Icon(Icons.delete, color: Colors.red),
title: Text(
contact.isChannel ? l10n.deleteChannel : l10n.deleteContact,
style: const TextStyle(color: Colors.red),
@@ -788,12 +786,13 @@ class ContactTile extends StatelessWidget {
paddedPathBytes: parsedRoute.paddedPathBytes,
);
if (context.mounted) {
final routeLabel = parsedRoute.hopCount == 0
? AppLocalizations.of(context)!.direct
: parsedRoute.canonicalText;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(
context,
)!.contactRouteSet(parsedRoute.canonicalText),
AppLocalizations.of(context)!.contactRouteSet(routeLabel),
),
),
);
@@ -1321,14 +1320,16 @@ class _NeighboursSheetState extends State<_NeighboursSheet> {
final timestampOrMs = int.tryParse(parts[1]);
final snrRaw = int.tryParse(parts[2]);
final isMs = timestampOrMs != null && timestampOrMs < 1e9;
parsed.add(_Neighbour(
publicKeyHex: keyHex,
lastSeenAt: !isMs && timestampOrMs != null
? DateTime.fromMillisecondsSinceEpoch(timestampOrMs * 1000)
: null,
lastSeenMs: isMs ? timestampOrMs : null,
snrDb: snrRaw != null ? snrRaw / 4.0 : null,
));
parsed.add(
_Neighbour(
publicKeyHex: keyHex,
lastSeenAt: !isMs && timestampOrMs != null
? DateTime.fromMillisecondsSinceEpoch(timestampOrMs * 1000)
: null,
lastSeenMs: isMs ? timestampOrMs : null,
snrDb: snrRaw != null ? snrRaw / 4.0 : null,
),
);
}
}
@@ -1401,41 +1402,35 @@ class _NeighboursSheetState extends State<_NeighboursSheet> {
child: _loading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
_error!,
textAlign: TextAlign.center,
),
),
)
: _neighbours.isEmpty
? const Center(child: Text('No neighbours found'))
: ListView.builder(
padding: const EdgeInsets.only(bottom: 16),
itemCount: _neighbours.length,
itemBuilder: (context, index) {
final n = _neighbours[index];
final name =
_resolveNeighbourName(n.publicKeyHex);
final parts = <String>[
if (n.snrDb != null)
'SNR ${n.snrDb!.toStringAsFixed(1)} dB',
if (n.lastSeenAt != null)
_formatAge(n.lastSeenAt!),
if (n.lastSeenMs != null)
'${n.lastSeenMs}ms ago',
];
return ListTile(
leading: const Icon(Icons.router_outlined),
title: Text(name),
subtitle: parts.isNotEmpty
? Text(parts.join(''))
: null,
);
},
),
? Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(_error!, textAlign: TextAlign.center),
),
)
: _neighbours.isEmpty
? const Center(child: Text('No neighbours found'))
: ListView.builder(
padding: const EdgeInsets.only(bottom: 16),
itemCount: _neighbours.length,
itemBuilder: (context, index) {
final n = _neighbours[index];
final name = _resolveNeighbourName(n.publicKeyHex);
final parts = <String>[
if (n.snrDb != null)
'SNR ${n.snrDb!.toStringAsFixed(1)} dB',
if (n.lastSeenAt != null) _formatAge(n.lastSeenAt!),
if (n.lastSeenMs != null) '${n.lastSeenMs}ms ago',
];
return ListTile(
leading: const Icon(Icons.router_outlined),
title: Text(name),
subtitle: parts.isNotEmpty
? Text(parts.join(''))
: null,
);
},
),
),
],
),

View File

@@ -401,7 +401,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
color: colorScheme.onPrimaryContainer,
size: 20,
),
const SizedBox(width: 12),
SizedBox(width: 12),
Expanded(
child: Text(
AppLocalizations.of(context)!.enterPasswordInfo,
@@ -488,7 +488,7 @@ class _RoomLoginSheetState extends State<RoomLoginSheet> {
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.login),
: Icon(Icons.login),
label: Text(
_isLoggingIn
? AppLocalizations.of(context)!.loggingInDots

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:meshcore_client/meshcore_client.dart';
import '../../l10n/app_localizations.dart';
class SpectrumScanPanel extends StatelessWidget {
final ThemeData theme;
@@ -103,12 +104,12 @@ class SpectrumScanPanel extends StatelessWidget {
children: [
_LegendChip(
color: theme.colorScheme.primary,
label: 'Quiet',
label: AppLocalizations.of(context)!.quiet,
),
const _LegendChip(color: Colors.orange, label: 'Moderate'),
_LegendChip(color: Colors.orange, label: AppLocalizations.of(context)!.moderate),
_LegendChip(
color: theme.colorScheme.error,
label: 'Busy',
label: AppLocalizations.of(context)!.busy,
),
],
),
@@ -211,8 +212,8 @@ class SpectrumScanPanel extends StatelessWidget {
onPressed: selectedFrequencyKhz == null
? null
: onApplySelected,
icon: const Icon(Icons.north_east),
label: const Text('Use selected frequency'),
icon: Icon(Icons.north_east),
label: Text(AppLocalizations.of(context)!.useSelectedFrequency),
),
),
],

View File

@@ -100,7 +100,7 @@ class CompassContactList extends StatelessWidget {
// Persons/Team section
if (showContacts && persons.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 4),
padding: EdgeInsets.only(left: 16, bottom: 8, top: 4),
child: Text(
l10n.teamMembers,
style: Theme.of(
@@ -135,7 +135,7 @@ class CompassContactList extends StatelessWidget {
// Repeaters section
if (showRepeaters && repeaters.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 12),
padding: EdgeInsets.only(left: 16, bottom: 8, top: 12),
child: Text(
l10n.repeaters,
style: Theme.of(
@@ -151,7 +151,7 @@ class CompassContactList extends StatelessWidget {
// Rooms section
if (rooms.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 12),
padding: EdgeInsets.only(left: 16, bottom: 8, top: 12),
child: Text(
l10n.rooms,
style: Theme.of(

View File

@@ -49,7 +49,7 @@ class _CompassFiltersState extends State<CompassFilters> {
title: Row(
children: [
const Icon(Icons.filter_list, size: 20),
const SizedBox(width: 8),
SizedBox(width: 8),
Text(l10n.filterMarkers),
],
),
@@ -62,7 +62,7 @@ class _CompassFiltersState extends State<CompassFilters> {
_CompactFilterItem(
icon: Icons.timeline,
color: Colors.blue,
label: 'Location Trail',
label: l10n.locationTrail,
value: mapProvider.isTrailVisible,
onChanged: (value) {
mapProvider.toggleTrailVisibility();
@@ -100,7 +100,7 @@ class _CompassFiltersState extends State<CompassFilters> {
const SizedBox(height: 4),
// SAR Markers section
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8, top: 4),
padding: EdgeInsets.only(left: 8, bottom: 8, top: 4),
child: Text(
l10n.sarMarkers,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
@@ -118,7 +118,7 @@ class _CompassFiltersState extends State<CompassFilters> {
setDialogState(() {});
},
),
const SizedBox(height: 4),
SizedBox(height: 4),
_CompactFilterItem(
icon: Icons.local_fire_department,
color: Colors.red,
@@ -129,7 +129,7 @@ class _CompassFiltersState extends State<CompassFilters> {
setDialogState(() {});
},
),
const SizedBox(height: 4),
SizedBox(height: 4),
_CompactFilterItem(
icon: Icons.home_work,
color: Colors.orange,
@@ -164,7 +164,7 @@ class _CompassFiltersState extends State<CompassFilters> {
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return IconButton(
icon: const Icon(Icons.filter_list),
icon: Icon(Icons.filter_list),
tooltip: l10n.filterMarkersTooltip,
onPressed: () => _showFilterDialog(),
);

View File

@@ -565,7 +565,7 @@ class _LocationFormatToggleState extends State<_LocationFormatToggle> {
Widget build(BuildContext context) {
final position = widget.position;
if (position == null) {
return const SizedBox.shrink();
return SizedBox.shrink();
}
final l10n = AppLocalizations.of(context)!;

View File

@@ -64,7 +64,7 @@ class CompassSarList extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 8),
padding: EdgeInsets.only(left: 16, top: 16, bottom: 8),
child: Text(
l10n.sarMarkers,
style: Theme.of(context).textTheme.titleMedium?.copyWith(

View File

@@ -603,7 +603,7 @@ class DrawingToolbar extends StatelessWidget {
child: Row(
children: [
const Icon(Icons.share),
const SizedBox(width: 12),
SizedBox(width: 12),
Expanded(
child: Text(
AppLocalizations.of(context)!.shareDrawingsCount(
@@ -620,7 +620,7 @@ class DrawingToolbar extends StatelessWidget {
const Divider(),
// Option: Send to Public Channel
ListTile(
leading: const Icon(Icons.public, color: Colors.blue),
leading: Icon(Icons.public, color: Colors.blue),
title: Text(AppLocalizations.of(context)!.publicChannel),
subtitle: Text(AppLocalizations.of(context)!.broadcastToAll),
onTap: () async {

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../l10n/app_localizations.dart';
class MapLegend extends StatelessWidget {
final int teamMemberCount;
@@ -35,31 +36,31 @@ class MapLegend extends StatelessWidget {
_LegendItem(
icon: Icons.person,
color: Theme.of(context).colorScheme.primary,
label: 'Team',
label: AppLocalizations.of(context)!.team,
count: teamMemberCount,
),
_LegendItem(
icon: Icons.person_pin,
color: Colors.green,
label: 'Found',
label: AppLocalizations.of(context)!.found,
count: foundPersonCount,
),
_LegendItem(
icon: Icons.local_fire_department,
color: Colors.red,
label: 'Fire',
label: AppLocalizations.of(context)!.fire,
count: fireCount,
),
_LegendItem(
icon: Icons.home_work,
color: Colors.orange,
label: 'Staging',
label: AppLocalizations.of(context)!.staging,
count: stagingAreaCount,
),
_LegendItem(
icon: Icons.inventory_2,
color: Colors.purple,
label: 'Object',
label: AppLocalizations.of(context)!.object,
count: objectCount,
),
],

View File

@@ -98,7 +98,7 @@ class _MapMessageOverlayState extends State<MapMessageOverlay> {
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
SizedBox(width: 8),
Expanded(
child: Text(
AppLocalizations.of(context)!.recentMessages,

View File

@@ -39,7 +39,7 @@ class TrailControls extends StatelessWidget {
Row(
children: [
const Icon(Icons.timeline, size: 24),
const SizedBox(width: 12),
SizedBox(width: 12),
Text(
l10n.locationTrail,
style: const TextStyle(
@@ -53,7 +53,7 @@ class TrailControls extends StatelessWidget {
// Trail visibility toggle
SwitchListTile(
secondary: const Icon(Icons.visibility),
secondary: Icon(Icons.visibility),
title: Text(l10n.showTrailOnMap),
subtitle: Text(
mapProvider.isTrailVisible
@@ -75,7 +75,7 @@ class TrailControls extends StatelessWidget {
children: [
OutlinedButton.icon(
onPressed: () => _importTrail(context, mapProvider),
icon: const Icon(Icons.file_upload_outlined),
icon: Icon(Icons.file_upload_outlined),
label: Text(l10n.importFromClipboard),
),
if (mapProvider.currentTrail != null &&
@@ -86,7 +86,7 @@ class TrailControls extends StatelessWidget {
mapProvider.currentTrail!,
customName: 'My Trail',
),
icon: const Icon(Icons.file_download_outlined),
icon: Icon(Icons.file_download_outlined),
label: Text(l10n.exportToClipboard),
),
],
@@ -116,13 +116,13 @@ class TrailControls extends StatelessWidget {
mapProvider.totalTrailDistance,
),
),
const SizedBox(height: 8),
SizedBox(height: 8),
_buildStatRow(
icon: Icons.access_time,
label: l10n.duration,
value: _formatDuration(mapProvider.trailDuration),
),
const SizedBox(height: 8),
SizedBox(height: 8),
_buildStatRow(
icon: Icons.place,
label: l10n.points,
@@ -139,7 +139,7 @@ class TrailControls extends StatelessWidget {
onPressed: () {
_showClearConfirmation(context, mapProvider, l10n);
},
icon: const Icon(Icons.delete_outline),
icon: Icon(Icons.delete_outline),
label: Text(l10n.clearTrail),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
@@ -161,7 +161,7 @@ class TrailControls extends StatelessWidget {
size: 48,
color: Colors.grey,
),
const SizedBox(height: 8),
SizedBox(height: 8),
Text(
l10n.noTrailRecorded,
style: const TextStyle(
@@ -169,7 +169,7 @@ class TrailControls extends StatelessWidget {
fontSize: 16,
),
),
const SizedBox(height: 8),
SizedBox(height: 8),
Text(
l10n.startTrackingToRecord,
style: const TextStyle(
@@ -191,7 +191,7 @@ class TrailControls extends StatelessWidget {
Row(
children: [
const Icon(Icons.people, size: 20),
const SizedBox(width: 8),
SizedBox(width: 8),
Text(
l10n.contactTrails,
style: const TextStyle(
@@ -205,7 +205,7 @@ class TrailControls extends StatelessWidget {
// Show All Contact Trails toggle
SwitchListTile(
secondary: const Icon(Icons.route),
secondary: Icon(Icons.route),
title: Text(l10n.showAllContactTrails),
subtitle: Text(
contactsWithTrails.isEmpty

View File

@@ -200,7 +200,7 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
),
);
}),
const SizedBox(height: 24),
SizedBox(height: 24),
Text(
AppLocalizations.of(context)!.sendTo,
style: TextStyle(
@@ -231,7 +231,7 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
color: Colors.red.withValues(alpha: 0.3),
),
),
child: const Text('No destinations available'),
child: Text(AppLocalizations.of(context)!.noDestinationsAvailableLabel),
);
}
@@ -426,7 +426,7 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
(!_sendToAllContacts && _selectedContact == null)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Select marker type and destination'),
content: Text(AppLocalizations.of(context)!.selectMarkerTypeAndDestination),
),
);
return;
@@ -449,8 +449,8 @@ class _CustomMapSarUpdateSheetState extends State<CustomMapSarUpdateSheet> {
Navigator.pop(context);
}
},
icon: const Icon(Icons.send),
label: const Text('Send'),
icon: Icon(Icons.send),
label: Text(AppLocalizations.of(context)!.send),
),
),
),

View File

@@ -288,7 +288,7 @@ class _MessageBubbleState extends State<MessageBubble> {
children: [
if (!isOwnMessage && widget.onReply != null)
ListTile(
leading: const Icon(Icons.reply),
leading: Icon(Icons.reply),
title: Text(AppLocalizations.of(context)!.reply),
onTap: () {
Navigator.pop(context);
@@ -297,7 +297,7 @@ class _MessageBubbleState extends State<MessageBubble> {
),
// Copy text option
ListTile(
leading: const Icon(Icons.copy),
leading: Icon(Icons.copy),
title: Text(AppLocalizations.of(context)!.copyText),
onTap: () {
Clipboard.setData(ClipboardData(text: widget.message.text));
@@ -329,7 +329,7 @@ class _MessageBubbleState extends State<MessageBubble> {
}
return ListTile(
leading: const Icon(Icons.bookmark_add),
leading: Icon(Icons.bookmark_add),
title: Text(AppLocalizations.of(context)!.saveAsTemplate),
onTap: () {
Navigator.pop(context);
@@ -342,7 +342,7 @@ class _MessageBubbleState extends State<MessageBubble> {
if (widget.message.isSarMarker &&
widget.message.sarGpsCoordinates != null)
ListTile(
leading: const Icon(Icons.share_location),
leading: Icon(Icons.share_location),
title: Text(AppLocalizations.of(context)!.shareLocation),
onTap: () {
Navigator.pop(context);
@@ -352,7 +352,7 @@ class _MessageBubbleState extends State<MessageBubble> {
// Navigate to drawing option (only for drawing messages)
if (widget.message.isDrawing && widget.message.drawingId != null)
ListTile(
leading: const Icon(Icons.map),
leading: Icon(Icons.map),
title: Text(AppLocalizations.of(context)!.navigateToDrawing),
onTap: () {
Navigator.pop(context);
@@ -362,7 +362,7 @@ class _MessageBubbleState extends State<MessageBubble> {
// Copy coordinates option (only for drawing messages)
if (widget.message.isDrawing && widget.message.drawingId != null)
ListTile(
leading: const Icon(Icons.copy),
leading: Icon(Icons.copy),
title: Text(AppLocalizations.of(context)!.copyCoordinates),
onTap: () {
Navigator.pop(context);
@@ -372,7 +372,7 @@ class _MessageBubbleState extends State<MessageBubble> {
// Hide from map option (only for drawing messages)
if (widget.message.isDrawing && widget.message.drawingId != null)
ListTile(
leading: const Icon(Icons.visibility_off),
leading: Icon(Icons.visibility_off),
title: Text(AppLocalizations.of(context)!.hideFromMap),
onTap: () {
Navigator.pop(context);
@@ -381,7 +381,7 @@ class _MessageBubbleState extends State<MessageBubble> {
),
// Technical details option
ListTile(
leading: const Icon(Icons.data_object),
leading: Icon(Icons.data_object),
title: Text(AppLocalizations.of(context)!.technicalDetails),
onTap: () {
Navigator.pop(context);
@@ -392,8 +392,8 @@ class _MessageBubbleState extends State<MessageBubble> {
widget.message.pathLen > 0 &&
widget.message.pathLen < 255)
ListTile(
leading: const Icon(Icons.route),
title: const Text('Trace'),
leading: Icon(Icons.route),
title: Text(AppLocalizations.of(context)!.trace),
onTap: () {
Navigator.pop(context);
_showTraceSheet(context);
@@ -401,7 +401,7 @@ class _MessageBubbleState extends State<MessageBubble> {
),
// Delete message option
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
leading: Icon(Icons.delete, color: Colors.red),
title: Text(
AppLocalizations.of(context)!.delete,
style: const TextStyle(color: Colors.red),
@@ -686,7 +686,7 @@ class _MessageBubbleState extends State<MessageBubble> {
void copyField(String value) {
Clipboard.setData(ClipboardData(text: value));
ToastLogger.success(context, l10n.textCopiedToClipboard);
ToastLogger.success(context, AppLocalizations.of(context)!.textCopiedToClipboard);
}
showModalBottomSheet(
@@ -708,14 +708,14 @@ class _MessageBubbleState extends State<MessageBubble> {
children: [
Expanded(
child: Text(
l10n.messageTechnicalDetails,
AppLocalizations.of(context)!.messageTechnicalDetails,
style: Theme.of(sheetContext).textTheme.titleLarge,
),
),
IconButton(
onPressed: () => Navigator.pop(sheetContext),
icon: const Icon(Icons.close),
tooltip: l10n.close,
icon: Icon(Icons.close),
tooltip: AppLocalizations.of(context)!.close,
),
],
),
@@ -758,17 +758,17 @@ class _MessageBubbleState extends State<MessageBubble> {
if (widget.message.lastEchoRssiDbm != null ||
snrDb != null ||
rssiDbm != null) ...[
const SizedBox(height: 12),
SizedBox(height: 12),
_techSection(
sheetContext,
icon: Icons.network_check,
title: l10n.linkQuality,
title: AppLocalizations.of(context)!.linkQuality,
child: Column(
children: [
if (rssiDbm != null)
_signalRow(
sheetContext,
label: 'RSSI',
label: AppLocalizations.of(context)!.rssi,
valueLabel: '$rssiDbm dBm',
normalized:
((rssiDbm.toDouble() + 120.0) / 70.0)
@@ -780,10 +780,10 @@ class _MessageBubbleState extends State<MessageBubble> {
: Colors.redAccent,
),
if (snrDb != null) ...[
const SizedBox(height: 8),
SizedBox(height: 8),
_signalRow(
sheetContext,
label: 'SNR',
label: AppLocalizations.of(context)!.snr,
valueLabel: '${snrDb.toStringAsFixed(1)} dB',
normalized: ((snrDb + 20.0) / 40.0).clamp(
0.0,
@@ -800,21 +800,21 @@ class _MessageBubbleState extends State<MessageBubble> {
),
),
],
const SizedBox(height: 12),
SizedBox(height: 12),
_techSection(
sheetContext,
icon: Icons.tune,
title: l10n.delivery,
title: AppLocalizations.of(context)!.delivery,
child: Column(
children: [
_detailRow(
sheetContext,
label: l10n.status,
label: AppLocalizations.of(context)!.status,
value: widget.message.deliveryStatus.name,
),
_detailRow(
sheetContext,
label: 'Received (RFC3339)',
label: AppLocalizations.of(context)!.receivedRfc3339,
value: _formatRfc3339(widget.message.receivedAt),
onCopy: () => copyField(
_formatRfc3339(widget.message.receivedAt),
@@ -823,14 +823,14 @@ class _MessageBubbleState extends State<MessageBubble> {
if (widget.message.expectedAckTag != null)
_detailRow(
sheetContext,
label: l10n.expectedAckTag,
label: AppLocalizations.of(context)!.expectedAckTag,
value: widget.message.expectedAckTag!
.toString(),
),
if (receptionDetails?.senderToReceiptMs != null)
_detailRow(
sheetContext,
label: 'Sender to receipt',
label: AppLocalizations.of(context)!.senderToReceipt,
value: _formatDurationMs(
receptionDetails!.senderToReceiptMs!,
),
@@ -838,7 +838,7 @@ class _MessageBubbleState extends State<MessageBubble> {
if (receptionDetails?.estimatedTransmitMs != null)
_detailRow(
sheetContext,
label: 'Estimated tx',
label: AppLocalizations.of(context)!.estimatedTx,
value: _formatDurationMs(
receptionDetails!.estimatedTransmitMs!,
),
@@ -846,7 +846,7 @@ class _MessageBubbleState extends State<MessageBubble> {
if (receptionDetails?.postTransmitDelayMs != null)
_detailRow(
sheetContext,
label: 'Post-tx delay',
label: AppLocalizations.of(context)!.posttxDelay,
value: _formatDurationMs(
receptionDetails!.postTransmitDelayMs!,
),
@@ -854,44 +854,44 @@ class _MessageBubbleState extends State<MessageBubble> {
if (widget.receivedCopies > 1)
_detailRow(
sheetContext,
label: 'Received copies',
label: AppLocalizations.of(context)!.receivedCopies,
value: '${widget.receivedCopies}',
),
if (widget.message.suggestedTimeoutMs != null)
_detailRow(
sheetContext,
label: 'ACK timeout',
label: AppLocalizations.of(context)!.ackTimeout,
value:
'${widget.message.suggestedTimeoutMs} ms',
),
if (retryCause != null)
_detailRow(
sheetContext,
label: 'Retry cause',
label: AppLocalizations.of(context)!.retryCause,
value: retryCause,
),
if (retryMode != null)
_detailRow(
sheetContext,
label: 'Retry mode',
label: AppLocalizations.of(context)!.retryMode,
value: retryMode,
),
if (widget.message.roundTripTimeMs != null)
_detailRow(
sheetContext,
label: l10n.roundTrip,
label: AppLocalizations.of(context)!.roundTrip,
value: '${widget.message.roundTripTimeMs} ms',
),
if (widget.message.retryAttempt > 0)
_detailRow(
sheetContext,
label: l10n.retryAttempt,
label: AppLocalizations.of(context)!.retryAttempt,
value: '${widget.message.retryAttempt}/4',
),
if (widget.message.lastRetryAt != null)
_detailRow(
sheetContext,
label: 'Last retry',
label: AppLocalizations.of(context)!.lastRetry,
value: _formatRfc3339(
widget.message.lastRetryAt!,
),
@@ -902,49 +902,49 @@ class _MessageBubbleState extends State<MessageBubble> {
if (widget.message.usedFloodFallback)
_detailRow(
sheetContext,
label: l10n.floodFallback,
value: l10n.yes,
label: AppLocalizations.of(context)!.floodFallback,
value: AppLocalizations.of(context)!.yes,
),
if (routeMetadata?.canonicalPath
case final routePath?)
_detailRow(
sheetContext,
label: 'Selected path',
label: AppLocalizations.of(context)!.selectedPath,
value: routePath,
onCopy: () => copyField(routePath),
),
if (retryResult != null)
_detailRow(
sheetContext,
label: 'Retry result',
label: AppLocalizations.of(context)!.retryResult,
value: retryResult,
),
if (packetPathHex != null)
_detailRow(
sheetContext,
label: 'Path bytes',
label: AppLocalizations.of(context)!.pathBytes,
value: packetPathHex,
onCopy: () => copyField(packetPathHex),
),
],
),
),
const SizedBox(height: 12),
SizedBox(height: 12),
_techSection(
sheetContext,
icon: Icons.badge,
title: l10n.identity,
title: AppLocalizations.of(context)!.identity,
child: Column(
children: [
_detailRow(
sheetContext,
label: l10n.messageId,
label: AppLocalizations.of(context)!.messageId,
value: widget.message.id,
onCopy: () => copyField(widget.message.id),
),
_detailRow(
sheetContext,
label: l10n.sender,
label: AppLocalizations.of(context)!.sender,
value:
senderName ??
widget.message.senderName ??
@@ -953,20 +953,20 @@ class _MessageBubbleState extends State<MessageBubble> {
if (senderPrefixHex != null)
_detailRow(
sheetContext,
label: l10n.senderKey,
label: AppLocalizations.of(context)!.senderKey,
value: senderPrefixHex,
onCopy: () => copyField(senderPrefixHex),
),
if (recipientName != null)
_detailRow(
sheetContext,
label: l10n.recipient,
label: AppLocalizations.of(context)!.recipient,
value: recipientName,
),
if (recipientPrefixHex != null)
_detailRow(
sheetContext,
label: l10n.recipientKey,
label: AppLocalizations.of(context)!.recipientKey,
value: recipientPrefixHex,
onCopy: () => copyField(recipientPrefixHex),
),
@@ -974,51 +974,51 @@ class _MessageBubbleState extends State<MessageBubble> {
),
),
if (widget.message.isVoice) ...[
const SizedBox(height: 12),
SizedBox(height: 12),
_techSection(
sheetContext,
icon: Icons.graphic_eq,
title: l10n.voice,
title: AppLocalizations.of(context)!.voice,
child: Column(
children: [
_detailRow(
sheetContext,
label: l10n.voiceId,
label: AppLocalizations.of(context)!.voiceId,
value: widget.message.voiceId ?? '-',
),
_detailRow(
sheetContext,
label: l10n.envelope,
label: AppLocalizations.of(context)!.envelope,
value: envelope != null
? 'VE3 compact'
: l10n.unknown,
: AppLocalizations.of(context)!.unknown,
),
if (voiceSession != null)
_detailRow(
sheetContext,
label: l10n.sessionProgress,
label: AppLocalizations.of(context)!.sessionProgress,
value:
'${voiceSession.receivedCount}/${voiceSession.total} segments',
),
if (voiceSession != null)
_detailRow(
sheetContext,
label: l10n.complete,
label: AppLocalizations.of(context)!.complete,
value: voiceSession.isComplete
? l10n.yes
: l10n.no,
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
if (transferDetails != null)
_detailRow(
sheetContext,
label: 'Transfers',
label: AppLocalizations.of(context)!.transfers,
value: '${transferDetails.totalTransfers}',
),
if (transferDetails != null &&
transferDetails.downloaders.isNotEmpty)
_detailRow(
sheetContext,
label: 'Downloaded by',
label: AppLocalizations.of(context)!.downloadedBy,
value: _formatDownloaderSummary(
transferDetails,
),
@@ -1026,7 +1026,7 @@ class _MessageBubbleState extends State<MessageBubble> {
if (voiceTxEstimate > Duration.zero)
_detailRow(
sheetContext,
label: 'Estimated tx',
label: AppLocalizations.of(context)!.estimatedTx,
value: voiceTxEstimate.inSeconds < 60
? '~${voiceTxEstimate.inSeconds}s'
: '~${voiceTxEstimate.inMinutes}m ${voiceTxEstimate.inSeconds % 60}s',
@@ -1036,32 +1036,32 @@ class _MessageBubbleState extends State<MessageBubble> {
),
],
if (imageEnvelope != null) ...[
const SizedBox(height: 12),
SizedBox(height: 12),
_techSection(
sheetContext,
icon: Icons.image_outlined,
title: 'Image',
title: AppLocalizations.of(context)!.image,
child: Column(
children: [
_detailRow(
sheetContext,
label: l10n.envelope,
label: AppLocalizations.of(context)!.envelope,
value: 'IE1',
),
_detailRow(
sheetContext,
label: 'Format',
label: AppLocalizations.of(context)!.format,
value: imageEnvelope.format.label,
),
_detailRow(
sheetContext,
label: 'Dimensions',
label: AppLocalizations.of(context)!.dimensions,
value:
'${imageEnvelope.width}×${imageEnvelope.height}',
),
_detailRow(
sheetContext,
label: 'Segments',
label: AppLocalizations.of(context)!.segments,
value: imageSession != null
? '${imageSession.receivedCount}/${imageSession.total}'
: '${imageEnvelope.total}',
@@ -1069,22 +1069,22 @@ class _MessageBubbleState extends State<MessageBubble> {
if (imageSession != null)
_detailRow(
sheetContext,
label: l10n.complete,
label: AppLocalizations.of(context)!.complete,
value: imageSession.isComplete
? l10n.yes
: l10n.no,
? AppLocalizations.of(context)!.yes
: AppLocalizations.of(context)!.no,
),
if (transferDetails != null)
_detailRow(
sheetContext,
label: 'Transfers',
label: AppLocalizations.of(context)!.transfers,
value: '${transferDetails.totalTransfers}',
),
if (transferDetails != null &&
transferDetails.downloaders.isNotEmpty)
_detailRow(
sheetContext,
label: 'Downloaded by',
label: AppLocalizations.of(context)!.downloadedBy,
value: _formatDownloaderSummary(
transferDetails,
),
@@ -1092,7 +1092,7 @@ class _MessageBubbleState extends State<MessageBubble> {
if (imageTxEstimate > Duration.zero)
_detailRow(
sheetContext,
label: 'Estimated tx',
label: AppLocalizations.of(context)!.estimatedTx,
value: imageTxEstimate.inSeconds < 60
? '~${imageTxEstimate.inSeconds}s'
: '~${imageTxEstimate.inMinutes}m ${imageTxEstimate.inSeconds % 60}s',
@@ -1107,7 +1107,7 @@ class _MessageBubbleState extends State<MessageBubble> {
dense: true,
visualDensity: VisualDensity.compact,
title: Text(
l10n.rawDump,
AppLocalizations.of(context)!.rawDump,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@@ -1432,7 +1432,7 @@ class _MessageBubbleState extends State<MessageBubble> {
final lon = coords.longitude.toStringAsFixed(6);
// Build share text
final shareText = l10n.shareLocationText(
final shareText = AppLocalizations.of(context)!.shareLocationText(
markerInfo,
lat,
lon,
@@ -1441,7 +1441,7 @@ class _MessageBubbleState extends State<MessageBubble> {
// Share the location
SharePlus.instance.share(
ShareParams(text: shareText, subject: l10n.sarLocationShare),
ShareParams(text: shareText, subject: AppLocalizations.of(context)!.sarLocationShare),
);
}
@@ -1852,14 +1852,14 @@ class _MessageBubbleState extends State<MessageBubble> {
}
} else if (message.isChannelMessage) {
if (message.channelIdx == 0) {
channelDisplayName = l10n.publicChannel;
channelDisplayName = AppLocalizations.of(context)!.publicChannel;
} else {
final channelContact = contactsProvider.channels.where((c) {
return c.publicKey.length > 1 && c.publicKey[1] == message.channelIdx;
}).firstOrNull;
channelDisplayName =
channelContact?.getLocalizedDisplayName(context) ??
'${l10n.channel} ${message.channelIdx}';
'${AppLocalizations.of(context)!.channel} ${message.channelIdx}';
}
if (isOwnMessage) {
@@ -1869,14 +1869,14 @@ class _MessageBubbleState extends State<MessageBubble> {
final recipientSubtitle =
isOwnMessage && message.isChannelMessage && recipientDisplayName != null
? '${l10n.channel}: $recipientDisplayName'
? '${AppLocalizations.of(context)!.channel}: $recipientDisplayName'
: recipientDisplayName;
final directCounterpartLabel = !message.isChannelMessage
? (isOwnMessage ? recipientSubtitle : l10n.you)
? (isOwnMessage ? recipientSubtitle : AppLocalizations.of(context)!.you)
: null;
final receivedChannelSubtitle =
!isOwnMessage && message.isChannelMessage && channelDisplayName != null
? '${l10n.channel}: $channelDisplayName'
? '${AppLocalizations.of(context)!.channel}: $channelDisplayName'
: null;
final shouldFloatBubble = widget.isCompact;
@@ -1992,7 +1992,7 @@ class _MessageBubbleState extends State<MessageBubble> {
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.draw, size: 16, color: Colors.white),
const SizedBox(width: 4),
SizedBox(width: 4),
Text(
AppLocalizations.of(context)!.mapDrawing,
style: Theme.of(context).textTheme.labelSmall
@@ -2029,7 +2029,7 @@ class _MessageBubbleState extends State<MessageBubble> {
size: 16,
color: Colors.white,
),
const SizedBox(width: 4),
SizedBox(width: 4),
Text(
AppLocalizations.of(context)!.sarAlert,
style: Theme.of(context).textTheme.labelSmall
@@ -2522,7 +2522,7 @@ class _MessageBubbleState extends State<MessageBubble> {
? Colors.red
: Colors.orange,
),
const SizedBox(width: 3),
SizedBox(width: 3),
Text(
message.deliveredRecipientsCount ==
message.recipients!.length

View File

@@ -55,12 +55,12 @@ class MessagesContent extends StatelessWidget {
size: 64,
color: Theme.of(context).disabledColor,
),
const SizedBox(height: 16),
SizedBox(height: 16),
Text(
AppLocalizations.of(context)!.noMessagesYet,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
SizedBox(height: 8),
Text(
AppLocalizations.of(context)!.pullDownToSync,
style: Theme.of(context).textTheme.bodyMedium,

View File

@@ -111,7 +111,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
// Search field
Padding(
padding: const EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
@@ -154,7 +154,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
context: context,
icon: Icons.all_inbox,
title: l10n.showAll,
subtitle: 'All messages',
subtitle: l10n.allMessages,
unreadCount: widget.unreadCount,
isSelected: _isSelected('all', null),
onTap: () {
@@ -181,7 +181,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
),
if (filteredChannels.isEmpty)
Padding(
padding: const EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: Text(
l10n.noChannelsFound,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
@@ -234,7 +234,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
),
if (filteredContacts.isEmpty)
Padding(
padding: const EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: Text(
l10n.noContactsFound,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
@@ -283,7 +283,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
),
if (filteredRooms.isEmpty)
Padding(
padding: const EdgeInsets.all(16),
padding: EdgeInsets.all(16),
child: Text(
l10n.noRoomsFound,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
@@ -323,7 +323,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
size: 64,
color: Theme.of(context).disabledColor,
),
const SizedBox(height: 16),
SizedBox(height: 16),
Text(
l10n.noRecipientsAvailable,
style: Theme.of(context).textTheme.bodyLarge

View File

@@ -381,7 +381,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
),
);
}),
const SizedBox(height: 16),
SizedBox(height: 16),
// Destination selection (compact dropdown with rooms, channels, and all contacts)
Text(
@@ -505,7 +505,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
size: 18,
color: colorScheme.onSurface,
),
const SizedBox(width: 12),
SizedBox(width: 12),
Expanded(
child: Text(
AppLocalizations.of(context)!.allTeamContacts,
@@ -593,7 +593,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
color: Colors.green,
size: 20,
),
const SizedBox(width: 8),
SizedBox(width: 8),
Expanded(
child: Text(
AppLocalizations.of(context)!.directMessagesInfo(chatContactsCount),
@@ -718,7 +718,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
),
),
),
const SizedBox(width: 16),
SizedBox(width: 16),
Text(
AppLocalizations.of(context)!.gettingLocation,
style: TextStyle(color: colorScheme.onSurface),
@@ -979,7 +979,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
size: 16,
color: Colors.blue,
),
const SizedBox(width: 8),
SizedBox(width: 8),
Expanded(
child: Text(
AppLocalizations.of(context)!.exampleCoordinates,
@@ -994,7 +994,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
),
],
const SizedBox(height: 24),
SizedBox(height: 24),
// Optional notes
Text(
@@ -1193,7 +1193,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
borderRadius: BorderRadius.circular(8),
),
),
icon: const Icon(Icons.send, size: 20),
icon: Icon(Icons.send, size: 20),
label: Text(
AppLocalizations.of(context)!.sendSarMarker,
style: const TextStyle(

View File

@@ -9,6 +9,7 @@ import '../../providers/contacts_provider.dart';
import '../../providers/messages_provider.dart';
import '../../utils/tictactoe_message_parser.dart';
import '../../utils/toast_logger.dart';
import '../../l10n/app_localizations.dart';
class TicTacToeMessageBubble extends StatelessWidget {
final Message message;
@@ -33,7 +34,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
final messages = context.watch<MessagesProvider>().messages;
final selfKey = conn.deviceInfo.publicKey;
if (selfKey == null || selfKey.length < 6) {
return const Text('Tic-Tac-Toe unavailable');
return Text(AppLocalizations.of(context)!.tictactoeUnavailable);
}
final selfKey6 = _key6Hex(selfKey);
@@ -43,7 +44,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
isSentByMe: isSentByMe,
);
if (opponent == null) {
return const Text('Tic-Tac-Toe: opponent unknown');
return Text(AppLocalizations.of(context)!.tictactoeOpponentUnknown);
}
final opponentKey6 = _key6Hex(opponent.publicKey);
@@ -69,7 +70,7 @@ class TicTacToeMessageBubble extends StatelessWidget {
start ??= event.type == TicTacToeEventType.start ? event : null;
if (start == null) {
return const Text('Tic-Tac-Toe: waiting for start');
return Text(AppLocalizations.of(context)!.tictactoeWaitingForStart);
}
final xPlayer = start.playerKey6;

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import '../l10n/app_localizations.dart';
/// Dialog that requests location permissions on app startup
class PermissionRequestDialog extends StatefulWidget {
@@ -109,8 +110,8 @@ class _PermissionRequestDialogState extends State<PermissionRequestDialog> {
Icons.location_on,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 12),
const Text('Location Permission'),
SizedBox(width: 12),
Text(AppLocalizations.of(context)!.locationPermission),
],
),
content: Column(

View File

@@ -210,7 +210,7 @@ class _SarTemplateEditDialogState extends State<SarTemplateEditDialog> {
maxLines: 2,
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 16),
SizedBox(height: 16),
// Color picker
Text(
@@ -305,7 +305,7 @@ class _SarTemplateEditDialogState extends State<SarTemplateEditDialog> {
const SizedBox(width: 12),
ElevatedButton.icon(
onPressed: _save,
icon: const Icon(Icons.save),
icon: Icon(Icons.save),
label: Text(l10n.save),
),
],

View File

@@ -1090,7 +1090,7 @@ class SensorTelemetryCard extends StatelessWidget {
),
),
if (state == SensorRefreshState.timeout)
const _InlineAlertBadge(label: 'No response'),
_InlineAlertBadge(label: l10n.noResponse),
],
),
if (telemetry != null) ...[
@@ -1107,8 +1107,8 @@ class SensorTelemetryCard extends StatelessWidget {
),
),
if (state == SensorRefreshState.refreshing)
const _InlineStateMeta(
label: 'Refreshing',
_InlineStateMeta(
label: l10n.refreshing,
color: Color(0xFF266AC2),
spinning: true,
),
@@ -1119,8 +1119,8 @@ class SensorTelemetryCard extends StatelessWidget {
icon: Icons.check_circle,
),
if (state == SensorRefreshState.unavailable)
const _InlineStateMeta(
label: 'Unavailable',
_InlineStateMeta(
label: l10n.unavailable,
color: Color(0xFFB13B55),
icon: Icons.error_outline,
),
@@ -1153,17 +1153,17 @@ class SensorTelemetryCard extends StatelessWidget {
}
if (onCustomize != null) {
items.add(
const PopupMenuItem<String>(
PopupMenuItem<String>(
value: 'customize',
child: Text('Customize fields'),
child: Text(l10n.customizeFields),
),
);
}
if (onRemove != null) {
items.add(
const PopupMenuItem<String>(
PopupMenuItem<String>(
value: 'remove',
child: Text('Remove'),
child: Text(l10n.remove),
),
);
}

View File

@@ -54,10 +54,10 @@ class UpdateDialog extends StatelessWidget {
// Optional: Build timestamp
if (updateInfo.timestamp != null) ...[
const SizedBox(height: 12),
SizedBox(height: 12),
_buildInfoRow(
context,
label: 'Build Time',
label: AppLocalizations.of(context)!.buildTime,
value: _formatTimestamp(updateInfo.timestamp!),
),
],
@@ -131,8 +131,8 @@ class UpdateDialog extends StatelessWidget {
if (updateInfo.downloadUrl == null) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Download URL not available'),
SnackBar(
content: Text(AppLocalizations.of(context)!.downloadUrlNotAvailable),
backgroundColor: Colors.red,
),
);
@@ -147,8 +147,8 @@ class UpdateDialog extends StatelessWidget {
if (!canLaunch) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Cannot open download URL'),
SnackBar(
content: Text(AppLocalizations.of(context)!.cannotOpenDownloadUrl),
backgroundColor: Colors.red,
),
);