Compare commits

..

16 Commits

Author SHA1 Message Date
Winston Lowe ba4fd3eff5 Merge branch 'dev-offline' of github.com:zjs81/meshcore-open into dev-offline 2026-05-27 10:31:23 -07:00
Winston Lowe beb3e1996d fix: Improve error handling when restoring last companion public key and update disconnection message in Dutch localization 2026-05-27 10:26:19 -07:00
Winston Lowe 3f4e6f4e13 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 18:49:25 -07:00
Winston Lowe e1cc285c8a fix: Correct channel addition logic to prevent duplicates based on pskHex 2026-05-12 18:31:55 -07:00
Winston Lowe f4b32e8a8a Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 18:30:39 -07:00
Winston Lowe 01bf95c98e fix: Implement disconnect method in _FakeMeshCoreConnector for TCP and USB flow tests 2026-05-12 18:21:18 -07:00
Winston Lowe fa044dd204 feat: Add dialog messages for companion connection and disconnection
- Introduced new dialog messages for connecting to a companion and handling disconnection across multiple languages.
- Updated localization files for French, Hungarian, Italian, Japanese, Korean, Bulgarian, German, English, Spanish, Dutch, Polish, Portuguese, Russian, Slovak, Slovenian, Swedish, Ukrainian, and Chinese.
- Modified the contacts and map screens to utilize the new dialog messages.
- Enhanced the disconnect confirmation dialog to show a message upon successful disconnection.
- Updated app bar to conditionally display radio stats based on companion connection status.
2026-05-12 18:01:31 -07:00
Winston Lowe 72fea3fc32 fix: Enhance channel deduplication logic in loadChannels method 2026-05-12 18:01:31 -07:00
Winston Lowe f0bd61144c fix: Remove unnecessary whitespace in keyedMessageIds initialization 2026-05-12 18:01:31 -07:00
Winston Lowe 61c897630c fix: Improve message key handling and enhance contact tile key uniqueness 2026-05-12 18:01:31 -07:00
Winston Lowe a270e2e6d1 fix: Add byteCount initialization in PathRecord.fromJson method 2026-05-12 18:01:31 -07:00
Winston Lowe 247db6a36d fix: Initialize _selfPublicKey with lastCompanionPublicKeyHex in MeshCoreConnector 2026-05-12 18:01:31 -07:00
Winston Lowe 78d08afb47 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 18:01:31 -07:00
Winston Lowe c77264cc81 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-12 18:01:31 -07:00
Winston Lowe d6ed8c5f13 feat: Enhance companion connection features and UI updates
- Added functionality to load and restore the last companion's scope on app startup.
- Implemented caching mechanisms for contacts, channels, and messages related to the current companion.
- Updated UI to reflect connection status, including disabling message input when disconnected.
- Introduced new dialog prompts to inform users when they need to connect to a companion for accessing features.
- Refactored navigation logic to improve user experience when disconnected, directing users to the scanner screen.
- Added localization strings for new companion connection prompts in multiple languages.
2026-05-12 18:01:31 -07:00
Winston Lowe 209fee48ca Add byteCount field to PathRecord class with default value 2026-05-12 17:59:58 -07:00
127 changed files with 13449 additions and 32004 deletions
-3
View File
@@ -91,6 +91,3 @@ keystore.properties
# Cloudflare Wrangler
.wrangler
# Claude Code local working dir (worktrees, jobs, settings)
.claude/
File diff suppressed because it is too large Load Diff
@@ -19,7 +19,6 @@ class MeshCoreUsbManager {
String? get activePortKey => _activePortKey;
String? get activePortDisplayLabel => _activePortLabel ?? _activePortKey;
bool get isConnected => _service.isConnected;
Object? get lastError => _service.lastError;
Stream<Uint8List> get frameStream => _service.frameStream;
// --- Configuration ---
+2 -13
View File
@@ -224,12 +224,6 @@ const int reqTypeGetTelemetry = 0x03;
const int reqTypeGetAccessList = 0x05;
const int reqTypeGetNeighbors = 0x06;
Uint8List buildTelemetryBinaryPayload() {
// Room servers/repeaters read byte 1 as an inverse telemetry permission mask.
// Zero means "request every telemetry field allowed for this contact".
return Uint8List.fromList([reqTypeGetTelemetry, 0x00, 0x00, 0x00, 0x00]);
}
// Repeater response codes
const int respServerLoginOk = 0;
@@ -457,13 +451,8 @@ String pubKeyToHex(Uint8List pubKey) {
// Helper to convert hex string to public key
Uint8List hexToPubKey(String hex) {
if (hex.length != pubKeySize * 2) {
throw FormatException(
'Public key hex must be ${pubKeySize * 2} chars, got ${hex.length}',
);
}
final result = Uint8List(pubKeySize);
for (int i = 0; i < pubKeySize; i++) {
for (int i = 0; i < pubKeySize && i * 2 + 1 < hex.length; i++) {
result[i] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
}
return result;
@@ -956,7 +945,7 @@ Uint8List buildSendTelemetryReq(Uint8List? pubKey) {
writer.writeBytes(Uint8List(3)); // reserved bytes
writer.writeBytes(pubKey);
} else {
writer.writeBytes(Uint8List(3)); // reserved bytes
writer.writeBytes(Uint8List(4)); // reserved bytes
}
return writer.toBytes();
}
-4
View File
@@ -3,10 +3,6 @@ class MeshCoreUuids {
static const String rxCharacteristic = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
/// Known advertised-name prefixes used by stock MeshCore firmware builds.
/// Discovery no longer filters on these (it filters on the [service] UUID so
/// that community forks with custom names are still found); kept for
/// reference and possible future display heuristics.
static const List<String> deviceNamePrefixes = [
"MeshCore-",
"Whisper-",
+9 -209
View File
@@ -96,34 +96,6 @@ class CayenneLpp {
}
switch (type) {
case lppDigitalInput:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt8(),
});
break;
case lppDigitalOutput:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt8(),
});
break;
case lppAnalogInput:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readInt16BE() / 100,
});
break;
case lppAnalogOutput:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readInt16BE() / 100,
});
break;
case lppGenericSensor:
telemetry.add({
'channel': channel,
@@ -159,17 +131,6 @@ class CayenneLpp {
'value': buffer.readUInt8() / 2,
});
break;
case lppAccelerometer:
telemetry.add({
'channel': channel,
'type': type,
'value': {
'x': buffer.readInt16BE() / 1000,
'y': buffer.readInt16BE() / 1000,
'z': buffer.readInt16BE() / 1000,
},
});
break;
case lppBarometricPressure:
telemetry.add({
'channel': channel,
@@ -177,13 +138,6 @@ class CayenneLpp {
'value': buffer.readUInt16BE() / 10,
});
break;
case lppAltitude:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readInt16BE(),
});
break;
case lppVoltage:
telemetry.add({
'channel': channel,
@@ -198,13 +152,6 @@ class CayenneLpp {
'value': buffer.readInt16BE() / 1000,
});
break;
case lppFrequency:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt32BE(),
});
break;
case lppPercentage:
telemetry.add({
'channel': channel,
@@ -226,56 +173,6 @@ class CayenneLpp {
'value': buffer.readUInt16BE(),
});
break;
case lppDistance:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt32BE() / 1000,
});
break;
case lppEnergy:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt32BE() / 1000,
});
break;
case lppDirection:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt16BE(),
});
break;
case lppUnixTime:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt32BE(),
});
break;
case lppGyrometer:
telemetry.add({
'channel': channel,
'type': type,
'value': {
'x': buffer.readInt16BE() / 100,
'y': buffer.readInt16BE() / 100,
'z': buffer.readInt16BE() / 100,
},
});
break;
case lppColour:
telemetry.add({
'channel': channel,
'type': type,
'value': {
'red': buffer.readUInt8(),
'green': buffer.readUInt8(),
'blue': buffer.readUInt8(),
},
});
break;
case lppGps:
telemetry.add({
'channel': channel,
@@ -287,24 +184,6 @@ class CayenneLpp {
},
});
break;
case lppSwitch:
telemetry.add({
'channel': channel,
'type': type,
'value': buffer.readUInt8(),
});
break;
case lppPolyline:
final size = buffer.readUInt8();
telemetry.add({
'channel': channel,
'type': type,
'value': {
'size': size,
'data': _bytesToHex(_readPolylinePayload(buffer, size)),
},
});
break;
default:
return telemetry;
}
@@ -337,19 +216,6 @@ class CayenneLpp {
);
switch (type) {
case lppDigitalInput:
channelData['values']['digitalInput'] = buffer.readUInt8();
break;
case lppDigitalOutput:
channelData['values']['digitalOutput'] = buffer.readUInt8();
break;
case lppAnalogInput:
channelData['values']['analogInput'] = buffer.readInt16BE() / 100.0;
break;
case lppAnalogOutput:
channelData['values']['analogOutput'] =
buffer.readInt16BE() / 100.0;
break;
case lppGenericSensor:
channelData['values']['generic'] = buffer.readUInt32BE();
break;
@@ -365,29 +231,15 @@ class CayenneLpp {
case lppRelativeHumidity:
channelData['values']['humidity'] = buffer.readUInt8() / 2.0;
break;
case lppAccelerometer:
channelData['values']['accelerometer'] = {
'x': buffer.readInt16BE() / 1000.0,
'y': buffer.readInt16BE() / 1000.0,
'z': buffer.readInt16BE() / 1000.0,
};
break;
case lppBarometricPressure:
channelData['values']['pressure'] = buffer.readUInt16BE() / 10.0;
break;
case lppAltitude:
// MeshCore encodes standalone barometric altitude as LPP type 121.
channelData['values']['altitude'] = buffer.readInt16BE();
break;
case lppVoltage:
channelData['values']['voltage'] = buffer.readInt16BE() / 100.0;
break;
case lppCurrent:
channelData['values']['current'] = buffer.readInt16BE() / 1000.0;
break;
case lppFrequency:
channelData['values']['frequency'] = buffer.readUInt32BE();
break;
case lppPercentage:
channelData['values']['percentage'] = buffer.readUInt8();
break;
@@ -397,32 +249,6 @@ class CayenneLpp {
case lppPower:
channelData['values']['power'] = buffer.readUInt16BE();
break;
case lppDistance:
channelData['values']['distance'] = buffer.readUInt32BE() / 1000.0;
break;
case lppEnergy:
channelData['values']['energy'] = buffer.readUInt32BE() / 1000.0;
break;
case lppDirection:
channelData['values']['direction'] = buffer.readUInt16BE();
break;
case lppUnixTime:
channelData['values']['time'] = buffer.readUInt32BE();
break;
case lppGyrometer:
channelData['values']['gyrometer'] = {
'x': buffer.readInt16BE() / 100.0,
'y': buffer.readInt16BE() / 100.0,
'z': buffer.readInt16BE() / 100.0,
};
break;
case lppColour:
channelData['values']['colour'] = {
'red': buffer.readUInt8(),
'green': buffer.readUInt8(),
'blue': buffer.readUInt8(),
};
break;
case lppGps:
channelData['values']['gps'] = {
'latitude': buffer.readInt24BE() / 10000.0,
@@ -430,48 +256,22 @@ class CayenneLpp {
'altitude': buffer.readInt24BE() / 100.0,
};
break;
case lppSwitch:
channelData['values']['switch'] = buffer.readUInt8() != 0;
break;
case lppPolyline:
final size = buffer.readUInt8();
channelData['values']['polyline'] = {
'size': size,
'data': _bytesToHex(_readPolylinePayload(buffer, size)),
};
break;
// Add more types as needed...
default:
// Stop parsing to avoid losing alignment on an unknown LPP type.
return _sortedChannelValues(channels);
//Stopped parsing to avoid misalignment
return channels.values.toList();
}
}
return _sortedChannelValues(channels);
final List<Map<String, dynamic>> channelsOut = channels.values.toList();
channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
return channelsOut;
} catch (e) {
// Handle parsing errors, possibly due to malformed data
appLogger.error('Error parsing Cayenne LPP data: $e');
// Preserve any fields parsed before the malformed value.
return _sortedChannelValues(channels);
return <
Map<String, dynamic>
>[]; // Return an empty list on error to avoid crashing the app
}
}
static Uint8List _readPolylinePayload(BufferReader buffer, int size) {
final declaredPayloadSize = size > 0 ? size - 1 : 0;
final availablePayloadSize = declaredPayloadSize <= buffer.remaining
? declaredPayloadSize
: buffer.remaining;
return buffer.readBytes(availablePayloadSize);
}
static List<Map<String, dynamic>> _sortedChannelValues(
Map<int, Map<String, dynamic>> channels,
) {
final channelsOut = channels.values.toList();
channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
return channelsOut;
}
static String _bytesToHex(Uint8List bytes) {
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
}
-59
View File
@@ -1,59 +0,0 @@
import 'package:flutter/material.dart';
import '../connector/meshcore_protocol.dart';
import '../utils/emoji_utils.dart';
IconData contactTypeIcon(int type) {
switch (type) {
case advTypeChat:
return Icons.chat;
case advTypeRepeater:
return Icons.cell_tower;
case advTypeRoom:
return Icons.group;
case advTypeSensor:
return Icons.sensors;
default:
return Icons.device_unknown;
}
}
Color contactTypeColor(int type) {
switch (type) {
case advTypeChat:
return Colors.blue;
case advTypeRepeater:
return Colors.orange;
case advTypeRoom:
return Colors.purple;
case advTypeSensor:
return Colors.green;
default:
return Colors.grey;
}
}
Color colorForName(String name) {
const colors = [
Colors.blue,
Colors.green,
Colors.orange,
Colors.purple,
Colors.pink,
Colors.teal,
Colors.indigo,
Colors.cyan,
Colors.amber,
Colors.deepOrange,
];
return colors[name.hashCode.abs() % colors.length];
}
String firstCharacterOrEmoji(String name) {
if (name.isEmpty) return '?';
final emoji = firstEmoji(name);
if (emoji != null) return emoji;
final runes = name.runes.toList();
if (runes.isEmpty) return '?';
return String.fromCharCode(runes[0]).toUpperCase();
}
+13 -18
View File
@@ -8,29 +8,24 @@ class PathHelper {
.join(',');
}
static String hopHex(int byte) {
return byte.toRadixString(16).padLeft(2, '0').toUpperCase();
}
static String? hopName(int byte, List<Contact> allContacts) {
final matches = allContacts
.where(
(c) =>
c.publicKey.first == byte &&
(c.type == advTypeRepeater || c.type == advTypeRoom),
)
.toList();
if (matches.isEmpty) return null;
if (matches.length == 1) return matches.first.name;
return matches.map((c) => c.name).join(' | ');
}
static String resolvePathNames(
List<int> pathBytes,
List<Contact> allContacts,
) {
return pathBytes
.map((b) => hopName(b, allContacts) ?? hopHex(b))
.map((b) {
final hex = b.toRadixString(16).padLeft(2, '0').toUpperCase();
final matches = allContacts
.where(
(c) =>
c.publicKey.first == b &&
(c.type == advTypeRepeater || c.type == advTypeRoom),
)
.toList();
if (matches.isEmpty) return hex;
if (matches.length == 1) return matches.first.name;
return matches.map((c) => c.name).join(' | ');
})
.join(' \u2192 ');
}
}
-70
View File
@@ -1,70 +0,0 @@
import 'package:latlong2/latlong.dart';
import '../connector/meshcore_protocol.dart';
import '../models/contact.dart';
class PathHopResolver {
const PathHopResolver._();
static List<Contact?> resolve({
required List<int> pathBytes,
required List<Contact> contacts,
LatLng? endpoint,
bool resolveFromEnd = false,
}) {
final candidatesByPrefix = <int, List<Contact>>{};
for (final contact in contacts) {
if (contact.publicKey.isEmpty) continue;
if (contact.type != advTypeRepeater && contact.type != advTypeRoom) {
continue;
}
candidatesByPrefix
.putIfAbsent(contact.publicKey.first, () => <Contact>[])
.add(contact);
}
for (final candidates in candidatesByPrefix.values) {
candidates.sort((a, b) => b.lastSeen.compareTo(a.lastSeen));
}
final resolved = List<Contact?>.filled(pathBytes.length, null);
final indexes = resolveFromEnd
? List<int>.generate(pathBytes.length, (i) => pathBytes.length - 1 - i)
: List<int>.generate(pathBytes.length, (i) => i);
final distance = Distance();
var previousPosition = endpoint;
for (final index in indexes) {
final candidates = candidatesByPrefix[pathBytes[index]];
if (candidates == null || candidates.isEmpty) continue;
var bestIndex = 0;
if (previousPosition != null && candidates.length > 1) {
double? nearestDistance;
for (var i = 0; i < candidates.length; i++) {
final position = _positionOf(candidates[i]);
if (position == null) continue;
final candidateDistance = distance(previousPosition, position);
if (nearestDistance == null || candidateDistance < nearestDistance) {
nearestDistance = candidateDistance;
bestIndex = i;
}
}
}
final contact = candidates.removeAt(bestIndex);
resolved[index] = contact;
previousPosition = _positionOf(contact) ?? previousPosition;
}
return resolved;
}
static LatLng? _positionOf(Contact contact) {
if (!contact.hasLocation ||
contact.latitude == null ||
contact.longitude == null) {
return null;
}
return LatLng(contact.latitude!, contact.longitude!);
}
}
+1 -13
View File
@@ -25,19 +25,7 @@ void showDismissibleSnackBar(
DismissDirection? dismissDirection,
Clip? clipBehavior,
}) {
// Callers often reach here after an async gap; the context may already be
// unmounted, or deactivated (popped but not yet disposed) — ancestor
// lookups on a deactivated element throw. Showing nothing is the right
// outcome in both cases.
if (!context.mounted) return;
var isActive = true;
assert(() {
isActive = (context as Element).debugIsActive;
return true;
}());
if (!isActive) return;
final messenger = ScaffoldMessenger.maybeOf(context);
if (messenger == null) return;
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(
SnackBar(
key: key,
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Изтрий",
"common_enable": "Активирай",
"common_disable": "Деактивирай",
"common_autoRefresh": "Автоматично обновяване",
"common_interval": "Интервал",
"common_reboot": "Рестартирай",
"common_loading": "Зареждане...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Цифров вход",
"telemetry_digitalOutputLabel": "Цифров изход",
"telemetry_analogInputLabel": "Аналогов вход",
"telemetry_analogOutputLabel": "Аналогов изход",
"telemetry_genericLabel": "Общ сензор",
"telemetry_luminosityLabel": "Осветеност",
"telemetry_presenceLabel": "Присъствие",
"telemetry_humidityLabel": "Влажност",
"telemetry_accelerometerLabel": "Акселерометър",
"telemetry_pressureLabel": "Налягане",
"telemetry_altitudeLabel": "Надморска височина",
"telemetry_frequencyLabel": "Честота",
"telemetry_percentageLabel": "Процент",
"telemetry_concentrationLabel": "Концентрация",
"telemetry_powerLabel": "Мощност",
"telemetry_distanceLabel": "Разстояние",
"telemetry_energyLabel": "Енергия",
"telemetry_directionLabel": "Посока",
"telemetry_timeLabel": "Време",
"telemetry_gyrometerLabel": "Жироскоп",
"telemetry_colourLabel": "Цвят",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Превключвател",
"telemetry_polylineLabel": "Полилиния",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Брой заявки",
"telemetry_error": "Неуспешно получаване на данни",
"telemetry_noData": "Няма налични данни за телеметрията.",
"telemetry_channelTitle": "Канал {channel}",
"@telemetry_channelTitle": {
@@ -2138,9 +2099,6 @@
"translation_composerTitle": "Преведете преди да изпратите",
"translation_enableSubtitle": "Превеждайте входящите съобщения и позволявайте предварително превеждане преди изпращане.",
"translation_composerSubtitle": "Контролира началния статус на иконата за превод, създадена от композитора.",
"translation_autoIncomingTitle": "Автоматичен превод на съобщения",
"translation_autoIncomingSubtitle": "Превежда автоматично съобщенията за известия, както и за чатове или канали.",
"translation_translateMessage": "Преведи съобщението",
"translation_targetLanguage": "Целеви език",
"translation_useAppLanguage": "Използвайте езика на приложението",
"translation_downloadedModelLabel": "Изтегнат модел",
@@ -2354,97 +2312,8 @@
"chat_newMessages": "Нови съобщения",
"settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)",
"repeater_chanUtil": "Използване на канала",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_pending": "Изпращане",
"common_undo": "Отмяни",
"messageStatus_delivered": "Доставен",
"messageStatus_sent": "Изпратено",
"messageStatus_failed": "Не успях да изпратя",
"messageStatus_repeated": "Слушах го многократно",
"contacts_moreOptions": "Повече възможности",
"contacts_searchOpen": "Търсене на контакти",
"contacts_searchClose": "Затвори търсене",
"routing_title": "Маршрутизиране",
"routing_modeAuto": "Автомобил",
"routing_modeFlood": "Наводнение",
"routing_modeManual": "Ръководство",
"routing_modeAutoHint": "Автоматично избира най-известния път, като при липса на информация, използва стратегия за \"запълване\" на празните пространства.",
"routing_modeFloodHint": "Излъчване през всички ретранслатори. Най-надежният начин, но изисква повече време на въздуха.",
"routing_modeManualHint": "Винаги следва точно пътя, който сте определили.",
"routing_currentRoute": "Текущ маршрут",
"routing_directNoHops": "Директ – без превключватели",
"routing_noPathYet": "Все още няма път. Съобщението продължава да се изпраща, докато не бъде открит маршрут.",
"routing_floodBroadcast": "Предаване през всички ретранслатори",
"routing_editPath": "Редактиране на пътя",
"routing_forgetPath": "Забравете за пътя",
"routing_knownPaths": "Известни маршрути",
"routing_knownPathsHint": "Натиснете бутона, за да превключите към него.",
"routing_inUse": "В експлоатация",
"routing_qualityStrong": "Силен първи скок",
"routing_qualityGood": "Добър първи опит",
"routing_qualityFair": "Първият добър скок",
"routing_qualityWorked": "Беше изпълнено/Доведено до край",
"routing_qualityFlood": "Получено чрез информация, разпространена в резултат на навод.",
"routing_qualityUntested": "Не тестван",
"routing_neverWorked": "никога не е потвърдено",
"routing_floodDelivery": "Доставка при навод",
"pathEditor_title": "Създаване на път",
"pathEditor_hopCounter": "{count} от 64 различни вида малц",
"pathEditor_noHops": "Все още няма добавени хмел. Можете да използвате бутоните по-долу, за да ги добавите по ред, или да запазите рецептата без хмел, за да я изпратите директно.",
"pathEditor_addHops": "Добавете хмела в реда, в който е посочено.",
"pathEditor_searchRepeaters": "Търсене на повтори",
"pathEditor_advancedHex": "Разширено: необработен шестничен път",
"pathEditor_hexLabel": "Префикси на шестнадесетична система",
"pathEditor_hexHelper": "Два шест-символни идентификатора на скок, разделени със запетаи",
"pathEditor_invalidTokens": "Невалидно: {tokens}",
"routing_lastWorked": "worked {when}",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_tooManyHops": "Максимум 64 крачета",
"pathEditor_usePath": "Използвайте този маршрут.",
"pathEditor_removeHop": "Премахнете хмела",
"pathEditor_unknownHop": "Неизвестен репитер",
"map_zoomIn": "Увеличи",
"map_zoomOut": "Приближете се по-малко",
"map_centerMap": "Карта на центъра",
"chrome_bluetoothRequiresChromium": "Web Bluetooth изисква браузър, базиран на Chromium.",
"channels_communityShortId": "Идентификационен номер: {id}...",
"pathTrace_legendGpsConfirmed": "GPS потвърдено",
"pathTrace_legendInferred": "Извлечена позиция"
"dialog_connectCompanion": "Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.",
"dialog_disconnectedTitle": "Прекъснато",
"dialog_disconnectedMessage": "Свързването ви с вашия спътник е прекъснато.",
"contact_connectCompanion": "Свържете се с спътник, за да получите достъп до функциите на repeater и room server."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Löschen",
"common_enable": "Aktivieren",
"common_disable": "Deaktivieren",
"common_autoRefresh": "Automatische Aktualisierung",
"common_interval": "Intervall",
"common_reboot": "Neustart",
"common_loading": "Laden...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digitaleingang",
"telemetry_digitalOutputLabel": "Digitalausgang",
"telemetry_analogInputLabel": "Analogeingang",
"telemetry_analogOutputLabel": "Analogausgang",
"telemetry_genericLabel": "Allgemeiner Sensor",
"telemetry_luminosityLabel": "Helligkeit",
"telemetry_presenceLabel": "Anwesenheit",
"telemetry_humidityLabel": "Luftfeuchtigkeit",
"telemetry_accelerometerLabel": "Beschleunigungsmesser",
"telemetry_pressureLabel": "Druck",
"telemetry_altitudeLabel": "Höhe",
"telemetry_frequencyLabel": "Frequenz",
"telemetry_percentageLabel": "Prozentsatz",
"telemetry_concentrationLabel": "Konzentration",
"telemetry_powerLabel": "Leistung",
"telemetry_distanceLabel": "Entfernung",
"telemetry_energyLabel": "Energie",
"telemetry_directionLabel": "Richtung",
"telemetry_timeLabel": "Zeit",
"telemetry_gyrometerLabel": "Gyroskop",
"telemetry_colourLabel": "Farbe",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Schalter",
"telemetry_polylineLabel": "Polylinie",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Anzahl der Anfragen",
"telemetry_error": "Daten konnten nicht abgerufen werden",
"telemetry_noData": "Keine Telemetriedaten verfügbar.",
"telemetry_channelTitle": "Kanal {channel}",
"@telemetry_channelTitle": {
@@ -2166,9 +2127,6 @@
"translation_enableSubtitle": "Nachrichten empfangen und übersetzen sowie die Möglichkeit bieten, Nachrichten vor dem Versenden zu übersetzen.",
"translation_enableTitle": "Aktivieren Sie die Übersetzung",
"translation_composerSubtitle": "Steuert den Standardzustand des Icons für die Übersetzung des Komponisten.",
"translation_autoIncomingTitle": "Nachrichten automatisch übersetzen",
"translation_autoIncomingSubtitle": "Übersetzt Nachrichten für Benachrichtigungen sowie für Chats oder Kanäle automatisch.",
"translation_translateMessage": "Nachricht übersetzen",
"translation_targetLanguage": "Zielsprache",
"translation_useAppLanguage": "Verwenden Sie die App-Sprache",
"translation_downloadedModelLabel": "Heruntergeladenes Modell",
@@ -2382,97 +2340,8 @@
"settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten",
"repeater_chanUtil": "Nutzung des Kanals",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_sent": "Gesendet",
"messageStatus_delivered": "Geliefert",
"common_undo": "Rückgängig machen",
"messageStatus_pending": "Versenden",
"messageStatus_failed": "Nicht gesendet",
"messageStatus_repeated": "Wiederholt gehört",
"contacts_moreOptions": "Weitere Optionen",
"contacts_searchOpen": "Kontakte suchen",
"contacts_searchClose": "Erweiterte Suche",
"routing_title": "Routenplanung",
"routing_modeAuto": "Auto",
"routing_modeFlood": "Überschwemmung",
"routing_modeManual": "Handbuch",
"routing_modeFloodHint": "Übertragung über alle Repeater. Die zuverlässigste Methode, jedoch mit höherem Datenverbrauch.",
"routing_modeAutoHint": "Wählt automatisch den bekanntesten Pfad aus und verwendet eine Flutungsmethode, wenn kein Pfad bekannt ist.",
"routing_modeManualHint": "Sendet immer genau den von Ihnen festgelegten Weg.",
"routing_currentRoute": "Aktuelle Route",
"routing_directNoHops": "Direkt ohne Zwischenverstärkung",
"routing_noPathYet": "Noch kein Pfad gefunden. Die Nachricht wird gesendet, bis ein Weg entdeckt wurde.",
"routing_floodBroadcast": "Übertragung über jeden Repeater",
"routing_editPath": "Pfad bearbeiten",
"routing_forgetPath": "Vergiss den Weg",
"routing_knownPaths": "Bekannte Routen",
"routing_knownPathsHint": "Wählen Sie den Pfad, um zu diesem zu wechseln.",
"routing_inUse": "Im Gebrauch",
"routing_qualityStrong": "Ein starker erster Sprung",
"routing_qualityGood": "Ein guter erster Schritt",
"routing_qualityFair": "Erster erfolgreicher Schritt",
"routing_qualityWorked": "Hat erfolgreich geliefert",
"routing_qualityFlood": "Information erhalten durch Nachrichten über die Überschwemmung",
"routing_qualityUntested": "Nicht getestet",
"routing_lastWorked": "war beschäftigt {when}",
"routing_neverWorked": "nie bestätigt",
"routing_floodDelivery": "Lieferung bei Überschwemmung",
"pathEditor_title": "Pfad erstellen",
"pathEditor_hopCounter": "{count} von 64 Hopfengewächsen",
"pathEditor_noHops": "Noch keine Hopfen hinzugefügt. Klicken Sie auf die Schaltflächen unten, um sie nacheinander hinzuzufügen, oder speichern Sie die Rezepter ohne Hopfen, um sie direkt zu versenden.",
"pathEditor_addHops": "Fügen Sie die Hopfen in der richtigen Reihenfolge hinzu.",
"pathEditor_searchRepeaters": "Suche nach wiederholten Nachrichten",
"pathEditor_advancedHex": "Fortgeschritten: Roh-Hex-Pfad",
"pathEditor_hexLabel": "Hex-Präfixe",
"pathEditor_hexHelper": "Zwei Hexadezimalzeichen pro Sprung, getrennt durch Kommas",
"pathEditor_invalidTokens": "Ungültig: {tokens}",
"pathEditor_tooManyHops": "Maximal 64 Hopfengreifer",
"pathEditor_usePath": "Verwenden Sie diesen Pfad.",
"pathEditor_removeHop": "Hop entfernen",
"pathEditor_unknownHop": "Unbekannter Repeater",
"map_zoomIn": "Zoomen",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "Auszoomen",
"map_centerMap": "Zentralkarte",
"chrome_bluetoothRequiresChromium": "Web Bluetooth benötigt einen Chromium-Browser.",
"channels_communityShortId": "ID: {id}…",
"pathTrace_legendGpsConfirmed": "GPS-Bestätigung",
"pathTrace_legendInferred": "Abgeleitete Position"
"dialog_connectCompanion": "Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.",
"dialog_disconnectedTitle": "Getrennt",
"dialog_disconnectedMessage": "Du wurdest von deinem Begleiter getrennt.",
"contact_connectCompanion": "Mit einem Companion verbinden, um auf Repeater- und Raumserver-Funktionen zuzugreifen."
}
+75 -301
View File
@@ -28,12 +28,6 @@
"common_remove": "Remove",
"common_enable": "Enable",
"common_disable": "Disable",
"common_undo": "Undo",
"messageStatus_sent": "Sent",
"messageStatus_delivered": "Delivered",
"messageStatus_pending": "Sending",
"messageStatus_failed": "Failed to send",
"messageStatus_repeated": "Heard repeated",
"common_reboot": "Reboot",
"common_loading": "Loading...",
"common_notAvailable": "—",
@@ -53,8 +47,6 @@
}
}
},
"common_autoRefresh": "Autorefresh",
"common_interval": "Interval",
"scanner_title": "MeshCore Open",
"connectionChoiceUsbLabel": "USB",
"connectionChoiceBluetoothLabel": "Bluetooth",
@@ -143,7 +135,6 @@
"scanner_chromeRequired": "Chrome Browser Required",
"scanner_chromeRequiredMessage": "This web application requires Google Chrome or a Chromium-based browser for Bluetooth support.",
"scanner_enableBluetooth": "Enable Bluetooth",
"scanner_bluetoothWebUnsupported": "Bluetooth isn't available in the browser. Connect over USB instead.",
"device_quickSwitch": "Quick switch",
"device_meshcore": "MeshCore",
"settings_title": "Settings",
@@ -304,6 +295,17 @@
"appSettings_routeWeightFailureDecrementSubtitle": "Weight removed from a path after failed delivery",
"appSettings_maxMessageRetries": "Max Message Retries",
"appSettings_maxMessageRetriesSubtitle": "Number of retry attempts before marking a message as failed",
"path_routeWeight": "{weight}/{max}",
"@path_routeWeight": {
"placeholders": {
"weight": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"appSettings_battery": "Battery",
"appSettings_batteryChemistry": "Battery Chemistry",
"appSettings_batteryChemistryPerDevice": "Set per device ({deviceName})",
@@ -449,9 +451,6 @@
}
},
"contacts_newGroup": "New Group",
"contacts_moreOptions": "More options",
"contacts_searchOpen": "Search contacts",
"contacts_searchClose": "Close search",
"contacts_groupName": "Group name",
"contacts_groupNameRequired": "Group name is required",
"contacts_groupNameReserved": "This group name is reserved",
@@ -776,6 +775,15 @@
}
},
"debugFrame_hexDump": "Hex Dump:",
"chat_pathManagement": "Path Management",
"chat_ShowAllPaths": "Show all paths",
"chat_routingMode": "Routing mode",
"chat_autoUseSavedPath": "Auto (use saved path)",
"chat_forceFloodMode": "Force Flood Mode",
"chat_recentAckPaths": "Recent ACK Paths (tap to use):",
"chat_pathHistoryFull": "Path history is full. Remove entries to add new ones.",
"chat_hopSingular": "hop",
"chat_hopPlural": "hops",
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
"@chat_hopsCount": {
"placeholders": {
@@ -784,80 +792,31 @@
}
}
},
"chat_successes": "successes",
"chat_score": "Score",
"chat_removePath": "Remove path",
"chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.",
"chat_pathActions": "Path Actions:",
"chat_setCustomPath": "Set Custom Path",
"chat_setCustomPathSubtitle": "Manually specify routing path",
"chat_clearPath": "Clear Path",
"chat_clearPathSubtitle": "Force rediscovery on next send",
"chat_pathCleared": "Path cleared. Next message will rediscover route.",
"chat_floodModeSubtitle": "Use routing toggle in app bar",
"chat_floodModeEnabled": "Flood mode enabled. Toggle back via routing icon in app bar.",
"chat_fullPath": "Full Path",
"routing_title": "Routing",
"routing_modeAuto": "Auto",
"routing_modeFlood": "Flood",
"routing_modeManual": "Manual",
"routing_modeAutoHint": "Picks the best known path automatically, flooding when none is known.",
"routing_modeFloodHint": "Broadcasts through every repeater. Most reliable, but uses more airtime.",
"routing_modeManualHint": "Always sends along the exact path you set.",
"routing_currentRoute": "Current route",
"routing_directNoHops": "Direct — no repeater hops",
"routing_noPathYet": "No path yet. The next message floods until a route is discovered.",
"routing_floodBroadcast": "Broadcast through every repeater",
"routing_editPath": "Edit path",
"routing_forgetPath": "Forget path",
"routing_knownPaths": "Known paths",
"routing_knownPathsHint": "Tap a path to switch to it.",
"routing_inUse": "In use",
"routing_qualityStrong": "Strong first hop",
"routing_qualityGood": "Good first hop",
"routing_qualityFair": "Fair first hop",
"routing_qualityWorked": "Has delivered",
"routing_qualityFlood": "Heard via flood",
"routing_qualityUntested": "Untested",
"routing_lastWorked": "worked {when}",
"@routing_lastWorked": {
"chat_pathDetailsNotAvailable": "Path details not available yet. Try sending a message to refresh.",
"chat_pathSetHops": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
"@chat_pathSetHops": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"routing_neverWorked": "never confirmed",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"hopCount": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"routing_floodDelivery": "Flood delivery",
"pathEditor_title": "Build Path",
"pathEditor_hopCounter": "{count} of 64 hops",
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"pathEditor_noHops": "No hops yet. Tap repeaters below to add them in order, or save with no hops to send direct.",
"pathEditor_addHops": "Add hops in order",
"pathEditor_searchRepeaters": "Search repeaters",
"pathEditor_advancedHex": "Advanced: raw hex path",
"pathEditor_hexLabel": "Hex prefixes",
"pathEditor_hexHelper": "Two hex characters per hop, separated by commas",
"pathEditor_invalidTokens": "Invalid: {tokens}",
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"status": {
"type": "String"
}
}
},
"pathEditor_tooManyHops": "Maximum 64 hops",
"pathEditor_usePath": "Use this path",
"pathEditor_removeHop": "Remove hop",
"pathEditor_unknownHop": "Unknown repeater",
"chat_pathSavedLocally": "Saved locally. Connect to sync.",
"chat_pathDeviceConfirmed": "Device confirmed.",
"chat_pathDeviceNotConfirmed": "Device not confirmed yet.",
@@ -901,17 +860,6 @@
},
"chat_invalidLink": "Invalid link format",
"map_title": "Node Map",
"map_searchHint": "Search node name or ID",
"map_activity": "Activity",
"map_online": "Online",
"map_recent": "Recent",
"map_stale": "Stale",
"map_visible": "Visible",
"map_hidden": "Hidden",
"map_centerOnNode": "Center on node",
"map_details": "Details",
"map_noGps": "No GPS",
"map_noResults": "No matching nodes",
"map_lineOfSight": "Line of Sight",
"map_losScreenTitle": "Line of Sight",
"map_noNodesWithLocation": "No nodes with location data",
@@ -1114,6 +1062,9 @@
"time_allTime": "All Time",
"dialog_disconnect": "Disconnect",
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
"dialog_disconnectedTitle": "Disconnected",
"dialog_disconnectedMessage": "You have been disconnected from your companion.",
"dialog_connectCompanion": "Connect to a companion to access repeater and room server features.",
"login_repeaterLogin": "Repeater Login",
"login_roomLogin": "Room Server Login",
"login_password": "Password",
@@ -1150,8 +1101,41 @@
"login_failedMessage": "Login failed. Either the password is incorrect or the repeater is unreachable.",
"common_reload": "Reload",
"common_clear": "Clear",
"path_currentPath": "Current path: {path}",
"@path_currentPath": {
"placeholders": {
"path": {
"type": "String"
}
}
},
"path_usingHopsPath": "Using {count} {count, plural, =1{hop} other{hops}} path",
"@path_usingHopsPath": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"path_enterCustomPath": "Enter Custom Path",
"path_currentPathLabel": "Current path",
"path_hexPrefixInstructions": "Enter 2-character hex prefixes for each hop, separated by commas.",
"path_hexPrefixExample": "Example: A1,F2,3C (each node uses first byte of its public key)",
"path_labelHexPrefixes": "Path (hex prefixes)",
"path_helperMaxHops": "Max 64 hops. Each prefix is 2 hex characters (1 byte)",
"path_selectFromContacts": "Or select from contacts:",
"path_noRepeatersFound": "No repeaters or room servers found.",
"path_customPathsRequire": "Custom paths require intermediate hops that can relay messages.",
"path_invalidHexPrefixes": "Invalid hex prefixes: {prefixes}",
"@path_invalidHexPrefixes": {
"placeholders": {
"prefixes": {
"type": "String"
}
}
},
"path_tooLong": "Path too long. Maximum 64 hops allowed.",
"path_setPath": "Set Path",
"repeater_management": "Repeater Management",
"room_management": "Room Server Management",
"repeater_guest": "Repeater Information",
@@ -1178,6 +1162,9 @@
},
"repeater_statusTitle": "Repeater Status",
"repeater_routingMode": "Routing mode",
"repeater_autoUseSavedPath": "Auto (use saved path)",
"repeater_forceFloodMode": "Force Flood Mode",
"repeater_pathManagement": "Path management",
"repeater_refresh": "Refresh",
"repeater_statusRequestTimeout": "Status request timed out.",
"repeater_errorLoadingStatus": "Error loading status: {error}",
@@ -1677,120 +1664,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digital Input",
"telemetry_digitalOutputLabel": "Digital Output",
"telemetry_analogInputLabel": "Analog Input",
"telemetry_analogOutputLabel": "Analog Output",
"telemetry_genericLabel": "Generic Sensor",
"telemetry_luminosityLabel": "Luminosity",
"telemetry_presenceLabel": "Presence",
"telemetry_humidityLabel": "Humidity",
"telemetry_accelerometerLabel": "Accelerometer",
"telemetry_pressureLabel": "Pressure",
"telemetry_altitudeLabel": "Altitude",
"telemetry_frequencyLabel": "Frequency",
"telemetry_percentageLabel": "Percentage",
"telemetry_concentrationLabel": "Concentration",
"telemetry_powerLabel": "Power",
"telemetry_distanceLabel": "Distance",
"telemetry_energyLabel": "Energy",
"telemetry_directionLabel": "Direction",
"telemetry_timeLabel": "Time",
"telemetry_gyrometerLabel": "Gyrometer",
"telemetry_colourLabel": "Colour",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Switch",
"telemetry_polylineLabel": "Polyline",
"telemetry_altitudeValue": "{meters} m",
"@telemetry_altitudeValue": {
"placeholders": {
"meters": {
"type": "String"
}
}
},
"telemetry_frequencyValue": "{hertz} Hz",
"@telemetry_frequencyValue": {
"placeholders": {
"hertz": {
"type": "String"
}
}
},
"telemetry_pressureValue": "{hpa} hPa",
"@telemetry_pressureValue": {
"placeholders": {
"hpa": {
"type": "String"
}
}
},
"telemetry_luminosityValue": "{lux} lx",
"@telemetry_luminosityValue": {
"placeholders": {
"lux": {
"type": "String"
}
}
},
"telemetry_powerValue": "{watts} W",
"@telemetry_powerValue": {
"placeholders": {
"watts": {
"type": "String"
}
}
},
"telemetry_distanceValue": "{meters} m",
"@telemetry_distanceValue": {
"placeholders": {
"meters": {
"type": "String"
}
}
},
"telemetry_energyValue": "{kilowattHours} kWh",
"@telemetry_energyValue": {
"placeholders": {
"kilowattHours": {
"type": "String"
}
}
},
"telemetry_directionValue": "{degrees}°",
"@telemetry_directionValue": {
"placeholders": {
"degrees": {
"type": "String"
}
}
},
"telemetry_concentrationValue": "{ppm} ppm",
"@telemetry_concentrationValue": {
"placeholders": {
"ppm": {
"type": "String"
}
}
},
"telemetry_percentageValue": "{percent}%",
"@telemetry_percentageValue": {
"placeholders": {
"percent": {
"type": "String"
}
}
},
"telemetry_analogValue": "{value}",
"@telemetry_analogValue": {
"placeholders": {
"value": {
"type": "String"
}
}
},
"telemetry_autoFetchQuantity": "Requests quantity",
"telemetry_error": "Unable to retrieve data",
"neighbors_receivedData": "Received Neighbors Data",
"neighbors_requestTimedOut": "Neighbors request timed out.",
"neighbors_errorLoading": "Error loading neighbors: {error}",
@@ -2418,9 +2291,6 @@
"translation_enableSubtitle": "Translate incoming messages and allow pre-send translation.",
"translation_composerTitle": "Translate before sending",
"translation_composerSubtitle": "Controls the default state of the composer translation icon.",
"translation_autoIncomingTitle": "Auto-translate incoming messages",
"translation_autoIncomingSubtitle": "Translates Messages for notification and for chat or channel automatically.",
"translation_translateMessage": "Translate message",
"translation_targetLanguage": "Target language",
"translation_useAppLanguage": "Use app language",
"translation_downloadedModelLabel": "Downloaded model",
@@ -2499,101 +2369,5 @@
"contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room",
"contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown",
"map_zoomIn": "Zoom in",
"map_zoomOut": "Zoom out",
"map_centerMap": "Center map",
"chrome_bluetoothRequiresChromium": "Web Bluetooth requires a Chromium browser",
"channels_communityShortId": "ID: {id}...",
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"pathTrace_legendGpsConfirmed": "GPS confirmed",
"pathTrace_legendInferred": "Inferred position",
"pathMap_viewSingle": "Single",
"pathMap_viewCombined": "Combined",
"pathMap_play": "Play",
"pathMap_pause": "Pause",
"pathMap_replay": "Replay",
"pathMap_stepBack": "Previous hop",
"pathMap_stepForward": "Next hop",
"pathMap_animationOn": "Show packet animation",
"pathMap_animationOff": "Hide packet animation",
"pathMap_hopOf": "Hop {current} of {total}",
"@pathMap_hopOf": {
"placeholders": {
"current": {
"type": "int"
},
"total": {
"type": "int"
}
}
},
"pathMap_observedPaths": "Observed paths: {count}",
"@pathMap_observedPaths": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"pathMap_primary": "Primary",
"pathMap_alternate": "Alt {index}",
"@pathMap_alternate": {
"placeholders": {
"index": {
"type": "int"
}
}
},
"pathMap_hopCount": "{count, plural, =1{1 hop} other{{count} hops}}",
"@pathMap_hopCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"pathMap_gpsCount": "{confirmed}/{total} GPS",
"@pathMap_gpsCount": {
"placeholders": {
"confirmed": {
"type": "int"
},
"total": {
"type": "int"
}
}
},
"pathMap_legendShared": "Shared segment",
"pathMap_legendEstimated": "Estimated segment",
"pathMap_sharedNodeCount": "Used by {count} paths",
"@pathMap_sharedNodeCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"pathMap_partialAnimation": "{count, plural, =1{1 hop has no location — the shown path is partial} other{{count} hops have no location — the shown path is partial}}",
"@pathMap_partialAnimation": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"pathMap_showAllPaths": "Show all",
"pathMap_hidePath": "Hide path",
"pathMap_showPath": "Show path",
"pathMap_collapsePanel": "Collapse panel",
"pathMap_expandPanel": "Expand panel",
"pathMap_noLocation": "No location",
"pathMap_followPacket": "Lock view to packet",
"pathMap_unfollowPacket": "Unlock view from packet"
}
"contact_typeUnknown": "Unknown"
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Eliminar",
"common_enable": "Activar",
"common_disable": "Desactivar",
"common_autoRefresh": "Actualización automática",
"common_interval": "Intervalo",
"common_reboot": "Reiniciar",
"common_loading": "Cargando...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Entrada digital",
"telemetry_digitalOutputLabel": "Salida digital",
"telemetry_analogInputLabel": "Entrada analógica",
"telemetry_analogOutputLabel": "Salida analógica",
"telemetry_genericLabel": "Sensor genérico",
"telemetry_luminosityLabel": "Luminosidad",
"telemetry_presenceLabel": "Presencia",
"telemetry_humidityLabel": "Humedad",
"telemetry_accelerometerLabel": "Acelerómetro",
"telemetry_pressureLabel": "Presión",
"telemetry_altitudeLabel": "Altitud",
"telemetry_frequencyLabel": "Frecuencia",
"telemetry_percentageLabel": "Porcentaje",
"telemetry_concentrationLabel": "Concentración",
"telemetry_powerLabel": "Potencia",
"telemetry_distanceLabel": "Distancia",
"telemetry_energyLabel": "Energía",
"telemetry_directionLabel": "Dirección",
"telemetry_timeLabel": "Hora",
"telemetry_gyrometerLabel": "Girómetro",
"telemetry_colourLabel": "Color",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Interruptor",
"telemetry_polylineLabel": "Polilínea",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Número de solicitudes",
"telemetry_error": "No se pudieron obtener los datos",
"telemetry_noData": "No hay datos de telemetría disponibles.",
"telemetry_channelTitle": "Canal {channel}",
"@telemetry_channelTitle": {
@@ -2167,9 +2128,6 @@
"translation_enableTitle": "Habilitar la traducción",
"translation_composerTitle": "Traducir antes de enviar",
"translation_composerSubtitle": "Controla el estado predeterminado del icono de traducción del compositor.",
"translation_autoIncomingTitle": "Traducir mensajes automáticamente",
"translation_autoIncomingSubtitle": "Traduce mensajes para notificaciones y para chats o canales automáticamente.",
"translation_translateMessage": "Traducir mensaje",
"translation_targetLanguage": "Idioma de destino",
"translation_useAppLanguage": "Utilizar el idioma de la aplicación",
"translation_downloadedModelLabel": "Modelo descargado",
@@ -2382,97 +2340,8 @@
"settings_companionDebugLogSubtitle": "Comandos, respuestas y datos brutos para protocolos BLE/TCP/USB",
"chat_markAsUnread": "Marcar como no leído",
"repeater_chanUtil": "Utilización del canal",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_pending": "Enviar",
"common_undo": "Deshacer",
"messageStatus_sent": "Sentido",
"messageStatus_delivered": "Entregado",
"messageStatus_failed": "No se pudo enviar",
"messageStatus_repeated": "Escuché repetidamente",
"contacts_moreOptions": "Más opciones",
"contacts_searchOpen": "Buscar contactos",
"contacts_searchClose": "Búsqueda avanzada",
"routing_title": "Ruteo",
"routing_modeAuto": "Coche",
"routing_modeFlood": "Inundación",
"routing_modeManual": "Manual",
"routing_modeAutoHint": "Selecciona automáticamente la ruta más conocida, y si no hay ninguna ruta conocida, utiliza la ruta más directa.",
"routing_modeFloodHint": "Transmisiones a través de todos los repetidores. Es la opción más fiable, pero utiliza más tiempo de transmisión.",
"routing_modeManualHint": "Siempre sigue exactamente la ruta que usted ha definido.",
"routing_currentRoute": "Ruta actual",
"routing_directNoHops": "Directo — sin saltos de repetidor",
"routing_noPathYet": "Aún no hay un camino definido. El mensaje se envía continuamente hasta que se encuentre una ruta.",
"routing_floodBroadcast": "Transmisión a través de todos los repetidores.",
"routing_editPath": "Editar ruta",
"routing_forgetPath": "Olvídate del camino",
"routing_knownPaths": "Rutas conocidas",
"routing_knownPathsHint": "Seleccione una opción para cambiar a esa.",
"routing_inUse": "En uso",
"routing_qualityStrong": "Primer salto exitoso",
"routing_qualityGood": "Primer paso exitoso",
"routing_qualityWorked": "Ha cumplido",
"routing_qualityFair": "Primer salto de calidad",
"routing_qualityFlood": "Se ha escuchado a través de rumores.",
"routing_qualityUntested": "Sin probar",
"routing_lastWorked": "trabajó {when}",
"routing_neverWorked": "nunca confirmado",
"routing_floodDelivery": "Entrega por inundación",
"pathEditor_title": "Crear ruta",
"pathEditor_hopCounter": "{count} de 64 granos de lúpulo",
"pathEditor_noHops": "Aún no se han añadido lúpulos. Haga clic en los repetidores para añadirlos en el orden deseado, o guarde la receta sin lúpulos para enviarla directamente.",
"pathEditor_addHops": "Añadir los lúpulos en el orden adecuado.",
"pathEditor_searchRepeaters": "Buscar repetidores",
"pathEditor_advancedHex": "Avanzado: ruta hexadecimal sin procesar",
"pathEditor_hexLabel": "Prefijos hexadecimales",
"pathEditor_hexHelper": "Dos caracteres hexadecimales por salto, separados por comas.",
"pathEditor_invalidTokens": "Inválido: {tokens}",
"pathEditor_tooManyHops": "Máximo 64 saltos",
"pathEditor_usePath": "Utilice esta ruta.",
"pathEditor_removeHop": "Eliminar el lúpulo",
"pathEditor_unknownHop": "Repetidor desconocido",
"map_zoomIn": "Acercar",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "Acercar",
"map_centerMap": "Mapa del centro",
"chrome_bluetoothRequiresChromium": "Web Bluetooth requiere un navegador Chromium.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "Confirmado mediante GPS",
"pathTrace_legendInferred": "Posición inferida"
"dialog_connectCompanion": "Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.",
"dialog_disconnectedTitle": "Desconectado",
"dialog_disconnectedMessage": "Te has desconectado de tu compañero.",
"contact_connectCompanion": "Conéctate a un compañero para acceder a las funciones del repetidor y del servidor de la sala."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Supprimer",
"common_enable": "Activer",
"common_disable": "Désactiver",
"common_autoRefresh": "Actualisation automatique",
"common_interval": "Intervalle",
"common_reboot": "Redémarrer",
"common_loading": "Chargement...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Entrée numérique",
"telemetry_digitalOutputLabel": "Sortie numérique",
"telemetry_analogInputLabel": "Entrée analogique",
"telemetry_analogOutputLabel": "Sortie analogique",
"telemetry_genericLabel": "Capteur générique",
"telemetry_luminosityLabel": "Luminosité",
"telemetry_presenceLabel": "Présence",
"telemetry_humidityLabel": "Humidité",
"telemetry_accelerometerLabel": "Accéléromètre",
"telemetry_pressureLabel": "Pression",
"telemetry_altitudeLabel": "Altitude",
"telemetry_frequencyLabel": "Fréquence",
"telemetry_percentageLabel": "Pourcentage",
"telemetry_concentrationLabel": "Concentration",
"telemetry_powerLabel": "Puissance",
"telemetry_distanceLabel": "Distance",
"telemetry_energyLabel": "Énergie",
"telemetry_directionLabel": "Direction",
"telemetry_timeLabel": "Heure",
"telemetry_gyrometerLabel": "Gyromètre",
"telemetry_colourLabel": "Couleur",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Interrupteur",
"telemetry_polylineLabel": "Polyligne",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Nombre de requêtes",
"telemetry_error": "Impossible de récupérer les données",
"telemetry_noData": "Aucune donnée de télémétrie disponible.",
"telemetry_channelTitle": "Canal {channel}",
"@telemetry_channelTitle": {
@@ -2138,9 +2099,6 @@
"translation_title": "Traduction",
"translation_enableSubtitle": "Traduire les messages entrants et permettre la traduction avant l'envoi.",
"translation_composerSubtitle": "Contrôle l'état par défaut de l'icône de traduction du composant.",
"translation_autoIncomingTitle": "Traduire automatiquement les messages",
"translation_autoIncomingSubtitle": "Traduit automatiquement les messages pour les notifications et pour les discussions ou les canaux.",
"translation_translateMessage": "Traduire le message",
"translation_targetLanguage": "Langue cible",
"translation_useAppLanguage": "Utiliser la langue de l'application",
"translation_downloadedModelLabel": "Modèle téléchargé",
@@ -2361,97 +2319,8 @@
"chat_newMessages": "Nouveaux messages",
"settings_companionDebugLogSubtitle": "Commandes, réponses et données brutes pour les protocoles BLE/TCP/USB",
"repeater_chanUtil": "Utilisation du canal",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"common_undo": "Annuler",
"messageStatus_sent": "Envoyer",
"messageStatus_delivered": "Livré",
"messageStatus_pending": "Envoyer",
"messageStatus_failed": "Échec de l'envoi",
"messageStatus_repeated": "Répété plusieurs fois",
"contacts_searchOpen": "Rechercher des contacts",
"contacts_moreOptions": "Plus d'options",
"contacts_searchClose": "Recherche avancée",
"routing_title": "Planification des itinéraires",
"routing_modeAuto": "Voiture",
"routing_modeFlood": "Inondation",
"routing_modeManual": "Manuel",
"routing_modeFloodHint": "Diffusion via tous les répéteurs. La méthode la plus fiable, mais qui utilise plus de temps d'antenne.",
"routing_modeAutoHint": "Sélectionne automatiquement le chemin le plus connu, et utilise la méthode de \"inondation\" si aucun chemin n'est connu.",
"routing_modeManualHint": "Il suit toujours le chemin précis que vous avez défini.",
"routing_currentRoute": "Itinéraire actuel",
"routing_directNoHops": "Direct — sans relais",
"routing_noPathYet": "Aucune voie encore trouvée. Le message suivant est envoyé jusqu'à ce qu'une route soit découverte.",
"routing_floodBroadcast": "Diffusion via tous les répéteurs",
"routing_editPath": "Modifier le chemin",
"routing_forgetPath": "Oubliez le chemin",
"routing_knownPaths": "Chemins connus",
"routing_knownPathsHint": "Créez un raccourci pour y accéder.",
"routing_inUse": "En cours d'utilisation",
"routing_qualityStrong": "Première étape réussie",
"routing_qualityGood": "Première étape réussie",
"routing_qualityFair": "Première étape réussie",
"routing_qualityWorked": "A livré",
"routing_qualityFlood": "Rapporté par des informations provenant de plusieurs sources.",
"routing_qualityUntested": "Non testé",
"routing_lastWorked": "a travaillé {when}",
"routing_neverWorked": "jamais confirmé",
"routing_floodDelivery": "Livraison en cas de inondation",
"pathEditor_hopCounter": "{count} parmi 64 houblons",
"pathEditor_title": "Créer un chemin",
"pathEditor_noHops": "Aucun houblon ajouté pour le moment. Cliquez sur les répétiteurs ci-dessous pour les ajouter dans l'ordre souhaité, ou enregistrez sans houblon pour envoyer directement.",
"pathEditor_addHops": "Ajoutez les houblons dans l'ordre souhaité.",
"pathEditor_searchRepeaters": "Rechercher des répétiteurs",
"pathEditor_advancedHex": "Avancé : chemin hexadécimal brut",
"pathEditor_hexLabel": "Préfixes hexadécimaux",
"pathEditor_hexHelper": "Deux caractères hexadécimaux par saut, séparés par des virgules.",
"pathEditor_invalidTokens": "Incorrect : {tokens}",
"pathEditor_tooManyHops": "Maximum 64 sauts",
"pathEditor_usePath": "Utilisez ce chemin.",
"pathEditor_removeHop": "Éliminer le haricot",
"pathEditor_unknownHop": "Répéteur non identifié",
"map_zoomIn": "Zoomez",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "Zoomez",
"map_centerMap": "Carte du centre",
"chrome_bluetoothRequiresChromium": "Web Bluetooth nécessite un navigateur Chromium.",
"channels_communityShortId": "ID : {id}…",
"pathTrace_legendGpsConfirmed": "Le GPS a confirmé.",
"pathTrace_legendInferred": "Position déduite"
"dialog_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.",
"dialog_disconnectedTitle": "Déconnecté",
"dialog_disconnectedMessage": "Vous avez été déconnecté de votre compagnon.",
"contact_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités du répéteur et du serveur de salle."
}
+4 -135
View File
@@ -27,8 +27,6 @@
"common_remove": "Eltávolít",
"common_enable": "Engedélyezés",
"common_disable": "Leteteszt",
"common_autoRefresh": "Automatikus frissítés",
"common_interval": "Intervallum",
"common_reboot": "Újraindítás",
"common_loading": "Betöltés...",
"common_notAvailable": "—",
@@ -1468,43 +1466,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digitális bemenet",
"telemetry_digitalOutputLabel": "Digitális kimenet",
"telemetry_analogInputLabel": "Analóg bemenet",
"telemetry_analogOutputLabel": "Analóg kimenet",
"telemetry_genericLabel": "Általános érzékelő",
"telemetry_luminosityLabel": "Fényerő",
"telemetry_presenceLabel": "Jelenlét",
"telemetry_humidityLabel": "Páratartalom",
"telemetry_accelerometerLabel": "Gyorsulásmérő",
"telemetry_pressureLabel": "Nyomás",
"telemetry_altitudeLabel": "Magasság",
"telemetry_frequencyLabel": "Frekvencia",
"telemetry_percentageLabel": "Százalék",
"telemetry_concentrationLabel": "Koncentráció",
"telemetry_powerLabel": "Teljesítmény",
"telemetry_distanceLabel": "Távolság",
"telemetry_energyLabel": "Energia",
"telemetry_directionLabel": "Irány",
"telemetry_timeLabel": "Idő",
"telemetry_gyrometerLabel": "Giroszkóp",
"telemetry_colourLabel": "Szín",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Kapcsoló",
"telemetry_polylineLabel": "Töröttvonal",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Kérések száma",
"telemetry_error": "Nem sikerült lekérni az adatokat",
"telemetry_noData": "Nincsenek elérhető telemetriadatok.",
"telemetry_channelTitle": "{channel} csatorna",
"@telemetry_channelTitle": {
@@ -2176,9 +2137,6 @@
"translation_enableSubtitle": "Fordítsa az érkező üzeneteket, és lehetővé tegye a küldés előtti fordítást.",
"translation_composerTitle": "Fordítsa el, mielőtt elküldi",
"translation_composerSubtitle": "Ellenőrzi a zeneszerző fordítási ikon alapértékét.",
"translation_autoIncomingTitle": "Üzenetek automatikus fordítása",
"translation_autoIncomingSubtitle": "Automatikusan lefordítja az üzeneteket az értesítésekhez, valamint a csevegésekhez vagy csatornákhoz.",
"translation_translateMessage": "Üzenet fordítása",
"translation_targetLanguage": "Célnyelv",
"translation_useAppLanguage": "Használja az alkalmazás nyelvének beállítását.",
"translation_downloadedModelLabel": "Letöltött modell",
@@ -2392,97 +2350,8 @@
"settings_companionDebugLog": "Párhuzamos hibakeresési napló",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB parancsok, válaszok és alapvető adatok",
"repeater_chanUtil": "Csatorna-használat",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_delivered": "Szállítva",
"common_undo": "Még egyszer",
"messageStatus_sent": "Elküldve",
"messageStatus_pending": "Elküldés",
"messageStatus_failed": "Nem sikerült elküldeni",
"messageStatus_repeated": "Ismételtem",
"contacts_moreOptions": "További lehetőségek",
"contacts_searchOpen": "Keresssz kapcsolatokat",
"contacts_searchClose": "Teljesítse a keresést",
"routing_title": "Útvonal meghatározás",
"routing_modeAuto": "Autó",
"routing_modeFlood": "Áradás",
"routing_modeManual": "Használati útmutató",
"routing_modeAutoHint": "Automatikusan kiválasztja a legismertebb útvonalat, és ha egyik sem ismert, akkor \"vízzel\" tölti ki.",
"routing_modeFloodHint": "Átvisszaadások minden erősítőn keresztül. A legmegbízhatóbb megoldás, de több időt igényel.",
"routing_modeManualHint": "Mindig pontosan az útvonalat követi, amelyet megad.",
"routing_currentRoute": "Jelenlegi útvonal",
"routing_directNoHops": "Közvetlen nincs átjáró állomás",
"routing_noPathYet": "Még nincs útvonal. A következő üzenet a keresésig vár.",
"routing_floodBroadcast": "Azonnali továbbítás minden erősítőn keresztül.",
"routing_editPath": "Útvonal szerkesztése",
"routing_forgetPath": "Felejtsd el a útvonalat",
"routing_knownPaths": "Jellegzetes útvonalak",
"routing_knownPathsHint": "Készíts egy útvonalat, hogy átválhass rá.",
"routing_inUse": "Használatban",
"routing_qualityStrong": "Erős első lépés",
"routing_qualityGood": "Jó első lépés",
"routing_qualityFair": "Jó első lépés",
"routing_qualityWorked": "Előállított",
"routing_qualityFlood": "Információt hallottam a katasztrófa miatt.",
"routing_qualityUntested": "Vizsgálatnak nem подвержен",
"routing_neverWorked": "sosem megerősítve",
"routing_floodDelivery": "Vízparti szállítás",
"pathEditor_title": "Út megépítése",
"pathEditor_hopCounter": "{count} db 64-ből",
"pathEditor_noHops": "Még nem adtam hozzá a bazsalikomot. A lent található gombokat használhatod, hogy sorrendben adjd hozzá, vagy mentheted anélkül, hogy bazsalikomot adnál hozzá, hogy közvetlenül elküldd.",
"pathEditor_addHops": "Adja hozzá a bazsaidat a megfelelő sorrendben.",
"pathEditor_searchRepeaters": "Ismétlő eszközök keresése",
"pathEditor_advancedHex": "Haladó szint: alapvető hex-út",
"pathEditor_hexLabel": "Hex előtagok",
"pathEditor_hexHelper": "Két hatjegyű szám minden lépésen, amelyek egymástól elválasztják a kommák.",
"pathEditor_invalidTokens": "Érvénytelen: {tokens}",
"routing_lastWorked": "worked {when}",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_tooManyHops": "A maximális szám 64.",
"pathEditor_usePath": "Használja ezt az útvonalat.",
"pathEditor_removeHop": "Távolítsa el a bazsalikomot",
"pathEditor_unknownHop": "Tudatlan erősítő",
"map_zoomIn": "Nagyítva",
"map_zoomOut": "Kicsökkentett nézet",
"map_centerMap": "Központi tér térkép",
"chrome_bluetoothRequiresChromium": "A Web Bluetooth-hoz egy Chromium-alapú böngésző szükséges.",
"channels_communityShortId": "Az azonosító: {id}...",
"pathTrace_legendGpsConfirmed": "GPS-en megerősítve",
"pathTrace_legendInferred": "Feltehető helyzet"
"dialog_connectCompanion": "Csatlakozz egy kísérőhöz az ismétlő- és szobaszerver-funkciók eléréséhez.",
"dialog_disconnectedTitle": "Kapcsolat megszakadt",
"dialog_disconnectedMessage": "A kapcsolat megszakadt a kísérővel.",
"contact_connectCompanion": "Csatlakozz egy kísérőhöz az ismétlő- és szobaszerver-funkciók eléréséhez."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Elimina",
"common_enable": "Abilita",
"common_disable": "Disattivare",
"common_autoRefresh": "Aggiornamento automatico",
"common_interval": "Intervallo",
"common_reboot": "Riavvia",
"common_loading": "Caricamento...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Ingresso digitale",
"telemetry_digitalOutputLabel": "Uscita digitale",
"telemetry_analogInputLabel": "Ingresso analogico",
"telemetry_analogOutputLabel": "Uscita analogica",
"telemetry_genericLabel": "Sensore generico",
"telemetry_luminosityLabel": "Luminosità",
"telemetry_presenceLabel": "Presenza",
"telemetry_humidityLabel": "Umidità",
"telemetry_accelerometerLabel": "Accelerometro",
"telemetry_pressureLabel": "Pressione",
"telemetry_altitudeLabel": "Altitudine",
"telemetry_frequencyLabel": "Frequenza",
"telemetry_percentageLabel": "Percentuale",
"telemetry_concentrationLabel": "Concentrazione",
"telemetry_powerLabel": "Potenza",
"telemetry_distanceLabel": "Distanza",
"telemetry_energyLabel": "Energia",
"telemetry_directionLabel": "Direzione",
"telemetry_timeLabel": "Ora",
"telemetry_gyrometerLabel": "Giroscopio",
"telemetry_colourLabel": "Colore",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Interruttore",
"telemetry_polylineLabel": "Polilinea",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Numero di richieste",
"telemetry_error": "Impossibile recuperare i dati",
"telemetry_noData": "Nessun dato di telemetria disponibile.",
"telemetry_channelTitle": "Canale {channel}",
"@telemetry_channelTitle": {
@@ -2139,9 +2100,6 @@
"translation_enableTitle": "Abilitare la traduzione",
"translation_title": "Traduzione",
"translation_composerSubtitle": "Controlla lo stato predefinito dell'icona di traduzione del compositore.",
"translation_autoIncomingTitle": "Traduci automaticamente i messaggi",
"translation_autoIncomingSubtitle": "Traduce automaticamente i messaggi per le notifiche e per le chat o i canali.",
"translation_translateMessage": "Traduci messaggio",
"translation_targetLanguage": "Lingua di destinazione",
"translation_useAppLanguage": "Utilizza la lingua dell'app",
"translation_downloadedModelLabel": "Modello scaricato",
@@ -2354,97 +2312,8 @@
"chat_newMessages": "Nuovi messaggi",
"chat_markAsUnread": "Segna come non letto",
"repeater_chanUtil": "Utilizzo del canale",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"common_undo": "Annulla",
"messageStatus_delivered": "Consegnato",
"messageStatus_sent": "Invia",
"messageStatus_pending": "Invio",
"messageStatus_failed": "Impossibile inviare",
"messageStatus_repeated": "Sentito ripetutamente",
"contacts_moreOptions": "Ulteriori opzioni",
"contacts_searchOpen": "Cerca contatti",
"contacts_searchClose": "Ricerca avanzata",
"routing_title": "Instradamento",
"routing_modeAuto": "Auto",
"routing_modeFlood": "Inondazione",
"routing_modeManual": "Manuale",
"routing_modeAutoHint": "Seleziona automaticamente il percorso più noto, e in caso di assenza di informazioni, utilizza un percorso casuale.",
"routing_modeFloodHint": "Trasmissioni tramite ogni ripetitore. Il metodo più affidabile, ma richiede più tempo di trasmissione.",
"routing_modeManualHint": "Invia sempre esattamente il percorso che hai definito.",
"routing_currentRoute": "Percorso attuale",
"routing_directNoHops": "Diretto — senza passaggi tramite ripetitori",
"routing_noPathYet": "Al momento non è stata individuata alcuna via. Il messaggio viene inviato ripetutamente finché non viene trovata una rotta.",
"routing_floodBroadcast": "Trasmissione attraverso ogni ripetitore",
"routing_editPath": "Percorso di modifica",
"routing_forgetPath": "Dimentica il percorso",
"routing_knownPaths": "Percorsi noti",
"routing_knownPathsHint": "Seleziona un percorso per accedere a questa opzione.",
"routing_inUse": "In uso",
"routing_qualityStrong": "Primo salto molto deciso",
"routing_qualityGood": "Primo tentativo di successo",
"routing_qualityFair": "Primo salto di qualità",
"routing_qualityWorked": "È stato consegnato",
"routing_qualityFlood": "Ho sentito tramite un messaggio urgente",
"routing_qualityUntested": "Non testato",
"routing_neverWorked": "mai confermato",
"routing_floodDelivery": "Consegna in caso di alluvione",
"pathEditor_title": "Creare percorso",
"pathEditor_hopCounter": "{count} tra 64 varietà di luppolo",
"pathEditor_noHops": "Al momento non ci sono ingredienti aggiuntivi. Per aggiungerli nell'ordine desiderato, cliccate sui ripetitori sottostanti. In alternativa, potete salvare la ricetta senza ingredienti aggiuntivi per inviarla direttamente.",
"pathEditor_addHops": "Aggiungere i luppoli nell'ordine desiderato.",
"pathEditor_searchRepeaters": "Ricerca ripetitori",
"pathEditor_advancedHex": "Avanzato: percorso esadecimale grezzo",
"pathEditor_hexLabel": "Prefissi esadecimali",
"pathEditor_hexHelper": "Due caratteri esadecimali per ogni salto, separati da virgole.",
"pathEditor_invalidTokens": "Non valido: {tokens}",
"routing_lastWorked": "worked {when}",
"pathEditor_tooManyHops": "Massimo 64 orari",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_usePath": "Utilizza questo percorso",
"pathEditor_removeHop": "Rimuovere il luppolo",
"pathEditor_unknownHop": "Ripetitore sconosciuto",
"map_zoomIn": "Ingrandisci",
"map_zoomOut": "Riduci la visualizzazione",
"map_centerMap": "Mappa del centro",
"channels_communityShortId": "ID: {id}...",
"chrome_bluetoothRequiresChromium": "Web Bluetooth richiede un browser basato su Chromium.",
"pathTrace_legendGpsConfirmed": "Il GPS conferma",
"pathTrace_legendInferred": "Posizione dedotta"
"dialog_connectCompanion": "Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.",
"dialog_disconnectedTitle": "Disconnesso",
"dialog_disconnectedMessage": "Sei stato disconnesso dal tuo compagno.",
"contact_connectCompanion": "Connettiti a un companion per accedere alle funzioni del repeater e del server di stanza."
}
+78 -209
View File
@@ -27,8 +27,6 @@
"common_remove": "削除",
"common_enable": "有効化する",
"common_disable": "無効化する",
"common_autoRefresh": "自動更新",
"common_interval": "間隔",
"common_reboot": "再起動",
"common_loading": "読み込み中...",
"common_notAvailable": "—",
@@ -216,7 +214,7 @@
"settings_txPower": "TX 信号電力 (dBm)",
"settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "無効な送信電力 (0-22 dBm)",
"settings_clientRepeat": "オフグリッドリピータ",
"settings_clientRepeat": "オフグリッド(電力網から孤立した状態)の繰り返し",
"settings_clientRepeatSubtitle": "このデバイスが、他のデバイスに対してメッシュパケットを繰り返し送信できるようにする。",
"settings_clientRepeatFreqWarning": "オフグリッドでの再送には、433MHz、869MHz、または918MHzの周波数が必要です。",
"settings_error": "エラー:{message}",
@@ -270,7 +268,7 @@
"appSettings_pathsWillBeCleared": "5回失敗した後、経路が再開されます。",
"appSettings_pathsWillNotBeCleared": "パスは自動で削除されません。",
"appSettings_autoRouteRotation": "自動ルートの切り替え",
"appSettings_autoRouteRotationSubtitle": "最適なルートと、フラッドモードを切り替える",
"appSettings_autoRouteRotationSubtitle": "最適なルートと、洪水モードを切り替える",
"appSettings_autoRouteRotationEnabled": "自動ルートの切り替え機能が有効になっています",
"appSettings_autoRouteRotationDisabled": "自動ルートの変更機能が無効になっています。",
"appSettings_maxRouteWeight": "最大ルート重量",
@@ -310,7 +308,7 @@
"appSettings_batteryLipo": "LiPo (3.0-4.2V)",
"appSettings_mapDisplay": "地図の表示",
"appSettings_showRepeaters": "繰り返し再生機能",
"appSettings_showRepeatersSubtitle": "地図上にリピータノードを表示する",
"appSettings_showRepeatersSubtitle": "地図上にリピータノードを表示する",
"appSettings_showChatNodes": "チャットノードの表示",
"appSettings_showChatNodesSubtitle": "地図上にチャットノードを表示する",
"appSettings_showOtherNodes": "他のノードを表示する",
@@ -424,7 +422,7 @@
}
}
},
"contacts_manageRepeater": "リピータの管理",
"contacts_manageRepeater": "リピータの管理",
"contacts_manageRoom": "ルームサーバーの管理",
"contacts_roomLogin": "ルームサーバーへのログイン",
"contacts_openChat": "自由な会話",
@@ -735,7 +733,7 @@
"chat_ShowAllPaths": "すべての経路を表示",
"chat_routingMode": "ルーティングモード",
"chat_autoUseSavedPath": "自動 (保存されたパスを使用)",
"chat_forceFloodMode": "強制的にフラッドモードを起動",
"chat_forceFloodMode": "強制的に洪水モードを起動",
"chat_recentAckPaths": "最近使用したACKパス(タップして使用):",
"chat_pathHistoryFull": "パスの履歴は完全です。エントリを削除して、新しいものを追加できます。",
"chat_hopSingular": "ジャンプ",
@@ -758,7 +756,7 @@
"chat_clearPathSubtitle": "次回送信時に、以前の情報を再取得する",
"chat_pathCleared": "経路が確保されました。次のメッセージでルートを再確認します。",
"chat_floodModeSubtitle": "アプリのバーにあるルーティング切り替え機能を使用する",
"chat_floodModeEnabled": "フラッドモードが有効になっています。アプリのメニューバーにあるルートアイコンを使用して、モードを切り替えることができます。",
"chat_floodModeEnabled": "洪水モードが有効になっています。アプリのメニューバーにあるルートアイコンを使用して、モードを切り替えることができます。",
"chat_fullPath": "フルパス",
"chat_pathDetailsNotAvailable": "経路の詳細については、まだ情報がありません。「リフレッシュ」ボタンを押して、再度お試しください。",
"chat_pathSetHops": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
@@ -779,7 +777,7 @@
"chat_path": "道",
"chat_publicKey": "公開鍵",
"chat_compressOutgoingMessages": "送信されるメッセージを圧縮する",
"chat_floodForced": "フラッド(強制的な)",
"chat_floodForced": "洪水(強制的な)",
"chat_directForced": "直接的な(強制的な)",
"chat_hopsForced": "{count} 本のホップ(強制的に採取)",
"@chat_hopsForced": {
@@ -789,7 +787,7 @@
}
}
},
"chat_floodAuto": "フラッド (自動)",
"chat_floodAuto": "洪水 (自動)",
"chat_direct": "直接",
"chat_poiShared": "共有されたPOI",
"chat_unread": "未読: {count}",
@@ -834,7 +832,7 @@
}
},
"map_chat": "チャット",
"map_repeater": "リピータ",
"map_repeater": "繰り返し送信装置",
"map_room": "部屋",
"map_sensor": "センサー",
"map_pinDm": "ピン(DM",
@@ -866,7 +864,7 @@
"map_filterNodes": "フィルタノード",
"map_nodeTypes": "ノードの種類",
"map_chatNodes": "チャットノード",
"map_repeaters": "リピータ",
"map_repeaters": "繰り返し送信装置",
"map_otherNodes": "その他のノード",
"map_keyPrefix": "主要なプレフィックス",
"map_filterByKeyPrefix": "主要なプレフィックスでフィルタリングする",
@@ -879,7 +877,7 @@
"map_lastSeenTime": "最後に確認された時間",
"map_sharedPin": "共有パスワード",
"map_joinRoom": "部屋に参加する",
"map_manageRepeater": "リピータの管理",
"map_manageRepeater": "リピータの管理",
"map_tapToAdd": "ノードをクリックして、パスに追加します。",
"map_runTrace": "パスの追跡を実行",
"map_removeLast": "最後のものを削除",
@@ -1012,12 +1010,12 @@
"login_enterPassword": "パスワードを入力してください",
"login_savePassword": "パスワードを保存する",
"login_savePasswordSubtitle": "パスワードは、このデバイスに安全に保存されます。",
"login_repeaterDescription": "設定やステータスにアクセスするために、リピータのパスワードを入力してください。",
"login_repeaterDescription": "設定やステータスにアクセスするために、リピータのパスワードを入力してください。",
"login_roomDescription": "設定やステータスへのアクセスには、部屋のパスワードを入力してください。",
"login_routing": "経路設定",
"login_routingMode": "ルーティングモード",
"login_autoUseSavedPath": "自動 (保存されたパスを使用)",
"login_forceFloodMode": "強制的にフラッドモードを起動",
"login_forceFloodMode": "強制的に洪水モードを起動",
"login_managePaths": "パスの管理",
"login_login": "ログイン",
"login_attempt": "試行回数:{current}/{max}",
@@ -1066,7 +1064,7 @@
"path_helperMaxHops": "最大64個のホップ。各プレフィックスは2つの16進数文字(1バイト)で構成されています。",
"path_selectFromContacts": "または、連絡先リストから選択してください:",
"path_noRepeatersFound": "繰り返し機能やルームサーバーは見つかりませんでした。",
"path_customPathsRequire": "カスタムパスには、メッセージを中継できるリピータが必要です。",
"path_customPathsRequire": "カスタムパスには、メッセージを中継できる中間地点が必要です。",
"path_invalidHexPrefixes": "無効な16進数プレフィックス: {prefixes}",
"@path_invalidHexPrefixes": {
"placeholders": {
@@ -1077,23 +1075,23 @@
},
"path_tooLong": "経路が長すぎる。最大64回のジャンプのみ許可。",
"path_setPath": "パスを設定",
"repeater_management": "リピータ管理",
"repeater_management": "リピータ管理",
"room_management": "ルームサーバーの管理",
"repeater_managementTools": "管理ツール",
"repeater_status": "ステータス",
"repeater_statusSubtitle": "リピータの状態、統計情報、および隣接するネットワークの情報を表示する",
"repeater_statusSubtitle": "リピータの状態、統計情報、および隣接するネットワークの情報を表示する",
"repeater_telemetry": "テレメトリー",
"repeater_telemetrySubtitle": "センサーおよびシステムの状態に関するテレメトリの表示",
"repeater_cli": "CLI(コマンドラインインターフェース)",
"repeater_cliSubtitle": "リピータへのコマンドを送信する",
"repeater_cliSubtitle": "リピータへのコマンドを送信する",
"repeater_neighbors": "近隣住民",
"repeater_neighborsSubtitle": "ゼロホップの隣接ノードを表示する。",
"repeater_settings": "設定",
"repeater_settingsSubtitle": "リピータのパラメータを設定する",
"repeater_settingsSubtitle": "リピータのパラメータを設定する",
"repeater_statusTitle": "再送ステータス",
"repeater_routingMode": "ルーティングモード",
"repeater_autoUseSavedPath": "自動 (保存されたパスを使用)",
"repeater_forceFloodMode": "強制的にフラッドモードを起動",
"repeater_forceFloodMode": "強制的に洪水モードを起動",
"repeater_pathManagement": "経路管理",
"repeater_refresh": "リフレッシュ",
"repeater_statusRequestTimeout": "ステータス情報の取得に失敗しました。",
@@ -1138,7 +1136,7 @@
}
}
},
"repeater_packetTxTotal": "合計: {total}, フラッド: {flood}, 直接: {direct}",
"repeater_packetTxTotal": "合計: {total}, 洪水: {flood}, 直接: {direct}",
"@repeater_packetTxTotal": {
"placeholders": {
"total": {
@@ -1152,7 +1150,7 @@
}
}
},
"repeater_packetRxTotal": "合計: {total}, フラッド: {flood}, 直接: {direct}",
"repeater_packetRxTotal": "合計: {total}, 洪水: {flood}, 直接: {direct}",
"@repeater_packetRxTotal": {
"placeholders": {
"total": {
@@ -1185,10 +1183,10 @@
}
}
},
"repeater_settingsTitle": "リピータ設定",
"repeater_settingsTitle": "リピータ設定",
"repeater_basicSettings": "基本設定",
"repeater_repeaterName": "リピータ名",
"repeater_repeaterNameHelper": "このリピータの名前",
"repeater_repeaterName": "送信装置名",
"repeater_repeaterNameHelper": "このリピータの名前",
"repeater_adminPassword": "管理者パスワード",
"repeater_adminPasswordHelper": "完全アクセス権のパスワード",
"repeater_guestPassword": "ゲスト用のパスワード",
@@ -1208,7 +1206,7 @@
"repeater_longitudeHelper": "度分表記(例:-122.4194",
"repeater_features": "特徴",
"repeater_packetForwarding": "パケット転送",
"repeater_packetForwardingSubtitle": "リピータがパケットを転送できるように設定する",
"repeater_packetForwardingSubtitle": "リピータがパケットを転送できるように設定する",
"repeater_guestAccess": "ゲストへのアクセス",
"repeater_guestAccessSubtitle": "ゲストへの読み取り専用アクセスを許可する",
"repeater_privacyMode": "プライバシーモード",
@@ -1223,7 +1221,7 @@
}
}
},
"repeater_floodAdvertInterval": "フラッドに関する広告の表示間隔",
"repeater_floodAdvertInterval": "洪水に関する広告の表示間隔",
"repeater_floodAdvertIntervalHours": "{hours} 時間",
"@repeater_floodAdvertIntervalHours": {
"placeholders": {
@@ -1234,15 +1232,15 @@
},
"repeater_encryptedAdvertInterval": "暗号化された広告表示間",
"repeater_dangerZone": "危険区域",
"repeater_rebootRepeater": "リピータを再起動する",
"repeater_rebootRepeaterSubtitle": "リピータデバイスを再起動する",
"repeater_rebootRepeaterConfirm": "本当にこのリピータを再起動したいですか?",
"repeater_rebootRepeater": "リピータを再起動する",
"repeater_rebootRepeaterSubtitle": "リピータデバイスを再起動する",
"repeater_rebootRepeaterConfirm": "本当にこのリピータを再起動したいですか?",
"repeater_regenerateIdentityKey": "IDキーの再生成",
"repeater_regenerateIdentityKeySubtitle": "新しい公開鍵/秘密鍵のペアを生成する",
"repeater_regenerateIdentityKeyConfirm": "これにより、リピータには新しい識別情報が割り当てられます。続行しますか?",
"repeater_regenerateIdentityKeyConfirm": "これにより、リピータには新しい識別情報が割り当てられます。続行しますか?",
"repeater_eraseFileSystem": "ファイルシステムを削除する",
"repeater_eraseFileSystemSubtitle": "リピータファイルシステムをフォーマットする",
"repeater_eraseFileSystemConfirm": "警告:この操作により、リピータ内のすべてのデータが消去されます。この操作は元に戻すことができません!",
"repeater_eraseFileSystemSubtitle": "リピータファイルシステムをフォーマットする",
"repeater_eraseFileSystemConfirm": "警告:この操作により、リピータ内のすべてのデータが消去されます。この操作は元に戻すことができません!",
"repeater_eraseSerialOnly": "Erase機能は、シリアルコンソール経由でのみ利用可能です。",
"repeater_commandSent": "送信されたコマンド: {command}",
"@repeater_commandSent": {
@@ -1270,7 +1268,7 @@
"repeater_refreshMultiAcks": "複数のACKをリフレッシュする",
"repeater_networkHealth": "ネットワークの状態",
"repeater_loopDetect": "ループ検出",
"repeater_loopDetectHelper": "ルーティングループを検知する",
"repeater_loopDetectHelper": "ルーティングループに見えるような、洪水パケットを送信する",
"repeater_loopDetectOff": "オフ",
"repeater_loopDetectMinimal": "最小限の",
"repeater_loopDetectModerate": "適度な",
@@ -1286,16 +1284,16 @@
}
},
"repeater_ownerInfo": "事業者の情報",
"repeater_ownerInfoHelper": "このリピータに関する公開メタデータ",
"repeater_ownerInfoHelper": "このリピータに関する公開メタデータ",
"repeater_refreshOwnerInfo": "オペレーター情報の更新",
"repeater_floodMax": "最大ホップ数",
"repeater_floodMaxHelper": "フラッドパケットが移動できる最大ホップ数 (0-64)",
"repeater_floodMaxHelper": "洪水パケットが移動できる最大ホップ数 (0-64)",
"repeater_advancedSettings": "高度な",
"repeater_advancedSettingsSubtitle": "経験豊富なオペレーター向けの調整ノブ",
"repeater_pathHashMode": "パスハッシュモード",
"repeater_pathHashModeHelper": "このリピータのIDをフローパス/ループ検出タグにエンコードするために使用されるバイト数。 0=1バイト (256個のID、最大64ホップ)、1=2バイト (65,000個のID、最大32ホップ)、2=3バイト (160万個のID、最大21ホップ)。 v1.13およびそれ以前のファームウェアでは、マルチバイトパスがサポートされていません。 v1.14以降のバージョンでは、一度ネットワークが起動されると、パスが一度だけ検出されます。",
"repeater_pathHashModeHelper": "このリピータのIDをフローパス/ループ検出タグにエンコードするために使用されるバイト数。 0=1バイト (256個のID、最大64ホップ)、1=2バイト (65,000個のID、最大32ホップ)、2=3バイト (160万個のID、最大21ホップ)。 v1.13およびそれ以前のファームウェアでは、マルチバイトパスがサポートされていません。 v1.14以降のバージョンでは、一度ネットワークが起動されると、パスが一度だけ検出されます。",
"repeater_txDelay": "フロイド・TXでの遅延",
"repeater_txDelayHelper": "フラッド時の交通量に対応するための再送信間隔を、パケットの通信時間を掛けた値(0~2、デフォルト0.5)で設定します。値を大きくすると衝突が減りますが、通信速度が遅くなります。",
"repeater_txDelayHelper": "洪水時の交通量に対応するための再送信間隔を、パケットの通信時間を掛けた値(0~2、デフォルト0.5)で設定します。値を大きくすると衝突が減りますが、通信速度が遅くなります。",
"repeater_directTxDelay": "直接的なTX遅延",
"repeater_directTxDelayHelper": "直接(フラッドではない)トラフィックに対する再送信間隔を、パケットの空中時間(0~2、デフォルト0.3)の倍数として設定する。",
"repeater_intThresh": "干渉閾値",
@@ -1303,10 +1301,10 @@
"repeater_agcResetInterval": "AGCのリセット間隔",
"repeater_agcResetIntervalHelper": "ラジオの自動ゲイン制御をリセットする頻度について:ゲインが固定状態になった場合に、回復するために、何度リセットするかを設定します。4の倍数でリセットする場合、0を設定すると、定期的なリセットは停止します。",
"repeater_actionsTitle": "行動",
"repeater_sendAdvert": "フラッドに関する広告を送信",
"repeater_sendAdvertSubtitle": "ネットワークを通じて、フラッドに関する広告を放送する",
"repeater_sendAdvert": "洪水に関する広告を送信",
"repeater_sendAdvertSubtitle": "ネットワークを通じて、洪水に関する広告を放送する",
"repeater_sendAdvertZeroHop": "ゼロホップ形式の広告を送信する",
"repeater_sendAdvertZeroHopSubtitle": "ワンホップでの広告放送(リピータなし)",
"repeater_sendAdvertZeroHopSubtitle": "ワンホップでの広告放送(中継なし)",
"repeater_clockSync": "現在、時刻を同期する",
"repeater_clockSyncSubtitle": "スマートフォンの時刻をルーターに設定する",
"repeater_actionSucceeded": "{action} が成功しました",
@@ -1328,7 +1326,7 @@
}
}
},
"repeater_settingsSavedRebootNeeded": "設定を保存しました — リピータを再起動して適用してください",
"repeater_settingsSavedRebootNeeded": "設定を保存しました — リピータを再起動して適用してください",
"repeater_settingsPartialFailure": "設定の一部でエラーが発生しました:{failures}",
"@repeater_settingsPartialFailure": {
"placeholders": {
@@ -1367,7 +1365,7 @@
}
}
},
"repeater_cliTitle": "リピータのコマンドラインインターフェース",
"repeater_cliTitle": "リピータのコマンドラインインターフェース",
"repeater_debugNextCommand": "次のコマンドのデバッグ",
"repeater_commandHelp": "コマンドヘルプ",
"repeater_clearHistory": "明確な歴史",
@@ -1401,14 +1399,14 @@
"repeater_cliHelpClearStats": "さまざまな統計カウンターをゼロにリセットする。",
"repeater_cliHelpSetAf": "空き時間係数を設定します。",
"repeater_cliHelpSetTx": "LoRaの送信電力をdBmで設定します。(設定変更後、再起動が必要です)",
"repeater_cliHelpSetRepeat": "このノードに対するリピータの役割を有効化または無効化します。",
"repeater_cliHelpSetRepeat": "このノードに対するリピータの役割を有効化または無効化します。",
"repeater_cliHelpSetAllowReadOnly": "(ルームサーバー設定)「オン」に設定した場合、空白のパスワードでのログインは可能ですが、ルームへの投稿はできません。(閲覧のみ)",
"repeater_cliHelpSetFloodMax": "インバウンドフラッパケットの最大ホップ数を設定します(最大値を超えた場合、パケットは転送されません)。",
"repeater_cliHelpSetFloodMax": "インバウンドフラッパケットの最大ホップ数を設定します(最大値を超えた場合、パケットは転送されません)。",
"repeater_cliHelpSetIntThresh": "干渉閾値を設定します(dB単位)。デフォルト値は14です。0に設定すると、チャンネル間の干渉を検出する機能を無効にします。",
"repeater_cliHelpSetAgcResetInterval": "オートゲインコントローラーのリセット間隔を設定します。 0 に設定すると無効化されます。",
"repeater_cliHelpSetMultiAcks": "「ダブルACK」機能の有効化または無効化を可能にします。",
"repeater_cliHelpSetAdvertInterval": "ローカル(ホップなし)の広告パケットを送信する間隔を分単位で設定します。 0 に設定すると、機能を無効にします。",
"repeater_cliHelpSetFloodAdvertInterval": "フラッド広告の送信間隔を時間単位で設定します。0に設定すると、送信を停止します。",
"repeater_cliHelpSetFloodAdvertInterval": "洪水広告の送信間隔を時間単位で設定します。0に設定すると、送信を停止します。",
"repeater_cliHelpSetGuestPassword": "ゲストのパスワードを設定/更新します。(繰り返し利用の場合、ゲストのログインは「統計情報を取得」のリクエストを送信できます)",
"repeater_cliHelpSetName": "広告の名前を設定します。",
"repeater_cliHelpSetLat": "広告表示の地図の緯度を設定します。(度分秒表記)",
@@ -1429,14 +1427,14 @@
"repeater_cliHelpLogStart": "パケットのログ記録を開始し、ファイルシステムに保存する。",
"repeater_cliHelpLogStop": "ファイルシステムへのパケットログの記録を停止する。",
"repeater_cliHelpLogErase": "ファイルシステムからパケットログを削除する。",
"repeater_cliHelpNeighbors": "ゼロホップ広告を通じて受信した他のリピータノードの一覧を表示します。各行は、IDプレフィックス(16進数)、タイムスタンプ、SNR(シグナル強度)の情報を4つ含みます。",
"repeater_cliHelpNeighbors": "ゼロホップ広告を通じて受信した他のリピータノードの一覧を表示します。各行は、IDプレフィックス(16進数)、タイムスタンプ、SNR(シグナル強度)の情報を4つ含みます。",
"repeater_cliHelpNeighborRemove": "隣接リストから、最初に一致するエントリ(pubkeyプレフィックス(16進数)で特定)を削除します。",
"repeater_cliHelpRegion": "(特定のシリーズのみ)定義されたすべての地域と、現在のフラッド許可状況を一覧表示します。",
"repeater_cliHelpRegion": "(特定のシリーズのみ)定義されたすべての地域と、現在の洪水許可状況を一覧表示します。",
"repeater_cliHelpRegionLoad": "注:これは特殊な複数コマンドの呼び出しです。その後の各コマンドは、地域名であり(スペースを使用して親階層を示し、少なくとも1つのスペースが必要です)、空行/コマンドで終了します。",
"repeater_cliHelpRegionGet": "指定された名前のプレフィックスを持つ地域を検索します(または、グローバルな範囲の場合は「*」)。結果として、「region-name (parent-name) 'F'」と返答します。",
"repeater_cliHelpRegionPut": "指定された名前で、領域の定義を追加または更新します。",
"repeater_cliHelpRegionRemove": "指定された名前を持つ領域の定義を削除します。(正確に一致している必要があり、子領域は存在してはなりません)",
"repeater_cliHelpRegionAllowf": "指定された領域に対して、「フラッド」アクセス許可を設定します。 (グローバル/従来のスコープには「*」を使用)",
"repeater_cliHelpRegionAllowf": "指定された領域に対して、「洪水」アクセス許可を設定します。 (グローバル/従来のスコープには「*」を使用)",
"repeater_cliHelpRegionDenyf": "指定された領域における「FLOOD」権限を削除します。(注:現時点では、グローバル/従来の範囲での使用は推奨されません!)",
"repeater_cliHelpRegionHome": "現在の「ホーム」地域に返信します。(まだ適用されていない、将来利用を予定)",
"repeater_cliHelpRegionHomeSet": "「ホーム」地域を設定します。",
@@ -1453,7 +1451,7 @@
"repeater_settingsCategory": "設定",
"repeater_bridge": "橋",
"repeater_logging": "ログ記録",
"repeater_neighborsRepeaterOnly": "近隣住民(リピータのみ)",
"repeater_neighborsRepeaterOnly": "近隣住民(リピータのみ)",
"repeater_regionManagementRepeaterOnly": "地域管理(ブロードキャスト用のみ)",
"repeater_regionNote": "地域レベルでの管理のため、地域定義と権限の管理を行うための機能が導入されました。",
"repeater_gpsManagement": "GPS管理",
@@ -1468,43 +1466,6 @@
}
}
},
"telemetry_digitalInputLabel": "デジタル入力",
"telemetry_digitalOutputLabel": "デジタル出力",
"telemetry_analogInputLabel": "アナログ入力",
"telemetry_analogOutputLabel": "アナログ出力",
"telemetry_genericLabel": "汎用センサー",
"telemetry_luminosityLabel": "照度",
"telemetry_presenceLabel": "在室",
"telemetry_humidityLabel": "湿度",
"telemetry_accelerometerLabel": "加速度計",
"telemetry_pressureLabel": "気圧",
"telemetry_altitudeLabel": "高度",
"telemetry_frequencyLabel": "周波数",
"telemetry_percentageLabel": "パーセント",
"telemetry_concentrationLabel": "濃度",
"telemetry_powerLabel": "電力",
"telemetry_distanceLabel": "距離",
"telemetry_energyLabel": "エネルギー",
"telemetry_directionLabel": "方向",
"telemetry_timeLabel": "時刻",
"telemetry_gyrometerLabel": "ジャイロメーター",
"telemetry_colourLabel": "色",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "スイッチ",
"telemetry_polylineLabel": "ポリライン",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "リクエスト数",
"telemetry_error": "データを取得できません",
"telemetry_noData": "テレメトリデータは利用できません。",
"telemetry_channelTitle": "チャンネル {channel}",
"@telemetry_channelTitle": {
@@ -1567,7 +1528,7 @@
}
}
},
"neighbors_repeatersNeighbors": "近隣のリピータ",
"neighbors_repeatersNeighbors": "繰り返し送信する、近隣",
"neighbors_noData": "近隣のデータは利用できません。",
"neighbors_unknownContact": "不明な {pubkey}",
"@neighbors_unknownContact": {
@@ -1588,12 +1549,12 @@
"channelPath_title": "パケットパス",
"channelPath_viewMap": "地図を表示する",
"channelPath_otherObservedPaths": "観察されたその他の経路",
"channelPath_repeaterHops": "リピータホップ",
"channelPath_repeaterHops": "ホップの繰り返し",
"channelPath_noHopDetails": "このパッケージに関する詳細な情報は提供されていません。",
"channelPath_messageDetails": "メッセージの詳細",
"channelPath_senderLabel": "送信者",
"channelPath_timeLabel": "時間",
"channelPath_repeatsLabel": "リピータ",
"channelPath_repeatsLabel": "繰り返し",
"channelPath_pathLabel": "{index} 番目の経路",
"channelPath_observedLabel": "観察",
"channelPath_observedPathTitle": "観察された経路 {index} • {hops}",
@@ -1631,7 +1592,7 @@
}
},
"channelPath_unknownPath": "不明",
"channelPath_floodPath": "フラッド",
"channelPath_floodPath": "洪水",
"channelPath_directPath": "直接",
"channelPath_observedZeroOf": "{total}個のホップ",
"@channelPath_observedZeroOf": {
@@ -1653,7 +1614,7 @@
}
},
"channelPath_mapTitle": "経路図",
"channelPath_noRepeaterLocations": "この経路にリピータの位置情報はありません。",
"channelPath_noRepeaterLocations": "この経路には、中継装置の設置場所がありません。",
"channelPath_primaryPath": "{index}番目の経路(主要経路)",
"@channelPath_primaryPath": {
"placeholders": {
@@ -1831,7 +1792,7 @@
"listFilter_addToFavorites": "お気に入りに追加",
"listFilter_removeFromFavorites": "お気に入りから削除",
"listFilter_users": "利用者",
"listFilter_repeaters": "リピータ",
"listFilter_repeaters": "繰り返し送信装置",
"listFilter_roomServers": "ルーム用サーバー",
"listFilter_unreadOnly": "未読のみ",
"listFilter_newGroup": "新しいグループ",
@@ -1976,8 +1937,8 @@
},
"contacts_pathTrace": "経路追跡",
"contacts_ping": "パング",
"contacts_repeaterPathTrace": "リピータまでの経路を追跡する",
"contacts_repeaterPing": "リピータにPING",
"contacts_repeaterPathTrace": "リピータまでの経路を追跡する",
"contacts_repeaterPing": "PING 繰り返し",
"contacts_roomPathTrace": "部屋のサーバーへの経路を追跡する",
"contacts_roomPing": "ピンルーム用サーバー",
"contacts_chatTraceRoute": "経路の追跡ルート",
@@ -1994,7 +1955,7 @@
"contacts_contactImported": "連絡先が登録されました。",
"contacts_contactImportFailed": "連絡先のインポートに失敗しました。",
"contacts_zeroHopAdvert": "ゼロホップ広告",
"contacts_floodAdvert": "フラッドに関する広告",
"contacts_floodAdvert": "洪水に関する広告",
"contacts_copyAdvertToClipboard": "広告をクリップボードにコピー",
"contacts_addContactFromClipboard": "クリップボードから連絡先を追加する",
"contacts_ShareContact": "連絡先をクリップボードにコピー",
@@ -2037,7 +1998,7 @@
}
},
"notification_receivedNewMessage": "新しいメッセージを受信",
"settings_gpxExportRepeaters": "GPX へのエクスポート用リピータ/ルームサーバー",
"settings_gpxExportRepeaters": "GPX へのエクスポート用リピータ/ルームサーバー",
"settings_gpxExportRepeatersSubtitle": "GPXファイルに場所情報を付加した、レピーター/ルームサーバーのエクスポート",
"settings_gpxExportContacts": "GPX 形式へのエクスポート",
"settings_gpxExportContactsSubtitle": "GPXファイルに位置情報を保存して、他の人と共有する。",
@@ -2047,20 +2008,20 @@
"settings_gpxExportNoContacts": "エクスポートする連絡先は存在しません。",
"settings_gpxExportNotAvailable": "このデバイス/OSではサポートされていません",
"settings_gpxExportError": "エクスポート時にエラーが発生しました。",
"settings_gpxExportRepeatersRoom": "リピータ/ルームサーバーの位置情報",
"settings_gpxExportRepeatersRoom": "中継装置およびルームサーバーの設置場所",
"settings_gpxExportChat": "関連施設",
"settings_gpxExportAllContacts": "すべての連絡先場所",
"settings_gpxExportShareText": "meshcore-openからエクスポートされた地図データ",
"settings_gpxExportShareSubject": "meshcore-open GPX形式の地図データのエクスポート",
"snrIndicator_nearByRepeaters": "近くのリピータ",
"snrIndicator_nearByRepeaters": "近くの電波中継局",
"snrIndicator_lastSeen": "最後に確認された場所",
"contactsSettings_title": "連絡先設定",
"contactsSettings_autoAddTitle": "自動検出",
"contactsSettings_otherTitle": "その他の連絡に関する設定",
"contactsSettings_autoAddUsersTitle": "自動でユーザーを追加する",
"contactsSettings_autoAddUsersSubtitle": "利用者が自動的に発見したユーザーを追加できるようにする。",
"contactsSettings_autoAddRepeatersTitle": "リピータを自動追加",
"contactsSettings_autoAddRepeatersSubtitle": "発見したリピータを、自動的に追加できるようにする。",
"contactsSettings_autoAddRepeatersTitle": "自動で繰り返し設定",
"contactsSettings_autoAddRepeatersSubtitle": "発見した中継局を、自動的に追加できるようにする。",
"contactsSettings_autoAddRoomServersTitle": "自動でルームサーバーを追加",
"contactsSettings_autoAddRoomServersSubtitle": "利用者が、発見した部屋のサーバーを自動的に追加できるようにする。",
"contactsSettings_autoAddSensorsTitle": "自動でセンサーを追加",
@@ -2162,7 +2123,7 @@
"contact_teleLocSubtitle": "位置情報共有を許可する",
"contact_teleEnv": "テレメトリ環境",
"contact_teleEnvSubtitle": "環境センサーのデータを共有することを許可する",
"map_showOverlaps": "リピータキーの重複",
"map_showOverlaps": "リピータキーの重複",
"map_runTraceWithReturnPath": "元の経路に戻る。",
"@translation_downloadFailed": {
"placeholders": {
@@ -2176,9 +2137,6 @@
"translation_composerTitle": "送信する前に翻訳する",
"translation_enableTitle": "翻訳機能を有効にする",
"translation_composerSubtitle": "作曲家翻訳アイコンのデフォルト状態を制御する。",
"translation_autoIncomingTitle": "メッセージを自動翻訳",
"translation_autoIncomingSubtitle": "通知やチャット、チャンネルのメッセージを自動的に翻訳します。",
"translation_translateMessage": "メッセージを翻訳",
"translation_targetLanguage": "翻訳対象言語",
"translation_useAppLanguage": "アプリの言語設定",
"translation_downloadedModelLabel": "ダウンロードしたモデル",
@@ -2232,7 +2190,7 @@
"repeater_clockSyncAfterLoginSubtitle": "ログインが成功した場合、自動的に「時刻同期」を送信する。",
"room_guest": "ルームサーバーに関する情報",
"chat_sendMessage": "メッセージを送信する",
"repeater_guest": "リピータに関する情報",
"repeater_guest": "繰り返し送信に関する情報",
"repeater_guestTools": "ゲスト向けツール",
"repeater_getCategory": "価値を取得する",
"repeater_powerMgmt": "電力管理",
@@ -2243,7 +2201,7 @@
"repeater_cliHelpStartOta": "サポートされているボードに対して、無線でファームウェアのアップデートを開始します。",
"repeater_cliHelpTime": "デバイスのクロックを、指定されたUnixエポックの秒数に設定します。クロックは逆方向に進むことはできません。",
"repeater_cliHelpBoard": "製造元の名前/ハードウェア識別子を表示します。",
"repeater_cliHelpDiscoverNeighbors": "近隣のノードに対して、ノードの探索リクエストを送信します。(リピータ機能のみ)",
"repeater_cliHelpDiscoverNeighbors": "近隣のノードに対して、ノードの探索リクエストを送信します。(中継機能のみ)",
"repeater_cliHelpPowersaving": "省電力モードがオンになっているかどうかを表示します。",
"repeater_cliHelpPowersavingOnOff": "省電力モード(対応している場合)を有効または無効にします。",
"repeater_cliHelpErase": "(シリアルモードのみ)デバイスのファイルシステムをフォーマットします。すべての設定と連絡先を消去します。",
@@ -2256,10 +2214,10 @@
"repeater_cliHelpSetFreq": "(シリアル設定のみ)特定の周波数のみを素早く設定できます。再起動が必要です。「ラジオ設定」を使用すると、ラジオのすべてのパラメータを設定できます。",
"repeater_cliHelpSetBridgeChannel": "ESPNowブリッジのみ)ブリッジで使用するWi-Fiチャンネル(1~14)を設定します。",
"repeater_cliHelpGetName": "設定されたノードの名前を表示します。",
"repeater_cliHelpGetRole": "ファームウェアの役割(リピータ、ルームサーバーなど)を表示します。",
"repeater_cliHelpGetRole": "ファームウェアの役割(リピータ、ルームサーバーなど)を表示します。",
"repeater_cliHelpGetPublicKey": "デバイスの公開鍵を表示します。",
"repeater_cliHelpGetPrvKey": "(シリアル番号のみ)デバイスのプライベートキーを表示します。機密情報として扱ってください。",
"repeater_cliHelpGetRepeat": "パケット転送(リピータ機能)が有効になっているかどうかを表示します。",
"repeater_cliHelpGetRepeat": "パケット転送(リピータ機能)が有効になっているかどうかを表示します。",
"repeater_cliHelpGetTx": "現在のTX(送信)電力のdBm値を表示します。",
"repeater_cliHelpGetFreq": "設定された無線周波数をMHzで表示します。",
"repeater_cliHelpGetRadio": "以下のすべての無線パラメータを表示: 周波数、帯域幅、スプレッドファクター、符号化レート。",
@@ -2271,18 +2229,18 @@
"repeater_cliHelpGetMultiAcks": "ダブルACKモードが有効 (1) か無効 (0) かを示す。",
"repeater_cliHelpGetAllowReadOnly": "ゲストによる読み取り専用アクセスが許可されているかどうかを示す。",
"repeater_cliHelpGetAdvertInterval": "ローカル広告の時間を分単位で表示します。",
"repeater_cliHelpGetFloodAdvertInterval": "フラッドに関する広告の放送時間を時間単位で表示します。",
"repeater_cliHelpGetFloodAdvertInterval": "洪水に関する広告の放送時間を時間単位で表示します。",
"repeater_cliHelpGetGuestPassword": "設定されたゲストパスワードを表示します。",
"repeater_cliHelpGetLat": "設定された緯度を表示します。",
"repeater_cliHelpGetLon": "設定された経度を表示します。",
"repeater_cliHelpGetRxDelay": "rxdelay の基本値を表示します。",
"repeater_cliHelpGetTxDelay": "フラッドモードにおける送信遅延の要因を示します。",
"repeater_cliHelpGetTxDelay": "洪水モードにおける送信遅延の要因を示します。",
"repeater_cliHelpGetDirectTxDelay": "ダイレクトモードの遅延要素を示します。",
"repeater_cliHelpGetFloodMax": "フラッドパケットの最大ホップ数を表示します。",
"repeater_cliHelpGetFloodMax": "最大浸水範囲の回数を表示します。",
"repeater_cliHelpGetOwnerInfo": "所有者の連絡先情報を表示します。",
"repeater_cliHelpGetPathHashMode": "パスハッシュモード(0/1/2)を表示します。",
"repeater_cliHelpGetLoopDetect": "ループ検出の感度を示す。",
"repeater_cliHelpGetAcl": "(シリアルのみ)リピータ上のアクセス制御設定を一覧表示します。",
"repeater_cliHelpGetAcl": "(シリアルのみ)リピータ上のアクセス制御設定を一覧表示します。",
"repeater_cliHelpGetBridgeEnabled": "橋が有効になっているかどうかを表示します。",
"repeater_cliHelpGetBridgeDelay": "橋の遅延時間をミリ秒(ms)で表示します。",
"repeater_cliHelpGetBridgeSource": "RX または TX パケットを橋渡ししているかどうかを示す。",
@@ -2300,8 +2258,8 @@
"repeater_cliHelpSensorList": "カスタムセンサーの設定をすべてリスト表示し、オプションで指定できる開始インデックスからページ分割して表示します。",
"repeater_cliHelpRegionDefault": "現在のデフォルトの地域範囲を表示します。",
"repeater_cliHelpRegionDefaultSet": "デフォルトの地域範囲を設定します。「<null>」を使用すると、設定をリセットできます。",
"repeater_cliHelpRegionListAllowed": "フラッド時の通行が許可されている地域の一覧",
"repeater_cliHelpRegionListDenied": "フラッドによる交通を遮断している地域の一覧",
"repeater_cliHelpRegionListAllowed": "洪水時の通行が許可されている地域の一覧",
"repeater_cliHelpRegionListDenied": "洪水による交通を遮断している地域の一覧",
"repeater_cliHelpStatsPackets": "(シリアルのみ)パケットレベルの統計情報を表示します。",
"repeater_cliHelpStatsRadio": "(シリーズのみ)ラジオの統計情報を表示します。",
"repeater_cliHelpStatsCore": "(シリアルのみ)主要なファームウェアの統計情報を表示します。",
@@ -2392,97 +2350,8 @@
"chat_newMessages": "新しいメッセージ",
"chat_markAsUnread": "未読としてマークする",
"repeater_chanUtil": "チャンネルの利用状況",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_delivered": "配達",
"common_undo": "元に戻す",
"messageStatus_pending": "送信",
"messageStatus_sent": "送信",
"messageStatus_failed": "送信できませんでした",
"contacts_moreOptions": "さらに多くの選択肢",
"messageStatus_repeated": "何度も聞いた",
"contacts_searchOpen": "連絡先を検索する",
"contacts_searchClose": "検索を終了",
"routing_modeFlood": "洪水",
"routing_title": "経路設定",
"routing_modeAuto": "自動車",
"routing_modeManual": "マニュアル",
"routing_modeAutoHint": "最も一般的な経路を自動的に選択し、経路が不明な場合は、水没状態にします。",
"routing_modeFloodHint": "すべてのリピーターを通じて放送。最も信頼性が高いですが、より多くの時間を使用します。",
"routing_modeManualHint": "常に、あなたが設定した正確な経路を辿って移動します。",
"routing_currentRoute": "現在までのルート",
"routing_directNoHops": "直接接続—中継装置を経由しない",
"routing_noPathYet": "まだ経路は確立されていません。「次のメッセージを送信し、経路が特定されるまで待ちます」。",
"routing_floodBroadcast": "すべてのリピーターを通じて放送",
"routing_editPath": "パスの編集",
"routing_forgetPath": "道にこだわらない",
"routing_knownPaths": "既知の経路",
"routing_knownPathsHint": "そのアプリケーションに切り替えるためのショートカットを作成します。",
"routing_inUse": "使用中",
"routing_qualityStrong": "最初の段階で大きな成果を上げる",
"routing_qualityGood": "最初の成功",
"routing_qualityFair": "最初の試みは成功を収めた",
"routing_qualityWorked": "完了しました",
"routing_qualityFlood": "氾濫によって伝聞",
"routing_qualityUntested": "未検証",
"routing_lastWorked": "{when}に勤務",
"routing_neverWorked": "確認されていない",
"routing_floodDelivery": "洪水による配送",
"pathEditor_title": "経路の作成",
"pathEditor_hopCounter": "64個のホップのうち、{count}個",
"pathEditor_noHops": "まだホップは追加されていません。ホップを順番に追加するには、以下の「タップ」ボタンをクリックしてください。または、ホップを一切追加せずに直接送信するには、「保存」ボタンをクリックしてください。",
"pathEditor_addHops": "ホップを、指定された順番に加える",
"pathEditor_searchRepeaters": "繰り返し検索",
"pathEditor_advancedHex": "高度なレベル:生のヘックスパス",
"pathEditor_hexLabel": "ヘックスプレフィックス",
"pathEditor_hexHelper": "各ホップごとに2つのハッシュ文字を、カンマで区切って記述",
"pathEditor_invalidTokens": "無効: {tokens}",
"pathEditor_tooManyHops": "最大64段階",
"pathEditor_usePath": "この経路を使用してください",
"pathEditor_removeHop": "ホップを取り除く",
"pathEditor_unknownHop": "不明な増幅器",
"map_zoomIn": "ズームイン",
"map_zoomOut": "ズームアウト",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_centerMap": "中心地図",
"chrome_bluetoothRequiresChromium": "Web Bluetooth は、Chromium ブラウザが必要です。",
"channels_communityShortId": "ID: {id}…",
"pathTrace_legendGpsConfirmed": "GPSによる確認",
"pathTrace_legendInferred": "推測される位置"
"dialog_connectCompanion": "コネクトしてリピーターとルームサーバー機能にアクセス",
"dialog_disconnectedTitle": "切断済み",
"dialog_disconnectedMessage": "コンパニオンとの接続が切れました。",
"contact_connectCompanion": "リピーターおよびルームサーバー機能にアクセスするには、コンパニオンに接続してください。"
}
+4 -135
View File
@@ -27,8 +27,6 @@
"common_remove": "제거",
"common_enable": "활성화",
"common_disable": "비활성화",
"common_autoRefresh": "자동 새로고침",
"common_interval": "간격",
"common_reboot": "재부팅",
"common_loading": "로딩 중...",
"common_notAvailable": "—",
@@ -1468,43 +1466,6 @@
}
}
},
"telemetry_digitalInputLabel": "디지털 입력",
"telemetry_digitalOutputLabel": "디지털 출력",
"telemetry_analogInputLabel": "아날로그 입력",
"telemetry_analogOutputLabel": "아날로그 출력",
"telemetry_genericLabel": "일반 센서",
"telemetry_luminosityLabel": "조도",
"telemetry_presenceLabel": "존재 감지",
"telemetry_humidityLabel": "습도",
"telemetry_accelerometerLabel": "가속도계",
"telemetry_pressureLabel": "압력",
"telemetry_altitudeLabel": "고도",
"telemetry_frequencyLabel": "주파수",
"telemetry_percentageLabel": "백분율",
"telemetry_concentrationLabel": "농도",
"telemetry_powerLabel": "전력",
"telemetry_distanceLabel": "거리",
"telemetry_energyLabel": "에너지",
"telemetry_directionLabel": "방향",
"telemetry_timeLabel": "시간",
"telemetry_gyrometerLabel": "자이로미터",
"telemetry_colourLabel": "색상",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "스위치",
"telemetry_polylineLabel": "폴리라인",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "요청 수",
"telemetry_error": "데이터를 가져올 수 없습니다",
"telemetry_noData": "텔레메트리 데이터는 제공되지 않습니다.",
"telemetry_channelTitle": "채널 {channel}",
"@telemetry_channelTitle": {
@@ -2176,9 +2137,6 @@
"translation_enableTitle": "번역 기능 활성화",
"translation_composerTitle": "보내기 전에 번역",
"translation_composerSubtitle": "컴포저 번역 아이콘의 기본 상태를 제어합니다.",
"translation_autoIncomingTitle": "메시지 자동 번역",
"translation_autoIncomingSubtitle": "알림과 채팅 또는 채널의 메시지를 자동으로 번역합니다.",
"translation_translateMessage": "메시지 번역",
"translation_targetLanguage": "목표 언어",
"translation_useAppLanguage": "앱 언어 사용",
"translation_downloadedModelLabel": "다운로드한 모델",
@@ -2392,97 +2350,8 @@
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 명령어, 응답 및 원시 데이터",
"chat_markAsUnread": "미리 읽지 않음으로 표시",
"repeater_chanUtil": "채널 활용도",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_pending": "발송",
"messageStatus_sent": "발송",
"messageStatus_delivered": "배송 완료",
"common_undo": "취소",
"messageStatus_failed": "발송 실패",
"messageStatus_repeated": "반복적으로 들었습니다",
"contacts_searchOpen": "연락처 검색",
"contacts_moreOptions": "더 많은 옵션",
"contacts_searchClose": "검색 닫기",
"routing_title": "라우팅",
"routing_modeAuto": "자동",
"routing_modeFlood": "홍수",
"routing_modeManual": "사용 설명서",
"routing_modeAutoHint": "가장 잘 알려진 경로를 자동으로 선택하고, 경로가 없을 경우에는 무작위로 경로를 선택합니다.",
"routing_modeFloodHint": "모든 증폭기를 통해 방송됩니다. 가장 안정적이지만, 더 많은 송출 시간을 사용합니다.",
"routing_modeManualHint": "항상 설정하신 정확한 경로를 따라 이동합니다.",
"routing_currentRoute": "현재 경로",
"routing_directNoHops": "직접 연결 중계 장치 사용 없이",
"routing_noPathYet": "아직 경로가 없습니다. 다음 메시지가 도착할 때까지 계속 탐색합니다.",
"routing_floodBroadcast": "모든 증폭기를 통해 방송",
"routing_editPath": "경로 편집",
"routing_forgetPath": "길을 잊어라",
"routing_knownPaths": "알려진 경로",
"routing_knownPathsHint": "해당 항목으로 전환하기 위한 경로를 선택합니다.",
"routing_inUse": "사용 중",
"routing_qualityStrong": "강력한 첫 번째 단계",
"routing_qualityGood": "좋은 첫 시작",
"routing_qualityFair": "처음 시도",
"routing_qualityWorked": "완료됨",
"routing_qualityFlood": "홍수 피해 상황을 통해 들었습니다.",
"routing_qualityUntested": "검증되지 않음",
"routing_lastWorked": "{when}에 일했습니다",
"routing_neverWorked": "확인되지 않음",
"routing_floodDelivery": "홍수 피해 지역 배송",
"pathEditor_title": "경로 만들기",
"pathEditor_hopCounter": "64개의 홉 중 {count}",
"pathEditor_noHops": "현재 홉은 추가되지 않았습니다. 아래의 탭을 사용하여 순서대로 추가하거나, 홉 없이 바로 전송하려면 \"홉 없음\"으로 저장하십시오.",
"pathEditor_addHops": "홉을 순서대로 첨가해주세요.",
"pathEditor_searchRepeaters": "반복 검색",
"pathEditor_advancedHex": "고급: 원시 헥스 경로",
"pathEditor_hexLabel": "헥스 접두사",
"pathEditor_hexHelper": "각 홉마다 2개의 6자리 숫자, 쉼표로 구분",
"pathEditor_invalidTokens": "유효하지 않음: {tokens}",
"pathEditor_tooManyHops": "최대 64개의 홉",
"pathEditor_usePath": "이 경로를 사용하세요",
"pathEditor_removeHop": "홉 제거",
"pathEditor_unknownHop": "알 수 없는 중계기",
"map_zoomIn": "줌 인",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "줌 아웃",
"map_centerMap": "중심 지도",
"chrome_bluetoothRequiresChromium": "웹 블루투스는 크롬 브라우저가 필요합니다.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS 확인 완료",
"pathTrace_legendInferred": "추론된 위치"
"dialog_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.",
"dialog_disconnectedTitle": "연결 끊김",
"dialog_disconnectedMessage": "컴패니언과의 연결이 끊어졌습니다.",
"contact_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요."
}
File diff suppressed because it is too large Load Diff
+148 -472
View File
@@ -92,24 +92,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get common_disable => 'Деактивирай';
@override
String get common_undo => 'Отмяни';
@override
String get messageStatus_sent => 'Изпратено';
@override
String get messageStatus_delivered => 'Доставен';
@override
String get messageStatus_pending => 'Изпращане';
@override
String get messageStatus_failed => 'Не успях да изпратя';
@override
String get messageStatus_repeated => 'Слушах го многократно';
@override
String get common_reboot => 'Рестартирай';
@@ -129,12 +111,6 @@ class AppLocalizationsBg extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Автоматично обновяване';
@override
String get common_interval => 'Интервал';
@override
String get scanner_title => 'MeshCore – Отворена версия';
@@ -318,10 +294,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Активирайте Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Бързо превключване';
@@ -818,6 +790,11 @@ class AppLocalizationsBg extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Батерия';
@@ -1019,15 +996,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get contacts_newGroup => 'Нова група';
@override
String get contacts_moreOptions => 'Повече възможности';
@override
String get contacts_searchOpen => 'Търсене на контакти';
@override
String get contacts_searchClose => 'Затвори търсене';
@override
String get contacts_groupName => 'Група';
@@ -1508,6 +1476,35 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Хексадесетичен Dump:';
@override
String get chat_pathManagement => 'Управление на пътища';
@override
String get chat_ShowAllPaths => 'Покажи всички пътища';
@override
String get chat_routingMode => 'Режим на маршрутизиране';
@override
String get chat_autoUseSavedPath => 'Автоматично (използвай запазения път)';
@override
String get chat_forceFloodMode => 'Принуди режим на наводняване';
@override
String get chat_recentAckPaths =>
'Неотдавни ACK пътища (докоснете, за да използвате):';
@override
String get chat_pathHistoryFull =>
'Историята на пътя е пълна. Премахнете записи, за да добавите нови.';
@override
String get chat_hopSingular => 'скочи';
@override
String get chat_hopPlural => 'скоци';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1519,6 +1516,12 @@ class AppLocalizationsBg extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Успехи';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Премахни пътя';
@@ -1526,146 +1529,52 @@ class AppLocalizationsBg extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Няма история на пътищата още.\nИзпратете съобщение, за да откриете пътища.';
@override
String get chat_pathActions => 'Действия по пътя:';
@override
String get chat_setCustomPath => 'Задайте персонализиран път';
@override
String get chat_setCustomPathSubtitle => 'Ръчно укажете маршрутен път';
@override
String get chat_clearPath => 'Почисти Път';
@override
String get chat_clearPathSubtitle =>
'Принуди преоткриване при следващо изпращане';
@override
String get chat_pathCleared =>
'Пътят е почистен. Следващото съобщение ще открие маршрута отново.';
@override
String get chat_floodModeSubtitle =>
'Използвайте превключвателя за маршрутизиране в лентата на приложението.';
@override
String get chat_floodModeEnabled =>
'Режим на наводнение е активиран. Включете го отново чрез иконката за маршрутизиране в лентата на приложението.';
@override
String get chat_fullPath => 'Пълен път';
@override
String get routing_title => 'Маршрутизиране';
String get chat_pathDetailsNotAvailable =>
'Детайлите за пътя все още не са налични. Опитайте да изпратите съобщение, за да освежите.';
@override
String get routing_modeAuto => 'Автомобил';
@override
String get routing_modeFlood => 'Наводнение';
@override
String get routing_modeManual => 'Ръководство';
@override
String get routing_modeAutoHint =>
'Автоматично избира най-известния път, като при липса на информация, използва стратегия за \"запълване\" на празните пространства.';
@override
String get routing_modeFloodHint =>
'Излъчване през всички ретранслатори. Най-надежният начин, но изисква повече време на въздуха.';
@override
String get routing_modeManualHint =>
'Винаги следва точно пътя, който сте определили.';
@override
String get routing_currentRoute => 'Текущ маршрут';
@override
String get routing_directNoHops => 'Директ – без превключватели';
@override
String get routing_noPathYet =>
'Все още няма път. Съобщението продължава да се изпраща, докато не бъде открит маршрут.';
@override
String get routing_floodBroadcast => 'Предаване през всички ретранслатори';
@override
String get routing_editPath => 'Редактиране на пътя';
@override
String get routing_forgetPath => 'Забравете за пътя';
@override
String get routing_knownPaths => 'Известни маршрути';
@override
String get routing_knownPathsHint =>
'Натиснете бутона, за да превключите към него.';
@override
String get routing_inUse => 'В експлоатация';
@override
String get routing_qualityStrong => 'Силен първи скок';
@override
String get routing_qualityGood => 'Добър първи опит';
@override
String get routing_qualityFair => 'Първият добър скок';
@override
String get routing_qualityWorked => 'Беше изпълнено/Доведено до край';
@override
String get routing_qualityFlood =>
'Получено чрез информация, разпространена в резултат на навод.';
@override
String get routing_qualityUntested => 'Не тестван';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Пътят е зададен: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'никога не е потвърдено';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Доставка при навод';
@override
String get pathEditor_title => 'Създаване на път';
@override
String pathEditor_hopCounter(int count) {
return '$count от 64 различни вида малц';
}
@override
String get pathEditor_noHops =>
'Все още няма добавени хмел. Можете да използвате бутоните по-долу, за да ги добавите по ред, или да запазите рецептата без хмел, за да я изпратите директно.';
@override
String get pathEditor_addHops => 'Добавете хмела в реда, в който е посочено.';
@override
String get pathEditor_searchRepeaters => 'Търсене на повтори';
@override
String get pathEditor_advancedHex => 'Разширено: необработен шестничен път';
@override
String get pathEditor_hexLabel => 'Префикси на шестнадесетична система';
@override
String get pathEditor_hexHelper =>
'Два шест-символни идентификатора на скок, разделени със запетаи';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Невалидно: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Максимум 64 крачета';
@override
String get pathEditor_usePath => 'Използвайте този маршрут.';
@override
String get pathEditor_removeHop => 'Премахнете хмела';
@override
String get pathEditor_unknownHop => 'Неизвестен репитер';
@override
String get chat_pathSavedLocally =>
'Запазено локално. Свържете се за синхронизиране.';
@@ -1742,39 +1651,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get map_title => 'Карта на възлите';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Линия на видимост';
@@ -2103,6 +1979,17 @@ class AppLocalizationsBg extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Сигурни ли сте, че искате да се откъснете от това устройство?';
@override
String get dialog_disconnectedTitle => 'Прекъснато';
@override
String get dialog_disconnectedMessage =>
'Свързването ви с вашия спътник е прекъснато.';
@override
String get dialog_connectCompanion =>
'Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.';
@override
String get login_repeaterLogin => 'Повторител Вход';
@@ -2168,13 +2055,66 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get common_clear => 'Изчисти';
@override
String path_currentPath(String path) {
return 'Текущ път: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Използване на $count $_temp0 път';
}
@override
String get path_enterCustomPath => 'Въведете персонализиран път';
@override
String get path_currentPathLabel => 'Текущ път';
@override
String get path_hexPrefixInstructions =>
'Въведете 2-символни шестнадесетични префикси за всеки хоп, разделени с кама.';
@override
String get path_hexPrefixExample =>
'A1,F2,3C (всяка нода използва първия байт от публичния си ключ)';
@override
String get path_labelHexPrefixes => 'Пътеки (шестнадесетични префикси)';
@override
String get path_helperMaxHops =>
'Максимум 64 скока. Всеки префикс е 2 шестнадесетични знака (1 байт).';
@override
String get path_selectFromContacts => 'Изберете от контакти:';
@override
String get path_noRepeatersFound =>
'Няма намерени репетитори или сървъри на стаи.';
@override
String get path_customPathsRequire =>
'Персонализираните пътища изискват междинни скокове, които могат да препращат съобщения.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Невалидни шестнадесетични префикси: $prefixes';
}
@override
String get path_tooLong =>
'Пътят е твърде дълъг. Максимум 64 скока са разрешени.';
@override
String get path_setPath => 'Задайте път';
@override
String get repeater_management => 'Управление на повторители';
@@ -2241,6 +2181,16 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get repeater_routingMode => 'Режим на маршрутизиране';
@override
String get repeater_autoUseSavedPath =>
'Автоматично (използвай запазения път)';
@override
String get repeater_forceFloodMode => 'Принуди режим на наводняване';
@override
String get repeater_pathManagement => 'Управление на пътища';
@override
String get repeater_refresh => 'Презареди';
@@ -3337,139 +3287,6 @@ class AppLocalizationsBg extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Цифров вход';
@override
String get telemetry_digitalOutputLabel => 'Цифров изход';
@override
String get telemetry_analogInputLabel => 'Аналогов вход';
@override
String get telemetry_analogOutputLabel => 'Аналогов изход';
@override
String get telemetry_genericLabel => 'Общ сензор';
@override
String get telemetry_luminosityLabel => 'Осветеност';
@override
String get telemetry_presenceLabel => 'Присъствие';
@override
String get telemetry_humidityLabel => 'Влажност';
@override
String get telemetry_accelerometerLabel => 'Акселерометър';
@override
String get telemetry_pressureLabel => 'Налягане';
@override
String get telemetry_altitudeLabel => 'Надморска височина';
@override
String get telemetry_frequencyLabel => 'Честота';
@override
String get telemetry_percentageLabel => 'Процент';
@override
String get telemetry_concentrationLabel => 'Концентрация';
@override
String get telemetry_powerLabel => 'Мощност';
@override
String get telemetry_distanceLabel => 'Разстояние';
@override
String get telemetry_energyLabel => 'Енергия';
@override
String get telemetry_directionLabel => 'Посока';
@override
String get telemetry_timeLabel => 'Време';
@override
String get telemetry_gyrometerLabel => 'Жироскоп';
@override
String get telemetry_colourLabel => 'Цвят';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Превключвател';
@override
String get telemetry_polylineLabel => 'Полилиния';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Брой заявки';
@override
String get telemetry_error => 'Неуспешно получаване на данни';
@override
String get neighbors_receivedData => 'Получени данни за съседи';
@@ -4355,16 +4172,6 @@ class AppLocalizationsBg extends AppLocalizations {
String get translation_composerSubtitle =>
'Контролира началния статус на иконата за превод, създадена от композитора.';
@override
String get translation_autoIncomingTitle => 'Автоматичен превод на съобщения';
@override
String get translation_autoIncomingSubtitle =>
'Превежда автоматично съобщенията за известия, както и за чатове или канали.';
@override
String get translation_translateMessage => 'Преведи съобщението';
@override
String get translation_targetLanguage => 'Целеви език';
@@ -4492,135 +4299,4 @@ class AppLocalizationsBg extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Увеличи';
@override
String get map_zoomOut => 'Приближете се по-малко';
@override
String get map_centerMap => 'Карта на центъра';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth изисква браузър, базиран на Chromium.';
@override
String channels_communityShortId(String id) {
return 'Идентификационен номер: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS потвърдено';
@override
String get pathTrace_legendInferred => 'Извлечена позиция';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+147 -475
View File
@@ -92,24 +92,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get common_disable => 'Deaktivieren';
@override
String get common_undo => 'Rückgängig machen';
@override
String get messageStatus_sent => 'Gesendet';
@override
String get messageStatus_delivered => 'Geliefert';
@override
String get messageStatus_pending => 'Versenden';
@override
String get messageStatus_failed => 'Nicht gesendet';
@override
String get messageStatus_repeated => 'Wiederholt gehört';
@override
String get common_reboot => 'Neustart';
@@ -129,12 +111,6 @@ class AppLocalizationsDe extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Automatische Aktualisierung';
@override
String get common_interval => 'Intervall';
@override
String get scanner_title => 'MeshCore Open-Version';
@@ -321,10 +297,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Bluetooth aktivieren';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Schnelles Umschalten';
@@ -814,6 +786,11 @@ class AppLocalizationsDe extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Akku';
@@ -1015,15 +992,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contacts_newGroup => 'Neue Gruppe';
@override
String get contacts_moreOptions => 'Weitere Optionen';
@override
String get contacts_searchOpen => 'Kontakte suchen';
@override
String get contacts_searchClose => 'Erweiterte Suche';
@override
String get contacts_groupName => 'Gruppenname';
@@ -1506,6 +1474,36 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Hex-Dump:';
@override
String get chat_pathManagement => 'Pfadverwaltung';
@override
String get chat_ShowAllPaths => 'Alle Pfade anzeigen';
@override
String get chat_routingMode => 'Routenmodus';
@override
String get chat_autoUseSavedPath =>
'Automatisch (gespeicherten Pfad verwenden)';
@override
String get chat_forceFloodMode => 'Flut-Modus erzwingen';
@override
String get chat_recentAckPaths =>
'Aktuelle ACK-Pfade (antippen, um zu verwenden):';
@override
String get chat_pathHistoryFull =>
'Die Pfadhistorie ist voll. Entferne Einträge, um neue hinzuzufügen.';
@override
String get chat_hopSingular => 'Sprung';
@override
String get chat_hopPlural => 'Sprünge';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1517,6 +1515,12 @@ class AppLocalizationsDe extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Erfolgreich';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Pfad entfernen';
@@ -1524,148 +1528,51 @@ class AppLocalizationsDe extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Keine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.';
@override
String get chat_pathActions => 'Pfadaktionen:';
@override
String get chat_setCustomPath => 'Lege benutzerdefinierten Pfad fest';
@override
String get chat_setCustomPathSubtitle => 'Manuellen Routenpfad festlegen';
@override
String get chat_clearPath => 'Pfad zurücksetzen';
@override
String get chat_clearPathSubtitle =>
'Setze Pfad zurück, erkenne neuen Pfad bei nächster Sendung.';
@override
String get chat_pathCleared =>
'Pfad zurückgesetzt. Nächste Nachricht wird Route neu entdecken.';
@override
String get chat_floodModeSubtitle =>
'Verwende den Routingschalter in der App-Leiste';
@override
String get chat_floodModeEnabled => 'Flutmodus aktiviert.';
@override
String get chat_fullPath => 'Vollständiger Pfad';
@override
String get routing_title => 'Routenplanung';
String get chat_pathDetailsNotAvailable =>
'Die Pfaddetails sind noch nicht verfügbar. Versuchen Sie, eine Nachricht zu senden, um zu aktualisieren.';
@override
String get routing_modeAuto => 'Auto';
@override
String get routing_modeFlood => 'Überschwemmung';
@override
String get routing_modeManual => 'Handbuch';
@override
String get routing_modeAutoHint =>
'Wählt automatisch den bekanntesten Pfad aus und verwendet eine Flutungsmethode, wenn kein Pfad bekannt ist.';
@override
String get routing_modeFloodHint =>
'Übertragung über alle Repeater. Die zuverlässigste Methode, jedoch mit höherem Datenverbrauch.';
@override
String get routing_modeManualHint =>
'Sendet immer genau den von Ihnen festgelegten Weg.';
@override
String get routing_currentRoute => 'Aktuelle Route';
@override
String get routing_directNoHops => 'Direkt ohne Zwischenverstärkung';
@override
String get routing_noPathYet =>
'Noch kein Pfad gefunden. Die Nachricht wird gesendet, bis ein Weg entdeckt wurde.';
@override
String get routing_floodBroadcast => 'Übertragung über jeden Repeater';
@override
String get routing_editPath => 'Pfad bearbeiten';
@override
String get routing_forgetPath => 'Vergiss den Weg';
@override
String get routing_knownPaths => 'Bekannte Routen';
@override
String get routing_knownPathsHint =>
'Wählen Sie den Pfad, um zu diesem zu wechseln.';
@override
String get routing_inUse => 'Im Gebrauch';
@override
String get routing_qualityStrong => 'Ein starker erster Sprung';
@override
String get routing_qualityGood => 'Ein guter erster Schritt';
@override
String get routing_qualityFair => 'Erster erfolgreicher Schritt';
@override
String get routing_qualityWorked => 'Hat erfolgreich geliefert';
@override
String get routing_qualityFlood =>
'Information erhalten durch Nachrichten über die Überschwemmung';
@override
String get routing_qualityUntested => 'Nicht getestet';
@override
String routing_lastWorked(String when) {
return 'war beschäftigt $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Pfad gesetzt: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nie bestätigt';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Lieferung bei Überschwemmung';
@override
String get pathEditor_title => 'Pfad erstellen';
@override
String pathEditor_hopCounter(int count) {
return '$count von 64 Hopfengewächsen';
}
@override
String get pathEditor_noHops =>
'Noch keine Hopfen hinzugefügt. Klicken Sie auf die Schaltflächen unten, um sie nacheinander hinzuzufügen, oder speichern Sie die Rezepter ohne Hopfen, um sie direkt zu versenden.';
@override
String get pathEditor_addHops =>
'Fügen Sie die Hopfen in der richtigen Reihenfolge hinzu.';
@override
String get pathEditor_searchRepeaters =>
'Suche nach wiederholten Nachrichten';
@override
String get pathEditor_advancedHex => 'Fortgeschritten: Roh-Hex-Pfad';
@override
String get pathEditor_hexLabel => 'Hex-Präfixe';
@override
String get pathEditor_hexHelper =>
'Zwei Hexadezimalzeichen pro Sprung, getrennt durch Kommas';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Ungültig: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maximal 64 Hopfengreifer';
@override
String get pathEditor_usePath => 'Verwenden Sie diesen Pfad.';
@override
String get pathEditor_removeHop => 'Hop entfernen';
@override
String get pathEditor_unknownHop => 'Unbekannter Repeater';
@override
String get chat_pathSavedLocally =>
'Lokal Gespeichert. Bitte Verbinden zum Synchronisieren.';
@@ -1741,39 +1648,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get map_title => 'Karte';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Sichtlinie';
@@ -2103,6 +1977,17 @@ class AppLocalizationsDe extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?';
@override
String get dialog_disconnectedTitle => 'Getrennt';
@override
String get dialog_disconnectedMessage =>
'Du wurdest von deinem Begleiter getrennt.';
@override
String get dialog_connectCompanion =>
'Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.';
@override
String get login_repeaterLogin => 'Beim Repeater anmelden';
@@ -2169,13 +2054,65 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get common_clear => 'Löschen';
@override
String path_currentPath(String path) {
return 'Aktiver Pfad: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Hops',
one: 'Hop',
);
return 'Verwenden Sie $count $_temp0 Pfad';
}
@override
String get path_enterCustomPath => 'Gebe Pfad ein';
@override
String get path_currentPathLabel => 'Aktueller Pfad';
@override
String get path_hexPrefixInstructions =>
'Gebe für jeden Zwischen-Hop das 2-stellige Hex-Präfix ein, getrennt durch Kommas.';
@override
String get path_hexPrefixExample =>
'Beispiel: A1,F2,3C (jeder Knoten verwendet den ersten Byte seines öffentlichen Schlüssels)';
@override
String get path_labelHexPrefixes => 'Pfad (Hex-Präfixe)';
@override
String get path_helperMaxHops =>
'Max 64 Sprünge. Jede Präfixe ist 2 Hexadezimalzeichen (1 Byte)';
@override
String get path_selectFromContacts => 'Oder wähle aus Kontakten aus:';
@override
String get path_noRepeatersFound =>
'Keine Repeater oder Raumserver gefunden.';
@override
String get path_customPathsRequire =>
'Benutzerdefinierte Pfade erfordern Zwischen-Hops, die Nachrichten weiterleiten können.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Ungültige Hexadezimal-Präfixe: $prefixes';
}
@override
String get path_tooLong => 'Pfad zu lang. Maximal 64 Hops erlaubt.';
@override
String get path_setPath => 'Pfad festlegen';
@override
String get repeater_management => 'Repeater-Verwaltung';
@@ -2240,6 +2177,16 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_routingMode => 'Routenmodus';
@override
String get repeater_autoUseSavedPath =>
'Automatisch (gespeicherten Pfad verwenden)';
@override
String get repeater_forceFloodMode => 'Flut-Modus erzwingen';
@override
String get repeater_pathManagement => 'Pfadverwaltung';
@override
String get repeater_refresh => 'Aktualisieren';
@@ -3346,139 +3293,6 @@ class AppLocalizationsDe extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Digitaleingang';
@override
String get telemetry_digitalOutputLabel => 'Digitalausgang';
@override
String get telemetry_analogInputLabel => 'Analogeingang';
@override
String get telemetry_analogOutputLabel => 'Analogausgang';
@override
String get telemetry_genericLabel => 'Allgemeiner Sensor';
@override
String get telemetry_luminosityLabel => 'Helligkeit';
@override
String get telemetry_presenceLabel => 'Anwesenheit';
@override
String get telemetry_humidityLabel => 'Luftfeuchtigkeit';
@override
String get telemetry_accelerometerLabel => 'Beschleunigungsmesser';
@override
String get telemetry_pressureLabel => 'Druck';
@override
String get telemetry_altitudeLabel => 'Höhe';
@override
String get telemetry_frequencyLabel => 'Frequenz';
@override
String get telemetry_percentageLabel => 'Prozentsatz';
@override
String get telemetry_concentrationLabel => 'Konzentration';
@override
String get telemetry_powerLabel => 'Leistung';
@override
String get telemetry_distanceLabel => 'Entfernung';
@override
String get telemetry_energyLabel => 'Energie';
@override
String get telemetry_directionLabel => 'Richtung';
@override
String get telemetry_timeLabel => 'Zeit';
@override
String get telemetry_gyrometerLabel => 'Gyroskop';
@override
String get telemetry_colourLabel => 'Farbe';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Schalter';
@override
String get telemetry_polylineLabel => 'Polylinie';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Anzahl der Anfragen';
@override
String get telemetry_error => 'Daten konnten nicht abgerufen werden';
@override
String get neighbors_receivedData => 'Empfangene Nachbarsdaten';
@@ -4373,17 +4187,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get translation_composerSubtitle =>
'Steuert den Standardzustand des Icons für die Übersetzung des Komponisten.';
@override
String get translation_autoIncomingTitle =>
'Nachrichten automatisch übersetzen';
@override
String get translation_autoIncomingSubtitle =>
'Übersetzt Nachrichten für Benachrichtigungen sowie für Chats oder Kanäle automatisch.';
@override
String get translation_translateMessage => 'Nachricht übersetzen';
@override
String get translation_targetLanguage => 'Zielsprache';
@@ -4513,135 +4316,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Zoomen';
@override
String get map_zoomOut => 'Auszoomen';
@override
String get map_centerMap => 'Zentralkarte';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth benötigt einen Chromium-Browser.';
@override
String channels_communityShortId(String id) {
return 'ID: $id';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS-Bestätigung';
@override
String get pathTrace_legendInferred => 'Abgeleitete Position';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+143 -471
View File
@@ -92,24 +92,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get common_disable => 'Disable';
@override
String get common_undo => 'Undo';
@override
String get messageStatus_sent => 'Sent';
@override
String get messageStatus_delivered => 'Delivered';
@override
String get messageStatus_pending => 'Sending';
@override
String get messageStatus_failed => 'Failed to send';
@override
String get messageStatus_repeated => 'Heard repeated';
@override
String get common_reboot => 'Reboot';
@@ -129,12 +111,6 @@ class AppLocalizationsEn extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Autorefresh';
@override
String get common_interval => 'Interval';
@override
String get scanner_title => 'MeshCore Open';
@@ -315,10 +291,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Enable Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Quick switch';
@@ -799,6 +771,11 @@ class AppLocalizationsEn extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Number of retry attempts before marking a message as failed';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Battery';
@@ -998,15 +975,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contacts_newGroup => 'New Group';
@override
String get contacts_moreOptions => 'More options';
@override
String get contacts_searchOpen => 'Search contacts';
@override
String get contacts_searchClose => 'Close search';
@override
String get contacts_groupName => 'Group name';
@@ -1478,6 +1446,34 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Hex Dump:';
@override
String get chat_pathManagement => 'Path Management';
@override
String get chat_ShowAllPaths => 'Show all paths';
@override
String get chat_routingMode => 'Routing mode';
@override
String get chat_autoUseSavedPath => 'Auto (use saved path)';
@override
String get chat_forceFloodMode => 'Force Flood Mode';
@override
String get chat_recentAckPaths => 'Recent ACK Paths (tap to use):';
@override
String get chat_pathHistoryFull =>
'Path history is full. Remove entries to add new ones.';
@override
String get chat_hopSingular => 'hop';
@override
String get chat_hopPlural => 'hops';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1489,6 +1485,12 @@ class AppLocalizationsEn extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'successes';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Remove path';
@@ -1496,144 +1498,50 @@ class AppLocalizationsEn extends AppLocalizations {
String get chat_noPathHistoryYet =>
'No path history yet.\nSend a message to discover paths.';
@override
String get chat_pathActions => 'Path Actions:';
@override
String get chat_setCustomPath => 'Set Custom Path';
@override
String get chat_setCustomPathSubtitle => 'Manually specify routing path';
@override
String get chat_clearPath => 'Clear Path';
@override
String get chat_clearPathSubtitle => 'Force rediscovery on next send';
@override
String get chat_pathCleared =>
'Path cleared. Next message will rediscover route.';
@override
String get chat_floodModeSubtitle => 'Use routing toggle in app bar';
@override
String get chat_floodModeEnabled =>
'Flood mode enabled. Toggle back via routing icon in app bar.';
@override
String get chat_fullPath => 'Full Path';
@override
String get routing_title => 'Routing';
String get chat_pathDetailsNotAvailable =>
'Path details not available yet. Try sending a message to refresh.';
@override
String get routing_modeAuto => 'Auto';
@override
String get routing_modeFlood => 'Flood';
@override
String get routing_modeManual => 'Manual';
@override
String get routing_modeAutoHint =>
'Picks the best known path automatically, flooding when none is known.';
@override
String get routing_modeFloodHint =>
'Broadcasts through every repeater. Most reliable, but uses more airtime.';
@override
String get routing_modeManualHint =>
'Always sends along the exact path you set.';
@override
String get routing_currentRoute => 'Current route';
@override
String get routing_directNoHops => 'Direct — no repeater hops';
@override
String get routing_noPathYet =>
'No path yet. The next message floods until a route is discovered.';
@override
String get routing_floodBroadcast => 'Broadcast through every repeater';
@override
String get routing_editPath => 'Edit path';
@override
String get routing_forgetPath => 'Forget path';
@override
String get routing_knownPaths => 'Known paths';
@override
String get routing_knownPathsHint => 'Tap a path to switch to it.';
@override
String get routing_inUse => 'In use';
@override
String get routing_qualityStrong => 'Strong first hop';
@override
String get routing_qualityGood => 'Good first hop';
@override
String get routing_qualityFair => 'Fair first hop';
@override
String get routing_qualityWorked => 'Has delivered';
@override
String get routing_qualityFlood => 'Heard via flood';
@override
String get routing_qualityUntested => 'Untested';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Path set: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'never confirmed';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Flood delivery';
@override
String get pathEditor_title => 'Build Path';
@override
String pathEditor_hopCounter(int count) {
return '$count of 64 hops';
}
@override
String get pathEditor_noHops =>
'No hops yet. Tap repeaters below to add them in order, or save with no hops to send direct.';
@override
String get pathEditor_addHops => 'Add hops in order';
@override
String get pathEditor_searchRepeaters => 'Search repeaters';
@override
String get pathEditor_advancedHex => 'Advanced: raw hex path';
@override
String get pathEditor_hexLabel => 'Hex prefixes';
@override
String get pathEditor_hexHelper =>
'Two hex characters per hop, separated by commas';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Invalid: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maximum 64 hops';
@override
String get pathEditor_usePath => 'Use this path';
@override
String get pathEditor_removeHop => 'Remove hop';
@override
String get pathEditor_unknownHop => 'Unknown repeater';
@override
String get chat_pathSavedLocally => 'Saved locally. Connect to sync.';
@@ -1707,39 +1615,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get map_title => 'Node Map';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Line of Sight';
@@ -2063,6 +1938,17 @@ class AppLocalizationsEn extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Are you sure you want to disconnect from this device?';
@override
String get dialog_disconnectedTitle => 'Disconnected';
@override
String get dialog_disconnectedMessage =>
'You have been disconnected from your companion.';
@override
String get dialog_connectCompanion =>
'Connect to a companion to access repeater and room server features.';
@override
String get login_repeaterLogin => 'Repeater Login';
@@ -2128,12 +2014,64 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get common_clear => 'Clear';
@override
String path_currentPath(String path) {
return 'Current path: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Using $count $_temp0 path';
}
@override
String get path_enterCustomPath => 'Enter Custom Path';
@override
String get path_currentPathLabel => 'Current path';
@override
String get path_hexPrefixInstructions =>
'Enter 2-character hex prefixes for each hop, separated by commas.';
@override
String get path_hexPrefixExample =>
'Example: A1,F2,3C (each node uses first byte of its public key)';
@override
String get path_labelHexPrefixes => 'Path (hex prefixes)';
@override
String get path_helperMaxHops =>
'Max 64 hops. Each prefix is 2 hex characters (1 byte)';
@override
String get path_selectFromContacts => 'Or select from contacts:';
@override
String get path_noRepeatersFound => 'No repeaters or room servers found.';
@override
String get path_customPathsRequire =>
'Custom paths require intermediate hops that can relay messages.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Invalid hex prefixes: $prefixes';
}
@override
String get path_tooLong => 'Path too long. Maximum 64 hops allowed.';
@override
String get path_setPath => 'Set Path';
@override
String get repeater_management => 'Repeater Management';
@@ -2197,6 +2135,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get repeater_routingMode => 'Routing mode';
@override
String get repeater_autoUseSavedPath => 'Auto (use saved path)';
@override
String get repeater_forceFloodMode => 'Force Flood Mode';
@override
String get repeater_pathManagement => 'Path management';
@override
String get repeater_refresh => 'Refresh';
@@ -3277,139 +3224,6 @@ class AppLocalizationsEn extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Digital Input';
@override
String get telemetry_digitalOutputLabel => 'Digital Output';
@override
String get telemetry_analogInputLabel => 'Analog Input';
@override
String get telemetry_analogOutputLabel => 'Analog Output';
@override
String get telemetry_genericLabel => 'Generic Sensor';
@override
String get telemetry_luminosityLabel => 'Luminosity';
@override
String get telemetry_presenceLabel => 'Presence';
@override
String get telemetry_humidityLabel => 'Humidity';
@override
String get telemetry_accelerometerLabel => 'Accelerometer';
@override
String get telemetry_pressureLabel => 'Pressure';
@override
String get telemetry_altitudeLabel => 'Altitude';
@override
String get telemetry_frequencyLabel => 'Frequency';
@override
String get telemetry_percentageLabel => 'Percentage';
@override
String get telemetry_concentrationLabel => 'Concentration';
@override
String get telemetry_powerLabel => 'Power';
@override
String get telemetry_distanceLabel => 'Distance';
@override
String get telemetry_energyLabel => 'Energy';
@override
String get telemetry_directionLabel => 'Direction';
@override
String get telemetry_timeLabel => 'Time';
@override
String get telemetry_gyrometerLabel => 'Gyrometer';
@override
String get telemetry_colourLabel => 'Colour';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Switch';
@override
String get telemetry_polylineLabel => 'Polyline';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Requests quantity';
@override
String get telemetry_error => 'Unable to retrieve data';
@override
String get neighbors_receivedData => 'Received Neighbors Data';
@@ -4282,17 +4096,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get translation_composerSubtitle =>
'Controls the default state of the composer translation icon.';
@override
String get translation_autoIncomingTitle =>
'Auto-translate incoming messages';
@override
String get translation_autoIncomingSubtitle =>
'Translates Messages for notification and for chat or channel automatically.';
@override
String get translation_translateMessage => 'Translate message';
@override
String get translation_targetLanguage => 'Target language';
@@ -4418,135 +4221,4 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Zoom in';
@override
String get map_zoomOut => 'Zoom out';
@override
String get map_centerMap => 'Center map';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth requires a Chromium browser';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS confirmed';
@override
String get pathTrace_legendInferred => 'Inferred position';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+147 -474
View File
@@ -92,24 +92,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get common_disable => 'Desactivar';
@override
String get common_undo => 'Deshacer';
@override
String get messageStatus_sent => 'Sentido';
@override
String get messageStatus_delivered => 'Entregado';
@override
String get messageStatus_pending => 'Enviar';
@override
String get messageStatus_failed => 'No se pudo enviar';
@override
String get messageStatus_repeated => 'Escuché repetidamente';
@override
String get common_reboot => 'Reiniciar';
@@ -129,12 +111,6 @@ class AppLocalizationsEs extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Actualización automática';
@override
String get common_interval => 'Intervalo';
@override
String get scanner_title => 'MeshCore: Versión abierta';
@@ -319,10 +295,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Habilitar Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Cambiar rápidamente';
@@ -813,6 +785,11 @@ class AppLocalizationsEs extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Número de intentos de reintento antes de marcar un mensaje como fallido.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Batería';
@@ -1014,15 +991,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get contacts_newGroup => 'Nuevo Grupo';
@override
String get contacts_moreOptions => 'Más opciones';
@override
String get contacts_searchOpen => 'Buscar contactos';
@override
String get contacts_searchClose => 'Búsqueda avanzada';
@override
String get contacts_groupName => 'Nombre del grupo';
@@ -1505,6 +1473,34 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Mapeo Hexadecimal:';
@override
String get chat_pathManagement => 'Gestión de Rutas';
@override
String get chat_ShowAllPaths => 'Mostrar todos los caminos';
@override
String get chat_routingMode => 'Modo de enrutamiento';
@override
String get chat_autoUseSavedPath => 'Auto (usar la ruta guardada)';
@override
String get chat_forceFloodMode => 'Modo Inundación Forzado';
@override
String get chat_recentAckPaths => 'Rutas de ACK Recientes (tocar para usar):';
@override
String get chat_pathHistoryFull =>
'El historial de rutas está completo. Eliminar entradas para añadir nuevas.';
@override
String get chat_hopSingular => 'salta';
@override
String get chat_hopPlural => 'salta';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1516,6 +1512,12 @@ class AppLocalizationsEs extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Éxitos';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Eliminar ruta';
@@ -1523,147 +1525,53 @@ class AppLocalizationsEs extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Aún no hay historial de rutas.\nEnvía un mensaje para descubrir rutas.';
@override
String get chat_pathActions => 'Acciones de Ruta:';
@override
String get chat_setCustomPath => 'Establecer Ruta Personalizada';
@override
String get chat_setCustomPathSubtitle =>
'Especificar manualmente la ruta de enrutamiento';
@override
String get chat_clearPath => 'Limpiar Ruta';
@override
String get chat_clearPathSubtitle =>
'Forzar redescubrimiento en el próximo envío';
@override
String get chat_pathCleared =>
'Ruta eliminada. El siguiente mensaje redescubrirá la ruta.';
@override
String get chat_floodModeSubtitle =>
'Utilizar el interruptor de enrutamiento en la barra de herramientas';
@override
String get chat_floodModeEnabled =>
'El modo de inundación está habilitado. Desactívalo mediante el icono de enrutamiento en la barra de herramientas de la aplicación.';
@override
String get chat_fullPath => 'Ruta completa';
@override
String get routing_title => 'Ruteo';
String get chat_pathDetailsNotAvailable =>
'Los detalles de la ruta aún no están disponibles. Intenta enviar un mensaje para refrescar.';
@override
String get routing_modeAuto => 'Coche';
@override
String get routing_modeFlood => 'Inundación';
@override
String get routing_modeManual => 'Manual';
@override
String get routing_modeAutoHint =>
'Selecciona automáticamente la ruta más conocida, y si no hay ninguna ruta conocida, utiliza la ruta más directa.';
@override
String get routing_modeFloodHint =>
'Transmisiones a través de todos los repetidores. Es la opción más fiable, pero utiliza más tiempo de transmisión.';
@override
String get routing_modeManualHint =>
'Siempre sigue exactamente la ruta que usted ha definido.';
@override
String get routing_currentRoute => 'Ruta actual';
@override
String get routing_directNoHops => 'Directo — sin saltos de repetidor';
@override
String get routing_noPathYet =>
'Aún no hay un camino definido. El mensaje se envía continuamente hasta que se encuentre una ruta.';
@override
String get routing_floodBroadcast =>
'Transmisión a través de todos los repetidores.';
@override
String get routing_editPath => 'Editar ruta';
@override
String get routing_forgetPath => 'Olvídate del camino';
@override
String get routing_knownPaths => 'Rutas conocidas';
@override
String get routing_knownPathsHint =>
'Seleccione una opción para cambiar a esa.';
@override
String get routing_inUse => 'En uso';
@override
String get routing_qualityStrong => 'Primer salto exitoso';
@override
String get routing_qualityGood => 'Primer paso exitoso';
@override
String get routing_qualityFair => 'Primer salto de calidad';
@override
String get routing_qualityWorked => 'Ha cumplido';
@override
String get routing_qualityFlood => 'Se ha escuchado a través de rumores.';
@override
String get routing_qualityUntested => 'Sin probar';
@override
String routing_lastWorked(String when) {
return 'trabajó $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Ruta establecida: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nunca confirmado';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Entrega por inundación';
@override
String get pathEditor_title => 'Crear ruta';
@override
String pathEditor_hopCounter(int count) {
return '$count de 64 granos de lúpulo';
}
@override
String get pathEditor_noHops =>
'Aún no se han añadido lúpulos. Haga clic en los repetidores para añadirlos en el orden deseado, o guarde la receta sin lúpulos para enviarla directamente.';
@override
String get pathEditor_addHops => 'Añadir los lúpulos en el orden adecuado.';
@override
String get pathEditor_searchRepeaters => 'Buscar repetidores';
@override
String get pathEditor_advancedHex =>
'Avanzado: ruta hexadecimal sin procesar';
@override
String get pathEditor_hexLabel => 'Prefijos hexadecimales';
@override
String get pathEditor_hexHelper =>
'Dos caracteres hexadecimales por salto, separados por comas.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Inválido: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Máximo 64 saltos';
@override
String get pathEditor_usePath => 'Utilice esta ruta.';
@override
String get pathEditor_removeHop => 'Eliminar el lúpulo';
@override
String get pathEditor_unknownHop => 'Repetidor desconocido';
@override
String get chat_pathSavedLocally =>
'Guardado localmente. Conéctate para sincronizar.';
@@ -1738,39 +1646,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get map_title => 'Mapa de Nodos';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Línea de visión';
@@ -2099,6 +1974,17 @@ class AppLocalizationsEs extends AppLocalizations {
String get dialog_disconnectConfirm =>
'¿Está seguro de que desea desconectarse de este dispositivo?';
@override
String get dialog_disconnectedTitle => 'Desconectado';
@override
String get dialog_disconnectedMessage =>
'Te has desconectado de tu compañero.';
@override
String get dialog_connectCompanion =>
'Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.';
@override
String get login_repeaterLogin => 'Iniciar sesión en el Repetidor';
@@ -2164,13 +2050,66 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get common_clear => 'Borrar';
@override
String path_currentPath(String path) {
return 'Ruta actual: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Usando $count $_temp0 ruta';
}
@override
String get path_enterCustomPath => 'Introducir Ruta Personalizada';
@override
String get path_currentPathLabel => 'Ruta actual';
@override
String get path_hexPrefixInstructions =>
'Introduzca los prefijos hexadecimales de 2 caracteres para cada salto, separados por comas.';
@override
String get path_hexPrefixExample =>
'Ejemplo: A1,F2,3C (cada nodo utiliza el primer byte de su clave pública).';
@override
String get path_labelHexPrefixes => 'Prefijos hexadecimales';
@override
String get path_helperMaxHops =>
'Máximo 64 saltos. Cada prefijo tiene 2 caracteres hexadecimales (1 byte).';
@override
String get path_selectFromContacts => 'O seleccionar de contactos:';
@override
String get path_noRepeatersFound =>
'No se encontraron repetidores ni servidores de sala.';
@override
String get path_customPathsRequire =>
'Las rutas personalizadas requieren saltos intermedios que pueden transmitir mensajes.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Prefijos hexadecimales inválidos: $prefixes';
}
@override
String get path_tooLong =>
'La ruta es demasiado larga. Se permiten un máximo de 64 saltos.';
@override
String get path_setPath => 'Establecer Ruta';
@override
String get repeater_management => 'Gestión de Repetidores';
@@ -2235,6 +2174,15 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get repeater_routingMode => 'Modo de enrutamiento';
@override
String get repeater_autoUseSavedPath => 'Auto (usar la ruta guardada)';
@override
String get repeater_forceFloodMode => 'Modo Inundación Forzado';
@override
String get repeater_pathManagement => 'Gestión de rutas';
@override
String get repeater_refresh => 'Actualizar';
@@ -3333,139 +3281,6 @@ class AppLocalizationsEs extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Entrada digital';
@override
String get telemetry_digitalOutputLabel => 'Salida digital';
@override
String get telemetry_analogInputLabel => 'Entrada analógica';
@override
String get telemetry_analogOutputLabel => 'Salida analógica';
@override
String get telemetry_genericLabel => 'Sensor genérico';
@override
String get telemetry_luminosityLabel => 'Luminosidad';
@override
String get telemetry_presenceLabel => 'Presencia';
@override
String get telemetry_humidityLabel => 'Humedad';
@override
String get telemetry_accelerometerLabel => 'Acelerómetro';
@override
String get telemetry_pressureLabel => 'Presión';
@override
String get telemetry_altitudeLabel => 'Altitud';
@override
String get telemetry_frequencyLabel => 'Frecuencia';
@override
String get telemetry_percentageLabel => 'Porcentaje';
@override
String get telemetry_concentrationLabel => 'Concentración';
@override
String get telemetry_powerLabel => 'Potencia';
@override
String get telemetry_distanceLabel => 'Distancia';
@override
String get telemetry_energyLabel => 'Energía';
@override
String get telemetry_directionLabel => 'Dirección';
@override
String get telemetry_timeLabel => 'Hora';
@override
String get telemetry_gyrometerLabel => 'Girómetro';
@override
String get telemetry_colourLabel => 'Color';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Interruptor';
@override
String get telemetry_polylineLabel => 'Polilínea';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Número de solicitudes';
@override
String get telemetry_error => 'No se pudieron obtener los datos';
@override
String get neighbors_receivedData => 'Recibidas Datos de Vecinos';
@@ -4359,17 +4174,6 @@ class AppLocalizationsEs extends AppLocalizations {
String get translation_composerSubtitle =>
'Controla el estado predeterminado del icono de traducción del compositor.';
@override
String get translation_autoIncomingTitle =>
'Traducir mensajes automáticamente';
@override
String get translation_autoIncomingSubtitle =>
'Traduce mensajes para notificaciones y para chats o canales automáticamente.';
@override
String get translation_translateMessage => 'Traducir mensaje';
@override
String get translation_targetLanguage => 'Idioma de destino';
@@ -4499,135 +4303,4 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Acercar';
@override
String get map_zoomOut => 'Acercar';
@override
String get map_centerMap => 'Mapa del centro';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth requiere un navegador Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'Confirmado mediante GPS';
@override
String get pathTrace_legendInferred => 'Posición inferida';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+149 -473
View File
@@ -92,24 +92,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get common_disable => 'Désactiver';
@override
String get common_undo => 'Annuler';
@override
String get messageStatus_sent => 'Envoyer';
@override
String get messageStatus_delivered => 'Livré';
@override
String get messageStatus_pending => 'Envoyer';
@override
String get messageStatus_failed => 'Échec de l\'envoi';
@override
String get messageStatus_repeated => 'Répété plusieurs fois';
@override
String get common_reboot => 'Redémarrer';
@@ -129,12 +111,6 @@ class AppLocalizationsFr extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Actualisation automatique';
@override
String get common_interval => 'Intervalle';
@override
String get scanner_title => 'MeshCore Open';
@@ -321,10 +297,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Activer le Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Basculement rapide';
@@ -819,6 +791,11 @@ class AppLocalizationsFr extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Nombre de tentatives de relance avant de marquer un message comme ayant échoué.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Batterie';
@@ -1020,15 +997,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get contacts_newGroup => 'Nouveau Groupe';
@override
String get contacts_moreOptions => 'Plus d\'options';
@override
String get contacts_searchOpen => 'Rechercher des contacts';
@override
String get contacts_searchClose => 'Recherche avancée';
@override
String get contacts_groupName => 'Nom du groupe';
@@ -1511,6 +1479,35 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Vidéo de Dump Hexadécimal :';
@override
String get chat_pathManagement => 'Gestion des chemins';
@override
String get chat_ShowAllPaths => 'Afficher tous les chemins';
@override
String get chat_routingMode => 'Mode de routage';
@override
String get chat_autoUseSavedPath => 'Auto (utiliser le chemin sauvegardé)';
@override
String get chat_forceFloodMode => 'Mode tout le réseau forcé';
@override
String get chat_recentAckPaths =>
'Chemins ACK récents (touchez pour utiliser) :';
@override
String get chat_pathHistoryFull =>
'L\'historique du chemin est plein. Supprimez les entrées pour en ajouter de nouvelles.';
@override
String get chat_hopSingular => 'saut';
@override
String get chat_hopPlural => 'sauts';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1522,6 +1519,12 @@ class AppLocalizationsFr extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Succès';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Supprimer le chemin';
@@ -1529,146 +1532,53 @@ class AppLocalizationsFr extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Aucune historique de parcours disponible.\nEnvoyez un message pour découvrir les parcours.';
@override
String get chat_pathActions => 'Actions du chemin :';
@override
String get chat_setCustomPath => 'Définir un chemin personnalisé';
@override
String get chat_setCustomPathSubtitle =>
'Spécifier manuellement le chemin de routage';
@override
String get chat_clearPath => 'Effacer le chemin';
@override
String get chat_clearPathSubtitle =>
'Forcer la redécouverte lors de la prochaine envoi';
@override
String get chat_pathCleared =>
'Le chemin est dégagé. Le prochain message redécouvrira le tracé.';
@override
String get chat_floodModeSubtitle =>
'Désactive l\'apprentissage du chemin (à éviter). Utiliser le commutateur de routage dans la barre d\'application pour rebasculer en mode auto par la suite.';
@override
String get chat_floodModeEnabled =>
'Le mode envoi à tout le réseau est activé. Changer via l\'icône de routage dans la barre d\'outils.';
@override
String get chat_fullPath => 'Chemin complet';
@override
String get routing_title => 'Planification des itinéraires';
String get chat_pathDetailsNotAvailable =>
'Les détails du chemin ne sont pas encore disponibles. Essayez d\'envoyer un message pour rafraîchir.';
@override
String get routing_modeAuto => 'Voiture';
@override
String get routing_modeFlood => 'Inondation';
@override
String get routing_modeManual => 'Manuel';
@override
String get routing_modeAutoHint =>
'Sélectionne automatiquement le chemin le plus connu, et utilise la méthode de \"inondation\" si aucun chemin n\'est connu.';
@override
String get routing_modeFloodHint =>
'Diffusion via tous les répéteurs. La méthode la plus fiable, mais qui utilise plus de temps d\'antenne.';
@override
String get routing_modeManualHint =>
'Il suit toujours le chemin précis que vous avez défini.';
@override
String get routing_currentRoute => 'Itinéraire actuel';
@override
String get routing_directNoHops => 'Direct — sans relais';
@override
String get routing_noPathYet =>
'Aucune voie encore trouvée. Le message suivant est envoyé jusqu\'à ce qu\'une route soit découverte.';
@override
String get routing_floodBroadcast => 'Diffusion via tous les répéteurs';
@override
String get routing_editPath => 'Modifier le chemin';
@override
String get routing_forgetPath => 'Oubliez le chemin';
@override
String get routing_knownPaths => 'Chemins connus';
@override
String get routing_knownPathsHint => 'Créez un raccourci pour y accéder.';
@override
String get routing_inUse => 'En cours d\'utilisation';
@override
String get routing_qualityStrong => 'Première étape réussie';
@override
String get routing_qualityGood => 'Première étape réussie';
@override
String get routing_qualityFair => 'Première étape réussie';
@override
String get routing_qualityWorked => 'A livré';
@override
String get routing_qualityFlood =>
'Rapporté par des informations provenant de plusieurs sources.';
@override
String get routing_qualityUntested => 'Non testé';
@override
String routing_lastWorked(String when) {
return 'a travaillé $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Chemin défini : $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'jamais confirmé';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Livraison en cas de inondation';
@override
String get pathEditor_title => 'Créer un chemin';
@override
String pathEditor_hopCounter(int count) {
return '$count parmi 64 houblons';
}
@override
String get pathEditor_noHops =>
'Aucun houblon ajouté pour le moment. Cliquez sur les répétiteurs ci-dessous pour les ajouter dans l\'ordre souhaité, ou enregistrez sans houblon pour envoyer directement.';
@override
String get pathEditor_addHops =>
'Ajoutez les houblons dans l\'ordre souhaité.';
@override
String get pathEditor_searchRepeaters => 'Rechercher des répétiteurs';
@override
String get pathEditor_advancedHex => 'Avancé : chemin hexadécimal brut';
@override
String get pathEditor_hexLabel => 'Préfixes hexadécimaux';
@override
String get pathEditor_hexHelper =>
'Deux caractères hexadécimaux par saut, séparés par des virgules.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Incorrect : $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maximum 64 sauts';
@override
String get pathEditor_usePath => 'Utilisez ce chemin.';
@override
String get pathEditor_removeHop => 'Éliminer le haricot';
@override
String get pathEditor_unknownHop => 'Répéteur non identifié';
@override
String get chat_pathSavedLocally =>
'Sauvegardé localement. Connectez-vous pour synchroniser.';
@@ -1745,39 +1655,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get map_title => 'Carte des nœuds';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Ligne de vue';
@@ -2108,6 +1985,17 @@ class AppLocalizationsFr extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?';
@override
String get dialog_disconnectedTitle => 'Déconnecté';
@override
String get dialog_disconnectedMessage =>
'Vous avez été déconnecté de votre compagnon.';
@override
String get dialog_connectCompanion =>
'Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.';
@override
String get login_repeaterLogin => 'Connexion au répéteur';
@@ -2173,13 +2061,66 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get common_clear => 'Effacer';
@override
String path_currentPath(String path) {
return 'Chemin actuel : $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Utiliser $count $_temp0 chemin';
}
@override
String get path_enterCustomPath => 'Entrer un chemin personnalisé';
@override
String get path_currentPathLabel => 'Chemin actuel';
@override
String get path_hexPrefixInstructions =>
'Entrez les préfixes hexadécimaux de 2 caractères pour chaque saut, séparés par des virgules.';
@override
String get path_hexPrefixExample =>
'Exemple : A1,F2,3C (chaque nœud utilise le premier octet de sa clé publique).';
@override
String get path_labelHexPrefixes => 'Préfixes hexadécimaux';
@override
String get path_helperMaxHops =>
'Max 64 sauts. Chaque préfixe fait 2 caractères hexadécimaux (1 octet)';
@override
String get path_selectFromContacts => 'Sélectionner à partir des contacts :';
@override
String get path_noRepeatersFound =>
'Aucun répéteur ou room server n\'a été trouvé.';
@override
String get path_customPathsRequire =>
'Les chemins personnalisés nécessitent des sauts intermédiaires qui peuvent transmettre des messages.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Préfixes hexadécimaux invalides : $prefixes';
}
@override
String get path_tooLong =>
'Le chemin est trop long. Maximum 64 sauts autorisés.';
@override
String get path_setPath => 'Définir le chemin';
@override
String get repeater_management => 'Gestion des répéteurs';
@@ -2245,6 +2186,16 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_routingMode => 'Mode de routage';
@override
String get repeater_autoUseSavedPath =>
'Auto (utiliser le chemin sauvegardé)';
@override
String get repeater_forceFloodMode => 'Mode tout le réseau forcé';
@override
String get repeater_pathManagement => 'Gestion des chemins';
@override
String get repeater_refresh => 'Rafraîchir';
@@ -3353,139 +3304,6 @@ class AppLocalizationsFr extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Entrée numérique';
@override
String get telemetry_digitalOutputLabel => 'Sortie numérique';
@override
String get telemetry_analogInputLabel => 'Entrée analogique';
@override
String get telemetry_analogOutputLabel => 'Sortie analogique';
@override
String get telemetry_genericLabel => 'Capteur générique';
@override
String get telemetry_luminosityLabel => 'Luminosité';
@override
String get telemetry_presenceLabel => 'Présence';
@override
String get telemetry_humidityLabel => 'Humidité';
@override
String get telemetry_accelerometerLabel => 'Accéléromètre';
@override
String get telemetry_pressureLabel => 'Pression';
@override
String get telemetry_altitudeLabel => 'Altitude';
@override
String get telemetry_frequencyLabel => 'Fréquence';
@override
String get telemetry_percentageLabel => 'Pourcentage';
@override
String get telemetry_concentrationLabel => 'Concentration';
@override
String get telemetry_powerLabel => 'Puissance';
@override
String get telemetry_distanceLabel => 'Distance';
@override
String get telemetry_energyLabel => 'Énergie';
@override
String get telemetry_directionLabel => 'Direction';
@override
String get telemetry_timeLabel => 'Heure';
@override
String get telemetry_gyrometerLabel => 'Gyromètre';
@override
String get telemetry_colourLabel => 'Couleur';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Interrupteur';
@override
String get telemetry_polylineLabel => 'Polyligne';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Nombre de requêtes';
@override
String get telemetry_error => 'Impossible de récupérer les données';
@override
String get neighbors_receivedData => 'Données des voisins reçues';
@@ -4386,17 +4204,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get translation_composerSubtitle =>
'Contrôle l\'état par défaut de l\'icône de traduction du composant.';
@override
String get translation_autoIncomingTitle =>
'Traduire automatiquement les messages';
@override
String get translation_autoIncomingSubtitle =>
'Traduit automatiquement les messages pour les notifications et pour les discussions ou les canaux.';
@override
String get translation_translateMessage => 'Traduire le message';
@override
String get translation_targetLanguage => 'Langue cible';
@@ -4525,135 +4332,4 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Zoomez';
@override
String get map_zoomOut => 'Zoomez';
@override
String get map_centerMap => 'Carte du centre';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth nécessite un navigateur Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID : $id';
}
@override
String get pathTrace_legendGpsConfirmed => 'Le GPS a confirmé.';
@override
String get pathTrace_legendInferred => 'Position déduite';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+149 -474
View File
@@ -92,24 +92,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get common_disable => 'Leteteszt';
@override
String get common_undo => 'Még egyszer';
@override
String get messageStatus_sent => 'Elküldve';
@override
String get messageStatus_delivered => 'Szállítva';
@override
String get messageStatus_pending => 'Elküldés';
@override
String get messageStatus_failed => 'Nem sikerült elküldeni';
@override
String get messageStatus_repeated => 'Ismételtem';
@override
String get common_reboot => 'Újraindítás';
@@ -129,12 +111,6 @@ class AppLocalizationsHu extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Automatikus frissítés';
@override
String get common_interval => 'Intervallum';
@override
String get scanner_title => 'MeshCore nyitott';
@@ -318,10 +294,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Engedje be a Bluetooth funkciót';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Gyors váltás';
@@ -817,6 +789,11 @@ class AppLocalizationsHu extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'A próbálkozások száma, mielőtt egy üzenetet hibásnak jelölünk.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Akku';
@@ -1020,15 +997,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get contacts_newGroup => 'Új csoport';
@override
String get contacts_moreOptions => 'További lehetőségek';
@override
String get contacts_searchOpen => 'Keresssz kapcsolatokat';
@override
String get contacts_searchClose => 'Teljesítse a keresést';
@override
String get contacts_groupName => 'Csoport neve';
@@ -1514,6 +1482,36 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Hex-dump:';
@override
String get chat_pathManagement => 'Útvonal-kezelés';
@override
String get chat_ShowAllPaths => 'Mutasson meg minden útvonalat';
@override
String get chat_routingMode => 'Útvonal-kezelési mód';
@override
String get chat_autoUseSavedPath =>
'Automatikus (az eddigi útvonal használata)';
@override
String get chat_forceFloodMode => 'Erőforrás-alapú áramlás mód';
@override
String get chat_recentAckPaths =>
'Legutóbbi használt útvonalak (gombra kattintva):';
@override
String get chat_pathHistoryFull =>
'Az előző lépések listája teljes. Törölj ki a bejegyzéseket, hogy újokat hozzáadhatsd.';
@override
String get chat_hopSingular => 'ugor';
@override
String get chat_hopPlural => 'babér';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1525,6 +1523,12 @@ class AppLocalizationsHu extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'sikerek';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Törölje a elérési útvonalat';
@@ -1532,148 +1536,52 @@ class AppLocalizationsHu extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Még nincs útvonal-történet.\nKüldjön egy üzenetet, hogy megtudja a lehetséges útvonalakat.';
@override
String get chat_pathActions => 'Céltúrások:';
@override
String get chat_setCustomPath => 'Beállítsd a saját útvonalat';
@override
String get chat_setCustomPathSubtitle => 'Kézzel megadott útvonal';
@override
String get chat_clearPath => 'Egyértelmű út';
@override
String get chat_clearPathSubtitle =>
'A parancs új küldéskor újra kell aktivizálnia.';
@override
String get chat_pathCleared =>
'Útvonal cleared. A következő üzenet újból feltérképezheti az útvonalat.';
@override
String get chat_floodModeSubtitle =>
'Használja a \"útvonal\" kapcsolót az alkalmazás sávjában.';
@override
String get chat_floodModeEnabled =>
'Árvízvédelmi mód bekapcsolva. A visszaállítás a alkalmazásban található útvonal ikon segítségével.';
@override
String get chat_fullPath => 'Teljes elérési út';
@override
String get routing_title => 'Útvonal meghatározás';
String get chat_pathDetailsNotAvailable =>
'Az útvonal részletei még nem elérhetők. Próbálja meg küldeni egy üzenetet, hogy frissítse az információkat.';
@override
String get routing_modeAuto => 'Autó';
@override
String get routing_modeFlood => 'Áradás';
@override
String get routing_modeManual => 'Használati útmutató';
@override
String get routing_modeAutoHint =>
'Automatikusan kiválasztja a legismertebb útvonalat, és ha egyik sem ismert, akkor \"vízzel\" tölti ki.';
@override
String get routing_modeFloodHint =>
'Átvisszaadások minden erősítőn keresztül. A legmegbízhatóbb megoldás, de több időt igényel.';
@override
String get routing_modeManualHint =>
'Mindig pontosan az útvonalat követi, amelyet megad.';
@override
String get routing_currentRoute => 'Jelenlegi útvonal';
@override
String get routing_directNoHops => 'Közvetlen nincs átjáró állomás';
@override
String get routing_noPathYet =>
'Még nincs útvonal. A következő üzenet a keresésig vár.';
@override
String get routing_floodBroadcast =>
'Azonnali továbbítás minden erősítőn keresztül.';
@override
String get routing_editPath => 'Útvonal szerkesztése';
@override
String get routing_forgetPath => 'Felejtsd el a útvonalat';
@override
String get routing_knownPaths => 'Jellegzetes útvonalak';
@override
String get routing_knownPathsHint =>
'Készíts egy útvonalat, hogy átválhass rá.';
@override
String get routing_inUse => 'Használatban';
@override
String get routing_qualityStrong => 'Erős első lépés';
@override
String get routing_qualityGood => 'Jó első lépés';
@override
String get routing_qualityFair => 'Jó első lépés';
@override
String get routing_qualityWorked => 'Előállított';
@override
String get routing_qualityFlood =>
'Információt hallottam a katasztrófa miatt.';
@override
String get routing_qualityUntested => 'Vizsgálatnak nem подвержен';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Path set: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'sosem megerősítve';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Vízparti szállítás';
@override
String get pathEditor_title => 'Út megépítése';
@override
String pathEditor_hopCounter(int count) {
return '$count db 64-ből';
}
@override
String get pathEditor_noHops =>
'Még nem adtam hozzá a bazsalikomot. A lent található gombokat használhatod, hogy sorrendben adjd hozzá, vagy mentheted anélkül, hogy bazsalikomot adnál hozzá, hogy közvetlenül elküldd.';
@override
String get pathEditor_addHops =>
'Adja hozzá a bazsaidat a megfelelő sorrendben.';
@override
String get pathEditor_searchRepeaters => 'Ismétlő eszközök keresése';
@override
String get pathEditor_advancedHex => 'Haladó szint: alapvető hex-út';
@override
String get pathEditor_hexLabel => 'Hex előtagok';
@override
String get pathEditor_hexHelper =>
'Két hatjegyű szám minden lépésen, amelyek egymástól elválasztják a kommák.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Érvénytelen: $tokens';
}
@override
String get pathEditor_tooManyHops => 'A maximális szám 64.';
@override
String get pathEditor_usePath => 'Használja ezt az útvonalat.';
@override
String get pathEditor_removeHop => 'Távolítsa el a bazsalikomot';
@override
String get pathEditor_unknownHop => 'Tudatlan erősítő';
@override
String get chat_pathSavedLocally =>
'Helyileg mentve. Kapcsolódjon a szinkronizáláshoz.';
@@ -1748,39 +1656,6 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get map_title => 'Grafikus ábrázás';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Látási vonal';
@@ -2111,6 +1986,16 @@ class AppLocalizationsHu extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Biztosan szeretné kiírni ezt a készüléket?';
@override
String get dialog_disconnectedTitle => 'Lejárat';
@override
String get dialog_disconnectedMessage => 'Lehentetőtől megszakadtál.';
@override
String get dialog_connectCompanion =>
'Csatlakozzon egy kísérőhöz a ismétlő és szobaszerver funkciók eléréséhez.';
@override
String get login_repeaterLogin => 'Ismételt bejelentkezés';
@@ -2177,13 +2062,67 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get common_clear => 'Egyértelmű';
@override
String path_currentPath(String path) {
return 'Jelenlegi útvonal: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'ugrások',
one: 'ugrás',
);
return '$count $_temp0 útvonal használata';
}
@override
String get path_enterCustomPath => 'Adja meg a saját elérési útvonalat';
@override
String get path_currentPathLabel => 'Jelenlegi útvonal';
@override
String get path_hexPrefixInstructions =>
'Adja meg a 2 karakteres hexadecimális előtagokat minden lépéshez, tagolva kommával.';
@override
String get path_hexPrefixExample =>
'Példa: A1, F2, 3C (minden csomó az első részét használja a nyilvános kulcsából)';
@override
String get path_labelHexPrefixes => 'Út (hex-prefixek)';
@override
String get path_helperMaxHops =>
'A maximális hossz 64 karakter. Minden előző rész 2 hatos számjegyből áll (1 bájt).';
@override
String get path_selectFromContacts =>
'Válasszon a kontaktlista elembek közül:';
@override
String get path_noRepeatersFound =>
'Nincs megtalálva semmilyen ismétlődő vagy helyiség-szolgáltató szervert.';
@override
String get path_customPathsRequire =>
'Az egyedi útvonalaknak szükségük van átjáró pontokra, amelyek képesek üzeneteket továbbítani.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Érvénytelen hexadecimális előtagok: $prefixes';
}
@override
String get path_tooLong =>
'Az út túl hosszú. A maximális engedélyezett lépések száma 64.';
@override
String get path_setPath => 'Útvonal meghatározása';
@override
String get repeater_management => 'Adatkapcsolás kezelése';
@@ -2249,6 +2188,16 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get repeater_routingMode => 'Útvonal-kezelési mód';
@override
String get repeater_autoUseSavedPath =>
'Automatikus (az eddigi útvonal használata)';
@override
String get repeater_forceFloodMode => 'Erőforrás-alapú áramlás mód';
@override
String get repeater_pathManagement => 'Útvonal-kezelés';
@override
String get repeater_refresh => 'Újrafriszol';
@@ -3347,139 +3296,6 @@ class AppLocalizationsHu extends AppLocalizations {
return '$celsius °C / $fahrenheit °F';
}
@override
String get telemetry_digitalInputLabel => 'Digitális bemenet';
@override
String get telemetry_digitalOutputLabel => 'Digitális kimenet';
@override
String get telemetry_analogInputLabel => 'Analóg bemenet';
@override
String get telemetry_analogOutputLabel => 'Analóg kimenet';
@override
String get telemetry_genericLabel => 'Általános érzékelő';
@override
String get telemetry_luminosityLabel => 'Fényerő';
@override
String get telemetry_presenceLabel => 'Jelenlét';
@override
String get telemetry_humidityLabel => 'Páratartalom';
@override
String get telemetry_accelerometerLabel => 'Gyorsulásmérő';
@override
String get telemetry_pressureLabel => 'Nyomás';
@override
String get telemetry_altitudeLabel => 'Magasság';
@override
String get telemetry_frequencyLabel => 'Frekvencia';
@override
String get telemetry_percentageLabel => 'Százalék';
@override
String get telemetry_concentrationLabel => 'Koncentráció';
@override
String get telemetry_powerLabel => 'Teljesítmény';
@override
String get telemetry_distanceLabel => 'Távolság';
@override
String get telemetry_energyLabel => 'Energia';
@override
String get telemetry_directionLabel => 'Irány';
@override
String get telemetry_timeLabel => 'Idő';
@override
String get telemetry_gyrometerLabel => 'Giroszkóp';
@override
String get telemetry_colourLabel => 'Szín';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Kapcsoló';
@override
String get telemetry_polylineLabel => 'Töröttvonal';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Kérések száma';
@override
String get telemetry_error => 'Nem sikerült lekérni az adatokat';
@override
String get neighbors_receivedData => 'Kapott szomszédok adatait';
@@ -4375,16 +4191,6 @@ class AppLocalizationsHu extends AppLocalizations {
String get translation_composerSubtitle =>
'Ellenőrzi a zeneszerző fordítási ikon alapértékét.';
@override
String get translation_autoIncomingTitle => 'Üzenetek automatikus fordítása';
@override
String get translation_autoIncomingSubtitle =>
'Automatikusan lefordítja az üzeneteket az értesítésekhez, valamint a csevegésekhez vagy csatornákhoz.';
@override
String get translation_translateMessage => 'Üzenet fordítása';
@override
String get translation_targetLanguage => 'Célnyelv';
@@ -4513,135 +4319,4 @@ class AppLocalizationsHu extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Nagyítva';
@override
String get map_zoomOut => 'Kicsökkentett nézet';
@override
String get map_centerMap => 'Központi tér térkép';
@override
String get chrome_bluetoothRequiresChromium =>
'A Web Bluetooth-hoz egy Chromium-alapú böngésző szükséges.';
@override
String channels_communityShortId(String id) {
return 'Az azonosító: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS-en megerősítve';
@override
String get pathTrace_legendInferred => 'Feltehető helyzet';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+147 -475
View File
@@ -92,24 +92,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get common_disable => 'Disattivare';
@override
String get common_undo => 'Annulla';
@override
String get messageStatus_sent => 'Invia';
@override
String get messageStatus_delivered => 'Consegnato';
@override
String get messageStatus_pending => 'Invio';
@override
String get messageStatus_failed => 'Impossibile inviare';
@override
String get messageStatus_repeated => 'Sentito ripetutamente';
@override
String get common_reboot => 'Riavvia';
@@ -129,12 +111,6 @@ class AppLocalizationsIt extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Aggiornamento automatico';
@override
String get common_interval => 'Intervallo';
@override
String get scanner_title => 'MeshCore Open';
@@ -321,10 +297,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Abilita il Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Passa velocemente';
@@ -816,6 +788,11 @@ class AppLocalizationsIt extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Numero di tentativi di riprova prima di considerare un messaggio come fallito.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Batteria';
@@ -1016,15 +993,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get contacts_newGroup => 'Nuovo Gruppo';
@override
String get contacts_moreOptions => 'Ulteriori opzioni';
@override
String get contacts_searchOpen => 'Cerca contatti';
@override
String get contacts_searchClose => 'Ricerca avanzata';
@override
String get contacts_groupName => 'Nome gruppo';
@@ -1507,6 +1475,34 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Dumpa Esadecimale:';
@override
String get chat_pathManagement => 'Gestione Percorsi';
@override
String get chat_ShowAllPaths => 'Mostra tutti i percorsi';
@override
String get chat_routingMode => 'Modalità di routing';
@override
String get chat_autoUseSavedPath => 'Utilizza il percorso salvato';
@override
String get chat_forceFloodMode => 'Modalità Inondamento Forzato';
@override
String get chat_recentAckPaths => 'Percorsi ACK Recenti (tocca per usare):';
@override
String get chat_pathHistoryFull =>
'La cronologia del percorso è piena. Rimuovi gli elementi per aggiungere nuovi.';
@override
String get chat_hopSingular => 'salta';
@override
String get chat_hopPlural => 'salta';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1518,6 +1514,12 @@ class AppLocalizationsIt extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'successi';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Rimuovi percorso';
@@ -1525,148 +1527,53 @@ class AppLocalizationsIt extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Non c\'è ancora una cronologia del percorso.\nInvia un messaggio per scoprire i percorsi.';
@override
String get chat_pathActions => 'Azioni Percorso:';
@override
String get chat_setCustomPath => 'Imposta Percorso Personalizzato';
@override
String get chat_setCustomPathSubtitle =>
'Specifica manualmente il percorso di routing';
@override
String get chat_clearPath => 'Cancella Percorso';
@override
String get chat_clearPathSubtitle =>
'Riprova la scoperta alla prossima invio';
@override
String get chat_pathCleared =>
'Percorso sgomberato. Il prossimo messaggio riidentifierà il percorso.';
@override
String get chat_floodModeSubtitle =>
'Utilizza l\'interruttore di routing nella barra delle applicazioni';
@override
String get chat_floodModeEnabled =>
'Modalità alluvione abilitata. Disattivala tramite l\'icona di routing nella barra in alto.';
@override
String get chat_fullPath => 'Percorso Completo';
@override
String get routing_title => 'Instradamento';
String get chat_pathDetailsNotAvailable =>
'I dettagli del percorso non sono ancora disponibili. Prova a inviare un messaggio per ricaricare.';
@override
String get routing_modeAuto => 'Auto';
@override
String get routing_modeFlood => 'Inondazione';
@override
String get routing_modeManual => 'Manuale';
@override
String get routing_modeAutoHint =>
'Seleziona automaticamente il percorso più noto, e in caso di assenza di informazioni, utilizza un percorso casuale.';
@override
String get routing_modeFloodHint =>
'Trasmissioni tramite ogni ripetitore. Il metodo più affidabile, ma richiede più tempo di trasmissione.';
@override
String get routing_modeManualHint =>
'Invia sempre esattamente il percorso che hai definito.';
@override
String get routing_currentRoute => 'Percorso attuale';
@override
String get routing_directNoHops =>
'Diretto — senza passaggi tramite ripetitori';
@override
String get routing_noPathYet =>
'Al momento non è stata individuata alcuna via. Il messaggio viene inviato ripetutamente finché non viene trovata una rotta.';
@override
String get routing_floodBroadcast =>
'Trasmissione attraverso ogni ripetitore';
@override
String get routing_editPath => 'Percorso di modifica';
@override
String get routing_forgetPath => 'Dimentica il percorso';
@override
String get routing_knownPaths => 'Percorsi noti';
@override
String get routing_knownPathsHint =>
'Seleziona un percorso per accedere a questa opzione.';
@override
String get routing_inUse => 'In uso';
@override
String get routing_qualityStrong => 'Primo salto molto deciso';
@override
String get routing_qualityGood => 'Primo tentativo di successo';
@override
String get routing_qualityFair => 'Primo salto di qualità';
@override
String get routing_qualityWorked => 'È stato consegnato';
@override
String get routing_qualityFlood => 'Ho sentito tramite un messaggio urgente';
@override
String get routing_qualityUntested => 'Non testato';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Percorso impostato: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'mai confermato';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Consegna in caso di alluvione';
@override
String get pathEditor_title => 'Creare percorso';
@override
String pathEditor_hopCounter(int count) {
return '$count tra 64 varietà di luppolo';
}
@override
String get pathEditor_noHops =>
'Al momento non ci sono ingredienti aggiuntivi. Per aggiungerli nell\'ordine desiderato, cliccate sui ripetitori sottostanti. In alternativa, potete salvare la ricetta senza ingredienti aggiuntivi per inviarla direttamente.';
@override
String get pathEditor_addHops =>
'Aggiungere i luppoli nell\'ordine desiderato.';
@override
String get pathEditor_searchRepeaters => 'Ricerca ripetitori';
@override
String get pathEditor_advancedHex => 'Avanzato: percorso esadecimale grezzo';
@override
String get pathEditor_hexLabel => 'Prefissi esadecimali';
@override
String get pathEditor_hexHelper =>
'Due caratteri esadecimali per ogni salto, separati da virgole.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Non valido: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Massimo 64 orari';
@override
String get pathEditor_usePath => 'Utilizza questo percorso';
@override
String get pathEditor_removeHop => 'Rimuovere il luppolo';
@override
String get pathEditor_unknownHop => 'Ripetitore sconosciuto';
@override
String get chat_pathSavedLocally =>
'Salvatato localmente. Connetti per sincronizzare.';
@@ -1742,39 +1649,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get map_title => 'Mappa Nodi';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Linea di vista';
@@ -2102,6 +1976,17 @@ class AppLocalizationsIt extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Sei sicuro di voler disconnetterti da questo dispositivo?';
@override
String get dialog_disconnectedTitle => 'Disconnesso';
@override
String get dialog_disconnectedMessage =>
'Sei stato disconnesso dal tuo compagno.';
@override
String get dialog_connectCompanion =>
'Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.';
@override
String get login_repeaterLogin => 'Login Ripetitore';
@@ -2167,13 +2052,66 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get common_clear => 'Cancella';
@override
String path_currentPath(String path) {
return 'Percorso corrente: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Utilizzare $count $_temp0 percorso';
}
@override
String get path_enterCustomPath => 'Inserisci percorso personalizzato';
@override
String get path_currentPathLabel => 'Percorso corrente';
@override
String get path_hexPrefixInstructions =>
'Inserire i prefissi esadecimali a 2 caratteri per ogni salto, separati da virgole.';
@override
String get path_hexPrefixExample =>
'Esempio: A1,F2,3C (ogni nodo utilizza il primo byte della sua chiave pubblica)';
@override
String get path_labelHexPrefixes => 'Prefisso esadecimale (percorso)';
@override
String get path_helperMaxHops =>
'Massimo 64 salti. Ogni prefisso è composto da 2 caratteri esadecimali (1 byte)';
@override
String get path_selectFromContacts => 'Seleziona da contatti:';
@override
String get path_noRepeatersFound =>
'Non sono stati trovati ripetitori o server di stanza.';
@override
String get path_customPathsRequire =>
'I percorsi personalizzati richiedono salti intermedi che possono inoltrare messaggi.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Prefissi esadecimali non validi: $prefixes';
}
@override
String get path_tooLong =>
'Il percorso è troppo lungo. Massimo 64 salti consentiti.';
@override
String get path_setPath => 'Imposta Percorso';
@override
String get repeater_management => 'Gestione Ripetitori';
@@ -2240,6 +2178,15 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get repeater_routingMode => 'Modalità di routing';
@override
String get repeater_autoUseSavedPath => 'Percorso salvato automatico';
@override
String get repeater_forceFloodMode => 'Modalità Inondamento Forzato';
@override
String get repeater_pathManagement => 'Gestione dei percorsi';
@override
String get repeater_refresh => 'Aggiorna';
@@ -3340,139 +3287,6 @@ class AppLocalizationsIt extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Ingresso digitale';
@override
String get telemetry_digitalOutputLabel => 'Uscita digitale';
@override
String get telemetry_analogInputLabel => 'Ingresso analogico';
@override
String get telemetry_analogOutputLabel => 'Uscita analogica';
@override
String get telemetry_genericLabel => 'Sensore generico';
@override
String get telemetry_luminosityLabel => 'Luminosità';
@override
String get telemetry_presenceLabel => 'Presenza';
@override
String get telemetry_humidityLabel => 'Umidità';
@override
String get telemetry_accelerometerLabel => 'Accelerometro';
@override
String get telemetry_pressureLabel => 'Pressione';
@override
String get telemetry_altitudeLabel => 'Altitudine';
@override
String get telemetry_frequencyLabel => 'Frequenza';
@override
String get telemetry_percentageLabel => 'Percentuale';
@override
String get telemetry_concentrationLabel => 'Concentrazione';
@override
String get telemetry_powerLabel => 'Potenza';
@override
String get telemetry_distanceLabel => 'Distanza';
@override
String get telemetry_energyLabel => 'Energia';
@override
String get telemetry_directionLabel => 'Direzione';
@override
String get telemetry_timeLabel => 'Ora';
@override
String get telemetry_gyrometerLabel => 'Giroscopio';
@override
String get telemetry_colourLabel => 'Colore';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Interruttore';
@override
String get telemetry_polylineLabel => 'Polilinea';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Numero di richieste';
@override
String get telemetry_error => 'Impossibile recuperare i dati';
@override
String get neighbors_receivedData => 'Ricevute dati vicini';
@@ -4366,17 +4180,6 @@ class AppLocalizationsIt extends AppLocalizations {
String get translation_composerSubtitle =>
'Controlla lo stato predefinito dell\'icona di traduzione del compositore.';
@override
String get translation_autoIncomingTitle =>
'Traduci automaticamente i messaggi';
@override
String get translation_autoIncomingSubtitle =>
'Traduce automaticamente i messaggi per le notifiche e per le chat o i canali.';
@override
String get translation_translateMessage => 'Traduci messaggio';
@override
String get translation_targetLanguage => 'Lingua di destinazione';
@@ -4505,135 +4308,4 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Ingrandisci';
@override
String get map_zoomOut => 'Riduci la visualizzazione';
@override
String get map_centerMap => 'Mappa del centro';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth richiede un browser basato su Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'Il GPS conferma';
@override
String get pathTrace_legendInferred => 'Posizione dedotta';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
File diff suppressed because it is too large Load Diff
+141 -466
View File
@@ -92,24 +92,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get common_disable => '비활성화';
@override
String get common_undo => '취소';
@override
String get messageStatus_sent => '발송';
@override
String get messageStatus_delivered => '배송 완료';
@override
String get messageStatus_pending => '발송';
@override
String get messageStatus_failed => '발송 실패';
@override
String get messageStatus_repeated => '반복적으로 들었습니다';
@override
String get common_reboot => '재부팅';
@@ -129,12 +111,6 @@ class AppLocalizationsKo extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => '자동 새로고침';
@override
String get common_interval => '간격';
@override
String get scanner_title => 'MeshCore 공개';
@@ -307,10 +283,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get scanner_enableBluetooth => '블루투스 활성화';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => '빠른 전환';
@@ -771,6 +743,11 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get appSettings_maxMessageRetriesSubtitle => '메시지를 실패로 처리하기 전 시도 횟수';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => '배터리';
@@ -962,15 +939,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get contacts_newGroup => '새로운 그룹';
@override
String get contacts_moreOptions => '더 많은 옵션';
@override
String get contacts_searchOpen => '연락처 검색';
@override
String get contacts_searchClose => '검색 닫기';
@override
String get contacts_groupName => '그룹 이름';
@@ -1437,6 +1405,34 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get debugFrame_hexDump => '헥스 덤프:';
@override
String get chat_pathManagement => '경로 관리';
@override
String get chat_ShowAllPaths => '모든 경로 표시';
@override
String get chat_routingMode => '라우팅 방식';
@override
String get chat_autoUseSavedPath => '자동 (저장된 경로 사용)';
@override
String get chat_forceFloodMode => '강수 모드 활성화';
@override
String get chat_recentAckPaths => '최근 사용한 ACK 경로 (사용하려면 탭):';
@override
String get chat_pathHistoryFull =>
'이력 기록은 이미 가득 차 있습니다. 항목을 삭제하여 새로운 항목을 추가할 수 있습니다.';
@override
String get chat_hopSingular => '점프';
@override
String get chat_hopPlural => '';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1448,146 +1444,61 @@ class AppLocalizationsKo extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => '성공 사례';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => '경로 제거';
@override
String get chat_noPathHistoryYet => '아직 경로 기록이 없습니다.\n경로를 찾기 위해 메시지를 보내세요.';
@override
String get chat_pathActions => '경로 작업:';
@override
String get chat_setCustomPath => '사용자 지정 경로 설정';
@override
String get chat_setCustomPathSubtitle => '수동으로 경로를 지정';
@override
String get chat_clearPath => '명확한 길';
@override
String get chat_clearPathSubtitle => '다음 전송 시, 강제 재전송 설정';
@override
String get chat_pathCleared => '경로가 확보되었습니다. 다음 메시지는 경로를 다시 찾을 것입니다.';
@override
String get chat_floodModeSubtitle => '앱 바에서 라우팅 스위치를 사용';
@override
String get chat_floodModeEnabled =>
'홍수 모드 활성화됨. 앱 바의 경로 아이콘을 사용하여 다시 전환할 수 있습니다.';
@override
String get chat_fullPath => '전체 경로';
@override
String get routing_title => '라우팅';
String get chat_pathDetailsNotAvailable =>
'경로 정보는 아직 제공되지 않습니다. 메시지를 보내어 다시 시도해 보세요.';
@override
String get routing_modeAuto => '자동';
@override
String get routing_modeFlood => '홍수';
@override
String get routing_modeManual => '사용 설명서';
@override
String get routing_modeAutoHint =>
'가장 잘 알려진 경로를 자동으로 선택하고, 경로가 없을 경우에는 무작위로 경로를 선택합니다.';
@override
String get routing_modeFloodHint =>
'모든 증폭기를 통해 방송됩니다. 가장 안정적이지만, 더 많은 송출 시간을 사용합니다.';
@override
String get routing_modeManualHint => '항상 설정하신 정확한 경로를 따라 이동합니다.';
@override
String get routing_currentRoute => '현재 경로';
@override
String get routing_directNoHops => '직접 연결 중계 장치 사용 없이';
@override
String get routing_noPathYet => '아직 경로가 없습니다. 다음 메시지가 도착할 때까지 계속 탐색합니다.';
@override
String get routing_floodBroadcast => '모든 증폭기를 통해 방송';
@override
String get routing_editPath => '경로 편집';
@override
String get routing_forgetPath => '길을 잊어라';
@override
String get routing_knownPaths => '알려진 경로';
@override
String get routing_knownPathsHint => '해당 항목으로 전환하기 위한 경로를 선택합니다.';
@override
String get routing_inUse => '사용 중';
@override
String get routing_qualityStrong => '강력한 첫 번째 단계';
@override
String get routing_qualityGood => '좋은 첫 시작';
@override
String get routing_qualityFair => '처음 시도';
@override
String get routing_qualityWorked => '완료됨';
@override
String get routing_qualityFlood => '홍수 피해 상황을 통해 들었습니다.';
@override
String get routing_qualityUntested => '검증되지 않음';
@override
String routing_lastWorked(String when) {
return '$when에 일했습니다';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Path set: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => '확인되지 않음';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => '홍수 피해 지역 배송';
@override
String get pathEditor_title => '경로 만들기';
@override
String pathEditor_hopCounter(int count) {
return '64개의 홉 중 $count';
}
@override
String get pathEditor_noHops =>
'현재 홉은 추가되지 않았습니다. 아래의 탭을 사용하여 순서대로 추가하거나, 홉 없이 바로 전송하려면 \"홉 없음\"으로 저장하십시오.';
@override
String get pathEditor_addHops => '홉을 순서대로 첨가해주세요.';
@override
String get pathEditor_searchRepeaters => '반복 검색';
@override
String get pathEditor_advancedHex => '고급: 원시 헥스 경로';
@override
String get pathEditor_hexLabel => '헥스 접두사';
@override
String get pathEditor_hexHelper => '각 홉마다 2개의 6자리 숫자, 쉼표로 구분';
@override
String pathEditor_invalidTokens(String tokens) {
return '유효하지 않음: $tokens';
}
@override
String get pathEditor_tooManyHops => '최대 64개의 홉';
@override
String get pathEditor_usePath => '이 경로를 사용하세요';
@override
String get pathEditor_removeHop => '홉 제거';
@override
String get pathEditor_unknownHop => '알 수 없는 중계기';
@override
String get chat_pathSavedLocally => '로컬에 저장. 동기화 연결';
@@ -1660,39 +1571,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get map_title => '노드 매핑';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => '시야';
@@ -2012,6 +1890,15 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get dialog_disconnectConfirm => '이 장치와의 연결을 해제하시겠습니까?';
@override
String get dialog_disconnectedTitle => '연결 끊김';
@override
String get dialog_disconnectedMessage => '컴패니언과의 연결이 끊어졌습니다.';
@override
String get dialog_connectCompanion => '리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.';
@override
String get login_repeaterLogin => '다시 로그인';
@@ -2074,12 +1961,64 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get common_clear => '명확하게';
@override
String path_currentPath(String path) {
return '현재 경로: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Using $count $_temp0 path';
}
@override
String get path_enterCustomPath => '사용자 지정 경로 입력';
@override
String get path_currentPathLabel => '현재 경로';
@override
String get path_hexPrefixInstructions =>
'각 단계에 대한 2자리 헥사데진 접두사를 쉼표로 구분하여 입력하세요.';
@override
String get path_hexPrefixExample =>
'예시: A1, F2, 3C (각 노드는 자신의 공개 키의 첫 번째 바이트를 사용)';
@override
String get path_labelHexPrefixes => '경로 (헥스 접두사)';
@override
String get path_helperMaxHops =>
'최대 64개의 홉. 각 접두사는 2개의 16진수 문자(1바이트)로 구성됩니다.';
@override
String get path_selectFromContacts => '또 연락처 목록에서 선택:';
@override
String get path_noRepeatersFound => '반복 장치 또는 서버는 찾을 수 없습니다.';
@override
String get path_customPathsRequire =>
'사용자 정의 경로에는 메시지를 전달할 수 있는 중간 경로가 필요합니다.';
@override
String path_invalidHexPrefixes(String prefixes) {
return '유효하지 않은 16진수 접두사: $prefixes';
}
@override
String get path_tooLong => '경로가 너무 길어. 최대 64개의 연결만 허용됩니다.';
@override
String get path_setPath => '경로 설정';
@override
String get repeater_management => '리피터 관리';
@@ -2141,6 +2080,15 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get repeater_routingMode => '라우팅 방식';
@override
String get repeater_autoUseSavedPath => '자동 (저장된 경로 사용)';
@override
String get repeater_forceFloodMode => '강수 모드 활성화';
@override
String get repeater_pathManagement => '경로 관리';
@override
String get repeater_refresh => '새롭게';
@@ -3157,139 +3105,6 @@ class AppLocalizationsKo extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => '디지털 입력';
@override
String get telemetry_digitalOutputLabel => '디지털 출력';
@override
String get telemetry_analogInputLabel => '아날로그 입력';
@override
String get telemetry_analogOutputLabel => '아날로그 출력';
@override
String get telemetry_genericLabel => '일반 센서';
@override
String get telemetry_luminosityLabel => '조도';
@override
String get telemetry_presenceLabel => '존재 감지';
@override
String get telemetry_humidityLabel => '습도';
@override
String get telemetry_accelerometerLabel => '가속도계';
@override
String get telemetry_pressureLabel => '압력';
@override
String get telemetry_altitudeLabel => '고도';
@override
String get telemetry_frequencyLabel => '주파수';
@override
String get telemetry_percentageLabel => '백분율';
@override
String get telemetry_concentrationLabel => '농도';
@override
String get telemetry_powerLabel => '전력';
@override
String get telemetry_distanceLabel => '거리';
@override
String get telemetry_energyLabel => '에너지';
@override
String get telemetry_directionLabel => '방향';
@override
String get telemetry_timeLabel => '시간';
@override
String get telemetry_gyrometerLabel => '자이로미터';
@override
String get telemetry_colourLabel => '색상';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => '스위치';
@override
String get telemetry_polylineLabel => '폴리라인';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => '요청 수';
@override
String get telemetry_error => '데이터를 가져올 수 없습니다';
@override
String get neighbors_receivedData => '이웃 정보 수집';
@@ -4136,16 +3951,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get translation_composerSubtitle => '컴포저 번역 아이콘의 기본 상태를 제어합니다.';
@override
String get translation_autoIncomingTitle => '메시지 자동 번역';
@override
String get translation_autoIncomingSubtitle =>
'알림과 채팅 또는 채널의 메시지를 자동으로 번역합니다.';
@override
String get translation_translateMessage => '메시지 번역';
@override
String get translation_targetLanguage => '목표 언어';
@@ -4268,134 +4073,4 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => '줌 인';
@override
String get map_zoomOut => '줌 아웃';
@override
String get map_centerMap => '중심 지도';
@override
String get chrome_bluetoothRequiresChromium => '웹 블루투스는 크롬 브라우저가 필요합니다.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS 확인 완료';
@override
String get pathTrace_legendInferred => '추론된 위치';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+147 -470
View File
@@ -92,24 +92,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get common_disable => 'Uitschakelen';
@override
String get common_undo => 'Achterhalen/Annuleren';
@override
String get messageStatus_sent => 'Verzonden';
@override
String get messageStatus_delivered => 'Leverd';
@override
String get messageStatus_pending => 'Verzenden';
@override
String get messageStatus_failed => 'Niet verzonden';
@override
String get messageStatus_repeated => 'Hearsay, herhaald';
@override
String get common_reboot => 'Herstarten';
@@ -129,12 +111,6 @@ class AppLocalizationsNl extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Automatisch vernieuwen';
@override
String get common_interval => 'Tijdsinterval';
@override
String get scanner_title => 'MeshCore Open';
@@ -317,10 +293,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Activeer Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Snelle overschakeling';
@@ -808,6 +780,11 @@ class AppLocalizationsNl extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Batterij';
@@ -1008,15 +985,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get contacts_newGroup => 'Nieuwe Groep';
@override
String get contacts_moreOptions => 'Meer opties';
@override
String get contacts_searchOpen => 'Zoek contactpersonen';
@override
String get contacts_searchClose => 'Zoeken';
@override
String get contacts_groupName => 'Groepnaam';
@@ -1494,6 +1462,34 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Hex-dump:';
@override
String get chat_pathManagement => 'Beheer van Paden';
@override
String get chat_ShowAllPaths => 'Toon alle paden';
@override
String get chat_routingMode => 'Routeerwijze';
@override
String get chat_autoUseSavedPath => 'Automatisch (gebruik opgeslagen pad)';
@override
String get chat_forceFloodMode => 'Dwing Floodsmodus';
@override
String get chat_recentAckPaths => 'Recente ACK Paden (tik om te gebruiken):';
@override
String get chat_pathHistoryFull =>
'De voorgeschiedenis is vol. Verwijder vermeldingen om er nieuwe aan toe te voegen.';
@override
String get chat_hopSingular => 'Hop';
@override
String get chat_hopPlural => 'hoppen';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1505,6 +1501,12 @@ class AppLocalizationsNl extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Succesvol';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Pad verwijderen';
@@ -1512,144 +1514,52 @@ class AppLocalizationsNl extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Geen geschiedenis van paden nog beschikbaar.\nVerzend een bericht om paden te ontdekken.';
@override
String get chat_pathActions => 'Padacties:';
@override
String get chat_setCustomPath => 'Stel aangepaste pad in';
@override
String get chat_setCustomPathSubtitle => 'Handmatig routepad specificeren';
@override
String get chat_clearPath => 'Duidelijke Pad';
@override
String get chat_clearPathSubtitle =>
'Dwing herontdekking bij volgende verzending';
@override
String get chat_pathCleared =>
'Pad is vrijgegeven. Volgende bericht herontdekt route.';
@override
String get chat_floodModeSubtitle =>
'Gebruik de route-schakelaar in de app-balk';
@override
String get chat_floodModeEnabled =>
'Floodmodus is ingeschakeld. Schakel dit uit via het route-icoon in de app-balk.';
@override
String get chat_fullPath => 'Volledige Pad';
@override
String get routing_title => 'Routeplanning';
String get chat_pathDetailsNotAvailable =>
'De paddetails zijn nog niet beschikbaar. Probeer een bericht te sturen om te vernieuwen.';
@override
String get routing_modeAuto => 'Auto';
@override
String get routing_modeFlood => 'Overstroming';
@override
String get routing_modeManual => 'Handleiding';
@override
String get routing_modeAutoHint =>
'Selecteert automatisch het bekendste pad, en gebruikt een flood-algoritme als er geen bekend pad is.';
@override
String get routing_modeFloodHint =>
'Uitzendingen via elke zender. De meest betrouwbare methode, maar vereist meer uitzendtijd.';
@override
String get routing_modeManualHint =>
'Stuurt altijd de exacte route die u heeft aangegeven.';
@override
String get routing_currentRoute => 'Huidige route';
@override
String get routing_directNoHops => 'Direct zonder tussenliggende schakels';
@override
String get routing_noPathYet =>
'Er is nog geen route gevonden. De berichten blijven binnenkomen totdat een route is ontdekt.';
@override
String get routing_floodBroadcast => 'Uitgestoten via elke zender.';
@override
String get routing_editPath => 'Pad bewerken';
@override
String get routing_forgetPath => 'Vergeet het pad';
@override
String get routing_knownPaths => 'Bekende routes';
@override
String get routing_knownPathsHint => 'Maak een route om er naartoe te gaan.';
@override
String get routing_inUse => 'In gebruik';
@override
String get routing_qualityStrong => 'Sterke eerste sprong';
@override
String get routing_qualityGood => 'Een goede eerste stap';
@override
String get routing_qualityFair => 'Een goede eerste hop';
@override
String get routing_qualityWorked => 'Is geleverd';
@override
String get routing_qualityFlood => 'Hears via een overstroming';
@override
String get routing_qualityUntested => 'Niet getest';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Pad ingesteld: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nooit bevestigd';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes zijn behaald, $failures zijn mislukt';
}
@override
String get routing_floodDelivery => 'Levering bij overstroming';
@override
String get pathEditor_title => 'Pad creëren';
@override
String pathEditor_hopCounter(int count) {
return '$count van 64 hopgranen';
}
@override
String get pathEditor_noHops =>
'Er zijn nog geen hop toegevoegd. Klik op de onderstaande knoppen om ze in de juiste volgorde toe te voegen, of sla de bestelling op zonder hop om deze direct te versturen.';
@override
String get pathEditor_addHops => 'Voeg hop toe in de juiste volgorde.';
@override
String get pathEditor_searchRepeaters => 'Zoek naar herhaaldelijke zenders';
@override
String get pathEditor_advancedHex => 'Geavanceerd: ruwe hex-pad';
@override
String get pathEditor_hexLabel => 'Hex-voorkanten';
@override
String get pathEditor_hexHelper =>
'Twee hex-tekens per stap, gescheiden door komma\'s';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Ongeldig: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maximaal 64 hopken';
@override
String get pathEditor_usePath => 'Gebruik deze route.';
@override
String get pathEditor_removeHop => 'Verwijder de hop';
@override
String get pathEditor_unknownHop => 'Onbekend type zender';
@override
String get chat_pathSavedLocally =>
'Opgeslagen lokaal. Verbinden om te synchroniseren.';
@@ -1725,39 +1635,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get map_title => 'Kaart van de knopen';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Zichtlijn';
@@ -2086,6 +1963,17 @@ class AppLocalizationsNl extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?';
@override
String get dialog_disconnectedTitle => 'Verbroken';
@override
String get dialog_disconnectedMessage =>
'Je bent losgekoppeld van je companion.';
@override
String get dialog_connectCompanion =>
'Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.';
@override
String get login_repeaterLogin => 'Inloggen Repeater';
@@ -2151,12 +2039,65 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get common_clear => 'Schoonmaken';
@override
String path_currentPath(String path) {
return 'Huidige pad: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Gebruik $count $_temp0 pad';
}
@override
String get path_enterCustomPath => 'Voer aangepaste pad in';
@override
String get path_currentPathLabel => 'Huidige pad';
@override
String get path_hexPrefixInstructions =>
'Voer 2-letter hex-voorgiffen voor elke hop in, gescheiden door komma\'s.';
@override
String get path_hexPrefixExample =>
'Voorbeeld: A1,F2,3C (elke node gebruikt het eerste byte van zijn openbare sleutel)';
@override
String get path_labelHexPrefixes => 'Pad (hex-voorkeursletters)';
@override
String get path_helperMaxHops =>
'Maximaal 64 sprongen. Elke prefix is 2 hexadecimale tekens (1 byte)';
@override
String get path_selectFromContacts => 'Of select contacten:';
@override
String get path_noRepeatersFound => 'Geen repeaters of roomservers gevonden.';
@override
String get path_customPathsRequire =>
'Aangepaste paden vereisen tussentse overstappen die berichten kunnen doorgeven.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Ongeldige hex-voorkeursletters: $prefixes';
}
@override
String get path_tooLong =>
'Pad is te lang. Maximaal 64 sprongen zijn toegestaan.';
@override
String get path_setPath => 'Stel Pad in';
@override
String get repeater_management => 'Beheer Repeaters';
@@ -2221,6 +2162,16 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get repeater_routingMode => 'Routeerwijze';
@override
String get repeater_autoUseSavedPath =>
'Automatisch (gebruik opgeslagen pad)';
@override
String get repeater_forceFloodMode => 'Dwing Floodmodus Af';
@override
String get repeater_pathManagement => 'Beheer van paden';
@override
String get repeater_refresh => 'Vernieuwen';
@@ -3316,139 +3267,6 @@ class AppLocalizationsNl extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Digitale ingang';
@override
String get telemetry_digitalOutputLabel => 'Digitale uitgang';
@override
String get telemetry_analogInputLabel => 'Analoge ingang';
@override
String get telemetry_analogOutputLabel => 'Analoge uitgang';
@override
String get telemetry_genericLabel => 'Algemene sensor';
@override
String get telemetry_luminosityLabel => 'Lichtsterkte';
@override
String get telemetry_presenceLabel => 'Aanwezigheid';
@override
String get telemetry_humidityLabel => 'Luchtvochtigheid';
@override
String get telemetry_accelerometerLabel => 'Versnellingsmeter';
@override
String get telemetry_pressureLabel => 'Druk';
@override
String get telemetry_altitudeLabel => 'Hoogte';
@override
String get telemetry_frequencyLabel => 'Frequentie';
@override
String get telemetry_percentageLabel => 'Percentage';
@override
String get telemetry_concentrationLabel => 'Concentratie';
@override
String get telemetry_powerLabel => 'Vermogen';
@override
String get telemetry_distanceLabel => 'Afstand';
@override
String get telemetry_energyLabel => 'Energie';
@override
String get telemetry_directionLabel => 'Richting';
@override
String get telemetry_timeLabel => 'Tijd';
@override
String get telemetry_gyrometerLabel => 'Gyrometer';
@override
String get telemetry_colourLabel => 'Kleur';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Schakelaar';
@override
String get telemetry_polylineLabel => 'Polylijn';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Aantal aanvragen';
@override
String get telemetry_error => 'Kan gegevens niet ophalen';
@override
String get neighbors_receivedData => 'Ontvangen Buurdata';
@@ -4339,16 +4157,6 @@ class AppLocalizationsNl extends AppLocalizations {
String get translation_composerSubtitle =>
'Stelt de standaardstatus van het pictogram voor de vertaling van de componist in.';
@override
String get translation_autoIncomingTitle => 'Berichten automatisch vertalen';
@override
String get translation_autoIncomingSubtitle =>
'Vertaalt berichten automatisch voor meldingen en voor chats of kanalen.';
@override
String get translation_translateMessage => 'Bericht vertalen';
@override
String get translation_targetLanguage => 'Doeltaal';
@@ -4476,135 +4284,4 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Inzoomen';
@override
String get map_zoomOut => 'Inzoomen';
@override
String get map_centerMap => 'Centraal overzicht';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth vereist een Chromium-browser.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS-locatie bevestigd';
@override
String get pathTrace_legendInferred => 'Afgeleide positie';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+150 -475
View File
@@ -92,24 +92,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get common_disable => 'Wyłącz';
@override
String get common_undo => 'Wycofaj';
@override
String get messageStatus_sent => 'Wysłane';
@override
String get messageStatus_delivered => 'Dostarczone';
@override
String get messageStatus_pending => 'Wysyłanie';
@override
String get messageStatus_failed => 'Nie udało się wysłać';
@override
String get messageStatus_repeated => 'Usłyszałem to wielokrotnie';
@override
String get common_reboot => 'Uruchom ponownie';
@@ -129,12 +111,6 @@ class AppLocalizationsPl extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Automatyczne odświeżanie';
@override
String get common_interval => 'Interwał';
@override
String get scanner_title => 'MeshCore wersja open source';
@@ -322,10 +298,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Włącz Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Szybka zmiana';
@@ -818,6 +790,11 @@ class AppLocalizationsPl extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Bateria';
@@ -1026,15 +1003,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get contacts_newGroup => 'Nowa Grupa';
@override
String get contacts_moreOptions => 'Więcej opcji';
@override
String get contacts_searchOpen => 'Wyszukaj kontakty';
@override
String get contacts_searchClose => 'Zaawansowane wyszukiwanie';
@override
String get contacts_groupName => 'Nazwa grupy';
@@ -1517,6 +1485,35 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Zrzut hex:';
@override
String get chat_pathManagement => 'Zarządzanie ścieżkami';
@override
String get chat_ShowAllPaths => 'Pokaż wszystkie ścieżki';
@override
String get chat_routingMode => 'Tryb routingu';
@override
String get chat_autoUseSavedPath => 'Automatyczne (użyj zapisanej ścieżki)';
@override
String get chat_forceFloodMode => 'Wymuś tryb zalewowy';
@override
String get chat_recentAckPaths =>
'Ostatnie ścieżki ACK (naciśnij, aby użyć):';
@override
String get chat_pathHistoryFull =>
'Historia ścieżek jest pełna. Usuń wpisy, aby dodać nowe.';
@override
String get chat_hopSingular => 'skok';
@override
String get chat_hopPlural => 'skoki';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1530,6 +1527,12 @@ class AppLocalizationsPl extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Sukcesy';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Usuń ścieżkę';
@@ -1537,148 +1540,52 @@ class AppLocalizationsPl extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Brak historii ścieżek.\nWyślij wiadomość, aby odkryć ścieżki.';
@override
String get chat_pathActions => 'Działania ścieżki:';
@override
String get chat_setCustomPath => 'Ustaw ścieżkę niestandardową';
@override
String get chat_setCustomPathSubtitle => 'Ręcznie określ trasę.';
@override
String get chat_clearPath => 'Wyczyść Ścieżkę';
@override
String get chat_clearPathSubtitle =>
'Wymuś ponowne wyznaczenie trasy przy następnym wysłaniu';
@override
String get chat_pathCleared =>
'Ścieżka wyczyszczona. Następna wiadomość odnajdzie trasę.';
@override
String get chat_floodModeSubtitle =>
'Użyj przełącznika routingu w pasku narzędzi.';
@override
String get chat_floodModeEnabled =>
'Tryb zalewowy włączony. Przełącz z powrotem ikoną routingu w pasku aplikacji.';
@override
String get chat_fullPath => 'Pełna ścieżka';
@override
String get routing_title => 'Planowanie tras';
String get chat_pathDetailsNotAvailable =>
'Szczegóły ścieżki jeszcze niedostępne. Spróbuj wysłać wiadomość, aby odświeżyć.';
@override
String get routing_modeAuto => 'Samochód';
@override
String get routing_modeFlood => 'Powódź';
@override
String get routing_modeManual => 'Instrukcja obsługi';
@override
String get routing_modeAutoHint =>
'Automatycznie wybiera najpopularniejszą ścieżkę, a w przypadku braku znanej, przechodzi do trybu \"przepływu\".';
@override
String get routing_modeFloodHint =>
'Transmisje za pośrednictwem każdego repeatera. Najbardziej niezawodna metoda, ale zużywa więcej czasu transmisji.';
@override
String get routing_modeManualHint =>
'Zawsze prowadzi dokładnie po trasie, którą określiłeś.';
@override
String get routing_currentRoute => 'Obecna trasa';
@override
String get routing_directNoHops =>
'Bezpośrednio bez pośrednictwa repeaterów';
@override
String get routing_noPathYet =>
'Na razie nie ma żadnej ścieżki. Komunikacja trwa do momentu, gdy zostanie odkryta trasa.';
@override
String get routing_floodBroadcast =>
'Transmisja za pośrednictwem każdego urządzenia powielającego';
@override
String get routing_editPath => 'Edytuj ścieżkę';
@override
String get routing_forgetPath => 'Zapomnij o ścieżce';
@override
String get routing_knownPaths => 'Znane trasy';
@override
String get routing_knownPathsHint =>
'Wybierz ścieżkę, aby przełączyć się na nią.';
@override
String get routing_inUse => 'W użyciu';
@override
String get routing_qualityStrong => 'Silny pierwszy skok';
@override
String get routing_qualityGood => 'Świetny początek';
@override
String get routing_qualityFair => 'Świetny pierwszy krzak';
@override
String get routing_qualityWorked => 'Zostało dostarczone';
@override
String get routing_qualityFlood => 'Usłyszano dzięki doniesieniom';
@override
String get routing_qualityUntested => 'Nieużywany';
@override
String routing_lastWorked(String when) {
return 'pracował $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Ścieżka ustawiona: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nigdy nie zostało potwierdzone';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Dostawa w przypadku powodzi';
@override
String get pathEditor_title => 'Stworzenie ścieżki';
@override
String pathEditor_hopCounter(int count) {
return '$count z 64 rodzajów chmielu';
}
@override
String get pathEditor_noHops =>
'Na razie nie dodano żadnych chmielu. Aby dodać je w odpowiedniej kolejności, kliknij w odpowiednie przyciski poniżej, lub zapisz przepis bez chmielu, aby wysłać go bezpośrednio.';
@override
String get pathEditor_addHops => 'Dodawaj chmiel zgodnie z kolejnością.';
@override
String get pathEditor_searchRepeaters => 'Funkcje powtarzania';
@override
String get pathEditor_advancedHex =>
'Zaawansowane: ścieżka w formacie szesnastkowym';
@override
String get pathEditor_hexLabel => 'Prefiksy heksadecymalne';
@override
String get pathEditor_hexHelper =>
'Dwa znaki szesnastkowe na każdym kroku, oddzielone przecinkami';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Nieprawidłowe: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maksymalnie 64 hopów';
@override
String get pathEditor_usePath => 'Użyj tej ścieżki.';
@override
String get pathEditor_removeHop => 'Usuń dziką psiankę';
@override
String get pathEditor_unknownHop => 'Nieznany repeater';
@override
String get chat_pathSavedLocally =>
'Zapisano lokalnie. Połącz się, aby zsynchronizować.';
@@ -1754,39 +1661,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get map_title => 'Mapa węzłów';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Linia wzroku';
@@ -2116,6 +1990,17 @@ class AppLocalizationsPl extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Czy na pewno chcesz się odłączyć od tego urządzenia?';
@override
String get dialog_disconnectedTitle => 'Rozłączono';
@override
String get dialog_disconnectedMessage =>
'Zostałeś rozłączony ze swoim towarzyszem.';
@override
String get dialog_connectCompanion =>
'Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.';
@override
String get login_repeaterLogin => 'Logowanie do przekaźnika';
@@ -2181,13 +2066,68 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get common_clear => 'Wyczyść';
@override
String path_currentPath(String path) {
return 'Aktualna ścieżka: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'skoków',
many: 'skoków',
few: 'skoki',
one: 'skok',
);
return 'Użyj ścieżki $count $_temp0.';
}
@override
String get path_enterCustomPath => 'Wprowadź własną ścieżkę';
@override
String get path_currentPathLabel => 'Aktualna ścieżka';
@override
String get path_hexPrefixInstructions =>
'Wprowadź 2-znakowe prefiksy szesnastkowe dla każdego skoku, oddzielone przecinkami.';
@override
String get path_hexPrefixExample =>
'A1,F2,3C (każdy węzeł używa pierwszego bajtu swojego klucza publicznego)';
@override
String get path_labelHexPrefixes => 'Ścieżka (prefiksy hex)';
@override
String get path_helperMaxHops =>
'Maksymalnie 64 skoki. Każdy prefiks ma 2 znaki szesnastkowe (1 bajt).';
@override
String get path_selectFromContacts => 'Albo wybierz z kontaktów:';
@override
String get path_noRepeatersFound =>
'Nie znaleziono przekaźników ani serwerów pokoi.';
@override
String get path_customPathsRequire =>
'Dostosowane ścieżki wymagają pośrednich skoków, które mogą przekazywać wiadomości.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Nieprawidłowe prefiksy szesnastkowe: $prefixes';
}
@override
String get path_tooLong =>
'Ścieżka jest zbyt długa. Dozwolonych skoków wynosi 64.';
@override
String get path_setPath => 'Ustaw Ścieżkę';
@override
String get repeater_management => 'Zarządzanie przekaźnikami';
@@ -2252,6 +2192,16 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get repeater_routingMode => 'Tryb routingu';
@override
String get repeater_autoUseSavedPath =>
'Automatycznie (użyj zapisanej ścieżki)';
@override
String get repeater_forceFloodMode => 'Wymuś tryb zalewowy';
@override
String get repeater_pathManagement => 'Zarządzanie ścieżkami';
@override
String get repeater_refresh => 'Odśwież';
@@ -3349,139 +3299,6 @@ class AppLocalizationsPl extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Wejście cyfrowe';
@override
String get telemetry_digitalOutputLabel => 'Wyjście cyfrowe';
@override
String get telemetry_analogInputLabel => 'Wejście analogowe';
@override
String get telemetry_analogOutputLabel => 'Wyjście analogowe';
@override
String get telemetry_genericLabel => 'Czujnik ogólny';
@override
String get telemetry_luminosityLabel => 'Jasność';
@override
String get telemetry_presenceLabel => 'Obecność';
@override
String get telemetry_humidityLabel => 'Wilgotność';
@override
String get telemetry_accelerometerLabel => 'Akcelerometr';
@override
String get telemetry_pressureLabel => 'Ciśnienie';
@override
String get telemetry_altitudeLabel => 'Wysokość';
@override
String get telemetry_frequencyLabel => 'Częstotliwość';
@override
String get telemetry_percentageLabel => 'Procent';
@override
String get telemetry_concentrationLabel => 'Stężenie';
@override
String get telemetry_powerLabel => 'Moc';
@override
String get telemetry_distanceLabel => 'Odległość';
@override
String get telemetry_energyLabel => 'Energia';
@override
String get telemetry_directionLabel => 'Kierunek';
@override
String get telemetry_timeLabel => 'Czas';
@override
String get telemetry_gyrometerLabel => 'Żyrometr';
@override
String get telemetry_colourLabel => 'Kolor';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Przełącznik';
@override
String get telemetry_polylineLabel => 'Polilinia';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Liczba żądań';
@override
String get telemetry_error => 'Nie udało się pobrać danych';
@override
String get neighbors_receivedData => 'Otrzymano dane sąsiedztwa';
@@ -4378,17 +4195,6 @@ class AppLocalizationsPl extends AppLocalizations {
String get translation_composerSubtitle =>
'Kontroluje domyślny stan ikony tłumaczenia w edytorze.';
@override
String get translation_autoIncomingTitle =>
'Automatycznie tłumacz wiadomości';
@override
String get translation_autoIncomingSubtitle =>
'Automatycznie tłumaczy wiadomości do powiadomień oraz do czatów lub kanałów.';
@override
String get translation_translateMessage => 'Przetłumacz wiadomość';
@override
String get translation_targetLanguage => 'Język docelowy';
@@ -4514,135 +4320,4 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Przybliż';
@override
String get map_zoomOut => 'Przybliż z powrotem';
@override
String get map_centerMap => 'Mapa centrum';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth wymaga przeglądarki Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS potwierdzone';
@override
String get pathTrace_legendInferred => 'Wywnioskowana pozycja';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+147 -475
View File
@@ -92,24 +92,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get common_disable => 'Desativar';
@override
String get common_undo => 'Desfazer';
@override
String get messageStatus_sent => 'Enviado';
@override
String get messageStatus_delivered => 'Entregue';
@override
String get messageStatus_pending => 'Enviar';
@override
String get messageStatus_failed => 'Falhou ao enviar';
@override
String get messageStatus_repeated => 'Ouvi repetidamente';
@override
String get common_reboot => 'Reiniciar';
@@ -129,12 +111,6 @@ class AppLocalizationsPt extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Atualização automática';
@override
String get common_interval => 'Intervalo';
@override
String get scanner_title => 'MeshCore: Versão aberta';
@@ -320,10 +296,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Ative o Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Mudar rapidamente';
@@ -815,6 +787,11 @@ class AppLocalizationsPt extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Número de tentativas de reenvio antes de classificar uma mensagem como falha.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Bateria';
@@ -1016,15 +993,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get contacts_newGroup => 'Novo Grupo';
@override
String get contacts_moreOptions => 'Mais opções';
@override
String get contacts_searchOpen => 'Pesquisar contatos';
@override
String get contacts_searchClose => 'Pesquisa avançada';
@override
String get contacts_groupName => 'Nome do grupo';
@@ -1504,6 +1472,34 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Espaço Hexadecimal:';
@override
String get chat_pathManagement => 'Gerenciamento de Caminhos';
@override
String get chat_ShowAllPaths => 'Mostrar todos os caminhos';
@override
String get chat_routingMode => 'Modo de roteamento';
@override
String get chat_autoUseSavedPath => 'Auto (usar caminho salvo)';
@override
String get chat_forceFloodMode => 'Modo de Inundação Forçado';
@override
String get chat_recentAckPaths => 'Rotas de ACK Recentes (toque para usar):';
@override
String get chat_pathHistoryFull =>
'O histórico está cheio. Remova entradas para adicionar novas.';
@override
String get chat_hopSingular => 'pule';
@override
String get chat_hopPlural => 'salta';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1515,6 +1511,12 @@ class AppLocalizationsPt extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Sucessos';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Remover caminho';
@@ -1522,148 +1524,53 @@ class AppLocalizationsPt extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Ainda não há histórico de caminhos.\nEnvie uma mensagem para descobrir caminhos.';
@override
String get chat_pathActions => 'Ações do Caminho:';
@override
String get chat_setCustomPath => 'Definir Caminho Personalizado';
@override
String get chat_setCustomPathSubtitle =>
'Especifique manualmente o caminho de roteamento';
@override
String get chat_clearPath => 'Limpar Caminho';
@override
String get chat_clearPathSubtitle =>
'Forçar a descoberta na próxima transmissão';
@override
String get chat_pathCleared =>
'Caminho limpo. A próxima mensagem redescobrirá a rota.';
@override
String get chat_floodModeSubtitle =>
'Use a chave de roteamento na barra de ferramentas';
@override
String get chat_floodModeEnabled =>
'Modo de inundação ativado. Desative-o novamente através do ícone de roteamento na barra de ferramentas.';
@override
String get chat_fullPath => 'Caminho Completo';
@override
String get routing_title => 'Rotas';
String get chat_pathDetailsNotAvailable =>
'Os detalhes do caminho ainda não estão disponíveis. Tente enviar uma mensagem para atualizar.';
@override
String get routing_modeAuto => 'Carro';
@override
String get routing_modeFlood => 'Inundação';
@override
String get routing_modeManual => 'Manual';
@override
String get routing_modeAutoHint =>
'Seleciona automaticamente o caminho mais conhecido, e, se nenhum caminho conhecido for encontrado, utiliza a estratégia de \"inundação\".';
@override
String get routing_modeFloodHint =>
'Transmissão através de todos os repetidores. É a opção mais confiável, mas utiliza mais tempo de transmissão.';
@override
String get routing_modeManualHint =>
'Sempre segue exatamente o caminho que você define.';
@override
String get routing_currentRoute => 'Rota atual';
@override
String get routing_directNoHops => 'Direto sem saltos de repetidor';
@override
String get routing_noPathYet =>
'Ainda não há um caminho definido. A mensagem continua a ser enviada até que uma rota seja encontrada.';
@override
String get routing_floodBroadcast =>
'Transmissão através de todos os repetidores';
@override
String get routing_editPath => 'Editar caminho';
@override
String get routing_forgetPath => 'Esqueça o caminho';
@override
String get routing_knownPaths => 'Rotas conhecidas';
@override
String get routing_knownPathsHint =>
'Toque em um caminho para alternar para ele.';
@override
String get routing_inUse => 'Em uso';
@override
String get routing_qualityStrong => 'Primeiro salto notável';
@override
String get routing_qualityGood => 'Primeiro salto bem-sucedido';
@override
String get routing_qualityFair => 'Primeira etapa bem-sucedida';
@override
String get routing_qualityWorked => 'Foi entregue';
@override
String get routing_qualityFlood =>
'Informação obtida através de relatos generalizados.';
@override
String get routing_qualityUntested => 'Não testado';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Caminho definido: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nunca confirmado';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery =>
'Entrega em áreas afetadas por inundações';
@override
String get pathEditor_title => 'Criar Caminho';
@override
String pathEditor_hopCounter(int count) {
return '$count de 64 gramas de lúpulo';
}
@override
String get pathEditor_noHops =>
'Ainda não há lúpulos adicionados. Clique nos repetidores abaixo para adicioná-los na ordem desejada, ou salve sem adicionar lúpulos para enviar diretamente.';
@override
String get pathEditor_addHops => 'Adicione os lúpulos na seguinte ordem.';
@override
String get pathEditor_searchRepeaters => 'Encontrar repetidores';
@override
String get pathEditor_advancedHex => 'Avançado: caminho hexadecimal bruto';
@override
String get pathEditor_hexLabel => 'Prefixos hexadecimais';
@override
String get pathEditor_hexHelper =>
'Dois caracteres hexadecimais por salto, separados por vírgulas.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Inválido: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Máximo de 64 saltos';
@override
String get pathEditor_usePath => 'Utilize este caminho.';
@override
String get pathEditor_removeHop => 'Remova o lúpulo';
@override
String get pathEditor_unknownHop => 'Repetidor desconhecido';
@override
String get chat_pathSavedLocally =>
'Salvo localmente. Conectar para sincronizar.';
@@ -1738,39 +1645,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get map_title => 'Mapa de Nós';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Linha de visão';
@@ -2099,6 +1973,17 @@ class AppLocalizationsPt extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Tem certeza de que deseja desconectar deste dispositivo?';
@override
String get dialog_disconnectedTitle => 'Desconectado';
@override
String get dialog_disconnectedMessage =>
'Você foi desconectado do seu companheiro.';
@override
String get dialog_connectCompanion =>
'Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.';
@override
String get login_repeaterLogin => 'Login ao Repetidor';
@@ -2164,13 +2049,66 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get common_clear => 'Limpar';
@override
String path_currentPath(String path) {
return 'Caminho atual: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Usando $count $_temp0 caminho';
}
@override
String get path_enterCustomPath => 'Insira Caminho Personalizado';
@override
String get path_currentPathLabel => 'Caminho atual';
@override
String get path_hexPrefixInstructions =>
'Insira os prefixos hexadecimais de 2 caracteres para cada salto, separados por vírgulas.';
@override
String get path_hexPrefixExample =>
'A1,F2,3C (cada nó usa o primeiro byte de sua chave pública)';
@override
String get path_labelHexPrefixes => 'Prefixo Hexadecimal';
@override
String get path_helperMaxHops =>
'Máximo de 64 saltos. Cada prefixo tem 2 caracteres hexadecimais (1 byte)';
@override
String get path_selectFromContacts => 'Ou selecione de contatos:';
@override
String get path_noRepeatersFound =>
'Não foram encontrados repetidores ou servidores de sala.';
@override
String get path_customPathsRequire =>
'Caminhos personalizados exigem saltos intermediários que podem transmitir mensagens.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Prefixos hexadecimais inválidos: $prefixes';
}
@override
String get path_tooLong =>
'Caminho muito longo. Máximo de 64 saltos permitidos.';
@override
String get path_setPath => 'Definir Caminho';
@override
String get repeater_management => 'Gerenciamento de Repetidor';
@@ -2235,6 +2173,15 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get repeater_routingMode => 'Modo de roteamento';
@override
String get repeater_autoUseSavedPath => 'Auto (usar caminho salvo)';
@override
String get repeater_forceFloodMode => 'Modo de Inundação Forçado';
@override
String get repeater_pathManagement => 'Gerenciamento de caminhos';
@override
String get repeater_refresh => 'Atualizar';
@@ -3333,139 +3280,6 @@ class AppLocalizationsPt extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Entrada digital';
@override
String get telemetry_digitalOutputLabel => 'Saída digital';
@override
String get telemetry_analogInputLabel => 'Entrada analógica';
@override
String get telemetry_analogOutputLabel => 'Saída analógica';
@override
String get telemetry_genericLabel => 'Sensor genérico';
@override
String get telemetry_luminosityLabel => 'Luminosidade';
@override
String get telemetry_presenceLabel => 'Presença';
@override
String get telemetry_humidityLabel => 'Humidade';
@override
String get telemetry_accelerometerLabel => 'Acelerómetro';
@override
String get telemetry_pressureLabel => 'Pressão';
@override
String get telemetry_altitudeLabel => 'Altitude';
@override
String get telemetry_frequencyLabel => 'Frequência';
@override
String get telemetry_percentageLabel => 'Percentagem';
@override
String get telemetry_concentrationLabel => 'Concentração';
@override
String get telemetry_powerLabel => 'Potência';
@override
String get telemetry_distanceLabel => 'Distância';
@override
String get telemetry_energyLabel => 'Energia';
@override
String get telemetry_directionLabel => 'Direção';
@override
String get telemetry_timeLabel => 'Hora';
@override
String get telemetry_gyrometerLabel => 'Girómetro';
@override
String get telemetry_colourLabel => 'Cor';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Interruptor';
@override
String get telemetry_polylineLabel => 'Polilinha';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Número de solicitações';
@override
String get telemetry_error => 'Não foi possível obter os dados';
@override
String get neighbors_receivedData => 'Dados dos Vizinhos Recebidos';
@@ -4356,17 +4170,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get translation_composerSubtitle =>
'Controla o estado padrão do ícone de tradução do compositor.';
@override
String get translation_autoIncomingTitle =>
'Traduzir mensagens automaticamente';
@override
String get translation_autoIncomingSubtitle =>
'Traduz automaticamente mensagens para notificações e para chats ou canais.';
@override
String get translation_translateMessage => 'Traduzir mensagem';
@override
String get translation_targetLanguage => 'Língua-alvo';
@@ -4493,135 +4296,4 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Ampliar';
@override
String get map_zoomOut => 'Ampliar';
@override
String get map_centerMap => 'Mapa do centro';
@override
String get chrome_bluetoothRequiresChromium =>
'O Web Bluetooth requer um navegador Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS confirmado';
@override
String get pathTrace_legendInferred => 'Posição inferida';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+151 -477
View File
@@ -92,24 +92,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get common_disable => 'Выключить';
@override
String get common_undo => 'Отменить';
@override
String get messageStatus_sent => 'Отправлено';
@override
String get messageStatus_delivered => 'Доставлено';
@override
String get messageStatus_pending => 'Отправка';
@override
String get messageStatus_failed => 'Не удалось отправить';
@override
String get messageStatus_repeated => 'Услышал несколько раз';
@override
String get common_reboot => 'Перезагрузить';
@@ -129,12 +111,6 @@ class AppLocalizationsRu extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Автообновление';
@override
String get common_interval => 'Интервал';
@override
String get scanner_title => 'MeshCore Open';
@@ -320,10 +296,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Включите Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Быстрое переключение';
@@ -817,6 +789,11 @@ class AppLocalizationsRu extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Батарея';
@@ -1017,15 +994,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get contacts_newGroup => 'Новая группа';
@override
String get contacts_moreOptions => 'Больше вариантов';
@override
String get contacts_searchOpen => 'Найти контакты';
@override
String get contacts_searchClose => 'Закрыть поиск';
@override
String get contacts_groupName => 'Имя группы';
@@ -1505,6 +1473,35 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Шестнадцатеричный дамп:';
@override
String get chat_pathManagement => 'Управление маршрутами';
@override
String get chat_ShowAllPaths => 'Показать все пути';
@override
String get chat_routingMode => 'Режим маршрутизации';
@override
String get chat_autoUseSavedPath => 'Авто (использовать сохранённый маршрут)';
@override
String get chat_forceFloodMode => 'Принудительный режим рассылки';
@override
String get chat_recentAckPaths =>
'Недавние подтверждённые маршруты (нажмите, чтобы использовать):';
@override
String get chat_pathHistoryFull =>
'История маршрутов заполнена. Удалите записи, чтобы добавить новые.';
@override
String get chat_hopSingular => 'хоп';
@override
String get chat_hopPlural => 'хопов';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1518,6 +1515,12 @@ class AppLocalizationsRu extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'успешно';
@override
String get chat_score => 'Оценка';
@override
String get chat_removePath => 'Удалить маршрут';
@@ -1525,150 +1528,54 @@ class AppLocalizationsRu extends AppLocalizations {
String get chat_noPathHistoryYet =>
'История маршрутов пока пуста.\nОтправьте сообщение, чтобы обнаружить маршруты.';
@override
String get chat_pathActions => 'Действия с маршрутом:';
@override
String get chat_setCustomPath => 'Указать маршрут вручную';
@override
String get chat_setCustomPathSubtitle => 'Вручную задать маршрут передачи';
@override
String get chat_clearPath => 'Очистить маршрут';
@override
String get chat_clearPathSubtitle =>
'Принудительно обновить маршрут при следующей отправке';
@override
String get chat_pathCleared =>
'Маршрут очищен. Следующее сообщение обновит маршрут.';
@override
String get chat_floodModeSubtitle =>
'Используйте переключатель маршрутизации в панели приложения';
@override
String get chat_floodModeEnabled =>
'Режим рассылки включён. Отключите через значок маршрутизации в панели приложения.';
@override
String get chat_fullPath => 'Полный маршрут';
@override
String get routing_title => 'Маршрутизация';
String get chat_pathDetailsNotAvailable =>
'Детали маршрута ещё недоступны. Попробуйте отправить сообщение для обновления.';
@override
String get routing_modeAuto => 'Авто';
@override
String get routing_modeFlood => 'Наводнение';
@override
String get routing_modeManual => 'Инструкция';
@override
String get routing_modeAutoHint =>
'Автоматически выбирает наиболее известный путь, и если такой путь неизвестен, использует алгоритм поиска пути.';
@override
String get routing_modeFloodHint =>
'Передача сигнала через все ретрансляторы. Самый надежный способ, но требует больше времени на передачу.';
@override
String get routing_modeManualHint =>
'Всегда следует точно по указанному вами маршруту.';
@override
String get routing_currentRoute => 'Текущий маршрут';
@override
String get routing_directNoHops =>
'Прямое соединение – без использования ретрансляторов';
@override
String get routing_noPathYet =>
'Пока нет пути. Следующее сообщение будет отправлено до тех пор, пока не будет обнаружен маршрут.';
@override
String get routing_floodBroadcast => 'Транслируется через все ретрансляторы';
@override
String get routing_editPath => 'Изменить путь';
@override
String get routing_forgetPath => 'Забудьте о маршруте';
@override
String get routing_knownPaths => 'Известные маршруты';
@override
String get routing_knownPathsHint =>
'Создайте маршрут для переключения на этот пункт.';
@override
String get routing_inUse => 'В эксплуатации';
@override
String get routing_qualityStrong => 'Сильный первый скачок';
@override
String get routing_qualityGood => 'Хорошее начало';
@override
String get routing_qualityFair => 'Первый хороший урожай';
@override
String get routing_qualityWorked => 'Осуществлено';
@override
String get routing_qualityFlood =>
'Узнал из новостей, распространяющихся в интернете.';
@override
String get routing_qualityUntested => 'Непроверенный';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'хопов',
many: 'хопов',
few: 'хопа',
one: 'хоп',
);
return 'Маршрут установлен: $hopCount $_temp0$status';
}
@override
String get routing_neverWorked => 'никогда не было подтверждено';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Доставка при затоплении';
@override
String get pathEditor_title => 'Создать маршрут';
@override
String pathEditor_hopCounter(int count) {
return '$count из 64 хмеля';
}
@override
String get pathEditor_noHops =>
'На данный момент хмель еще не добавлен. Чтобы добавить его, нажмите на соответствующие кнопки ниже в нужном порядке, или сохраните рецепт без хмеля, чтобы отправить его напрямую.';
@override
String get pathEditor_addHops =>
'Добавляйте хмель в соответствии с указанным порядком.';
@override
String get pathEditor_searchRepeaters => 'Поиск повторителей';
@override
String get pathEditor_advancedHex =>
'Продвинутый уровень: прямой путь в шестнадцатеричном формате';
@override
String get pathEditor_hexLabel => 'Префиксы шестнадцатеричной системы';
@override
String get pathEditor_hexHelper =>
'Два шестнадцатеричных символа на каждом шаге, разделенные запятыми.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Неверно: $tokens';
}
@override
String get pathEditor_tooManyHops =>
'Максимальное количество ингредиентов – 64';
@override
String get pathEditor_usePath => 'Используйте этот путь';
@override
String get pathEditor_removeHop => 'Удалить хмель';
@override
String get pathEditor_unknownHop => 'Неизвестный ретранслятор';
@override
String get chat_pathSavedLocally =>
'Сохранено локально. Подключитесь для синхронизации.';
@@ -1743,39 +1650,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get map_title => 'Карта нод';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Линия видимости';
@@ -2103,6 +1977,17 @@ class AppLocalizationsRu extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Вы уверены, что хотите отключиться от этого устройства?';
@override
String get dialog_disconnectedTitle => 'Отключено';
@override
String get dialog_disconnectedMessage =>
'Вы были отключены от вашего компаньона.';
@override
String get dialog_connectCompanion =>
'Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.';
@override
String get login_repeaterLogin => 'Вход в репитер';
@@ -2169,12 +2054,66 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get common_clear => 'Очистить';
@override
String path_currentPath(String path) {
return 'Текущий маршрут: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'хопов',
many: 'хопов',
few: 'хопа',
one: 'хоп',
);
return 'Используется маршрут из $count $_temp0';
}
@override
String get path_enterCustomPath => 'Введите маршрут вручную';
@override
String get path_currentPathLabel => 'Текущий маршрут';
@override
String get path_hexPrefixInstructions =>
'Введите 2-символьные шестнадцатеричные префиксы для каждого хопа, разделённые запятыми.';
@override
String get path_hexPrefixExample =>
'Пример: A1,F2,3C (каждый узел использует первый байт своего публичного ключа)';
@override
String get path_labelHexPrefixes => 'Маршрут (шестнадцатеричные префиксы)';
@override
String get path_helperMaxHops =>
'Максимум 64 хопа. Каждый префикс — 2 шестнадцатеричных символа (1 байт)';
@override
String get path_selectFromContacts => 'Или выберите из контактов:';
@override
String get path_noRepeatersFound => 'Репитеры или серверы комнат не найдены.';
@override
String get path_customPathsRequire =>
'Пользовательские маршруты требуют промежуточных узлов, способных ретранслировать сообщения.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Недопустимые шестнадцатеричные префиксы: $prefixes';
}
@override
String get path_tooLong => 'Маршрут слишком длинный. Максимум 64 хопа.';
@override
String get path_setPath => 'Установить маршрут';
@override
String get repeater_management => 'Управление репитером';
@@ -2239,6 +2178,16 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get repeater_routingMode => 'Режим маршрутизации';
@override
String get repeater_autoUseSavedPath =>
'Авто (использовать сохранённый маршрут)';
@override
String get repeater_forceFloodMode => 'Принудительный режим рассылки';
@override
String get repeater_pathManagement => 'Управление маршрутами';
@override
String get repeater_refresh => 'Обновить';
@@ -3339,139 +3288,6 @@ class AppLocalizationsRu extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Цифровой вход';
@override
String get telemetry_digitalOutputLabel => 'Цифровой выход';
@override
String get telemetry_analogInputLabel => 'Аналоговый вход';
@override
String get telemetry_analogOutputLabel => 'Аналоговый выход';
@override
String get telemetry_genericLabel => 'Общий датчик';
@override
String get telemetry_luminosityLabel => 'Освещённость';
@override
String get telemetry_presenceLabel => 'Присутствие';
@override
String get telemetry_humidityLabel => 'Влажность';
@override
String get telemetry_accelerometerLabel => 'Акселерометр';
@override
String get telemetry_pressureLabel => 'Давление';
@override
String get telemetry_altitudeLabel => 'Высота';
@override
String get telemetry_frequencyLabel => 'Частота';
@override
String get telemetry_percentageLabel => 'Процент';
@override
String get telemetry_concentrationLabel => 'Концентрация';
@override
String get telemetry_powerLabel => 'Мощность';
@override
String get telemetry_distanceLabel => 'Расстояние';
@override
String get telemetry_energyLabel => 'Энергия';
@override
String get telemetry_directionLabel => 'Направление';
@override
String get telemetry_timeLabel => 'Время';
@override
String get telemetry_gyrometerLabel => 'Гирометр';
@override
String get telemetry_colourLabel => 'Цвет';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Переключатель';
@override
String get telemetry_polylineLabel => 'Полилиния';
@override
String telemetry_altitudeValue(String meters) {
return '$meters м';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Гц';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa гПа';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux лк';
}
@override
String telemetry_powerValue(String watts) {
return '$watts Вт';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters м';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours кВт⋅ч';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Количество запросов';
@override
String get telemetry_error => 'Не удалось получить данные';
@override
String get neighbors_receivedData => 'Полученные данные о соседях';
@@ -4372,17 +4188,6 @@ class AppLocalizationsRu extends AppLocalizations {
String get translation_composerSubtitle =>
'Управляет исходным состоянием значка перевода, предоставляемого редактором.';
@override
String get translation_autoIncomingTitle =>
'Автоматически переводить сообщения';
@override
String get translation_autoIncomingSubtitle =>
'Автоматически переводит сообщения для уведомлений, а также для чатов и каналов.';
@override
String get translation_translateMessage => 'Перевести сообщение';
@override
String get translation_targetLanguage => 'Целевой язык';
@@ -4509,135 +4314,4 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get contact_typeUnknown => 'Неизвестно';
@override
String get map_zoomIn => 'Увеличить масштаб';
@override
String get map_zoomOut => 'Увеличить масштаб';
@override
String get map_centerMap => 'Карта центра';
@override
String get chrome_bluetoothRequiresChromium =>
'Для работы Web Bluetooth требуется браузер на основе Chromium.';
@override
String channels_communityShortId(String id) {
return 'Идентификатор: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS подтверждено';
@override
String get pathTrace_legendInferred => 'Выведенная позиция';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+148 -474
View File
@@ -92,24 +92,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get common_disable => 'Zakázať';
@override
String get common_undo => 'Zrušiť';
@override
String get messageStatus_sent => 'Odoslané';
@override
String get messageStatus_delivered => 'Doručené';
@override
String get messageStatus_pending => 'Odoslanie';
@override
String get messageStatus_failed => 'Neúspešné odeslanie';
@override
String get messageStatus_repeated => 'Slyšal som to opakovane';
@override
String get common_reboot => 'Restartovať';
@@ -129,12 +111,6 @@ class AppLocalizationsSk extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Automatické obnovenie';
@override
String get common_interval => 'Časový interval';
@override
String get scanner_title => 'MeshCore Verzia pre verejnosť';
@@ -319,10 +295,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Povolte Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Rýchle prepínač';
@@ -804,6 +776,11 @@ class AppLocalizationsSk extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Počet pokusov o odošleť pred označením správy ako neúspešnej';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Batéria';
@@ -1005,15 +982,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get contacts_newGroup => 'Nová skupina';
@override
String get contacts_moreOptions => 'Ďalšie možnosti';
@override
String get contacts_searchOpen => 'Vyhľadajte kontakty';
@override
String get contacts_searchClose => 'Zavrieť vyhľadávanie';
@override
String get contacts_groupName => 'Názov skupiny';
@@ -1495,6 +1463,35 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Hexová analýza:';
@override
String get chat_pathManagement => 'Správa ciest';
@override
String get chat_ShowAllPaths => 'Zobraziť všetky cesty';
@override
String get chat_routingMode => 'Režim trasy';
@override
String get chat_autoUseSavedPath => 'Použiť uloženú cestu';
@override
String get chat_forceFloodMode =>
'Zavrieť režim núdzového povodňového režimu';
@override
String get chat_recentAckPaths => 'Nedávne cesty ACK (klepni na použitie):';
@override
String get chat_pathHistoryFull =>
'História ciest je plná. Odstráňte záznamy, aby ste mohli pridať nové.';
@override
String get chat_hopSingular => 'Skok';
@override
String get chat_hopPlural => 'Skákať';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1506,6 +1503,12 @@ class AppLocalizationsSk extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Úspechy';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Odstrániť cestu';
@@ -1513,148 +1516,52 @@ class AppLocalizationsSk extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Zatiaľ žiadna história trás.\nPošlite správu a objavte trasy.';
@override
String get chat_pathActions => 'Cesty:';
@override
String get chat_setCustomPath => 'Nastaviť vlastnú cestu';
@override
String get chat_setCustomPathSubtitle => 'Ručne zadajte trasu.';
@override
String get chat_clearPath => 'Vyčistiš cestu';
@override
String get chat_clearPathSubtitle =>
'Znovu nájsť vynútene pri nasledujúcej pošlite';
@override
String get chat_pathCleared =>
'Cesta vyčistená. Nasledujúce prepočetné získa trasu znova.';
@override
String get chat_floodModeSubtitle =>
'Použite prepínanie trasy v navigačnom paneli.';
@override
String get chat_floodModeEnabled =>
'Odosporňovacia prevádzka je zapnutá. Vypnite ju znova cez ikonu routovania v navigačnom páse.';
@override
String get chat_fullPath => 'Celá cesta';
@override
String get routing_title => 'Navigácia';
String get chat_pathDetailsNotAvailable =>
'Podrobnosti o ceste zatiaľ dostupné nie sú. Skúste poslať správu na obnovenie.';
@override
String get routing_modeAuto => 'Auto';
@override
String get routing_modeFlood => 'Povodňová vlna';
@override
String get routing_modeManual => 'Ručná príručka';
@override
String get routing_modeAutoHint =>
'Automaticky vyberá najznámejší trasa, a ak žiadna nie je známa, použije náhodnú trasu.';
@override
String get routing_modeFloodHint =>
'Prenos prostredníctvom všetkých opakovačov. Najspoľahlivejší spôsob, ale vyžaduje viac času vysielania.';
@override
String get routing_modeManualHint =>
'Vždy dodáva presne podľa zadaného trasy.';
@override
String get routing_currentRoute => 'Aktuálna trasa';
@override
String get routing_directNoHops => 'Priamo bez prechodných trás';
@override
String get routing_noPathYet =>
'Zatiaľ neexistuje žiadna cesta. Nasledujúce správy budú pokračovať, kým sa nenájde trasa.';
@override
String get routing_floodBroadcast =>
'Prenos prostredníctvom každého opakovača';
@override
String get routing_editPath => 'Upraviť trasu';
@override
String get routing_forgetPath => 'Zabudnite na trasu';
@override
String get routing_knownPaths => 'Známe cesty';
@override
String get routing_knownPathsHint =>
'Kliknite na cestu, aby ste sa k nej presunuli.';
@override
String get routing_inUse => 'V prevádzke';
@override
String get routing_qualityStrong => 'Silný prvý krok';
@override
String get routing_qualityGood => 'Úspešný prvý krok';
@override
String get routing_qualityFair => 'Prvá, spravodlivá fáza';
@override
String get routing_qualityWorked => 'Dosiahnutý úspech';
@override
String get routing_qualityFlood =>
'Zistil som to z informácií, ktoré som získal v dôsledku povodňovej situácie.';
@override
String get routing_qualityUntested => 'Neotestované';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Cesta nastavená: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nikedy nebolo potvrdené';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Doručenie v prípade povodní';
@override
String get pathEditor_title => 'Vytvorenie cesty';
@override
String pathEditor_hopCounter(int count) {
return '$count z 64 chmelových zŕš';
}
@override
String get pathEditor_noHops =>
'Zatiaľ žiadne chmel. Kliknite na opakované, aby ste ich pridali postupne, alebo uložte bez chmelu, aby ste ho mohli poslať priamo.';
@override
String get pathEditor_addHops => 'Pridávajte chmel podľa zadaného poriadku.';
@override
String get pathEditor_searchRepeaters => 'Hľadať opakované';
@override
String get pathEditor_advancedHex => 'Pokročilé: pôvodná hexová cesta';
@override
String get pathEditor_hexLabel => 'Prefiksy pre hexadecimálne čísla';
@override
String get pathEditor_hexHelper =>
'Dve hexové čísla na každý krok, oddelené čiarkami';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Neplatné: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maximálne 64 krokov';
@override
String get pathEditor_usePath => 'Použite túto cestu';
@override
String get pathEditor_removeHop => 'Odstráňte chmel';
@override
String get pathEditor_unknownHop =>
'Neznáme zariadenie na opakované vysielanie';
@override
String get chat_pathSavedLocally =>
'Uložené lokálne. Spojte sa na synchronizáciu.';
@@ -1730,39 +1637,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get map_title => 'Mapa uzlov';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Úroveň výhľadu';
@@ -2089,6 +1963,17 @@ class AppLocalizationsSk extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Ste si istý/á, že chcete odpojiť od tohto zariadenia?';
@override
String get dialog_disconnectedTitle => 'Odpojené';
@override
String get dialog_disconnectedMessage =>
'Od vášho spoločníka ste boli odpojený.';
@override
String get dialog_connectCompanion =>
'Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.';
@override
String get login_repeaterLogin => 'Opätovné prihlásenie';
@@ -2155,13 +2040,66 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get common_clear => 'Zmazať';
@override
String path_currentPath(String path) {
return 'Aktívna cesta: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Používa $count $_temp0 cestu';
}
@override
String get path_enterCustomPath => 'Zadajte vlastný priebeh';
@override
String get path_currentPathLabel => 'Aktuálny priebeh';
@override
String get path_hexPrefixInstructions =>
'Zadajte 2-miestne hexové predpony pre každú fázu, oddelené čiarkami.';
@override
String get path_hexPrefixExample =>
'A1,F2,3C (každý uzel používa prvý bajt svojho verejného kľúča)';
@override
String get path_labelHexPrefixes => 'Cesty (hexové predpony)';
@override
String get path_helperMaxHops =>
'Max 64 skokov. Každý prefix je 2 hexadecimálne znaky (1 bajt).';
@override
String get path_selectFromContacts => 'Vyberte sa z kontaktov:';
@override
String get path_noRepeatersFound =>
'Nenašli sa žiadne opakovače ani serverové miestnosti.';
@override
String get path_customPathsRequire =>
'Vlastné cesty vyžadujú medziletoch, ktoré môžu prenášať správky.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Neplatné hexové predpony: $prefixes';
}
@override
String get path_tooLong =>
'Cesta je príliš dlhá. Umožnené je maximum 64 skokov.';
@override
String get path_setPath => 'Nastaviť cestu';
@override
String get repeater_management => 'Správa opakérov';
@@ -2226,6 +2164,16 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get repeater_routingMode => 'Režim trasy';
@override
String get repeater_autoUseSavedPath => 'Použiť uloženú cestu';
@override
String get repeater_forceFloodMode =>
'Zavrieť režim núdzového povodňového režimu';
@override
String get repeater_pathManagement => 'Správa trás';
@override
String get repeater_refresh => 'Obnoviť';
@@ -3318,139 +3266,6 @@ class AppLocalizationsSk extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Digitálny vstup';
@override
String get telemetry_digitalOutputLabel => 'Digitálny výstup';
@override
String get telemetry_analogInputLabel => 'Analógový vstup';
@override
String get telemetry_analogOutputLabel => 'Analógový výstup';
@override
String get telemetry_genericLabel => 'Všeobecný senzor';
@override
String get telemetry_luminosityLabel => 'Osvetlenie';
@override
String get telemetry_presenceLabel => 'Prítomnosť';
@override
String get telemetry_humidityLabel => 'Vlhkosť';
@override
String get telemetry_accelerometerLabel => 'Akcelerometer';
@override
String get telemetry_pressureLabel => 'Tlak';
@override
String get telemetry_altitudeLabel => 'Nadmorská výška';
@override
String get telemetry_frequencyLabel => 'Frekvencia';
@override
String get telemetry_percentageLabel => 'Percento';
@override
String get telemetry_concentrationLabel => 'Koncentrácia';
@override
String get telemetry_powerLabel => 'Výkon';
@override
String get telemetry_distanceLabel => 'Vzdialenosť';
@override
String get telemetry_energyLabel => 'Energia';
@override
String get telemetry_directionLabel => 'Smer';
@override
String get telemetry_timeLabel => 'Čas';
@override
String get telemetry_gyrometerLabel => 'Gyrometer';
@override
String get telemetry_colourLabel => 'Farba';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Prepínač';
@override
String get telemetry_polylineLabel => 'Lomená čiara';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Počet požiadaviek';
@override
String get telemetry_error => 'Nepodarilo sa získať údaje';
@override
String get neighbors_receivedData => 'Obdielo dáta suseda';
@@ -4337,16 +4152,6 @@ class AppLocalizationsSk extends AppLocalizations {
String get translation_composerSubtitle =>
'Riadi výchoce stav ikony pre preklad, ktorú používa program.';
@override
String get translation_autoIncomingTitle => 'Automaticky prekladať správy';
@override
String get translation_autoIncomingSubtitle =>
'Automaticky prekladá správy pre upozornenia aj pre čet alebo kanál.';
@override
String get translation_translateMessage => 'Preložiť správu';
@override
String get translation_targetLanguage => 'Cieľový jazyk';
@@ -4475,135 +4280,4 @@ class AppLocalizationsSk extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Zväčšiť';
@override
String get map_zoomOut => 'Zmenť zamer zblízka';
@override
String get map_centerMap => 'Mapa centra';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth vyžaduje prehliadač Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS potvrdilo';
@override
String get pathTrace_legendInferred => 'Odvodená poloha';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+144 -471
View File
@@ -92,25 +92,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get common_disable => 'Izklopiti';
@override
String get common_undo => 'Preobrn';
@override
String get messageStatus_sent => 'Pošljeno';
@override
String get messageStatus_delivered => 'Dostavljeno';
@override
String get messageStatus_pending => 'Pošiljanje';
@override
String get messageStatus_failed =>
'Uspešno ni bilo mogo, da se sporočilo pošlje';
@override
String get messageStatus_repeated => 'Slišal sem večkrat';
@override
String get common_reboot => 'Ponoviti';
@@ -130,12 +111,6 @@ class AppLocalizationsSl extends AppLocalizations {
return '$percent %';
}
@override
String get common_autoRefresh => 'Samodejno osveževanje';
@override
String get common_interval => 'Časovni interval';
@override
String get scanner_title => 'MeshCore Odprto';
@@ -318,10 +293,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Omogočite Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Hitro preklop';
@@ -806,6 +777,11 @@ class AppLocalizationsSl extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Baterija';
@@ -1005,15 +981,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get contacts_newGroup => 'Nova skupina';
@override
String get contacts_moreOptions => 'Več možnosti';
@override
String get contacts_searchOpen => 'Iskanje kontaktov';
@override
String get contacts_searchClose => 'Izklopi iskanje';
@override
String get contacts_groupName => 'Ime skupine';
@@ -1493,6 +1460,34 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Izpis heksadecimalnih vrednosti:';
@override
String get chat_pathManagement => 'Upravljanje poti';
@override
String get chat_ShowAllPaths => 'Prikaži vse poti';
@override
String get chat_routingMode => 'Navodilo za usmerjevalni način';
@override
String get chat_autoUseSavedPath => 'Avto (uporabi shranjeno pot)';
@override
String get chat_forceFloodMode => 'Nasilje obvezati v način';
@override
String get chat_recentAckPaths => 'Nedavni poti ACK (tap za uporabo):';
@override
String get chat_pathHistoryFull =>
'Zapiske o poti so popolni. Izbriši vnose, da dodaš nove.';
@override
String get chat_hopSingular => 'skok';
@override
String get chat_hopPlural => 'skokov';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1504,6 +1499,12 @@ class AppLocalizationsSl extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Uspešni';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Izbriši pot';
@@ -1511,144 +1512,51 @@ class AppLocalizationsSl extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Ni shranjenih poti.\nPošlji sporočilo za odkrivanje poti.';
@override
String get chat_pathActions => 'Potni ukazi:';
@override
String get chat_setCustomPath => 'Nastavi Prilozeno Pot';
@override
String get chat_setCustomPathSubtitle => 'Ročno določite potniško pot.';
@override
String get chat_clearPath => 'Počisti pot';
@override
String get chat_clearPathSubtitle => 'Ob naslednji pošiljanju znova zbrati.';
@override
String get chat_pathCleared =>
'Pot je očiščena. Naslednje sporočilo bo ponovno odkril pot.';
@override
String get chat_floodModeSubtitle =>
'Uporabi tipko usmerjevanja v meniju aplikacije.';
@override
String get chat_floodModeEnabled =>
'Narejena je bila omrežna modaliteta. Vklopi jo znova preko ikone v meniju aplikacije.';
@override
String get chat_fullPath => 'Polna pot';
@override
String get routing_title => 'Navigacija';
String get chat_pathDetailsNotAvailable =>
'Podrobnosti poti zaenkrat niso na voljo. Poskusite poslati sporočilo za osvežitev.';
@override
String get routing_modeAuto => 'Avto';
@override
String get routing_modeFlood => 'Poplavo';
@override
String get routing_modeManual => 'Navodilo';
@override
String get routing_modeAutoHint =>
'Samodejno izbere najbolj poznano pot, in sicer, ko ni na voljo nobena.';
@override
String get routing_modeFloodHint =>
'Prenosi preko vseh repetitorjev. Najzanesljivejši način, vendar zahteva več časa.';
@override
String get routing_modeManualHint =>
'Vedno sledi natančni poti, ki jo ste določili.';
@override
String get routing_currentRoute => 'Trenutna pot';
@override
String get routing_directNoHops => 'Neposredno brez prehodov';
@override
String get routing_noPathYet =>
'Žep trenutno ni mogoče najti. Naslednje sporočilo bo posredovano, dokler ne bo ugotovljeno, kje je pot.';
@override
String get routing_floodBroadcast => 'Prenos preko vseh repetitiv';
@override
String get routing_editPath => 'Uredi pot';
@override
String get routing_forgetPath => 'Pozabi na pot';
@override
String get routing_knownPaths => 'Poznati poti';
@override
String get routing_knownPathsHint => 'Kliknite na pot, da jo izberete.';
@override
String get routing_inUse => 'V uporabi';
@override
String get routing_qualityStrong => 'Močan prvi korak';
@override
String get routing_qualityGood => 'Prva uspešna faza';
@override
String get routing_qualityFair => 'Prva, uspešna faza';
@override
String get routing_qualityWorked => 'Izpolnil';
@override
String get routing_qualityFlood => 'Slišano preko poplave';
@override
String get routing_qualityUntested => 'Ne preizkušen';
@override
String routing_lastWorked(String when) {
return 'delal/a $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Pot nastavljen: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'nikoli ni bilo potrjeno';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Dostava zaradi poplave';
@override
String get pathEditor_title => 'Izgradnja poti';
@override
String pathEditor_hopCounter(int count) {
return '$count od 64 različnih sort hropa';
}
@override
String get pathEditor_noHops =>
'Še niso dodani hmelji. Za dodajanje hmelja v vrstnem redu kliknite na povezavo spodaj, ali pa shranite brez dodanega hmelja, da ga lahko posredujete neposredno.';
@override
String get pathEditor_addHops => 'Dodajte suho travo v skladu s postopkom.';
@override
String get pathEditor_searchRepeaters => 'Iskanje ponovitev';
@override
String get pathEditor_advancedHex => 'Napredno: surovi šestnajstni pot';
@override
String get pathEditor_hexLabel => 'Predfiks za heksadecimalno šifro';
@override
String get pathEditor_hexHelper =>
'Dva šestbitna znaka na vsak skok, ločena z vejico';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Neveljaven: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Največ 64 hopov';
@override
String get pathEditor_usePath => 'Uporabite to poto';
@override
String get pathEditor_removeHop => 'Odstranite hmelj';
@override
String get pathEditor_unknownHop => 'Neznani ponovitelj';
@override
String get chat_pathSavedLocally =>
'Shrano lokalno. Povežite se za sinhronizacijo.';
@@ -1723,39 +1631,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get map_title => 'Mapa omrežja';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Linija vida';
@@ -2086,6 +1961,17 @@ class AppLocalizationsSl extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Ste prepričani, da želite se odklopiti s tega naprave?';
@override
String get dialog_disconnectedTitle => 'Prekinjeno';
@override
String get dialog_disconnectedMessage =>
'Prekinjena povezava s vašim spre伴ovalcem.';
@override
String get dialog_connectCompanion =>
'Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.';
@override
String get login_repeaterLogin => 'Ponovni vnos';
@@ -2151,13 +2037,65 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get common_clear => 'Ponoviti';
@override
String path_currentPath(String path) {
return 'Trenutna pot: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Uporablja $count $_temp0 pot';
}
@override
String get path_enterCustomPath => 'Vnesite prilagojeno pot';
@override
String get path_currentPathLabel => 'Trenutna pot';
@override
String get path_hexPrefixInstructions =>
'Vnesite 2-karakterne heksadecimalne prefixe za vsako skopo, ločeno z zvezekami.';
@override
String get path_hexPrefixExample =>
'Primer: A1,F2,3C (vsak notranji element uporablja prvi bajt svojega javnega ključa)';
@override
String get path_labelHexPrefixes => 'Pot (heksafixne skrajšave)';
@override
String get path_helperMaxHops =>
'Maksimalno 64 skokov. Vsak prefiks je 2 heksadecimalna znamenja (1 bajt).';
@override
String get path_selectFromContacts => 'Izberi iz kontaktov:';
@override
String get path_noRepeatersFound =>
'Ne najdenih ponoviteljev ali strežnikov sob.';
@override
String get path_customPathsRequire =>
'Prilojene poti zahtevajo medhodne prenose, ki lahko prenašajo sporočila.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Neveljačni šesteročlenski prefiksi: $prefixes';
}
@override
String get path_tooLong => 'Pot je prevelika. Dovoljeno največ 64 skokov.';
@override
String get path_setPath => 'Nastavi Pot';
@override
String get repeater_management => 'Upravljanje ponovitve';
@@ -2223,6 +2161,15 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get repeater_routingMode => 'Navodilo za usmerjevalni način';
@override
String get repeater_autoUseSavedPath => 'Avto (uporabi shranjeno pot)';
@override
String get repeater_forceFloodMode => 'Nasilje obvezati v način';
@override
String get repeater_pathManagement => 'Upravljanje poti';
@override
String get repeater_refresh => 'Ponovno obnavljati';
@@ -3314,139 +3261,6 @@ class AppLocalizationsSl extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Digitalni vhod';
@override
String get telemetry_digitalOutputLabel => 'Digitalni izhod';
@override
String get telemetry_analogInputLabel => 'Analogni vhod';
@override
String get telemetry_analogOutputLabel => 'Analogni izhod';
@override
String get telemetry_genericLabel => 'Splošni senzor';
@override
String get telemetry_luminosityLabel => 'Osvetljenost';
@override
String get telemetry_presenceLabel => 'Prisotnost';
@override
String get telemetry_humidityLabel => 'Vlažnost';
@override
String get telemetry_accelerometerLabel => 'Merilnik pospeška';
@override
String get telemetry_pressureLabel => 'Tlak';
@override
String get telemetry_altitudeLabel => 'Nadmorska višina';
@override
String get telemetry_frequencyLabel => 'Frekvenca';
@override
String get telemetry_percentageLabel => 'Odstotek';
@override
String get telemetry_concentrationLabel => 'Koncentracija';
@override
String get telemetry_powerLabel => 'Moč';
@override
String get telemetry_distanceLabel => 'Razdalja';
@override
String get telemetry_energyLabel => 'Energija';
@override
String get telemetry_directionLabel => 'Smer';
@override
String get telemetry_timeLabel => 'Čas';
@override
String get telemetry_gyrometerLabel => 'Žiroskop';
@override
String get telemetry_colourLabel => 'Barva';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Stikalo';
@override
String get telemetry_polylineLabel => 'Polilinija';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Število zahtev';
@override
String get telemetry_error => 'Podatkov ni bilo mogoče pridobiti';
@override
String get neighbors_receivedData => 'Prejeto podatke o sosedih';
@@ -4336,16 +4150,6 @@ class AppLocalizationsSl extends AppLocalizations {
String get translation_composerSubtitle =>
'Ureja privzeto stanje ikone za prevod, ki jo uporablja avtor.';
@override
String get translation_autoIncomingTitle => 'Samodejno prevajaj sporočila';
@override
String get translation_autoIncomingSubtitle =>
'Samodejno prevaja sporočila za obvestila ter za klepete ali kanale.';
@override
String get translation_translateMessage => 'Prevedi sporočilo';
@override
String get translation_targetLanguage => 'Ciljna jezika';
@@ -4474,135 +4278,4 @@ class AppLocalizationsSl extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Povečaj';
@override
String get map_zoomOut => 'Povečajte pogled';
@override
String get map_centerMap => 'Krajšarska karta';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth zahteva brskalnik Chromium.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS potrdilo';
@override
String get pathTrace_legendInferred => 'Izpeljana lokacija';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+144 -471
View File
@@ -92,24 +92,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get common_disable => 'Inaktivera';
@override
String get common_undo => 'Ångra';
@override
String get messageStatus_sent => 'Sen';
@override
String get messageStatus_delivered => 'Levererad';
@override
String get messageStatus_pending => 'Skicka';
@override
String get messageStatus_failed => 'Misslyckades med att skicka';
@override
String get messageStatus_repeated => 'Hördes upprepade gånger';
@override
String get common_reboot => 'Start om';
@@ -129,12 +111,6 @@ class AppLocalizationsSv extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Automatisk uppdatering';
@override
String get common_interval => 'Intervall';
@override
String get scanner_title => 'MeshCore Öppen version';
@@ -316,10 +292,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Aktivera Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Snabb växling';
@@ -799,6 +771,11 @@ class AppLocalizationsSv extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Antal försök att skicka om ett meddelande innan det markeras som misslyckat.';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Batteri';
@@ -999,15 +976,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get contacts_newGroup => 'Ny grupp';
@override
String get contacts_moreOptions => 'Fler alternativ';
@override
String get contacts_searchOpen => 'Sök efter kontakter';
@override
String get contacts_searchClose => 'Avancerad sökning';
@override
String get contacts_groupName => 'Gruppnamn';
@@ -1486,6 +1454,35 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Hexdump:';
@override
String get chat_pathManagement => 'Stigarhantering';
@override
String get chat_ShowAllPaths => 'Visa alla vägar';
@override
String get chat_routingMode => 'Ruttläge';
@override
String get chat_autoUseSavedPath => 'Automatisk (använd sparad sökväg)';
@override
String get chat_forceFloodMode => 'Tvinga Översvämningsläge';
@override
String get chat_recentAckPaths =>
'Nyligen Ack-vägar (tryck för att använda):';
@override
String get chat_pathHistoryFull =>
'Historisk sökväg är full. Ta bort poster för att lägga till nya.';
@override
String get chat_hopSingular => 'hoppa';
@override
String get chat_hopPlural => 'hoppar';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1497,6 +1494,12 @@ class AppLocalizationsSv extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'framgångar';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => 'Ta bort sökväg';
@@ -1504,144 +1507,50 @@ class AppLocalizationsSv extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Ingen historik ännu.\nSkicka ett meddelande för att upptäcka spår.';
@override
String get chat_pathActions => 'Stigar:';
@override
String get chat_setCustomPath => 'Ange anpassad sökväg';
@override
String get chat_setCustomPathSubtitle => 'Ange ruttväg manuellt';
@override
String get chat_clearPath => 'Rensa Vägen';
@override
String get chat_clearPathSubtitle => 'Tvinga fram omstart vid nästa sändning';
@override
String get chat_pathCleared =>
'Routen är nu fri. Nästa meddelande kommer att upptäcka rutten igen.';
@override
String get chat_floodModeSubtitle => 'Använd routningsomkopplaren i appraden';
@override
String get chat_floodModeEnabled =>
'Översvämningsläge aktiverat. Stäng av via ruttikonen i appraden.';
@override
String get chat_fullPath => 'Fullständig sökväg';
@override
String get routing_title => 'Ruttplanering';
String get chat_pathDetailsNotAvailable =>
'Stigaruppgifterna är ännu inte tillgängliga. Försök att skicka ett meddelande för att uppdatera.';
@override
String get routing_modeAuto => 'Bil';
@override
String get routing_modeFlood => 'Översvämning';
@override
String get routing_modeManual => 'Instruktioner';
@override
String get routing_modeAutoHint =>
'Väljer automatiskt den bästa kända vägen, och använder en \"flooding\"-strategi om ingen väg är känd.';
@override
String get routing_modeFloodHint =>
'Sändningar via alla repetrar. Det mest pålitliga alternativet, men kräver mer sändtid.';
@override
String get routing_modeManualHint =>
'Skickar alltid den exakta väg du har angivit.';
@override
String get routing_currentRoute => 'Nuvarande rutt';
@override
String get routing_directNoHops => 'Direkt utan mellanliggande routrar';
@override
String get routing_noPathYet =>
'Ingen väg hittad ännu. Nästa meddelande skickas tills en rutt har upptäckts.';
@override
String get routing_floodBroadcast => 'Sändas via alla repetrar';
@override
String get routing_editPath => 'Redigera sökväg';
@override
String get routing_forgetPath => 'Glöm vägen';
@override
String get routing_knownPaths => 'Kända vägar';
@override
String get routing_knownPathsHint => 'Välj en väg för att byta till den.';
@override
String get routing_inUse => 'I användning';
@override
String get routing_qualityStrong => 'En stark start';
@override
String get routing_qualityGood => 'Bra första steg';
@override
String get routing_qualityFair => 'Bra första hopp';
@override
String get routing_qualityWorked => 'Har levererat';
@override
String get routing_qualityFlood => 'Fått information via nyhetsflöde';
@override
String get routing_qualityUntested => 'Ej testat';
@override
String routing_lastWorked(String when) {
return 'arbetade $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'hoppar',
one: 'hopp',
);
return 'Sökväg inställd: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'aldrig bekräftat';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Leverans vid översvämningsområde';
@override
String get pathEditor_title => 'Skapa väg';
@override
String pathEditor_hopCounter(int count) {
return '$count av 64 humlor';
}
@override
String get pathEditor_noHops =>
'Inga humle än. Använd knapparna nedan för att lägga till dem i rätt ordning, eller spara utan humle för att skicka direkt.';
@override
String get pathEditor_addHops => 'Tillsätt humlen i rätt ordning.';
@override
String get pathEditor_searchRepeaters => 'Sök efter återupptagna samtal';
@override
String get pathEditor_advancedHex => 'Avancerat: rå hex-sökväg';
@override
String get pathEditor_hexLabel => 'Hex-prefikser';
@override
String get pathEditor_hexHelper =>
'Två hex-tecken per steg, separerade med kommatecken.';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Ogiltigt: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Maximalt 64 humlörter';
@override
String get pathEditor_usePath => 'Använd denna väg';
@override
String get pathEditor_removeHop => 'Ta bort humlen';
@override
String get pathEditor_unknownHop => 'Okänd förstärkare';
@override
String get chat_pathSavedLocally =>
'Sparat lokalt. Anslut för att synkronisera.';
@@ -1716,39 +1625,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get map_title => 'Nodkarta';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Synlinje';
@@ -2074,6 +1950,17 @@ class AppLocalizationsSv extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Är du säker på att du vill koppla från enheten?';
@override
String get dialog_disconnectedTitle => 'Ansluten ej';
@override
String get dialog_disconnectedMessage =>
'Du har kopplats från din companion.';
@override
String get dialog_connectCompanion =>
'Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.';
@override
String get login_repeaterLogin => 'Återuppta Inloggning';
@@ -2139,13 +2026,65 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get common_clear => 'Rensa';
@override
String path_currentPath(String path) {
return 'Nuvarande sökväg: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
);
return 'Använda $count $_temp0 sökväg';
}
@override
String get path_enterCustomPath => 'Ange anpassad sökväg';
@override
String get path_currentPathLabel => 'Nuvarande sökväg';
@override
String get path_hexPrefixInstructions =>
'Ange 2-tecknets hex-prefett för varje hopp, åtskilda med komma.';
@override
String get path_hexPrefixExample =>
'Exempel: A1,F2,3C (varje nod använder det första bytet av sitt publika nyckel)';
@override
String get path_labelHexPrefixes => 'Hexprefixer';
@override
String get path_helperMaxHops =>
'Max 64 hopp. Varje prefix är 2 hex-tecken (1 byte)';
@override
String get path_selectFromContacts => 'Välj istället från kontakter:';
@override
String get path_noRepeatersFound =>
'Inga återuppspelare eller rumsservrar hittades.';
@override
String get path_customPathsRequire =>
'Anpassade sökvägar kräver mellansteg som kan vidarebefordra meddelanden.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Ogiltiga hex-prefikser: $prefixes';
}
@override
String get path_tooLong => 'Sökvägen är för lång. Max 64 hopp tillåtna.';
@override
String get path_setPath => 'Ange Sökväg';
@override
String get repeater_management => 'Återuppspelarens Hantering';
@@ -2210,6 +2149,15 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get repeater_routingMode => 'Ruttläge';
@override
String get repeater_autoUseSavedPath => 'Automatisk (använd sparad sökväg)';
@override
String get repeater_forceFloodMode => 'Tvinga Översvämningsläge';
@override
String get repeater_pathManagement => 'Stigarhantering';
@override
String get repeater_refresh => 'Uppdatera';
@@ -3294,139 +3242,6 @@ class AppLocalizationsSv extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Digital ingång';
@override
String get telemetry_digitalOutputLabel => 'Digital utgång';
@override
String get telemetry_analogInputLabel => 'Analog ingång';
@override
String get telemetry_analogOutputLabel => 'Analog utgång';
@override
String get telemetry_genericLabel => 'Allmän sensor';
@override
String get telemetry_luminosityLabel => 'Ljusstyrka';
@override
String get telemetry_presenceLabel => 'Närvaro';
@override
String get telemetry_humidityLabel => 'Luftfuktighet';
@override
String get telemetry_accelerometerLabel => 'Accelerometer';
@override
String get telemetry_pressureLabel => 'Tryck';
@override
String get telemetry_altitudeLabel => 'Höjd';
@override
String get telemetry_frequencyLabel => 'Frekvens';
@override
String get telemetry_percentageLabel => 'Procent';
@override
String get telemetry_concentrationLabel => 'Koncentration';
@override
String get telemetry_powerLabel => 'Effekt';
@override
String get telemetry_distanceLabel => 'Avstånd';
@override
String get telemetry_energyLabel => 'Energi';
@override
String get telemetry_directionLabel => 'Riktning';
@override
String get telemetry_timeLabel => 'Tid';
@override
String get telemetry_gyrometerLabel => 'Gyrometer';
@override
String get telemetry_colourLabel => 'Färg';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Brytare';
@override
String get telemetry_polylineLabel => 'Polylinje';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Antal förfrågningar';
@override
String get telemetry_error => 'Det gick inte att hämta data';
@override
String get neighbors_receivedData => 'Mottagna grannars data';
@@ -4309,17 +4124,6 @@ class AppLocalizationsSv extends AppLocalizations {
String get translation_composerSubtitle =>
'Styr standardtillståndet för kompositorns översättningsikon.';
@override
String get translation_autoIncomingTitle =>
'Översätt meddelanden automatiskt';
@override
String get translation_autoIncomingSubtitle =>
'Översätter meddelanden automatiskt för aviseringar och för chattar eller kanaler.';
@override
String get translation_translateMessage => 'Översätt meddelande';
@override
String get translation_targetLanguage => 'Målmedvetet språk';
@@ -4448,135 +4252,4 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => 'Zooma in';
@override
String get map_zoomOut => 'Zooma ut';
@override
String get map_centerMap => 'Kartöversikt';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth kräver en Chromium-baserad webbläsare.';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS-verifierat';
@override
String get pathTrace_legendInferred => 'Antagen position';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+151 -475
View File
@@ -92,24 +92,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get common_disable => 'Вимкнути';
@override
String get common_undo => 'Скасувати';
@override
String get messageStatus_sent => 'Надіслано';
@override
String get messageStatus_delivered => 'Доставлено';
@override
String get messageStatus_pending => 'Надсилання';
@override
String get messageStatus_failed => 'Не вдалося надіслати';
@override
String get messageStatus_repeated => 'Почув неодноразово';
@override
String get common_reboot => 'Перезавантажити';
@@ -129,12 +111,6 @@ class AppLocalizationsUk extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => 'Автооновлення';
@override
String get common_interval => 'Інтервал';
@override
String get scanner_title => 'MeshCore: Відкритий доступ';
@@ -319,10 +295,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get scanner_enableBluetooth => 'Увімкніть Bluetooth';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => 'Швидке перемикання';
@@ -811,6 +783,11 @@ class AppLocalizationsUk extends AppLocalizations {
String get appSettings_maxMessageRetriesSubtitle =>
'Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => 'Батарея';
@@ -1012,15 +989,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get contacts_newGroup => 'Нова група';
@override
String get contacts_moreOptions => 'Більше можливостей';
@override
String get contacts_searchOpen => 'Пошук контактів';
@override
String get contacts_searchClose => 'Закрити пошук';
@override
String get contacts_groupName => 'Назва групи';
@@ -1500,6 +1468,35 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get debugFrame_hexDump => 'Дамп Hex:';
@override
String get chat_pathManagement => 'Керування шляхами';
@override
String get chat_ShowAllPaths => 'Показати всі шляхи';
@override
String get chat_routingMode => 'Режим маршрутизації';
@override
String get chat_autoUseSavedPath => 'Авто (використовувати збережений шлях)';
@override
String get chat_forceFloodMode => 'Примусово через всю мережу';
@override
String get chat_recentAckPaths =>
'Підтверджені шляхи (натисніть, щоб використати):';
@override
String get chat_pathHistoryFull =>
'Історія шляхів заповнена. Видаліть записи, щоб додати нові.';
@override
String get chat_hopSingular => 'Перехід';
@override
String get chat_hopPlural => 'переходів';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
@@ -1513,6 +1510,12 @@ class AppLocalizationsUk extends AppLocalizations {
return '$count $_temp0';
}
@override
String get chat_successes => 'Успішно';
@override
String get chat_score => 'Оцінка';
@override
String get chat_removePath => 'Видалити шлях';
@@ -1520,148 +1523,54 @@ class AppLocalizationsUk extends AppLocalizations {
String get chat_noPathHistoryYet =>
'Історія шляхів недоступна.\nНадішліть повідомлення, щоб виявити шляхи.';
@override
String get chat_pathActions => 'Дії зі шляхом:';
@override
String get chat_setCustomPath => 'Встановити власний шлях';
@override
String get chat_setCustomPathSubtitle => 'Вказати шлях маршрутизації вручну';
@override
String get chat_clearPath => 'Очистити шлях';
@override
String get chat_clearPathSubtitle =>
'Примусово повторити пошук при наступному надсиланні';
@override
String get chat_pathCleared =>
'Шлях очищено. Наступне повідомлення оновить маршрут.';
@override
String get chat_floodModeSubtitle =>
'Використовувати перемикач маршрутизації в панелі застосунку';
@override
String get chat_floodModeEnabled =>
'Увімкнено режим «через всю мережу». Перемикайте через іконку маршрутизації на панелі інструментів.';
@override
String get chat_fullPath => 'Повний шлях';
@override
String get routing_title => 'Маршрутизація';
String get chat_pathDetailsNotAvailable =>
'Деталі шляху ще недоступні. Спробуйте надіслати повідомлення для оновлення.';
@override
String get routing_modeAuto => 'Автомобіль';
@override
String get routing_modeFlood => 'Повені';
@override
String get routing_modeManual => 'Інструкція';
@override
String get routing_modeAutoHint =>
'Автоматично обирає найкращий відомий шлях, та у разі відсутності відомого шляху, використовує алгоритм \"занурення\".';
@override
String get routing_modeFloodHint =>
'Передавання через усі ретранслятори. Найбільш надійний спосіб, але потребує більше часу.';
@override
String get routing_modeManualHint =>
'Завжди доставляє точно за вказаним вами маршрутом.';
@override
String get routing_currentRoute => 'Поточний маршрут';
@override
String get routing_directNoHops =>
'Пряме з\'єднання – без проміжних ретрансляторів';
@override
String get routing_noPathYet =>
'Поки що немає жодного шляху. Повідомлення продовжуються надходити, поки не буде знайдено маршрут.';
@override
String get routing_floodBroadcast => 'Поширення через усі ретранслятори';
@override
String get routing_editPath => 'Редагувати шлях';
@override
String get routing_forgetPath => 'Забудь про шлях';
@override
String get routing_knownPaths => 'Відомі маршрути';
@override
String get routing_knownPathsHint =>
'Виберіть опцію, щоб переключитися на неї.';
@override
String get routing_inUse => 'У робочому стані';
@override
String get routing_qualityStrong => 'Сильний перший стрибок';
@override
String get routing_qualityGood => 'Чудова перша спроба';
@override
String get routing_qualityFair => 'Перший, але вдалий, крок';
@override
String get routing_qualityWorked => 'Доставлено';
@override
String get routing_qualityFlood => 'Дізнався через новини';
@override
String get routing_qualityUntested => 'Не протестовано';
@override
String routing_lastWorked(String when) {
return 'worked $when';
String chat_pathSetHops(int hopCount, String status) {
String _temp0 = intl.Intl.pluralLogic(
hopCount,
locale: localeName,
other: 'переходів',
many: 'переходів',
few: 'переходи',
one: 'перехід',
);
return 'Шлях встановлено: $hopCount $_temp0 - $status';
}
@override
String get routing_neverWorked => 'ніколи не підтверджено';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => 'Доставка під час повені';
@override
String get pathEditor_title => 'Створити маршрут';
@override
String pathEditor_hopCounter(int count) {
return '$count з 64 штук хмелю';
}
@override
String get pathEditor_noHops =>
'Ще не додано хміль. Натисніть на відповідні кнопки, щоб додати його в потрібному порядку, або збережіть рецепт без хмілю, щоб відправити його безпосередньо.';
@override
String get pathEditor_addHops => 'Додавайте хміль у наступній послідовності.';
@override
String get pathEditor_searchRepeaters => 'Пошук повторювачів';
@override
String get pathEditor_advancedHex =>
'Просунутий рівень: пряма шлях у форматі шестнадцяткової системи.';
@override
String get pathEditor_hexLabel =>
'Префікси для шестнадцяткової системи числення';
@override
String get pathEditor_hexHelper =>
'Два шестизначні символи на кожний крок, розділені комами';
@override
String pathEditor_invalidTokens(String tokens) {
return 'Неправильно: $tokens';
}
@override
String get pathEditor_tooManyHops => 'Максимум 64 хмелеві колоди';
@override
String get pathEditor_usePath => 'Використовуйте цей шлях';
@override
String get pathEditor_removeHop => 'Видалити хміль';
@override
String get pathEditor_unknownHop => 'Невідомий ретранслятор';
@override
String get chat_pathSavedLocally =>
'Збережено локально. Підключіться для синхронізації.';
@@ -1736,39 +1645,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get map_title => 'Карта вузлів';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => 'Пряма видимість';
@@ -2096,6 +1972,17 @@ class AppLocalizationsUk extends AppLocalizations {
String get dialog_disconnectConfirm =>
'Ви впевнені, що хочете відключитись від цього пристрою?';
@override
String get dialog_disconnectedTitle => 'Від’єднано';
@override
String get dialog_disconnectedMessage =>
'Вас від’єднано від вашого супутника.';
@override
String get dialog_connectCompanion =>
'Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.';
@override
String get login_repeaterLogin => 'Вхід у ретранслятор';
@@ -2161,13 +2048,67 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get common_clear => 'Очистити';
@override
String path_currentPath(String path) {
return 'Поточний шлях: $path';
}
@override
String path_usingHopsPath(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'переходами',
many: 'переходами',
few: 'переходами',
one: 'переходом',
);
return 'Використання шляху з $count $_temp0';
}
@override
String get path_enterCustomPath => 'Ввести власний шлях';
@override
String get path_currentPathLabel => 'Поточний шлях';
@override
String get path_hexPrefixInstructions =>
'Введіть 2-символьні hex-префікси для кожного переходу, розділені комами.';
@override
String get path_hexPrefixExample =>
'Приклад: A1,F2,3C (кожен вузол використовує перший байт свого відкритого ключа).';
@override
String get path_labelHexPrefixes => 'Hex-префікси';
@override
String get path_helperMaxHops =>
'Макс. 64 переходи. Кожен префікс — 2 шістнадцяткові символи (1 байт)';
@override
String get path_selectFromContacts => 'Вибрати з контактів:';
@override
String get path_noRepeatersFound =>
'Ретрансляторів або серверів кімнат не знайдено.';
@override
String get path_customPathsRequire =>
'Власні шляхи вимагають проміжних вузлів, які можуть передавати повідомлення.';
@override
String path_invalidHexPrefixes(String prefixes) {
return 'Некоректні hex-префікси: $prefixes';
}
@override
String get path_tooLong => 'Шлях занадто довгий. Максимум 64 переходи.';
@override
String get path_setPath => 'Встановити шлях';
@override
String get repeater_management => 'Керування ретранслятором';
@@ -2232,6 +2173,16 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get repeater_routingMode => 'Режим маршрутизації';
@override
String get repeater_autoUseSavedPath =>
'Авто (використовувати збережений шлях)';
@override
String get repeater_forceFloodMode => 'Примусово через всю мережу';
@override
String get repeater_pathManagement => 'Керування шляхами';
@override
String get repeater_refresh => 'Оновити';
@@ -3334,139 +3285,6 @@ class AppLocalizationsUk extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => 'Цифровий вхід';
@override
String get telemetry_digitalOutputLabel => 'Цифровий вихід';
@override
String get telemetry_analogInputLabel => 'Аналоговий вхід';
@override
String get telemetry_analogOutputLabel => 'Аналоговий вихід';
@override
String get telemetry_genericLabel => 'Загальний датчик';
@override
String get telemetry_luminosityLabel => 'Освітленість';
@override
String get telemetry_presenceLabel => 'Присутність';
@override
String get telemetry_humidityLabel => 'Вологість';
@override
String get telemetry_accelerometerLabel => 'Акселерометр';
@override
String get telemetry_pressureLabel => 'Тиск';
@override
String get telemetry_altitudeLabel => 'Висота';
@override
String get telemetry_frequencyLabel => 'Частота';
@override
String get telemetry_percentageLabel => 'Відсоток';
@override
String get telemetry_concentrationLabel => 'Концентрація';
@override
String get telemetry_powerLabel => 'Потужність';
@override
String get telemetry_distanceLabel => 'Відстань';
@override
String get telemetry_energyLabel => 'Енергія';
@override
String get telemetry_directionLabel => 'Напрямок';
@override
String get telemetry_timeLabel => 'Час';
@override
String get telemetry_gyrometerLabel => 'Гірометр';
@override
String get telemetry_colourLabel => 'Колір';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => 'Перемикач';
@override
String get telemetry_polylineLabel => 'Полілінія';
@override
String telemetry_altitudeValue(String meters) {
return '$meters м';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Гц';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa гПа';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux лк';
}
@override
String telemetry_powerValue(String watts) {
return '$watts Вт';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters м';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours кВт⋅год';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => 'Кількість запитів';
@override
String get telemetry_error => 'Не вдалося отримати дані';
@override
String get neighbors_receivedData => 'Дані сусідів отримано';
@@ -4369,17 +4187,6 @@ class AppLocalizationsUk extends AppLocalizations {
String get translation_composerSubtitle =>
'Контролює стан ікон перекладу, який використовується за замовчуванням.';
@override
String get translation_autoIncomingTitle =>
'Автоматично перекладати повідомлення';
@override
String get translation_autoIncomingSubtitle =>
'Автоматично перекладає повідомлення для сповіщень, а також для чатів і каналів.';
@override
String get translation_translateMessage => 'Перекласти повідомлення';
@override
String get translation_targetLanguage => 'Цільова мова';
@@ -4508,135 +4315,4 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get contact_typeUnknown => 'Невідомо';
@override
String get map_zoomIn => 'Увійти в режим збільшення';
@override
String get map_zoomOut => 'Видалити зум';
@override
String get map_centerMap => 'Карта центру';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth вимагає браузера на основі Chromium';
@override
String channels_communityShortId(String id) {
return 'ID: $id...';
}
@override
String get pathTrace_legendGpsConfirmed => 'GPS підтверджено';
@override
String get pathTrace_legendInferred => 'Висновок щодо положення';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+122 -464
View File
@@ -92,24 +92,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get common_disable => '禁用';
@override
String get common_undo => '撤销';
@override
String get messageStatus_sent => '发送';
@override
String get messageStatus_delivered => '已送达';
@override
String get messageStatus_pending => '发送';
@override
String get messageStatus_failed => '发送失败';
@override
String get messageStatus_repeated => '多次听到';
@override
String get common_reboot => '重启';
@@ -129,12 +111,6 @@ class AppLocalizationsZh extends AppLocalizations {
return '$percent%';
}
@override
String get common_autoRefresh => '自动刷新';
@override
String get common_interval => '间隔';
@override
String get scanner_title => '连接设备';
@@ -305,10 +281,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get scanner_enableBluetooth => '启用蓝牙';
@override
String get scanner_bluetoothWebUnsupported =>
'Bluetooth isn\'t available in the browser. Connect over USB instead.';
@override
String get device_quickSwitch => '快速切换';
@@ -757,6 +729,11 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get appSettings_maxMessageRetriesSubtitle => '在将消息标记为失败之前,允许尝试的次数';
@override
String path_routeWeight(String weight, String max) {
return '$weight/$max';
}
@override
String get appSettings_battery => '电池';
@@ -948,15 +925,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get contacts_newGroup => '新建群聊';
@override
String get contacts_moreOptions => '更多选择';
@override
String get contacts_searchOpen => '搜索联系人';
@override
String get contacts_searchClose => '高级搜索';
@override
String get contacts_groupName => '群聊名称';
@@ -1423,149 +1391,85 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get debugFrame_hexDump => '十六进制数据:';
@override
String get chat_pathManagement => '路径管理';
@override
String get chat_ShowAllPaths => '显示所有路径';
@override
String get chat_routingMode => '路由模式';
@override
String get chat_autoUseSavedPath => '自动(使用保存的路径)';
@override
String get chat_forceFloodMode => '强制泛洪模式';
@override
String get chat_recentAckPaths => '最近使用的 ACK 路径(点击使用):';
@override
String get chat_pathHistoryFull => '路径历史已满,请删除后再添加。';
@override
String get chat_hopSingular => '';
@override
String get chat_hopPlural => '';
@override
String chat_hopsCount(int count) {
return '$count';
}
@override
String get chat_successes => '成功';
@override
String get chat_score => 'Score';
@override
String get chat_removePath => '移除路径';
@override
String get chat_noPathHistoryYet => '暂无路径历史。\n发送消息以探索路径。';
@override
String get chat_pathActions => '路径操作:';
@override
String get chat_setCustomPath => '设置自定义路径';
@override
String get chat_setCustomPathSubtitle => '手动指定路由路径';
@override
String get chat_clearPath => '清除路径';
@override
String get chat_clearPathSubtitle => '清除当前路径,下次发送将重新尝试。';
@override
String get chat_pathCleared => '路径已清除。下一条消息将重新路由。';
@override
String get chat_floodModeSubtitle => '在应用栏中切换路由模式。';
@override
String get chat_floodModeEnabled => '泛洪模式已启用。可通过应用栏的路由图标切换。';
@override
String get chat_fullPath => '完整路径';
@override
String get routing_title => '';
String get chat_pathDetailsNotAvailable => '径信息暂不可用,请尝试发送消息刷新。';
@override
String get routing_modeAuto => '汽车';
@override
String get routing_modeFlood => '洪水';
@override
String get routing_modeManual => '手册';
@override
String get routing_modeAutoHint => '自动选择已知最佳路径,当没有已知路径时,则进行“洪水”搜索。';
@override
String get routing_modeFloodHint => '通过所有中继站进行广播。 这种方式最可靠,但占用更多的时间。';
@override
String get routing_modeManualHint => '总是按照您设置的路径进行导航。';
@override
String get routing_currentRoute => '当前路线';
@override
String get routing_directNoHops => '直接连接— 无中继跳';
@override
String get routing_noPathYet => '目前还没有找到路径。直到找到路径,才会收到后续消息。';
@override
String get routing_floodBroadcast => '通过所有中继器进行广播';
@override
String get routing_editPath => '编辑路径';
@override
String get routing_forgetPath => '忘记原路';
@override
String get routing_knownPaths => '已知的路径';
@override
String get routing_knownPathsHint => '点击该路径以切换到它。';
@override
String get routing_inUse => '使用中';
@override
String get routing_qualityStrong => '强劲的初始阶段';
@override
String get routing_qualityGood => '不错的开端';
@override
String get routing_qualityFair => '第一次尝试,结果良好';
@override
String get routing_qualityWorked => '已完成';
@override
String get routing_qualityFlood => '通过新闻报道';
@override
String get routing_qualityUntested => '未经测试';
@override
String routing_lastWorked(String when) {
return '工作于 $when';
String chat_pathSetHops(int hopCount, String status) {
return '路径设置:$hopCount 跳 - $status';
}
@override
String get routing_neverWorked => '从未得到证实';
@override
String routing_deliveryCounts(int successes, int failures) {
return '$successes delivered, $failures failed';
}
@override
String get routing_floodDelivery => '洪水配送';
@override
String get pathEditor_title => '构建路径';
@override
String pathEditor_hopCounter(int count) {
return '$count of 64 hops';
}
@override
String get pathEditor_noHops =>
'目前还没有添加任何啤酒花。点击下面的“添加”按钮,按顺序添加,或者直接保存,不添加任何啤酒花。';
@override
String get pathEditor_addHops => '按照顺序添加啤酒花';
@override
String get pathEditor_searchRepeaters => '重复搜索';
@override
String get pathEditor_advancedHex => '高级:原始十六进制路径';
@override
String get pathEditor_hexLabel => '十六进制前缀';
@override
String get pathEditor_hexHelper => '每次跳跃,使用两个十六进制字符,用逗号分隔。';
@override
String pathEditor_invalidTokens(String tokens) {
return '无效:$tokens';
}
@override
String get pathEditor_tooManyHops => '最多 64 个跳跃';
@override
String get pathEditor_usePath => '请使用此路径';
@override
String get pathEditor_removeHop => '去除啤酒花';
@override
String get pathEditor_unknownHop => '未知的重复器';
@override
String get chat_pathSavedLocally => '已本地保存,连接设备后可同步。';
@@ -1638,39 +1542,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get map_title => '节点地图';
@override
String get map_searchHint => 'Search node name or ID';
@override
String get map_activity => 'Activity';
@override
String get map_online => 'Online';
@override
String get map_recent => 'Recent';
@override
String get map_stale => 'Stale';
@override
String get map_visible => 'Visible';
@override
String get map_hidden => 'Hidden';
@override
String get map_centerOnNode => 'Center on node';
@override
String get map_details => 'Details';
@override
String get map_noGps => 'No GPS';
@override
String get map_noResults => 'No matching nodes';
@override
String get map_lineOfSight => '视线';
@@ -1990,6 +1861,15 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get dialog_disconnectConfirm => '确定要断开与此设备的连接吗?';
@override
String get dialog_disconnectedTitle => '已断开连接';
@override
String get dialog_disconnectedMessage => '你已与你的伙伴断开连接。';
@override
String get dialog_connectCompanion => '连接伴机以访问中继器和房间服务器功能。';
@override
String get login_repeaterLogin => '转发节点登录';
@@ -2051,12 +1931,54 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get common_clear => '清除';
@override
String path_currentPath(String path) {
return '当前路径:$path';
}
@override
String path_usingHopsPath(int count) {
return '使用 $count 跳路径';
}
@override
String get path_enterCustomPath => '输入自定义路径';
@override
String get path_currentPathLabel => '当前路径';
@override
String get path_hexPrefixInstructions => '请输入每个中继节点的2字符十六进制前缀,用逗号分隔。';
@override
String get path_hexPrefixExample => '例如:A1, F2, 3C(每个节点使用其公钥的第一字节)';
@override
String get path_labelHexPrefixes => '路径(十六进制前缀)';
@override
String get path_helperMaxHops => '最多 64 跳。每个前缀由 2 个十六进制字符(1 字节)组成。';
@override
String get path_selectFromContacts => '或从联系人列表中选择:';
@override
String get path_noRepeatersFound => '未找到任何转发节点或房间服务器。';
@override
String get path_customPathsRequire => '自定义路径需要中间节点转发消息。';
@override
String path_invalidHexPrefixes(String prefixes) {
return '无效的十六进制前缀:$prefixes';
}
@override
String get path_tooLong => '路径过长,最多允许 64 跳。';
@override
String get path_setPath => '设置路径';
@override
String get repeater_management => '转发节点管理';
@@ -2117,6 +2039,15 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get repeater_routingMode => '路由模式';
@override
String get repeater_autoUseSavedPath => '自动(使用保存的路径)';
@override
String get repeater_forceFloodMode => '强制泛洪模式';
@override
String get repeater_pathManagement => '路径管理';
@override
String get repeater_refresh => '刷新';
@@ -3077,139 +3008,6 @@ class AppLocalizationsZh extends AppLocalizations {
return '$celsius°C / $fahrenheit°F';
}
@override
String get telemetry_digitalInputLabel => '数字输入';
@override
String get telemetry_digitalOutputLabel => '数字输出';
@override
String get telemetry_analogInputLabel => '模拟输入';
@override
String get telemetry_analogOutputLabel => '模拟输出';
@override
String get telemetry_genericLabel => '通用传感器';
@override
String get telemetry_luminosityLabel => '照度';
@override
String get telemetry_presenceLabel => '存在检测';
@override
String get telemetry_humidityLabel => '湿度';
@override
String get telemetry_accelerometerLabel => '加速度计';
@override
String get telemetry_pressureLabel => '气压';
@override
String get telemetry_altitudeLabel => '高度';
@override
String get telemetry_frequencyLabel => '频率';
@override
String get telemetry_percentageLabel => '百分比';
@override
String get telemetry_concentrationLabel => '浓度';
@override
String get telemetry_powerLabel => '功率';
@override
String get telemetry_distanceLabel => '距离';
@override
String get telemetry_energyLabel => '能量';
@override
String get telemetry_directionLabel => '方向';
@override
String get telemetry_timeLabel => '时间';
@override
String get telemetry_gyrometerLabel => '陀螺仪';
@override
String get telemetry_colourLabel => '颜色';
@override
String get telemetry_gpsLabel => 'GPS';
@override
String get telemetry_switchLabel => '开关';
@override
String get telemetry_polylineLabel => '折线';
@override
String telemetry_altitudeValue(String meters) {
return '$meters m';
}
@override
String telemetry_frequencyValue(String hertz) {
return '$hertz Hz';
}
@override
String telemetry_pressureValue(String hpa) {
return '$hpa hPa';
}
@override
String telemetry_luminosityValue(String lux) {
return '$lux lx';
}
@override
String telemetry_powerValue(String watts) {
return '$watts W';
}
@override
String telemetry_distanceValue(String meters) {
return '$meters m';
}
@override
String telemetry_energyValue(String kilowattHours) {
return '$kilowattHours kWh';
}
@override
String telemetry_directionValue(String degrees) {
return '$degrees°';
}
@override
String telemetry_concentrationValue(String ppm) {
return '$ppm ppm';
}
@override
String telemetry_percentageValue(String percent) {
return '$percent%';
}
@override
String telemetry_analogValue(String value) {
return '$value';
}
@override
String get telemetry_autoFetchQuantity => '请求次数';
@override
String get telemetry_error => '无法获取数据';
@override
String get neighbors_receivedData => '已接收邻居信息';
@@ -4027,15 +3825,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get translation_composerSubtitle => '控制作曲家翻译图标的默认状态。';
@override
String get translation_autoIncomingTitle => '自动翻译消息';
@override
String get translation_autoIncomingSubtitle => '自动为通知以及聊天或频道翻译消息。';
@override
String get translation_translateMessage => '翻译消息';
@override
String get translation_targetLanguage => '目标语言';
@@ -4158,135 +3947,4 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get contact_typeUnknown => 'Unknown';
@override
String get map_zoomIn => '放大';
@override
String get map_zoomOut => '放大';
@override
String get map_centerMap => '中心地图';
@override
String get chrome_bluetoothRequiresChromium =>
'Web Bluetooth 需要 Chromium 浏览器';
@override
String channels_communityShortId(String id) {
return 'ID$id...';
}
@override
String get pathTrace_legendGpsConfirmed => '通过GPS确认';
@override
String get pathTrace_legendInferred => '推测的位置';
@override
String get pathMap_viewSingle => 'Single';
@override
String get pathMap_viewCombined => 'Combined';
@override
String get pathMap_play => 'Play';
@override
String get pathMap_pause => 'Pause';
@override
String get pathMap_replay => 'Replay';
@override
String get pathMap_stepBack => 'Previous hop';
@override
String get pathMap_stepForward => 'Next hop';
@override
String get pathMap_animationOn => 'Show packet animation';
@override
String get pathMap_animationOff => 'Hide packet animation';
@override
String pathMap_hopOf(int current, int total) {
return 'Hop $current of $total';
}
@override
String pathMap_observedPaths(int count) {
return 'Observed paths: $count';
}
@override
String get pathMap_primary => 'Primary';
@override
String pathMap_alternate(int index) {
return 'Alt $index';
}
@override
String pathMap_hopCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops',
one: '1 hop',
);
return '$_temp0';
}
@override
String pathMap_gpsCount(int confirmed, int total) {
return '$confirmed/$total GPS';
}
@override
String get pathMap_legendShared => 'Shared segment';
@override
String get pathMap_legendEstimated => 'Estimated segment';
@override
String pathMap_sharedNodeCount(int count) {
return 'Used by $count paths';
}
@override
String pathMap_partialAnimation(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count hops have no location — the shown path is partial',
one: '1 hop has no location — the shown path is partial',
);
return '$_temp0';
}
@override
String get pathMap_showAllPaths => 'Show all';
@override
String get pathMap_hidePath => 'Hide path';
@override
String get pathMap_showPath => 'Show path';
@override
String get pathMap_collapsePanel => 'Collapse panel';
@override
String get pathMap_expandPanel => 'Expand panel';
@override
String get pathMap_noLocation => 'No location';
@override
String get pathMap_followPacket => 'Lock view to packet';
@override
String get pathMap_unfollowPacket => 'Unlock view from packet';
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Verwijderen",
"common_enable": "Activeren",
"common_disable": "Uitschakelen",
"common_autoRefresh": "Automatisch vernieuwen",
"common_interval": "Tijdsinterval",
"common_reboot": "Herstarten",
"common_loading": "Laden...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digitale ingang",
"telemetry_digitalOutputLabel": "Digitale uitgang",
"telemetry_analogInputLabel": "Analoge ingang",
"telemetry_analogOutputLabel": "Analoge uitgang",
"telemetry_genericLabel": "Algemene sensor",
"telemetry_luminosityLabel": "Lichtsterkte",
"telemetry_presenceLabel": "Aanwezigheid",
"telemetry_humidityLabel": "Luchtvochtigheid",
"telemetry_accelerometerLabel": "Versnellingsmeter",
"telemetry_pressureLabel": "Druk",
"telemetry_altitudeLabel": "Hoogte",
"telemetry_frequencyLabel": "Frequentie",
"telemetry_percentageLabel": "Percentage",
"telemetry_concentrationLabel": "Concentratie",
"telemetry_powerLabel": "Vermogen",
"telemetry_distanceLabel": "Afstand",
"telemetry_energyLabel": "Energie",
"telemetry_directionLabel": "Richting",
"telemetry_timeLabel": "Tijd",
"telemetry_gyrometerLabel": "Gyrometer",
"telemetry_colourLabel": "Kleur",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Schakelaar",
"telemetry_polylineLabel": "Polylijn",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Aantal aanvragen",
"telemetry_error": "Kan gegevens niet ophalen",
"telemetry_noData": "Geen telemetriedata beschikbaar.",
"telemetry_channelTitle": "Kanaal {channel}",
"@telemetry_channelTitle": {
@@ -2140,9 +2101,6 @@
"translation_composerTitle": "Vertaal voor verzending",
"translation_composerSubtitle": "Stelt de standaardstatus van het pictogram voor de vertaling van de componist in.",
"translation_useAppLanguage": "Gebruik de taal van de app",
"translation_autoIncomingTitle": "Berichten automatisch vertalen",
"translation_autoIncomingSubtitle": "Vertaalt berichten automatisch voor meldingen en voor chats of kanalen.",
"translation_translateMessage": "Bericht vertalen",
"translation_targetLanguage": "Doeltaal",
"translation_downloadedModelLabel": "Gedownloade model",
"translation_presetModelLabel": "Voorgeprogrammeerd Hugging Face-model",
@@ -2354,97 +2312,8 @@
"chat_markAsUnread": "Markeer als ongelezen",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB commando's, antwoorden en ruwe data",
"repeater_chanUtil": "Gebruik van het kanaal",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_sent": "Verzonden",
"common_undo": "Achterhalen/Annuleren",
"messageStatus_delivered": "Leverd",
"messageStatus_pending": "Verzenden",
"messageStatus_failed": "Niet verzonden",
"messageStatus_repeated": "Hearsay, herhaald",
"contacts_moreOptions": "Meer opties",
"contacts_searchOpen": "Zoek contactpersonen",
"contacts_searchClose": "Zoeken",
"routing_title": "Routeplanning",
"routing_modeAuto": "Auto",
"routing_modeFlood": "Overstroming",
"routing_modeManual": "Handleiding",
"routing_modeAutoHint": "Selecteert automatisch het bekendste pad, en gebruikt een flood-algoritme als er geen bekend pad is.",
"routing_modeFloodHint": "Uitzendingen via elke zender. De meest betrouwbare methode, maar vereist meer uitzendtijd.",
"routing_modeManualHint": "Stuurt altijd de exacte route die u heeft aangegeven.",
"routing_currentRoute": "Huidige route",
"routing_directNoHops": "Direct zonder tussenliggende schakels",
"routing_noPathYet": "Er is nog geen route gevonden. De berichten blijven binnenkomen totdat een route is ontdekt.",
"routing_floodBroadcast": "Uitgestoten via elke zender.",
"routing_editPath": "Pad bewerken",
"routing_forgetPath": "Vergeet het pad",
"routing_knownPaths": "Bekende routes",
"routing_knownPathsHint": "Maak een route om er naartoe te gaan.",
"routing_inUse": "In gebruik",
"routing_qualityStrong": "Sterke eerste sprong",
"routing_qualityGood": "Een goede eerste stap",
"routing_qualityFair": "Een goede eerste hop",
"routing_qualityWorked": "Is geleverd",
"routing_qualityFlood": "Hears via een overstroming",
"routing_qualityUntested": "Niet getest",
"routing_neverWorked": "nooit bevestigd",
"routing_deliveryCounts": "{successes} zijn behaald, {failures} zijn mislukt",
"routing_floodDelivery": "Levering bij overstroming",
"pathEditor_title": "Pad creëren",
"pathEditor_hopCounter": "{count} van 64 hopgranen",
"pathEditor_noHops": "Er zijn nog geen hop toegevoegd. Klik op de onderstaande knoppen om ze in de juiste volgorde toe te voegen, of sla de bestelling op zonder hop om deze direct te versturen.",
"pathEditor_addHops": "Voeg hop toe in de juiste volgorde.",
"pathEditor_searchRepeaters": "Zoek naar herhaaldelijke zenders",
"pathEditor_advancedHex": "Geavanceerd: ruwe hex-pad",
"pathEditor_hexLabel": "Hex-voorkanten",
"pathEditor_hexHelper": "Twee hex-tekens per stap, gescheiden door komma's",
"pathEditor_invalidTokens": "Ongeldig: {tokens}",
"pathEditor_tooManyHops": "Maximaal 64 hopken",
"pathEditor_usePath": "Gebruik deze route.",
"pathEditor_removeHop": "Verwijder de hop",
"pathEditor_unknownHop": "Onbekend type zender",
"map_zoomIn": "Inzoomen",
"routing_lastWorked": "worked {when}",
"map_zoomOut": "Inzoomen",
"map_centerMap": "Centraal overzicht",
"chrome_bluetoothRequiresChromium": "Web Bluetooth vereist een Chromium-browser.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS-locatie bevestigd",
"pathTrace_legendInferred": "Afgeleide positie"
"dialog_connectCompanion": "Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.",
"dialog_disconnectedTitle": "Verbroken",
"dialog_disconnectedMessage": "Je bent losgekoppeld van je companion.",
"contact_connectCompanion": "Maak verbinding met een companion om toegang te krijgen tot repeater- en roomserverfuncties."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Usuń",
"common_enable": "Włącz",
"common_disable": "Wyłącz",
"common_autoRefresh": "Automatyczne odświeżanie",
"common_interval": "Interwał",
"common_reboot": "Uruchom ponownie",
"common_loading": "Ładowanie...",
"common_notAvailable": "—",
@@ -1292,43 +1290,6 @@
}
}
},
"telemetry_digitalInputLabel": "Wejście cyfrowe",
"telemetry_digitalOutputLabel": "Wyjście cyfrowe",
"telemetry_analogInputLabel": "Wejście analogowe",
"telemetry_analogOutputLabel": "Wyjście analogowe",
"telemetry_genericLabel": "Czujnik ogólny",
"telemetry_luminosityLabel": "Jasność",
"telemetry_presenceLabel": "Obecność",
"telemetry_humidityLabel": "Wilgotność",
"telemetry_accelerometerLabel": "Akcelerometr",
"telemetry_pressureLabel": "Ciśnienie",
"telemetry_altitudeLabel": "Wysokość",
"telemetry_frequencyLabel": "Częstotliwość",
"telemetry_percentageLabel": "Procent",
"telemetry_concentrationLabel": "Stężenie",
"telemetry_powerLabel": "Moc",
"telemetry_distanceLabel": "Odległość",
"telemetry_energyLabel": "Energia",
"telemetry_directionLabel": "Kierunek",
"telemetry_timeLabel": "Czas",
"telemetry_gyrometerLabel": "Żyrometr",
"telemetry_colourLabel": "Kolor",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Przełącznik",
"telemetry_polylineLabel": "Polilinia",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Liczba żądań",
"telemetry_error": "Nie udało się pobrać danych",
"telemetry_noData": "Brak dostępnych danych telemetrycznych.",
"telemetry_channelTitle": "Kanał {channel}",
"@telemetry_channelTitle": {
@@ -2176,9 +2137,6 @@
"translation_enableTitle": "Włącz tłumaczenie",
"translation_enableSubtitle": "Tłumaczenie otrzymywanych wiadomości oraz umożliwienie tłumaczenia przed wysłaniem.",
"translation_composerSubtitle": "Kontroluje domyślny stan ikony tłumaczenia w edytorze.",
"translation_autoIncomingTitle": "Automatycznie tłumacz wiadomości",
"translation_autoIncomingSubtitle": "Automatycznie tłumaczy wiadomości do powiadomień oraz do czatów lub kanałów.",
"translation_translateMessage": "Przetłumacz wiadomość",
"translation_targetLanguage": "Język docelowy",
"translation_useAppLanguage": "Użyj języka aplikacji",
"translation_downloadedModelLabel": "Pobudowany model",
@@ -2392,97 +2350,8 @@
"chat_markAsUnread": "Oznacz jako nieprzeczytane",
"settings_companionDebugLog": "Log debugowania (dla pomocy w rozwiązywaniu problemów)",
"repeater_chanUtil": "Wykorzystanie kanału",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_sent": "Wysłane",
"messageStatus_delivered": "Dostarczone",
"messageStatus_pending": "Wysyłanie",
"common_undo": "Wycofaj",
"messageStatus_failed": "Nie udało się wysłać",
"messageStatus_repeated": "Usłyszałem to wielokrotnie",
"contacts_moreOptions": "Więcej opcji",
"contacts_searchOpen": "Wyszukaj kontakty",
"contacts_searchClose": "Zaawansowane wyszukiwanie",
"routing_title": "Planowanie tras",
"routing_modeAuto": "Samochód",
"routing_modeFlood": "Powódź",
"routing_modeManual": "Instrukcja obsługi",
"routing_modeAutoHint": "Automatycznie wybiera najpopularniejszą ścieżkę, a w przypadku braku znanej, przechodzi do trybu \"przepływu\".",
"routing_modeFloodHint": "Transmisje za pośrednictwem każdego repeatera. Najbardziej niezawodna metoda, ale zużywa więcej czasu transmisji.",
"routing_modeManualHint": "Zawsze prowadzi dokładnie po trasie, którą określiłeś.",
"routing_currentRoute": "Obecna trasa",
"routing_directNoHops": "Bezpośrednio bez pośrednictwa repeaterów",
"routing_noPathYet": "Na razie nie ma żadnej ścieżki. Komunikacja trwa do momentu, gdy zostanie odkryta trasa.",
"routing_floodBroadcast": "Transmisja za pośrednictwem każdego urządzenia powielającego",
"routing_editPath": "Edytuj ścieżkę",
"routing_forgetPath": "Zapomnij o ścieżce",
"routing_knownPaths": "Znane trasy",
"routing_knownPathsHint": "Wybierz ścieżkę, aby przełączyć się na nią.",
"routing_inUse": "W użyciu",
"routing_qualityStrong": "Silny pierwszy skok",
"routing_qualityGood": "Świetny początek",
"routing_qualityFair": "Świetny pierwszy krzak",
"routing_qualityWorked": "Zostało dostarczone",
"routing_qualityFlood": "Usłyszano dzięki doniesieniom",
"routing_qualityUntested": "Nieużywany",
"routing_lastWorked": "pracował {when}",
"routing_neverWorked": "nigdy nie zostało potwierdzone",
"routing_floodDelivery": "Dostawa w przypadku powodzi",
"pathEditor_title": "Stworzenie ścieżki",
"pathEditor_hopCounter": "{count} z 64 rodzajów chmielu",
"pathEditor_noHops": "Na razie nie dodano żadnych chmielu. Aby dodać je w odpowiedniej kolejności, kliknij w odpowiednie przyciski poniżej, lub zapisz przepis bez chmielu, aby wysłać go bezpośrednio.",
"pathEditor_addHops": "Dodawaj chmiel zgodnie z kolejnością.",
"pathEditor_searchRepeaters": "Funkcje powtarzania",
"pathEditor_advancedHex": "Zaawansowane: ścieżka w formacie szesnastkowym",
"pathEditor_hexLabel": "Prefiksy heksadecymalne",
"pathEditor_hexHelper": "Dwa znaki szesnastkowe na każdym kroku, oddzielone przecinkami",
"pathEditor_invalidTokens": "Nieprawidłowe: {tokens}",
"pathEditor_tooManyHops": "Maksymalnie 64 hopów",
"pathEditor_usePath": "Użyj tej ścieżki.",
"pathEditor_removeHop": "Usuń dziką psiankę",
"pathEditor_unknownHop": "Nieznany repeater",
"map_zoomIn": "Przybliż",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "Przybliż z powrotem",
"map_centerMap": "Mapa centrum",
"chrome_bluetoothRequiresChromium": "Web Bluetooth wymaga przeglądarki Chromium.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS potwierdzone",
"pathTrace_legendInferred": "Wywnioskowana pozycja"
"dialog_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.",
"dialog_disconnectedTitle": "Rozłączono",
"dialog_disconnectedMessage": "Zostałeś rozłączony ze swoim towarzyszem.",
"contact_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji repeatera i serwera pokojowego."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Remover",
"common_enable": "Ativar",
"common_disable": "Desativar",
"common_autoRefresh": "Atualização automática",
"common_interval": "Intervalo",
"common_reboot": "Reiniciar",
"common_loading": "Carregando...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Entrada digital",
"telemetry_digitalOutputLabel": "Saída digital",
"telemetry_analogInputLabel": "Entrada analógica",
"telemetry_analogOutputLabel": "Saída analógica",
"telemetry_genericLabel": "Sensor genérico",
"telemetry_luminosityLabel": "Luminosidade",
"telemetry_presenceLabel": "Presença",
"telemetry_humidityLabel": "Humidade",
"telemetry_accelerometerLabel": "Acelerómetro",
"telemetry_pressureLabel": "Pressão",
"telemetry_altitudeLabel": "Altitude",
"telemetry_frequencyLabel": "Frequência",
"telemetry_percentageLabel": "Percentagem",
"telemetry_concentrationLabel": "Concentração",
"telemetry_powerLabel": "Potência",
"telemetry_distanceLabel": "Distância",
"telemetry_energyLabel": "Energia",
"telemetry_directionLabel": "Direção",
"telemetry_timeLabel": "Hora",
"telemetry_gyrometerLabel": "Girómetro",
"telemetry_colourLabel": "Cor",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Interruptor",
"telemetry_polylineLabel": "Polilinha",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Número de solicitações",
"telemetry_error": "Não foi possível obter os dados",
"telemetry_noData": "Não estão disponíveis dados de telemetria.",
"telemetry_channelTitle": "Canal {channel}",
"@telemetry_channelTitle": {
@@ -2139,9 +2100,6 @@
"translation_enableTitle": "Ativar a tradução",
"translation_title": "Tradução",
"translation_composerSubtitle": "Controla o estado padrão do ícone de tradução do compositor.",
"translation_autoIncomingTitle": "Traduzir mensagens automaticamente",
"translation_autoIncomingSubtitle": "Traduz automaticamente mensagens para notificações e para chats ou canais.",
"translation_translateMessage": "Traduzir mensagem",
"translation_targetLanguage": "Língua-alvo",
"translation_useAppLanguage": "Utilize o idioma da aplicação",
"translation_downloadedModelLabel": "Modelo baixado",
@@ -2354,97 +2312,8 @@
"chat_markAsUnread": "Marcar como não lido",
"chat_newMessages": "Novas mensagens",
"repeater_chanUtil": "Utilização do canal",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"common_undo": "Desfazer",
"messageStatus_sent": "Enviado",
"messageStatus_pending": "Enviar",
"messageStatus_delivered": "Entregue",
"messageStatus_failed": "Falhou ao enviar",
"messageStatus_repeated": "Ouvi repetidamente",
"contacts_moreOptions": "Mais opções",
"contacts_searchOpen": "Pesquisar contatos",
"contacts_searchClose": "Pesquisa avançada",
"routing_title": "Rotas",
"routing_modeAuto": "Carro",
"routing_modeFlood": "Inundação",
"routing_modeManual": "Manual",
"routing_modeAutoHint": "Seleciona automaticamente o caminho mais conhecido, e, se nenhum caminho conhecido for encontrado, utiliza a estratégia de \"inundação\".",
"routing_modeFloodHint": "Transmissão através de todos os repetidores. É a opção mais confiável, mas utiliza mais tempo de transmissão.",
"routing_modeManualHint": "Sempre segue exatamente o caminho que você define.",
"routing_currentRoute": "Rota atual",
"routing_directNoHops": "Direto sem saltos de repetidor",
"routing_noPathYet": "Ainda não há um caminho definido. A mensagem continua a ser enviada até que uma rota seja encontrada.",
"routing_floodBroadcast": "Transmissão através de todos os repetidores",
"routing_editPath": "Editar caminho",
"routing_forgetPath": "Esqueça o caminho",
"routing_knownPaths": "Rotas conhecidas",
"routing_knownPathsHint": "Toque em um caminho para alternar para ele.",
"routing_inUse": "Em uso",
"routing_qualityStrong": "Primeiro salto notável",
"routing_qualityGood": "Primeiro salto bem-sucedido",
"routing_qualityFair": "Primeira etapa bem-sucedida",
"routing_qualityWorked": "Foi entregue",
"routing_qualityFlood": "Informação obtida através de relatos generalizados.",
"routing_qualityUntested": "Não testado",
"routing_neverWorked": "nunca confirmado",
"routing_floodDelivery": "Entrega em áreas afetadas por inundações",
"pathEditor_title": "Criar Caminho",
"pathEditor_hopCounter": "{count} de 64 gramas de lúpulo",
"pathEditor_noHops": "Ainda não há lúpulos adicionados. Clique nos repetidores abaixo para adicioná-los na ordem desejada, ou salve sem adicionar lúpulos para enviar diretamente.",
"pathEditor_addHops": "Adicione os lúpulos na seguinte ordem.",
"pathEditor_searchRepeaters": "Encontrar repetidores",
"pathEditor_advancedHex": "Avançado: caminho hexadecimal bruto",
"pathEditor_hexLabel": "Prefixos hexadecimais",
"pathEditor_hexHelper": "Dois caracteres hexadecimais por salto, separados por vírgulas.",
"pathEditor_invalidTokens": "Inválido: {tokens}",
"routing_lastWorked": "worked {when}",
"pathEditor_tooManyHops": "Máximo de 64 saltos",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_usePath": "Utilize este caminho.",
"pathEditor_removeHop": "Remova o lúpulo",
"pathEditor_unknownHop": "Repetidor desconhecido",
"map_zoomIn": "Ampliar",
"map_zoomOut": "Ampliar",
"map_centerMap": "Mapa do centro",
"chrome_bluetoothRequiresChromium": "O Web Bluetooth requer um navegador Chromium.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS confirmado",
"pathTrace_legendInferred": "Posição inferida"
"dialog_connectCompanion": "Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.",
"dialog_disconnectedTitle": "Desconectado",
"dialog_disconnectedMessage": "Você foi desconectado do seu companheiro.",
"contact_connectCompanion": "Conecte-se a um companheiro para acessar recursos de repetidor e servidor de sala."
}
+4 -135
View File
@@ -39,8 +39,6 @@
"common_notAvailable": "—",
"common_voltageValue": "{volts} В",
"common_percentValue": "{percent}%",
"common_autoRefresh": "Автообновление",
"common_interval": "Интервал",
"scanner_title": "MeshCore Open",
"scanner_scanning": "Поиск устройств...",
"scanner_connecting": "Подключение...",
@@ -688,43 +686,6 @@
"telemetry_voltageValue": "{volts}В",
"telemetry_currentValue": "{amps}А",
"telemetry_temperatureValue": "{celsius}°C / {fahrenheit}°F",
"telemetry_digitalInputLabel": "Цифровой вход",
"telemetry_digitalOutputLabel": "Цифровой выход",
"telemetry_analogInputLabel": "Аналоговый вход",
"telemetry_analogOutputLabel": "Аналоговый выход",
"telemetry_genericLabel": "Общий датчик",
"telemetry_luminosityLabel": "Освещённость",
"telemetry_presenceLabel": "Присутствие",
"telemetry_humidityLabel": "Влажность",
"telemetry_accelerometerLabel": "Акселерометр",
"telemetry_pressureLabel": "Давление",
"telemetry_altitudeLabel": "Высота",
"telemetry_frequencyLabel": "Частота",
"telemetry_percentageLabel": "Процент",
"telemetry_concentrationLabel": "Концентрация",
"telemetry_powerLabel": "Мощность",
"telemetry_distanceLabel": "Расстояние",
"telemetry_energyLabel": "Энергия",
"telemetry_directionLabel": "Направление",
"telemetry_timeLabel": "Время",
"telemetry_gyrometerLabel": "Гирометр",
"telemetry_colourLabel": "Цвет",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Переключатель",
"telemetry_polylineLabel": "Полилиния",
"telemetry_altitudeValue": "{meters} м",
"telemetry_frequencyValue": "{hertz} Гц",
"telemetry_pressureValue": "{hpa} гПа",
"telemetry_luminosityValue": "{lux} лк",
"telemetry_powerValue": "{watts} Вт",
"telemetry_distanceValue": "{meters} м",
"telemetry_energyValue": "{kilowattHours} кВт⋅ч",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Количество запросов",
"telemetry_error": "Не удалось получить данные",
"neighbors_receivedData": "Полученные данные о соседях",
"neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.",
"neighbors_errorLoading": "Ошибка загрузки соседей: {error}",
@@ -1307,9 +1268,6 @@
"translation_title": "Перевод",
"translation_enableTitle": "Включить перевод",
"translation_composerSubtitle": "Управляет исходным состоянием значка перевода, предоставляемого редактором.",
"translation_autoIncomingTitle": "Автоматически переводить сообщения",
"translation_autoIncomingSubtitle": "Автоматически переводит сообщения для уведомлений, а также для чатов и каналов.",
"translation_translateMessage": "Перевести сообщение",
"translation_targetLanguage": "Целевой язык",
"translation_useAppLanguage": "Используйте язык приложения",
"translation_downloadedModelLabel": "Загруженная модель",
@@ -1657,97 +1615,8 @@
"settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.",
"repeater_chanUtil": "Использование канала",
"settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_pending": "Отправка",
"common_undo": "Отменить",
"messageStatus_delivered": "Доставлено",
"messageStatus_sent": "Отправлено",
"messageStatus_failed": "Не удалось отправить",
"messageStatus_repeated": "Услышал несколько раз",
"contacts_moreOptions": "Больше вариантов",
"contacts_searchOpen": "Найти контакты",
"contacts_searchClose": "Закрыть поиск",
"routing_title": "Маршрутизация",
"routing_modeAuto": "Авто",
"routing_modeFlood": "Наводнение",
"routing_modeManual": "Инструкция",
"routing_modeAutoHint": "Автоматически выбирает наиболее известный путь, и если такой путь неизвестен, использует алгоритм поиска пути.",
"routing_modeFloodHint": "Передача сигнала через все ретрансляторы. Самый надежный способ, но требует больше времени на передачу.",
"routing_modeManualHint": "Всегда следует точно по указанному вами маршруту.",
"routing_currentRoute": "Текущий маршрут",
"routing_directNoHops": "Прямое соединение – без использования ретрансляторов",
"routing_noPathYet": "Пока нет пути. Следующее сообщение будет отправлено до тех пор, пока не будет обнаружен маршрут.",
"routing_floodBroadcast": "Транслируется через все ретрансляторы",
"routing_editPath": "Изменить путь",
"routing_forgetPath": "Забудьте о маршруте",
"routing_knownPaths": "Известные маршруты",
"routing_knownPathsHint": "Создайте маршрут для переключения на этот пункт.",
"routing_inUse": "В эксплуатации",
"routing_qualityStrong": "Сильный первый скачок",
"routing_qualityGood": "Хорошее начало",
"routing_qualityFair": "Первый хороший урожай",
"routing_qualityWorked": "Осуществлено",
"routing_qualityFlood": "Узнал из новостей, распространяющихся в интернете.",
"routing_qualityUntested": "Непроверенный",
"routing_neverWorked": "никогда не было подтверждено",
"routing_floodDelivery": "Доставка при затоплении",
"pathEditor_title": "Создать маршрут",
"pathEditor_hopCounter": "{count} из 64 хмеля",
"pathEditor_noHops": "На данный момент хмель еще не добавлен. Чтобы добавить его, нажмите на соответствующие кнопки ниже в нужном порядке, или сохраните рецепт без хмеля, чтобы отправить его напрямую.",
"pathEditor_addHops": "Добавляйте хмель в соответствии с указанным порядком.",
"pathEditor_searchRepeaters": "Поиск повторителей",
"pathEditor_advancedHex": "Продвинутый уровень: прямой путь в шестнадцатеричном формате",
"pathEditor_hexLabel": "Префиксы шестнадцатеричной системы",
"pathEditor_hexHelper": "Два шестнадцатеричных символа на каждом шаге, разделенные запятыми.",
"pathEditor_invalidTokens": "Неверно: {tokens}",
"routing_lastWorked": "worked {when}",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_tooManyHops": "Максимальное количество ингредиентов – 64",
"pathEditor_usePath": "Используйте этот путь",
"pathEditor_removeHop": "Удалить хмель",
"pathEditor_unknownHop": "Неизвестный ретранслятор",
"map_zoomIn": "Увеличить масштаб",
"map_zoomOut": "Увеличить масштаб",
"map_centerMap": "Карта центра",
"chrome_bluetoothRequiresChromium": "Для работы Web Bluetooth требуется браузер на основе Chromium.",
"channels_communityShortId": "Идентификатор: {id}...",
"pathTrace_legendGpsConfirmed": "GPS подтверждено",
"pathTrace_legendInferred": "Выведенная позиция"
"dialog_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.",
"dialog_disconnectedTitle": "Отключено",
"dialog_disconnectedMessage": "Вы были отключены от вашего компаньона.",
"contact_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям репитера и серверу комнаты."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Odstrániť",
"common_enable": "Povolit",
"common_disable": "Zakázať",
"common_autoRefresh": "Automatické obnovenie",
"common_interval": "Časový interval",
"common_reboot": "Restartovať",
"common_loading": "Načítavanie...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digitálny vstup",
"telemetry_digitalOutputLabel": "Digitálny výstup",
"telemetry_analogInputLabel": "Analógový vstup",
"telemetry_analogOutputLabel": "Analógový výstup",
"telemetry_genericLabel": "Všeobecný senzor",
"telemetry_luminosityLabel": "Osvetlenie",
"telemetry_presenceLabel": "Prítomnosť",
"telemetry_humidityLabel": "Vlhkosť",
"telemetry_accelerometerLabel": "Akcelerometer",
"telemetry_pressureLabel": "Tlak",
"telemetry_altitudeLabel": "Nadmorská výška",
"telemetry_frequencyLabel": "Frekvencia",
"telemetry_percentageLabel": "Percento",
"telemetry_concentrationLabel": "Koncentrácia",
"telemetry_powerLabel": "Výkon",
"telemetry_distanceLabel": "Vzdialenosť",
"telemetry_energyLabel": "Energia",
"telemetry_directionLabel": "Smer",
"telemetry_timeLabel": "Čas",
"telemetry_gyrometerLabel": "Gyrometer",
"telemetry_colourLabel": "Farba",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Prepínač",
"telemetry_polylineLabel": "Lomená čiara",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Počet požiadaviek",
"telemetry_error": "Nepodarilo sa získať údaje",
"telemetry_noData": "Nejsú dostupné žiadne údaje z telemetrie.",
"telemetry_channelTitle": "Kanál {channel}",
"@telemetry_channelTitle": {
@@ -2139,9 +2100,6 @@
"translation_composerTitle": "Preložte pred odeslaním",
"translation_title": "Preklad",
"translation_composerSubtitle": "Riadi výchoce stav ikony pre preklad, ktorú používa program.",
"translation_autoIncomingTitle": "Automaticky prekladať správy",
"translation_autoIncomingSubtitle": "Automaticky prekladá správy pre upozornenia aj pre čet alebo kanál.",
"translation_translateMessage": "Preložiť správu",
"translation_targetLanguage": "Cieľový jazyk",
"translation_useAppLanguage": "Použite jazyk aplikácie",
"translation_downloadedModelLabel": "Stiahnutý model",
@@ -2354,97 +2312,8 @@
"settings_companionDebugLog": "Logovanie pre ladenie (sprievodný log)",
"chat_newMessages": "Nové správy",
"repeater_chanUtil": "Využitie kanálu",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_sent": "Odoslané",
"messageStatus_delivered": "Doručené",
"messageStatus_pending": "Odoslanie",
"common_undo": "Zrušiť",
"messageStatus_failed": "Neúspešné odeslanie",
"messageStatus_repeated": "Slyšal som to opakovane",
"contacts_moreOptions": "Ďalšie možnosti",
"contacts_searchOpen": "Vyhľadajte kontakty",
"contacts_searchClose": "Zavrieť vyhľadávanie",
"routing_title": "Navigácia",
"routing_modeAuto": "Auto",
"routing_modeFlood": "Povodňová vlna",
"routing_modeManual": "Ručná príručka",
"routing_modeAutoHint": "Automaticky vyberá najznámejší trasa, a ak žiadna nie je známa, použije náhodnú trasu.",
"routing_modeFloodHint": "Prenos prostredníctvom všetkých opakovačov. Najspoľahlivejší spôsob, ale vyžaduje viac času vysielania.",
"routing_modeManualHint": "Vždy dodáva presne podľa zadaného trasy.",
"routing_currentRoute": "Aktuálna trasa",
"routing_directNoHops": "Priamo bez prechodných trás",
"routing_noPathYet": "Zatiaľ neexistuje žiadna cesta. Nasledujúce správy budú pokračovať, kým sa nenájde trasa.",
"routing_floodBroadcast": "Prenos prostredníctvom každého opakovača",
"routing_editPath": "Upraviť trasu",
"routing_forgetPath": "Zabudnite na trasu",
"routing_knownPaths": "Známe cesty",
"routing_knownPathsHint": "Kliknite na cestu, aby ste sa k nej presunuli.",
"routing_inUse": "V prevádzke",
"routing_qualityStrong": "Silný prvý krok",
"routing_qualityGood": "Úspešný prvý krok",
"routing_qualityFair": "Prvá, spravodlivá fáza",
"routing_qualityWorked": "Dosiahnutý úspech",
"routing_qualityFlood": "Zistil som to z informácií, ktoré som získal v dôsledku povodňovej situácie.",
"routing_qualityUntested": "Neotestované",
"routing_neverWorked": "nikedy nebolo potvrdené",
"routing_floodDelivery": "Doručenie v prípade povodní",
"pathEditor_title": "Vytvorenie cesty",
"pathEditor_hopCounter": "{count} z 64 chmelových zŕš",
"pathEditor_noHops": "Zatiaľ žiadne chmel. Kliknite na opakované, aby ste ich pridali postupne, alebo uložte bez chmelu, aby ste ho mohli poslať priamo.",
"pathEditor_addHops": "Pridávajte chmel podľa zadaného poriadku.",
"pathEditor_searchRepeaters": "Hľadať opakované",
"pathEditor_advancedHex": "Pokročilé: pôvodná hexová cesta",
"pathEditor_hexLabel": "Prefiksy pre hexadecimálne čísla",
"pathEditor_hexHelper": "Dve hexové čísla na každý krok, oddelené čiarkami",
"routing_lastWorked": "worked {when}",
"pathEditor_invalidTokens": "Neplatné: {tokens}",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_tooManyHops": "Maximálne 64 krokov",
"pathEditor_usePath": "Použite túto cestu",
"pathEditor_removeHop": "Odstráňte chmel",
"pathEditor_unknownHop": "Neznáme zariadenie na opakované vysielanie",
"map_zoomIn": "Zväčšiť",
"map_zoomOut": "Zmenť zamer zblízka",
"map_centerMap": "Mapa centra",
"chrome_bluetoothRequiresChromium": "Web Bluetooth vyžaduje prehliadač Chromium.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS potvrdilo",
"pathTrace_legendInferred": "Odvodená poloha"
"dialog_connectCompanion": "Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.",
"dialog_disconnectedTitle": "Odpojené",
"dialog_disconnectedMessage": "Od vášho spoločníka ste boli odpojený.",
"contact_connectCompanion": "Pripojte sa k spoločníkovi pre prístup k funkciám opakovača a miestneho servera."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Izbrisati",
"common_enable": "Omogoči",
"common_disable": "Izklopiti",
"common_autoRefresh": "Samodejno osveževanje",
"common_interval": "Časovni interval",
"common_reboot": "Ponoviti",
"common_loading": "Naložanje...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digitalni vhod",
"telemetry_digitalOutputLabel": "Digitalni izhod",
"telemetry_analogInputLabel": "Analogni vhod",
"telemetry_analogOutputLabel": "Analogni izhod",
"telemetry_genericLabel": "Splošni senzor",
"telemetry_luminosityLabel": "Osvetljenost",
"telemetry_presenceLabel": "Prisotnost",
"telemetry_humidityLabel": "Vlažnost",
"telemetry_accelerometerLabel": "Merilnik pospeška",
"telemetry_pressureLabel": "Tlak",
"telemetry_altitudeLabel": "Nadmorska višina",
"telemetry_frequencyLabel": "Frekvenca",
"telemetry_percentageLabel": "Odstotek",
"telemetry_concentrationLabel": "Koncentracija",
"telemetry_powerLabel": "Moč",
"telemetry_distanceLabel": "Razdalja",
"telemetry_energyLabel": "Energija",
"telemetry_directionLabel": "Smer",
"telemetry_timeLabel": "Čas",
"telemetry_gyrometerLabel": "Žiroskop",
"telemetry_colourLabel": "Barva",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Stikalo",
"telemetry_polylineLabel": "Polilinija",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Število zahtev",
"telemetry_error": "Podatkov ni bilo mogoče pridobiti",
"telemetry_noData": "Niso na voljo podatki o telemetriji.",
"telemetry_channelTitle": "Kanal {channel}",
"@telemetry_channelTitle": {
@@ -2138,9 +2099,6 @@
"translation_enableSubtitle": "Prevedite vstopne sporočila in omogočite predhodno prevajanje.",
"translation_enableTitle": "Omogočite prevod",
"translation_composerSubtitle": "Ureja privzeto stanje ikone za prevod, ki jo uporablja avtor.",
"translation_autoIncomingTitle": "Samodejno prevajaj sporočila",
"translation_autoIncomingSubtitle": "Samodejno prevaja sporočila za obvestila ter za klepete ali kanale.",
"translation_translateMessage": "Prevedi sporočilo",
"translation_targetLanguage": "Ciljna jezika",
"translation_useAppLanguage": "Uporabite jezik aplikacije",
"translation_downloadedModelLabel": "Naložen model",
@@ -2354,97 +2312,8 @@
"chat_newMessages": "Nove novice",
"settings_companionDebugLogSubtitle": "Navodila, odgovori in surova podatka za BLE/TCP/USB.",
"repeater_chanUtil": "Uporaba kanala",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"common_undo": "Preobrn",
"messageStatus_delivered": "Dostavljeno",
"messageStatus_sent": "Pošljeno",
"messageStatus_pending": "Pošiljanje",
"messageStatus_failed": "Uspešno ni bilo mogo, da se sporočilo pošlje",
"messageStatus_repeated": "Slišal sem večkrat",
"contacts_moreOptions": "Več možnosti",
"contacts_searchOpen": "Iskanje kontaktov",
"contacts_searchClose": "Izklopi iskanje",
"routing_title": "Navigacija",
"routing_modeAuto": "Avto",
"routing_modeFlood": "Poplavo",
"routing_modeManual": "Navodilo",
"routing_modeAutoHint": "Samodejno izbere najbolj poznano pot, in sicer, ko ni na voljo nobena.",
"routing_modeFloodHint": "Prenosi preko vseh repetitorjev. Najzanesljivejši način, vendar zahteva več časa.",
"routing_modeManualHint": "Vedno sledi natančni poti, ki jo ste določili.",
"routing_currentRoute": "Trenutna pot",
"routing_directNoHops": "Neposredno brez prehodov",
"routing_noPathYet": "Žep trenutno ni mogoče najti. Naslednje sporočilo bo posredovano, dokler ne bo ugotovljeno, kje je pot.",
"routing_floodBroadcast": "Prenos preko vseh repetitiv",
"routing_editPath": "Uredi pot",
"routing_forgetPath": "Pozabi na pot",
"routing_knownPaths": "Poznati poti",
"routing_knownPathsHint": "Kliknite na pot, da jo izberete.",
"routing_inUse": "V uporabi",
"routing_qualityStrong": "Močan prvi korak",
"routing_qualityGood": "Prva uspešna faza",
"routing_qualityFair": "Prva, uspešna faza",
"routing_qualityWorked": "Izpolnil",
"routing_qualityFlood": "Slišano preko poplave",
"routing_qualityUntested": "Ne preizkušen",
"routing_lastWorked": "delal/a {when}",
"routing_neverWorked": "nikoli ni bilo potrjeno",
"routing_floodDelivery": "Dostava zaradi poplave",
"pathEditor_title": "Izgradnja poti",
"pathEditor_hopCounter": "{count} od 64 različnih sort hropa",
"pathEditor_noHops": "Še niso dodani hmelji. Za dodajanje hmelja v vrstnem redu kliknite na povezavo spodaj, ali pa shranite brez dodanega hmelja, da ga lahko posredujete neposredno.",
"pathEditor_addHops": "Dodajte suho travo v skladu s postopkom.",
"pathEditor_searchRepeaters": "Iskanje ponovitev",
"pathEditor_advancedHex": "Napredno: surovi šestnajstni pot",
"pathEditor_hexLabel": "Predfiks za heksadecimalno šifro",
"pathEditor_hexHelper": "Dva šestbitna znaka na vsak skok, ločena z vejico",
"pathEditor_invalidTokens": "Neveljaven: {tokens}",
"pathEditor_tooManyHops": "Največ 64 hopov",
"pathEditor_usePath": "Uporabite to poto",
"pathEditor_removeHop": "Odstranite hmelj",
"pathEditor_unknownHop": "Neznani ponovitelj",
"map_zoomIn": "Povečaj",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "Povečajte pogled",
"map_centerMap": "Krajšarska karta",
"chrome_bluetoothRequiresChromium": "Web Bluetooth zahteva brskalnik Chromium.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS potrdilo",
"pathTrace_legendInferred": "Izpeljana lokacija"
"dialog_connectCompanion": "Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.",
"dialog_disconnectedTitle": "Prekinjeno",
"dialog_disconnectedMessage": "Prekinjena povezava s vašim spre伴ovalcem.",
"contact_connectCompanion": "Povežite se s ponсоbnikom za dostop do funkcij pon 반복nika in strežnika prostorov."
}
+4 -135
View File
@@ -33,8 +33,6 @@
"common_remove": "Ta bort",
"common_enable": "Aktivera",
"common_disable": "Inaktivera",
"common_autoRefresh": "Automatisk uppdatering",
"common_interval": "Intervall",
"common_reboot": "Start om",
"common_loading": "Laddar...",
"common_notAvailable": "—",
@@ -1282,43 +1280,6 @@
}
}
},
"telemetry_digitalInputLabel": "Digital ingång",
"telemetry_digitalOutputLabel": "Digital utgång",
"telemetry_analogInputLabel": "Analog ingång",
"telemetry_analogOutputLabel": "Analog utgång",
"telemetry_genericLabel": "Allmän sensor",
"telemetry_luminosityLabel": "Ljusstyrka",
"telemetry_presenceLabel": "Närvaro",
"telemetry_humidityLabel": "Luftfuktighet",
"telemetry_accelerometerLabel": "Accelerometer",
"telemetry_pressureLabel": "Tryck",
"telemetry_altitudeLabel": "Höjd",
"telemetry_frequencyLabel": "Frekvens",
"telemetry_percentageLabel": "Procent",
"telemetry_concentrationLabel": "Koncentration",
"telemetry_powerLabel": "Effekt",
"telemetry_distanceLabel": "Avstånd",
"telemetry_energyLabel": "Energi",
"telemetry_directionLabel": "Riktning",
"telemetry_timeLabel": "Tid",
"telemetry_gyrometerLabel": "Gyrometer",
"telemetry_colourLabel": "Färg",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Brytare",
"telemetry_polylineLabel": "Polylinje",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Antal förfrågningar",
"telemetry_error": "Det gick inte att hämta data",
"telemetry_noData": "Inga telemetridata tillgängliga.",
"telemetry_channelTitle": "Kanal {channel}",
"@telemetry_channelTitle": {
@@ -2139,9 +2100,6 @@
"translation_title": "Översättning",
"translation_composerTitle": "Översätt innan du skickar",
"translation_composerSubtitle": "Styr standardtillståndet för kompositorns översättningsikon.",
"translation_autoIncomingTitle": "Översätt meddelanden automatiskt",
"translation_autoIncomingSubtitle": "Översätter meddelanden automatiskt för aviseringar och för chattar eller kanaler.",
"translation_translateMessage": "Översätt meddelande",
"translation_targetLanguage": "Målmedvetet språk",
"translation_useAppLanguage": "Använd appens språk",
"translation_downloadedModelLabel": "Nedladdad modell",
@@ -2354,97 +2312,8 @@
"chat_newMessages": "Nya meddelanden",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-kommandon, svar och rådata",
"repeater_chanUtil": "Användning av kanal",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_sent": "Sen",
"messageStatus_delivered": "Levererad",
"common_undo": "Ångra",
"messageStatus_pending": "Skicka",
"messageStatus_failed": "Misslyckades med att skicka",
"messageStatus_repeated": "Hördes upprepade gånger",
"contacts_moreOptions": "Fler alternativ",
"contacts_searchOpen": "Sök efter kontakter",
"contacts_searchClose": "Avancerad sökning",
"routing_title": "Ruttplanering",
"routing_modeAuto": "Bil",
"routing_modeFlood": "Översvämning",
"routing_modeManual": "Instruktioner",
"routing_modeAutoHint": "Väljer automatiskt den bästa kända vägen, och använder en \"flooding\"-strategi om ingen väg är känd.",
"routing_modeFloodHint": "Sändningar via alla repetrar. Det mest pålitliga alternativet, men kräver mer sändtid.",
"routing_modeManualHint": "Skickar alltid den exakta väg du har angivit.",
"routing_currentRoute": "Nuvarande rutt",
"routing_directNoHops": "Direkt utan mellanliggande routrar",
"routing_noPathYet": "Ingen väg hittad ännu. Nästa meddelande skickas tills en rutt har upptäckts.",
"routing_floodBroadcast": "Sändas via alla repetrar",
"routing_editPath": "Redigera sökväg",
"routing_forgetPath": "Glöm vägen",
"routing_knownPaths": "Kända vägar",
"routing_knownPathsHint": "Välj en väg för att byta till den.",
"routing_inUse": "I användning",
"routing_qualityStrong": "En stark start",
"routing_qualityGood": "Bra första steg",
"routing_qualityFair": "Bra första hopp",
"routing_qualityWorked": "Har levererat",
"routing_qualityFlood": "Fått information via nyhetsflöde",
"routing_qualityUntested": "Ej testat",
"routing_lastWorked": "arbetade {when}",
"routing_neverWorked": "aldrig bekräftat",
"routing_floodDelivery": "Leverans vid översvämningsområde",
"pathEditor_title": "Skapa väg",
"pathEditor_hopCounter": "{count} av 64 humlor",
"pathEditor_noHops": "Inga humle än. Använd knapparna nedan för att lägga till dem i rätt ordning, eller spara utan humle för att skicka direkt.",
"pathEditor_addHops": "Tillsätt humlen i rätt ordning.",
"pathEditor_searchRepeaters": "Sök efter återupptagna samtal",
"pathEditor_advancedHex": "Avancerat: rå hex-sökväg",
"pathEditor_hexLabel": "Hex-prefikser",
"pathEditor_hexHelper": "Två hex-tecken per steg, separerade med kommatecken.",
"pathEditor_invalidTokens": "Ogiltigt: {tokens}",
"pathEditor_tooManyHops": "Maximalt 64 humlörter",
"pathEditor_usePath": "Använd denna väg",
"pathEditor_removeHop": "Ta bort humlen",
"pathEditor_unknownHop": "Okänd förstärkare",
"map_zoomIn": "Zooma in",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"map_zoomOut": "Zooma ut",
"map_centerMap": "Kartöversikt",
"chrome_bluetoothRequiresChromium": "Web Bluetooth kräver en Chromium-baserad webbläsare.",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS-verifierat",
"pathTrace_legendInferred": "Antagen position"
"dialog_connectCompanion": "Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.",
"dialog_disconnectedTitle": "Ansluten ej",
"dialog_disconnectedMessage": "Du har kopplats från din companion.",
"contact_connectCompanion": "Anslut till en companion för att få tillgång till repeater- och rumsserverfunktioner."
}
+4 -135
View File
@@ -34,8 +34,6 @@
"common_remove": "Прибрати",
"common_enable": "Увімкнути",
"common_disable": "Вимкнути",
"common_autoRefresh": "Автооновлення",
"common_interval": "Інтервал",
"common_reboot": "Перезавантажити",
"common_loading": "Завантаження...",
"common_notAvailable": "—",
@@ -1293,43 +1291,6 @@
}
}
},
"telemetry_digitalInputLabel": "Цифровий вхід",
"telemetry_digitalOutputLabel": "Цифровий вихід",
"telemetry_analogInputLabel": "Аналоговий вхід",
"telemetry_analogOutputLabel": "Аналоговий вихід",
"telemetry_genericLabel": "Загальний датчик",
"telemetry_luminosityLabel": "Освітленість",
"telemetry_presenceLabel": "Присутність",
"telemetry_humidityLabel": "Вологість",
"telemetry_accelerometerLabel": "Акселерометр",
"telemetry_pressureLabel": "Тиск",
"telemetry_altitudeLabel": "Висота",
"telemetry_frequencyLabel": "Частота",
"telemetry_percentageLabel": "Відсоток",
"telemetry_concentrationLabel": "Концентрація",
"telemetry_powerLabel": "Потужність",
"telemetry_distanceLabel": "Відстань",
"telemetry_energyLabel": "Енергія",
"telemetry_directionLabel": "Напрямок",
"telemetry_timeLabel": "Час",
"telemetry_gyrometerLabel": "Гірометр",
"telemetry_colourLabel": "Колір",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "Перемикач",
"telemetry_polylineLabel": "Полілінія",
"telemetry_altitudeValue": "{meters} м",
"telemetry_frequencyValue": "{hertz} Гц",
"telemetry_pressureValue": "{hpa} гПа",
"telemetry_luminosityValue": "{lux} лк",
"telemetry_powerValue": "{watts} Вт",
"telemetry_distanceValue": "{meters} м",
"telemetry_energyValue": "{kilowattHours} кВт⋅год",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "Кількість запитів",
"telemetry_error": "Не вдалося отримати дані",
"telemetry_noData": "Дані телеметрії недоступні.",
"telemetry_channelTitle": "Канал {channel}",
"@telemetry_channelTitle": {
@@ -2148,9 +2109,6 @@
"translation_enableTitle": "Увімкнути переклад",
"translation_enableSubtitle": "Перекладати отримані повідомлення та дозволяти попередній переклад перед відправкою.",
"translation_composerSubtitle": "Контролює стан ікон перекладу, який використовується за замовчуванням.",
"translation_autoIncomingTitle": "Автоматично перекладати повідомлення",
"translation_autoIncomingSubtitle": "Автоматично перекладає повідомлення для сповіщень, а також для чатів і каналів.",
"translation_translateMessage": "Перекласти повідомлення",
"translation_targetLanguage": "Цільова мова",
"translation_useAppLanguage": "Використовувати мову застосунку",
"translation_downloadedModelLabel": "Завантажений шаблон",
@@ -2334,97 +2292,8 @@
"chat_newMessages": "Нові повідомлення",
"chat_markAsUnread": "Позначити як непрочитане",
"repeater_chanUtil": "Використання каналу",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_delivered": "Доставлено",
"messageStatus_sent": "Надіслано",
"common_undo": "Скасувати",
"messageStatus_pending": "Надсилання",
"messageStatus_failed": "Не вдалося надіслати",
"messageStatus_repeated": "Почув неодноразово",
"contacts_moreOptions": "Більше можливостей",
"contacts_searchOpen": "Пошук контактів",
"contacts_searchClose": "Закрити пошук",
"routing_title": "Маршрутизація",
"routing_modeAuto": "Автомобіль",
"routing_modeFlood": "Повені",
"routing_modeManual": "Інструкція",
"routing_modeAutoHint": "Автоматично обирає найкращий відомий шлях, та у разі відсутності відомого шляху, використовує алгоритм \"занурення\".",
"routing_modeFloodHint": "Передавання через усі ретранслятори. Найбільш надійний спосіб, але потребує більше часу.",
"routing_modeManualHint": "Завжди доставляє точно за вказаним вами маршрутом.",
"routing_currentRoute": "Поточний маршрут",
"routing_directNoHops": "Пряме з'єднання – без проміжних ретрансляторів",
"routing_noPathYet": "Поки що немає жодного шляху. Повідомлення продовжуються надходити, поки не буде знайдено маршрут.",
"routing_floodBroadcast": "Поширення через усі ретранслятори",
"routing_editPath": "Редагувати шлях",
"routing_forgetPath": "Забудь про шлях",
"routing_knownPaths": "Відомі маршрути",
"routing_knownPathsHint": "Виберіть опцію, щоб переключитися на неї.",
"routing_inUse": "У робочому стані",
"routing_qualityStrong": "Сильний перший стрибок",
"routing_qualityGood": "Чудова перша спроба",
"routing_qualityFair": "Перший, але вдалий, крок",
"routing_qualityWorked": "Доставлено",
"routing_qualityFlood": "Дізнався через новини",
"routing_qualityUntested": "Не протестовано",
"routing_neverWorked": "ніколи не підтверджено",
"routing_floodDelivery": "Доставка під час повені",
"pathEditor_title": "Створити маршрут",
"pathEditor_hopCounter": "{count} з 64 штук хмелю",
"pathEditor_noHops": "Ще не додано хміль. Натисніть на відповідні кнопки, щоб додати його в потрібному порядку, або збережіть рецепт без хмілю, щоб відправити його безпосередньо.",
"pathEditor_addHops": "Додавайте хміль у наступній послідовності.",
"pathEditor_searchRepeaters": "Пошук повторювачів",
"pathEditor_advancedHex": "Просунутий рівень: пряма шлях у форматі шестнадцяткової системи.",
"pathEditor_hexLabel": "Префікси для шестнадцяткової системи числення",
"pathEditor_hexHelper": "Два шестизначні символи на кожний крок, розділені комами",
"pathEditor_invalidTokens": "Неправильно: {tokens}",
"routing_lastWorked": "worked {when}",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_tooManyHops": "Максимум 64 хмелеві колоди",
"pathEditor_usePath": "Використовуйте цей шлях",
"pathEditor_removeHop": "Видалити хміль",
"pathEditor_unknownHop": "Невідомий ретранслятор",
"map_zoomIn": "Увійти в режим збільшення",
"map_zoomOut": "Видалити зум",
"map_centerMap": "Карта центру",
"chrome_bluetoothRequiresChromium": "Web Bluetooth вимагає браузера на основі Chromium",
"channels_communityShortId": "ID: {id}...",
"pathTrace_legendGpsConfirmed": "GPS підтверджено",
"pathTrace_legendInferred": "Висновок щодо положення"
"dialog_connectCompanion": "Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.",
"dialog_disconnectedTitle": "Від’єднано",
"dialog_disconnectedMessage": "Вас від’єднано від вашого супутника.",
"contact_connectCompanion": "Підключіться до супутника, щоб отримати доступ до функцій репітера та серверів кімнат."
}
+4 -135
View File
@@ -34,8 +34,6 @@
"common_remove": "移除",
"common_enable": "启用",
"common_disable": "禁用",
"common_autoRefresh": "自动刷新",
"common_interval": "间隔",
"common_reboot": "重启",
"common_loading": "正在加载...",
"common_notAvailable": "—",
@@ -1312,43 +1310,6 @@
}
}
},
"telemetry_digitalInputLabel": "数字输入",
"telemetry_digitalOutputLabel": "数字输出",
"telemetry_analogInputLabel": "模拟输入",
"telemetry_analogOutputLabel": "模拟输出",
"telemetry_genericLabel": "通用传感器",
"telemetry_luminosityLabel": "照度",
"telemetry_presenceLabel": "存在检测",
"telemetry_humidityLabel": "湿度",
"telemetry_accelerometerLabel": "加速度计",
"telemetry_pressureLabel": "气压",
"telemetry_altitudeLabel": "高度",
"telemetry_frequencyLabel": "频率",
"telemetry_percentageLabel": "百分比",
"telemetry_concentrationLabel": "浓度",
"telemetry_powerLabel": "功率",
"telemetry_distanceLabel": "距离",
"telemetry_energyLabel": "能量",
"telemetry_directionLabel": "方向",
"telemetry_timeLabel": "时间",
"telemetry_gyrometerLabel": "陀螺仪",
"telemetry_colourLabel": "颜色",
"telemetry_gpsLabel": "GPS",
"telemetry_switchLabel": "开关",
"telemetry_polylineLabel": "折线",
"telemetry_altitudeValue": "{meters} m",
"telemetry_frequencyValue": "{hertz} Hz",
"telemetry_pressureValue": "{hpa} hPa",
"telemetry_luminosityValue": "{lux} lx",
"telemetry_powerValue": "{watts} W",
"telemetry_distanceValue": "{meters} m",
"telemetry_energyValue": "{kilowattHours} kWh",
"telemetry_directionValue": "{degrees}°",
"telemetry_concentrationValue": "{ppm} ppm",
"telemetry_percentageValue": "{percent}%",
"telemetry_analogValue": "{value}",
"telemetry_autoFetchQuantity": "请求次数",
"telemetry_error": "无法获取数据",
"telemetry_noData": "暂无遥测数据",
"telemetry_channelTitle": "频道 {channel}",
"@telemetry_channelTitle": {
@@ -2144,9 +2105,6 @@
"translation_composerTitle": "在发送之前进行翻译",
"translation_enableTitle": "启用翻译功能",
"translation_composerSubtitle": "控制作曲家翻译图标的默认状态。",
"translation_autoIncomingTitle": "自动翻译消息",
"translation_autoIncomingSubtitle": "自动为通知以及聊天或频道翻译消息。",
"translation_translateMessage": "翻译消息",
"translation_targetLanguage": "目标语言",
"translation_useAppLanguage": "使用应用程序语言",
"translation_downloadedModelLabel": "下载的模型",
@@ -2359,97 +2317,8 @@
"chat_newMessages": "新的消息",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据",
"repeater_chanUtil": "频道利用率",
"@routing_lastWorked": {
"placeholders": {
"when": {
"type": "String"
}
}
},
"@routing_deliveryCounts": {
"placeholders": {
"successes": {
"type": "int"
},
"failures": {
"type": "int"
}
}
},
"@pathEditor_hopCounter": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"@pathEditor_invalidTokens": {
"placeholders": {
"tokens": {
"type": "String"
}
}
},
"@channels_communityShortId": {
"placeholders": {
"id": {
"type": "String"
}
}
},
"messageStatus_sent": "发送",
"common_undo": "撤销",
"messageStatus_delivered": "已送达",
"messageStatus_pending": "发送",
"messageStatus_failed": "发送失败",
"messageStatus_repeated": "多次听到",
"contacts_moreOptions": "更多选择",
"contacts_searchOpen": "搜索联系人",
"contacts_searchClose": "高级搜索",
"routing_title": "路由",
"routing_modeAuto": "汽车",
"routing_modeFlood": "洪水",
"routing_modeManual": "手册",
"routing_modeAutoHint": "自动选择已知最佳路径,当没有已知路径时,则进行“洪水”搜索。",
"routing_modeFloodHint": "通过所有中继站进行广播。 这种方式最可靠,但占用更多的时间。",
"routing_modeManualHint": "总是按照您设置的路径进行导航。",
"routing_currentRoute": "当前路线",
"routing_directNoHops": "直接连接— 无中继跳",
"routing_noPathYet": "目前还没有找到路径。直到找到路径,才会收到后续消息。",
"routing_floodBroadcast": "通过所有中继器进行广播",
"routing_editPath": "编辑路径",
"routing_forgetPath": "忘记原路",
"routing_knownPaths": "已知的路径",
"routing_knownPathsHint": "点击该路径以切换到它。",
"routing_inUse": "使用中",
"routing_qualityStrong": "强劲的初始阶段",
"routing_qualityGood": "不错的开端",
"routing_qualityFair": "第一次尝试,结果良好",
"routing_qualityWorked": "已完成",
"routing_qualityFlood": "通过新闻报道",
"routing_qualityUntested": "未经测试",
"routing_lastWorked": "工作于 {when}",
"routing_neverWorked": "从未得到证实",
"routing_floodDelivery": "洪水配送",
"pathEditor_title": "构建路径",
"pathEditor_noHops": "目前还没有添加任何啤酒花。点击下面的“添加”按钮,按顺序添加,或者直接保存,不添加任何啤酒花。",
"pathEditor_addHops": "按照顺序添加啤酒花",
"pathEditor_searchRepeaters": "重复搜索",
"pathEditor_advancedHex": "高级:原始十六进制路径",
"pathEditor_hexLabel": "十六进制前缀",
"pathEditor_hexHelper": "每次跳跃,使用两个十六进制字符,用逗号分隔。",
"pathEditor_invalidTokens": "无效:{tokens}",
"pathEditor_tooManyHops": "最多 64 个跳跃",
"pathEditor_usePath": "请使用此路径",
"pathEditor_removeHop": "去除啤酒花",
"routing_deliveryCounts": "{successes} delivered, {failures} failed",
"pathEditor_hopCounter": "{count} of 64 hops",
"pathEditor_unknownHop": "未知的重复器",
"map_zoomIn": "放大",
"map_zoomOut": "放大",
"map_centerMap": "中心地图",
"chrome_bluetoothRequiresChromium": "Web Bluetooth 需要 Chromium 浏览器",
"channels_communityShortId": "ID{id}...",
"pathTrace_legendGpsConfirmed": "通过GPS确认",
"pathTrace_legendInferred": "推测的位置"
"dialog_connectCompanion": "连接伴机以访问中继器和房间服务器功能。",
"dialog_disconnectedTitle": "已断开连接",
"dialog_disconnectedMessage": "你已与你的伙伴断开连接。",
"contact_connectCompanion": "连接至伴侣设备以访问中继器和房间服务器功能。"
}
+22 -44
View File
@@ -1,15 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'screens/chrome_required_screen.dart';
import 'screens/contacts_screen.dart';
import 'utils/platform_info.dart';
import 'connector/meshcore_connector.dart';
import 'screens/scanner_screen.dart';
import 'services/storage_service.dart';
import 'services/message_retry_service.dart';
import 'services/path_history_service.dart';
@@ -24,21 +23,11 @@ import 'services/translation_service.dart';
import 'services/ui_view_state_service.dart';
import 'services/timeout_prediction_service.dart';
import 'storage/prefs_manager.dart';
import 'theme/mesh_theme.dart';
import 'utils/app_logger.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// On desktop, debugPrint is not suppressed in release builds and every
// call is a synchronous stdout write. The connector logs heavily on hot
// paths (frame handling, queue/channel sync), which shows up as syscall
// overhead on low-end Linux machines (issue #202). The in-app debug log
// screens are unaffected they store entries themselves.
if (kReleaseMode) {
debugPrint = (String? message, {int? wrapWidth}) {};
}
// Initialize SharedPreferences cache
await PrefsManager.initialize();
@@ -92,13 +81,8 @@ void main() async {
timeoutPredictionService: timeoutPredictionService,
);
await connector.loadContactCache();
await connector.loadChannelSettings();
await connector.loadCachedChannels();
// Load persisted channel messages
await connector.loadAllChannelMessages();
await connector.loadUnreadState();
await connector.restoreLastCompanionScope();
await connector.loadAllCachedDataForCurrentCompanion();
runApp(
MeshCoreApp(
@@ -201,8 +185,23 @@ class MeshCoreApp extends StatelessWidget {
locale: _localeFromSetting(
settingsService.settings.languageOverride,
),
theme: MeshTheme.light(),
darkTheme: MeshTheme.dark(),
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
useMaterial3: true,
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
),
),
themeMode: _themeModeFromSetting(
settingsService.settings.themeMode,
),
@@ -210,14 +209,11 @@ class MeshCoreApp extends StatelessWidget {
// Update notification service with resolved locale
final locale = Localizations.localeOf(context);
NotificationService().setLocale(locale);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: _systemUiOverlayStyle(context),
child: child ?? const SizedBox.shrink(),
);
return child ?? const SizedBox.shrink();
},
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
? const ChromeRequiredScreen()
: const ScannerScreen(),
: const ContactsScreen(),
);
},
),
@@ -235,24 +231,6 @@ class MeshCoreApp extends StatelessWidget {
}
}
SystemUiOverlayStyle _systemUiOverlayStyle(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final isDark = theme.brightness == Brightness.dark;
final iconBrightness = isDark ? Brightness.light : Brightness.dark;
// Keep Android system bars aligned with the resolved Flutter theme.
return SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: iconBrightness,
statusBarBrightness: isDark ? Brightness.dark : Brightness.light,
systemNavigationBarColor: colorScheme.surface,
systemNavigationBarIconBrightness: iconBrightness,
systemNavigationBarDividerColor: colorScheme.surface,
systemNavigationBarContrastEnforced: false,
);
}
Locale? _localeFromSetting(String? languageCode) {
if (languageCode == null) return null;
return Locale(languageCode);
+4 -12
View File
@@ -113,7 +113,6 @@ class AppSettings {
final int tcpServerPort;
final bool jumpToOldestUnread;
final bool translationEnabled;
final bool autoTranslateIncomingMessages;
final String? translationTargetLanguageCode;
final bool composerTranslationEnabled;
final String? translationModelSourceUrl;
@@ -141,7 +140,7 @@ class AppSettings {
this.mapKeyPrefix = '',
this.mapShowMarkers = true,
this.mapShowGuessedLocations = true,
this.enableMessageTracing = true,
this.enableMessageTracing = false,
this.mapCacheBounds,
this.mapCacheMinZoom = 10,
this.mapCacheMaxZoom = 15,
@@ -149,7 +148,7 @@ class AppSettings {
this.notifyOnNewMessage = true,
this.notifyOnNewChannelMessage = true,
this.notifyOnNewAdvert = true,
this.autoRouteRotationEnabled = true,
this.autoRouteRotationEnabled = false,
this.maxRouteWeight = 5.0,
this.initialRouteWeight = 3.0,
this.routeWeightSuccessIncrement = 0.5,
@@ -167,7 +166,6 @@ class AppSettings {
this.tcpServerPort = 0,
this.jumpToOldestUnread = false,
this.translationEnabled = false,
this.autoTranslateIncomingMessages = true,
this.translationTargetLanguageCode,
this.composerTranslationEnabled = false,
this.translationModelSourceUrl,
@@ -228,7 +226,6 @@ class AppSettings {
'tcp_server_port': tcpServerPort,
'jump_to_oldest_unread': jumpToOldestUnread,
'translation_enabled': translationEnabled,
'auto_translate_incoming_messages': autoTranslateIncomingMessages,
'translation_target_language_code': translationTargetLanguageCode,
'composer_translation_enabled': composerTranslationEnabled,
'translation_model_source_url': translationModelSourceUrl,
@@ -264,7 +261,7 @@ class AppSettings {
mapShowMarkers: json['map_show_markers'] as bool? ?? true,
mapShowGuessedLocations:
json['map_show_guessed_locations'] as bool? ?? true,
enableMessageTracing: json['enable_message_tracing'] as bool? ?? true,
enableMessageTracing: json['enable_message_tracing'] as bool? ?? false,
mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map(
(key, value) => MapEntry(key.toString(), (value as num).toDouble()),
),
@@ -276,7 +273,7 @@ class AppSettings {
json['notify_on_new_channel_message'] as bool? ?? true,
notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true,
autoRouteRotationEnabled:
json['auto_route_rotation_enabled'] as bool? ?? true,
json['auto_route_rotation_enabled'] as bool? ?? false,
maxRouteWeight: (json['max_route_weight'] as num?)?.toDouble() ?? 5.0,
initialRouteWeight:
(json['initial_route_weight'] as num?)?.toDouble() ?? 3.0,
@@ -310,8 +307,6 @@ class AppSettings {
tcpServerPort: json['tcp_server_port'] as int? ?? 0,
jumpToOldestUnread: json['jump_to_oldest_unread'] as bool? ?? false,
translationEnabled: json['translation_enabled'] as bool? ?? false,
autoTranslateIncomingMessages:
json['auto_translate_incoming_messages'] as bool? ?? true,
translationTargetLanguageCode:
json['translation_target_language_code'] as String?,
composerTranslationEnabled:
@@ -401,7 +396,6 @@ class AppSettings {
int? tcpServerPort,
bool? jumpToOldestUnread,
bool? translationEnabled,
bool? autoTranslateIncomingMessages,
Object? translationTargetLanguageCode = _unset,
bool? composerTranslationEnabled,
Object? translationModelSourceUrl = _unset,
@@ -459,8 +453,6 @@ class AppSettings {
tcpServerPort: tcpServerPort ?? this.tcpServerPort,
jumpToOldestUnread: jumpToOldestUnread ?? this.jumpToOldestUnread,
translationEnabled: translationEnabled ?? this.translationEnabled,
autoTranslateIncomingMessages:
autoTranslateIncomingMessages ?? this.autoTranslateIncomingMessages,
translationTargetLanguageCode: translationTargetLanguageCode == _unset
? this.translationTargetLanguageCode
: translationTargetLanguageCode as String?,
-69
View File
@@ -1,69 +0,0 @@
import 'dart:ui';
import 'package:latlong2/latlong.dart';
import 'path_history.dart';
/// One observed route rendered on the path map the live traced path
/// (primary) or an alternate from the contact's path history — resolved to
/// map coordinates with per-hop confidence flags.
class DisplayPath {
final String id;
final String label;
final Color color;
final bool isPrimary;
/// Outbound hop bytes, including hops that could not be placed on the map.
final List<int> hopBytes;
/// Resolved map points: self, each locatable hop, then the target when its
/// position is known. Hops with no position are skipped here but still
/// counted in [unresolvedHops].
final List<LatLng> points;
/// Display name for each entry of [points].
final List<String> pointLabels;
/// Whether each entry of [points] is a GPS-grade position (vs inferred).
final List<bool> pointConfirmed;
/// Per segment (length points-1): true when either endpoint is inferred or
/// unlocatable hops were skipped in between rendered dashed.
final List<bool> segmentEstimated;
/// Per segment: the transmission ordinal of the segment's destination,
/// used to highlight the matching hop-list row during animation.
final List<int> rowForSegment;
/// Total transmissions on the full route (including unlocatable hops).
final int totalTransmissions;
/// True when the route ends with a chat-target endpoint row.
final bool hasTargetEndpoint;
final int gpsConfirmedHops;
final int unresolvedHops;
final double distanceMeters;
/// History metadata; null for the live traced (primary) path.
final PathRecord? record;
const DisplayPath({
required this.id,
required this.label,
required this.color,
required this.isPrimary,
required this.hopBytes,
required this.points,
required this.pointLabels,
required this.pointConfirmed,
required this.segmentEstimated,
required this.rowForSegment,
required this.totalTransmissions,
required this.hasTargetEndpoint,
required this.gpsConfirmedHops,
required this.unresolvedHops,
required this.distanceMeters,
this.record,
});
}
+3
View File
@@ -3,6 +3,7 @@ class PathRecord {
final int tripTimeMs;
final DateTime? timestamp;
final bool wasFloodDiscovery;
final int byteCount;
final List<int> pathBytes;
final int successCount;
final int failureCount;
@@ -17,6 +18,7 @@ class PathRecord {
required this.successCount,
required this.failureCount,
this.routeWeight = 1.0,
this.byteCount = 0,
});
String get displayText =>
@@ -48,6 +50,7 @@ class PathRecord {
successCount: json['success_count'] as int? ?? 0,
failureCount: json['failure_count'] as int? ?? 0,
routeWeight: (json['route_weight'] as num?)?.toDouble() ?? 1.0,
byteCount: json['byte_count'] as int? ?? 0,
);
}
}
-177
View File
@@ -1,177 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:latlong2/latlong.dart';
/// Timeline state for the packet-flow animation on the path map.
///
/// The packet travels each segment over [segmentMs] (scaled by [speed]),
/// then dwells at the reached hop for [dwellMs] so the hop visibly lights up.
/// Overlay layers listen to this controller directly; [activeSegment] only
/// fires when the segment index changes so list highlights rebuild cheaply.
class PathPlaybackController extends ChangeNotifier {
static const double segmentMs = 1100;
static const double dwellMs = 380;
static const List<double> speedSteps = [0.5, 1.0, 2.0];
late final Ticker _ticker;
List<LatLng> _points = const [];
double _timelineMs = 0;
Duration _lastTick = Duration.zero;
bool _playing = false;
bool _started = false;
double _speed = 1.0;
/// Segment currently being traveled (clamped to the last segment), or -1
/// while the animation has not been started listeners use this for
/// hop-list highlighting without rebuilding every tick.
final ValueNotifier<int> activeSegment = ValueNotifier(-1);
PathPlaybackController(TickerProvider vsync) {
_ticker = vsync.createTicker(_onTick);
}
List<LatLng> get points => _points;
bool get hasPath => _points.length >= 2;
int get segmentCount => hasPath ? _points.length - 1 : 0;
bool get playing => _playing;
double get speed => _speed;
/// True once the user has started or stepped the animation; the packet
/// overlay renders only in this state.
bool get started => _started;
double get _slotMs => segmentMs + dwellMs;
double get _totalMs => segmentCount * _slotMs;
bool get isComplete => hasPath && _timelineMs >= _totalMs;
int get currentSegment {
if (!hasPath) return 0;
return (_timelineMs / _slotMs).floor().clamp(0, segmentCount - 1);
}
/// Travel progress through [currentSegment]; 1.0 while dwelling at its end.
double get segmentProgress {
if (!hasPath) return 0;
final within = _timelineMs - currentSegment * _slotMs;
return (within / segmentMs).clamp(0.0, 1.0);
}
/// Dwell progress (0..1) at the reached hop, or null while traveling.
double? get dwellProgress {
if (!hasPath || isComplete) return null;
final within = _timelineMs - currentSegment * _slotMs;
if (within < segmentMs) return null;
return ((within - segmentMs) / dwellMs).clamp(0.0, 1.0);
}
/// Index of the point the packet has most recently reached.
int get reachedPointIndex {
if (!hasPath) return 0;
if (isComplete) return _points.length - 1;
return segmentProgress >= 1.0 ? currentSegment + 1 : currentSegment;
}
LatLng get position {
if (!hasPath) return const LatLng(0, 0);
final seg = currentSegment;
final a = _points[seg];
final b = _points[seg + 1];
final t = segmentProgress;
return LatLng(
a.latitude + (b.latitude - a.latitude) * t,
a.longitude + (b.longitude - a.longitude) * t,
);
}
/// Replaces the path and resets the animation to the start.
void setPath(List<LatLng> points) {
_ticker.stop();
_points = List.unmodifiable(points);
_timelineMs = 0;
_playing = false;
_started = false;
activeSegment.value = -1;
notifyListeners();
}
void play() {
if (!hasPath) return;
if (isComplete) _timelineMs = 0;
_started = true;
_playing = true;
activeSegment.value = currentSegment;
if (!_ticker.isActive) {
_lastTick = Duration.zero;
_ticker.start();
}
notifyListeners();
}
void pause() {
_ticker.stop();
_playing = false;
notifyListeners();
}
void togglePlay() => _playing ? pause() : play();
void replay() {
if (!hasPath) return;
_timelineMs = 0;
activeSegment.value = 0;
play();
}
/// Stops playback and hides the packet overlay.
void stop() {
_ticker.stop();
_playing = false;
_started = false;
_timelineMs = 0;
activeSegment.value = -1;
notifyListeners();
}
void stepForward() => _jumpToPoint(reachedPointIndex + 1);
void stepBack() => _jumpToPoint(reachedPointIndex - 1);
void cycleSpeed() {
final index = speedSteps.indexOf(_speed);
_speed = speedSteps[(index + 1) % speedSteps.length];
notifyListeners();
}
void _jumpToPoint(int index) {
if (!hasPath) return;
_ticker.stop();
_playing = false;
_started = true;
final clamped = index.clamp(0, _points.length - 1);
// Land at the start of the dwell window so the hop pulse plays.
_timelineMs = clamped == 0 ? 0 : (clamped - 1) * _slotMs + segmentMs;
activeSegment.value = currentSegment;
notifyListeners();
}
void _onTick(Duration elapsed) {
final dtMs = (elapsed - _lastTick).inMicroseconds / 1000.0;
_lastTick = elapsed;
_timelineMs = (_timelineMs + dtMs * _speed).clamp(0.0, _totalMs);
if (_timelineMs >= _totalMs) {
_ticker.stop();
_playing = false;
}
if (activeSegment.value != currentSegment) {
activeSegment.value = currentSegment;
}
notifyListeners();
}
@override
void dispose() {
_ticker.dispose();
activeSegment.dispose();
super.dispose();
}
}
-270
View File
@@ -181,276 +181,6 @@ class RadioSettings {
txPowerDbm: 14,
),
),
(
'Russia Artyom (VVO)',
RadioSettings(
frequencyMHz: 864.281,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_6,
txPowerDbm: 20,
),
),
(
'Russia Biysk (BSK)',
RadioSettings(
frequencyMHz: 869.000,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Russia Chelyabinsk (CEK)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_6,
txPowerDbm: 20,
),
),
(
'Russia Cherepovets (CEE)',
RadioSettings(
frequencyMHz: 868.570,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Irkutsk (IKT)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Ivanovo (IWA)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Izhevsk (IJK)',
RadioSettings(
frequencyMHz: 868.732,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Kaluga (KLF)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Kazan (KZN)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_6,
txPowerDbm: 20,
),
),
(
'Russia Khabarovsk (KHV)',
RadioSettings(
frequencyMHz: 864.281,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_6,
txPowerDbm: 20,
),
),
(
'Russia Kirov (KVX)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Lipetsk (LPK)',
RadioSettings(
frequencyMHz: 868.950,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf9,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Moscow (MOW)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Nizhny Novgorod (GOJ)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_6,
txPowerDbm: 20,
),
),
(
'Russia Novosibirsk (OVB)',
RadioSettings(
frequencyMHz: 869.000,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf9,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Rostov-on-Don (ROV)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf9,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Ryazan (RZN)',
RadioSettings(
frequencyMHz: 868.880,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf9,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Russia Samara (KUF)',
RadioSettings(
frequencyMHz: 864.281,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Saratov (GSV)',
RadioSettings(
frequencyMHz: 864.281,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia St. Petersburg (LED)',
RadioSettings(
frequencyMHz: 868.856,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Tambov (TBW)',
RadioSettings(
frequencyMHz: 868.950,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf10,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Russia Tula (TYA)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Tver (KLD)',
RadioSettings(
frequencyMHz: 869.169,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Ufa (UFA)',
RadioSettings(
frequencyMHz: 868.732,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_8,
txPowerDbm: 20,
),
),
(
'Russia Volgograd (VOG)',
RadioSettings(
frequencyMHz: 869.525,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Russia Voronezh (VOZ)',
RadioSettings(
frequencyMHz: 868.731,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_6,
txPowerDbm: 20,
),
),
(
'Russia Yekaterinburg (SVX)',
RadioSettings(
frequencyMHz: 869.046,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_7,
txPowerDbm: 20,
),
),
(
'Switzerland',
RadioSettings(
+26 -78
View File
@@ -4,7 +4,6 @@ import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../services/app_debug_log_service.dart';
import '../theme/mesh_theme.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../helpers/snack_bar_builder.dart';
@@ -59,57 +58,25 @@ class AppDebugLogScreen extends StatelessWidget {
child: hasEntries
? ListView.separated(
itemCount: entries.length,
separatorBuilder: (_, _) =>
const Divider(height: 1, color: MeshPalette.line),
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final entry = entries[index];
return Container(
color: MeshPalette.bg,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
return ListTile(
dense: true,
leading: _buildLevelIcon(entry.level),
title: Text(
'[${entry.tag}] ${entry.message}',
style: const TextStyle(
fontSize: 12,
fontFamily: 'monospace',
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLevelIcon(context, entry.level),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text.rich(
TextSpan(
children: [
TextSpan(
text: '[${entry.tag}] ',
style: MeshTheme.mono(
fontSize: 11.5,
color: _levelColor(entry.level),
),
),
TextSpan(
text: entry.message,
style: MeshTheme.mono(
fontSize: 11.5,
color: MeshPalette.ink2,
),
),
],
),
),
const SizedBox(height: 2),
Text(
entry.formattedTime,
style: MeshTheme.mono(
fontSize: 9.5,
color: MeshPalette.ink4,
),
),
],
),
),
],
subtitle: Text(
entry.formattedTime,
style: TextStyle(
fontSize: 10,
color: Colors.grey[600],
),
),
);
},
@@ -118,25 +85,25 @@ class AppDebugLogScreen extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.bug_report_outlined,
size: 64,
color: MeshPalette.ink3,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
context.l10n.debugLog_noEntries,
style: const TextStyle(
style: TextStyle(
fontSize: 16,
color: MeshPalette.ink3,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
context.l10n.debugLog_enableInSettings,
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: MeshPalette.ink3,
color: Colors.grey[500],
),
),
],
@@ -148,37 +115,18 @@ class AppDebugLogScreen extends StatelessWidget {
);
}
Color _levelColor(AppDebugLogLevel level) {
Widget _buildLevelIcon(AppDebugLogLevel level) {
switch (level) {
case AppDebugLogLevel.info:
return MeshPalette.blue;
case AppDebugLogLevel.warning:
return MeshPalette.warn;
case AppDebugLogLevel.error:
return MeshPalette.alert;
}
}
Widget _buildLevelIcon(BuildContext context, AppDebugLogLevel level) {
switch (level) {
case AppDebugLogLevel.info:
return const Icon(
Icons.info_outline,
size: 18,
color: MeshPalette.blue,
);
return const Icon(Icons.info_outline, size: 18, color: Colors.blue);
case AppDebugLogLevel.warning:
return const Icon(
Icons.warning_amber_outlined,
size: 18,
color: MeshPalette.warn,
color: Colors.orange,
);
case AppDebugLogLevel.error:
return const Icon(
Icons.error_outline,
size: 18,
color: MeshPalette.alert,
);
return const Icon(Icons.error_outline, size: 18, color: Colors.red);
}
}
}
File diff suppressed because it is too large Load Diff
+19 -113
View File
@@ -4,7 +4,6 @@ import 'package:flutter/services.dart';
import '../l10n/l10n.dart';
import '../services/ble_debug_log_service.dart';
import '../connector/meshcore_protocol.dart';
import '../theme/mesh_theme.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../helpers/snack_bar_builder.dart';
@@ -33,7 +32,6 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
return Scaffold(
appBar: AppBar(
title: AdaptiveAppBarTitle(context.l10n.debugLog_bleTitle),
centerTitle: true,
actions: [
IconButton(
tooltip: context.l10n.debugLog_copyLog,
@@ -103,14 +101,23 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
itemCount: showingFrames
? entries.length
: rawEntries.length,
separatorBuilder: (_, _) =>
const Divider(height: 1, color: MeshPalette.line),
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
if (showingFrames) {
final entry = entries[index];
final time =
'${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}';
return GestureDetector(
return ListTile(
dense: true,
title: Text(entry.description),
subtitle: Text('${entry.hexPreview}\n$time'),
isThreeLine: true,
leading: Icon(
entry.outgoing
? Icons.upload
: Icons.download,
size: 18,
),
onLongPress: () async {
await Clipboard.setData(
ClipboardData(
@@ -124,60 +131,6 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
),
);
},
child: Container(
color: MeshPalette.bg,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Icon(
entry.outgoing
? Icons.upload
: Icons.download,
size: 18,
color: entry.outgoing
? MeshPalette.blue
: MeshPalette.signal,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
entry.description,
style: MeshTheme.mono(
fontSize: 11.5,
color: MeshPalette.ink,
),
),
const SizedBox(height: 2),
Text(
entry.hexPreview,
style: MeshTheme.mono(
fontSize: 10,
color: MeshPalette.ink3,
),
),
const SizedBox(height: 2),
Text(
time,
style: MeshTheme.mono(
fontSize: 9.5,
color: MeshPalette.ink4,
),
),
],
),
),
],
),
),
);
}
@@ -185,65 +138,18 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
final info = _decodeRawPacket(entry.payload);
final time =
'${entry.timestamp.hour.toString().padLeft(2, '0')}:${entry.timestamp.minute.toString().padLeft(2, '0')}:${entry.timestamp.second.toString().padLeft(2, '0')}';
return GestureDetector(
return ListTile(
dense: true,
title: Text(info.title),
subtitle: Text('${info.summary}\n$time'),
isThreeLine: true,
leading: const Icon(Icons.download, size: 18),
onTap: () => _showRawDialog(context, info),
child: Container(
color: MeshPalette.bg,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(
Icons.download,
size: 18,
color: MeshPalette.signal,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
info.title,
style: MeshTheme.mono(
fontSize: 11.5,
color: MeshPalette.ink,
),
),
const SizedBox(height: 2),
Text(
info.summary,
style: MeshTheme.mono(
fontSize: 10,
color: MeshPalette.ink3,
),
),
const SizedBox(height: 2),
Text(
time,
style: MeshTheme.mono(
fontSize: 9.5,
color: MeshPalette.ink4,
),
),
],
),
),
],
),
),
);
},
)
: Center(
child: Text(
context.l10n.debugLog_noBleActivity,
style: const TextStyle(color: MeshPalette.ink3),
),
child: Text(context.l10n.debugLog_noBleActivity),
),
),
],
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1034 -508
View File
File diff suppressed because it is too large Load Diff
+70 -86
View File
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import '../l10n/l10n.dart';
import '../theme/mesh_theme.dart';
import '../widgets/mesh_ui.dart';
class ChromeRequiredScreen extends StatelessWidget {
const ChromeRequiredScreen({super.key});
@@ -9,95 +7,81 @@ class ChromeRequiredScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Icon in tinted circle
Container(
width: 88,
height: 88,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: scheme.tertiary.withValues(alpha: 0.10),
border: Border.all(
color: scheme.tertiary.withValues(alpha: 0.25),
width: 1.5,
body: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 32),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isDark
? [const Color(0xFF1A1A1A), const Color(0xFF0D0D0D)]
: [const Color(0xFFF5F7FA), const Color(0xFFE4E7EB)],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.browser_not_supported_rounded,
size: 80,
color: Colors.orange,
),
),
const SizedBox(height: 32),
Text(
l10n.scanner_chromeRequired,
textAlign: TextAlign.center,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 16),
Text(
l10n.scanner_chromeRequiredMessage,
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
color: isDark ? Colors.white70 : Colors.black54,
height: 1.5,
),
),
const SizedBox(height: 48),
// We can't really "fix" it for them other than telling them to use Chrome
// but we can provide a nice visual.
Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Colors.blue.withValues(alpha: 0.3)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outline, size: 20, color: Colors.blue),
const SizedBox(width: 12),
Text(
"Web Bluetooth requires a Chromium browser",
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
child: Icon(
Icons.browser_not_supported_rounded,
size: 42,
color: scheme.tertiary,
),
),
const SizedBox(height: 28),
// Title
Text(
l10n.scanner_chromeRequired,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700,
color: scheme.onSurface,
letterSpacing: -0.3,
),
),
const SizedBox(height: 12),
// Body text
Text(
l10n.scanner_chromeRequiredMessage,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: scheme.onSurfaceVariant,
height: 1.55,
),
),
const SizedBox(height: 32),
// Info chip
MeshCard(
margin: EdgeInsets.zero,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
color: scheme.secondaryContainer.withValues(alpha: 0.35),
borderColor: scheme.outline.withValues(alpha: 0.3),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.info_outline,
size: 18,
color: scheme.secondary,
),
const SizedBox(width: 10),
Flexible(
child: Text(
l10n.chrome_bluetoothRequiresChromium,
style: MeshTheme.mono(
fontSize: 12,
color: scheme.onSecondaryContainer,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
],
),
),
],
],
),
),
),
],
),
),
);
+73 -227
View File
@@ -1,18 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';
import '../connector/meshcore_connector.dart';
import '../helpers/snack_bar_builder.dart';
import '../l10n/l10n.dart';
import '../models/community.dart';
import '../storage/community_store.dart';
import '../theme/mesh_theme.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/mesh_ui.dart';
import '../widgets/qr_scanner_widget.dart';
import '../helpers/snack_bar_builder.dart';
/// Screen for scanning community QR codes to join communities.
///
@@ -39,87 +35,16 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
centerTitle: true,
),
body: _isProcessing
? Container(
color: Theme.of(context).colorScheme.surface,
child: const Center(child: CircularProgressIndicator()),
)
? const Center(child: CircularProgressIndicator())
: QrScannerWidget(
onScanned: (data) => _handleScannedData(context, data),
validator: Community.isValidQrData,
onValidationFailed: (_) => _showInvalidQrError(context),
instructions: context.l10n.community_scanInstructions,
overlay: _buildThemedOverlay(context),
),
);
}
Widget _buildThemedOverlay(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
// Dark semi-transparent background with cutout
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.black.withValues(alpha: 0.5),
BlendMode.srcOut,
),
child: Stack(
fit: StackFit.expand,
children: [
Container(
decoration: const BoxDecoration(
color: Colors.black,
backgroundBlendMode: BlendMode.dstOut,
),
),
Center(
child: Container(
height: 250,
width: 250,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(16),
),
),
),
],
),
),
// Corner brackets on top
const ScannerCornerOverlay(
scanWindowSize: 250,
borderColor: MeshPalette.blue,
borderWidth: 2,
cornerLength: 24,
),
// Instructions pill below the scan window
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 250 + 24),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
child: Text(
context.l10n.community_scanInstructions,
style: const TextStyle(color: MeshPalette.ink2, fontSize: 13),
textAlign: TextAlign.center,
),
),
],
),
),
],
);
}
Future<void> _handleScannedData(BuildContext context, String data) async {
if (_isProcessing) return;
@@ -155,7 +80,7 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.community_invalidQrCode),
backgroundColor: MeshPalette.alert,
backgroundColor: Colors.red,
);
}
} finally {
@@ -171,74 +96,29 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.community_invalidQrCode),
backgroundColor: MeshPalette.warn,
backgroundColor: Colors.orange,
duration: const Duration(seconds: 2),
);
}
void _showAlreadyMemberDialog(BuildContext context, Community community) {
showMeshSheet(
context,
builder: (sheetContext) {
final sheetScheme = Theme.of(sheetContext).colorScheme;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
BottomSheetHeader(title: context.l10n.community_alreadyMember),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 4),
child: Text(
context.l10n.community_alreadyMemberMessage(community.name),
style: TextStyle(color: sheetScheme.onSurfaceVariant),
),
),
MeshCard(
child: Row(
children: [
const Icon(
Icons.groups,
color: MeshPalette.magenta,
size: 32,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
community.name,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15,
),
),
Text(
'ID: ${community.shortCommunityId}...',
style: MeshTheme.mono(
fontSize: 11.5,
color: sheetScheme.onSurfaceVariant,
),
),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: FilledButton(
onPressed: () {
Navigator.pop(sheetContext);
Navigator.pop(context);
},
child: Text(context.l10n.common_ok),
),
),
],
);
},
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(context.l10n.community_alreadyMember),
content: Text(
context.l10n.community_alreadyMemberMessage(community.name),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(dialogContext);
Navigator.pop(context);
},
child: Text(context.l10n.common_ok),
),
],
),
);
}
@@ -247,111 +127,77 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
Community community,
) async {
bool addPublicChannel = true;
final completer = Completer<bool>();
await showMeshSheet<void>(
context,
builder: (sheetContext) => StatefulBuilder(
builder: (sheetContext, setSheetState) {
final joinScheme = Theme.of(sheetContext).colorScheme;
return Column(
final result = await showDialog<bool>(
context: context,
builder: (dialogContext) => StatefulBuilder(
builder: (dialogContext, setDialogState) => AlertDialog(
title: Text(context.l10n.community_joinTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BottomSheetHeader(title: context.l10n.community_joinTitle),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 4),
child: Text(
context.l10n.community_joinConfirmation(community.name),
style: TextStyle(color: joinScheme.onSurfaceVariant),
),
),
MeshCard(
child: Row(
children: [
AvatarCircle(
name: community.name,
icon: Icons.groups,
color: MeshPalette.magenta,
size: 44,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
community.name,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15,
),
Text(context.l10n.community_joinConfirmation(community.name)),
const SizedBox(height: 16),
Row(
children: [
Icon(
Icons.groups,
color: Theme.of(dialogContext).colorScheme.primary,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
community.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
'ID: ${community.shortCommunityId}...',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
Text(
'ID: ${community.shortCommunityId}...',
style: MeshTheme.mono(
fontSize: 11.5,
color: joinScheme.onSurfaceVariant,
),
),
],
),
),
],
),
],
),
),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 8),
CheckboxListTile(
value: addPublicChannel,
onChanged: (value) {
setSheetState(() {
setDialogState(() {
addPublicChannel = value ?? true;
});
},
title: Text(context.l10n.community_addPublicChannel),
subtitle: Text(context.l10n.community_addPublicChannelHint),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
completer.complete(false);
Navigator.pop(sheetContext);
},
child: Text(context.l10n.common_cancel),
),
),
const SizedBox(width: 12),
Expanded(
child: FilledButton(
onPressed: () {
completer.complete(true);
Navigator.pop(sheetContext);
},
child: Text(context.l10n.community_join),
),
),
],
),
contentPadding: EdgeInsets.zero,
),
],
);
},
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: Text(context.l10n.common_cancel),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: Text(context.l10n.community_join),
),
],
),
),
);
// If sheet was dismissed without a button press, treat as cancel
if (!completer.isCompleted) {
completer.complete(false);
}
final result = await completer.future;
if (result && context.mounted) {
if (result == true && context.mounted) {
await _joinCommunity(context, community, addPublicChannel);
} else if (context.mounted) {
// User cancelled - go back
@@ -385,7 +231,7 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.community_joined(community.name)),
backgroundColor: MeshPalette.signal,
backgroundColor: Colors.green,
);
// Return to previous screen
+32 -114
View File
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart';
import 'package:meshcore_open/models/companion_radio_stats.dart';
import 'package:meshcore_open/l10n/l10n.dart';
import 'package:meshcore_open/theme/mesh_theme.dart';
import 'package:meshcore_open/widgets/mesh_ui.dart';
import 'package:provider/provider.dart';
class CompanionRadioStatsScreen extends StatefulWidget {
@@ -51,25 +49,6 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
super.dispose();
}
Widget _tile(String text, IconData icon, Color color) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row(
children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: 10),
Expanded(
child: Text(
text,
style: MeshTheme.mono(fontSize: 13, color: scheme.onSurface),
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
@@ -106,105 +85,44 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
valueListenable: connector.radioStatsNotifier,
builder: (context, stats, _) {
return ListView(
padding: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.all(16),
children: [
if (stats != null) ...[
const SectionHeader(
'Signal',
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
Text(
l10n.radioStats_noiseFloor(stats.noiseFloorDbm),
style: tt.titleMedium,
),
MeshCard(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_tile(
l10n.radioStats_noiseFloor(stats.noiseFloorDbm),
Icons.noise_aware,
scheme.onSurfaceVariant,
),
const Divider(height: 1),
_tile(
l10n.radioStats_lastRssi(stats.lastRssiDbm),
Icons.wifi_tethering,
scheme.onSurfaceVariant,
),
const Divider(height: 1),
_tile(
l10n.radioStats_lastSnr(
stats.lastSnrDb.toStringAsFixed(1),
),
Icons.signal_cellular_alt,
MeshTheme.snrColor(stats.lastSnrDb, blocked: false),
),
],
const SizedBox(height: 4),
Text(l10n.radioStats_lastRssi(stats.lastRssiDbm)),
Text(
l10n.radioStats_lastSnr(
stats.lastSnrDb.toStringAsFixed(1),
),
),
const SectionHeader(
'Airtime',
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
),
MeshCard(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_tile(
l10n.radioStats_txAir(stats.txAirSecs),
Icons.upload,
MeshPalette.blue,
),
const Divider(height: 1),
_tile(
l10n.radioStats_rxAir(stats.rxAirSecs),
Icons.download,
MeshPalette.blue,
),
],
),
),
] else ...[
const SizedBox(height: 80),
Center(
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 8),
Center(
child: Text(
l10n.radioStats_waiting,
style: TextStyle(color: scheme.onSurfaceVariant),
),
),
],
SectionHeader(
l10n.radioStats_chartCaption,
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
height: 200,
child: CustomPaint(
painter: _NoiseChartPainter(
samples: List<double>.from(_noiseHistory),
colorScheme: scheme,
textTheme: tt,
),
child: const SizedBox.expand(),
Text(l10n.radioStats_txAir(stats.txAirSecs)),
Text(l10n.radioStats_rxAir(stats.rxAirSecs)),
const SizedBox(height: 16),
] else
Text(l10n.radioStats_waiting),
const SizedBox(height: 16),
SizedBox(
height: 200,
child: CustomPaint(
painter: _NoiseChartPainter(
samples: List<double>.from(_noiseHistory),
colorScheme: scheme,
textTheme: tt,
),
child: const SizedBox.expand(),
),
),
const SizedBox(height: 8),
Text(
l10n.radioStats_chartCaption,
style: tt.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
],
);
},
@@ -292,10 +210,10 @@ class _NoiseChartPainter extends CustomPainter {
}
final span = maxV - minV;
for (var i = 0; i <= 4; i++) {
final v = maxV - span * i / 4;
for (var i = 0; i <= 2; i++) {
final v = maxV - span * i / 2;
final tp = _yAxisLabel(v);
final y = chart.top + (chart.height * i / 4) - tp.height / 2;
final y = chart.top + (chart.height * i / 2) - tp.height / 2;
tp.paint(canvas, Offset(4, y));
}
File diff suppressed because it is too large Load Diff
+128 -210
View File
@@ -7,14 +7,11 @@ import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../l10n/l10n.dart';
import '../l10n/contact_localization.dart';
import '../models/contact.dart';
import '../theme/mesh_theme.dart';
import '../utils/contact_search.dart';
import '../utils/platform_info.dart';
import '../widgets/app_bar.dart';
import '../widgets/list_filter_widget.dart';
import '../widgets/mesh_ui.dart';
import '../helpers/snack_bar_builder.dart';
enum DiscoverySortOption { lastSeen, name, type }
@@ -49,34 +46,6 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
: contact.lastSeen;
}
/// Node-type avatar color per design language.
Color _avatarColor(int type) {
switch (type) {
case advTypeRepeater:
return MeshPalette.warn;
case advTypeRoom:
return MeshPalette.magenta;
case advTypeSensor:
return const Color(0xFF4ACCC4); // teal
default:
return MeshPalette.blue;
}
}
/// Node-type avatar icon; null = show initials for chat nodes.
IconData? _avatarIcon(int type) {
switch (type) {
case advTypeRepeater:
return Icons.cell_tower;
case advTypeRoom:
return Icons.meeting_room;
case advTypeSensor:
return Icons.sensors;
default:
return null;
}
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
@@ -102,10 +71,7 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
PopupMenuItem(
child: Row(
children: [
Icon(
Icons.delete,
color: Theme.of(context).colorScheme.error,
),
const Icon(Icons.delete, color: Colors.red),
const SizedBox(width: 8),
Text(context.l10n.discoveredContacts_deleteContactAll),
],
@@ -123,185 +89,103 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
children: [
_buildFilters(filteredAndSorted, connector),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 220),
child: discoveredContacts.isEmpty
? Center(
key: const ValueKey('empty_all'),
child: Text(l10n.contacts_noContacts),
)
: filteredAndSorted.isEmpty
? Center(
key: const ValueKey('empty_filtered'),
child: Text(l10n.discoveredContacts_noMatching),
)
: ListView.builder(
key: const ValueKey('list'),
padding: const EdgeInsets.only(bottom: 24),
itemCount: filteredAndSorted.length,
itemBuilder: (context, index) {
final contact = filteredAndSorted[index];
final tile = _buildDiscoveryTile(
context,
contact,
connector,
index,
);
if (PlatformInfo.isDesktop) {
return GestureDetector(
onSecondaryTapUp: (_) =>
_showContactContextMenu(contact, connector),
child: tile,
);
}
return tile;
},
),
),
),
],
),
);
}
Widget _buildDiscoveryTile(
BuildContext context,
Contact contact,
MeshCoreConnector connector,
int index,
) {
final scheme = Theme.of(context).colorScheme;
final isChat = contact.type == advTypeChat;
return ListEntrance(
index: index,
child: MeshCard(
onTap: () async {
try {
final imported = await connector.importDiscoveredContact(contact);
if (!context.mounted) return;
if (!imported) {
showDismissibleSnackBar(
context,
content: Text(context.l10n.contacts_contactImportFailed),
);
return;
}
showDismissibleSnackBar(
context,
content: Text(context.l10n.discoveredContacts_contactAdded),
action: SnackBarAction(
label: context.l10n.common_undo,
onPressed: () => connector.removeContact(contact),
),
);
} catch (_) {
if (!context.mounted) return;
showDismissibleSnackBar(
context,
content: Text(context.l10n.contacts_contactImportFailed),
);
}
},
onLongPress: () => _showContactContextMenu(contact, connector),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
child: Row(
children: [
AvatarCircle(
name: contact.name,
size: 42,
color: isChat ? null : _avatarColor(contact.type),
icon: _avatarIcon(contact.type),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Name + type chip
Row(
children: [
Expanded(
child: Text(
child: discoveredContacts.isEmpty
? Center(child: Text(l10n.contacts_noContacts))
: filteredAndSorted.isEmpty
? Center(child: Text(l10n.discoveredContacts_noMatching))
: ListView.builder(
itemCount: filteredAndSorted.length,
itemBuilder: (context, index) {
final contact = filteredAndSorted[index];
final tile = ListTile(
leading: CircleAvatar(
backgroundColor: _getTypeColor(contact.type),
child: Icon(
_getTypeIcon(contact.type),
color: Colors.white,
size: 20,
),
),
title: Text(
contact.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 15,
),
),
),
const SizedBox(width: 6),
StatusChip(
label: contact.typeLabel(context.l10n).toUpperCase(),
color: _avatarColor(contact.type),
icon: _avatarIcon(contact.type),
),
],
),
const SizedBox(height: 3),
// Short pub key
Row(
children: [
Expanded(
child: Text(
subtitle: Text(
contact.shortPubKeyHex,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: MeshTheme.mono(
fontSize: 11,
color: scheme.onSurfaceVariant,
),
// Clamp text scaling in trailing section to prevent overflow while
// maintaining accessibility. Primary content (title/subtitle) scales normally.
trailing: MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(
MediaQuery.textScalerOf(
context,
).scale(1.0).clamp(1.0, 1.3),
),
),
child: SizedBox(
width: 120,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
_formatLastSeen(
context,
_resolveLastSeen(contact),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (contact.hasLocation)
Icon(
Icons.location_on,
size: 14,
color: Colors.grey[400],
),
if (contact.rawPacket != null)
const SizedBox(width: 2),
if (contact.rawPacket != null)
Icon(
Icons.cell_tower,
size: 14,
color: Colors.grey[400],
),
],
),
],
),
),
),
),
if (contact.hasLocation) ...[
const SizedBox(width: 6),
Icon(
Icons.location_on,
size: 13,
color: scheme.onSurfaceVariant.withValues(
alpha: 0.55,
),
),
],
if (contact.rawPacket != null) ...[
const SizedBox(width: 4),
Icon(
Icons.cell_tower,
size: 13,
color: scheme.onSurfaceVariant.withValues(
alpha: 0.55,
),
),
],
],
onTap: () {
connector.importDiscoveredContact(contact);
},
onLongPress: () =>
_showContactContextMenu(contact, connector),
);
if (PlatformInfo.isDesktop) {
return GestureDetector(
onSecondaryTapUp: (_) =>
_showContactContextMenu(contact, connector),
child: tile,
);
}
return tile;
},
),
],
),
),
const SizedBox(width: 10),
// Last seen time
MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(
MediaQuery.textScalerOf(context).scale(1.0).clamp(1.0, 1.3),
),
),
child: Text(
_formatLastSeen(context, _resolveLastSeen(contact)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: MeshTheme.mono(
fontSize: 11,
color: scheme.onSurfaceVariant,
),
),
),
],
),
),
],
),
);
}
@@ -310,17 +194,19 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
Contact contact,
MeshCoreConnector connector,
) async {
final action = await showMeshSheet<String>(
context,
final action = await showModalBottomSheet<String>(
context: context,
showDragHandle: true,
builder: (sheetContext) {
final l10n = context.l10n;
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
BottomSheetHeader(
title: contact.name,
subtitle: contact.typeLabel(l10n),
ListTile(
leading: const Icon(Icons.add_reaction_sharp),
title: Text(l10n.discoveredContacts_addContact),
onTap: () => Navigator.of(sheetContext).pop('import_contact'),
),
ListTile(
leading: const Icon(Icons.copy),
@@ -332,7 +218,6 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
title: Text(l10n.discoveredContacts_deleteContact),
onTap: () => Navigator.of(sheetContext).pop('delete_contact'),
),
const SizedBox(height: 8),
],
),
);
@@ -342,6 +227,9 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
if (!mounted || action == null) return;
switch (action) {
case 'import_contact':
connector.importDiscoveredContact(contact);
break;
case 'copy_contact':
if (contact.rawPacket == null) return;
final hexString = pubKeyToHex(contact.rawPacket!);
@@ -541,6 +429,36 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
}
}
IconData _getTypeIcon(int type) {
switch (type) {
case advTypeChat:
return Icons.chat;
case advTypeRepeater:
return Icons.cell_tower;
case advTypeRoom:
return Icons.group;
case advTypeSensor:
return Icons.sensors;
default:
return Icons.device_unknown;
}
}
Color _getTypeColor(int type) {
switch (type) {
case advTypeChat:
return Colors.blue;
case advTypeRepeater:
return Colors.orange;
case advTypeRoom:
return Colors.purple;
case advTypeSensor:
return Colors.green;
default:
return Colors.grey;
}
}
String _formatLastSeen(BuildContext context, DateTime lastSeen) {
final now = DateTime.now();
final diff = now.difference(lastSeen);
File diff suppressed because it is too large Load Diff
+126 -166
View File
@@ -10,9 +10,6 @@ import '../services/app_settings_service.dart';
import '../services/map_tile_cache_service.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../helpers/snack_bar_builder.dart';
import '../theme/mesh_theme.dart';
import '../widgets/mesh_ui.dart';
import '../widgets/themed_map_tile_layer.dart';
class MapCacheScreen extends StatefulWidget {
const MapCacheScreen({super.key});
@@ -79,34 +76,27 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
return Positioned(
top: 12,
left: 12,
child: DecoratedBox(
decoration: BoxDecoration(
color: MeshPalette.bg1.withValues(alpha: 0.90),
borderRadius: BorderRadius.circular(MeshRadii.md),
border: Border.all(color: MeshPalette.line2),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(MeshRadii.md),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: context.l10n.map_zoomIn,
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: context.l10n.map_zoomOut,
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: context.l10n.map_centerMap,
onPressed: _resetMapView,
),
],
),
child: Card(
elevation: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.add),
tooltip: 'Zoom in',
onPressed: () => _zoomMapBy(1),
),
IconButton(
icon: const Icon(Icons.remove),
tooltip: 'Zoom out',
onPressed: () => _zoomMapBy(-1),
),
IconButton(
icon: const Icon(Icons.my_location),
tooltip: 'Center map',
onPressed: _resetMapView,
),
],
),
),
);
@@ -291,7 +281,6 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
final tileCache = context.read<MapTileCacheService>();
final selectedBounds = _selectedBounds;
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
final progressValue = _estimatedTiles == 0
? 0.0
@@ -329,7 +318,13 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
),
),
children: [
ThemedMapTileLayer(tileCache: tileCache),
TileLayer(
urlTemplate: kMapTileUrlTemplate,
tileProvider: tileCache.tileProvider,
userAgentPackageName:
MapTileCacheService.userAgentPackageName,
maxZoom: 19,
),
if (selectedBounds != null)
PolygonLayer(
polygons: [
@@ -347,25 +342,14 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
Positioned(
top: 12,
right: 12,
child: DecoratedBox(
decoration: BoxDecoration(
color: MeshPalette.bg1.withValues(alpha: 0.93),
borderRadius: BorderRadius.circular(MeshRadii.md),
border: Border.all(color: MeshPalette.line2),
),
child: Card(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
padding: const EdgeInsets.all(8),
child: Text(
selectedBounds == null
? l10n.mapCache_noAreaSelected
: _formatBounds(selectedBounds, l10n),
style: MeshTheme.mono(
fontSize: 11,
color: MeshPalette.ink2,
),
style: const TextStyle(fontSize: 12),
),
),
),
@@ -375,133 +359,109 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
),
SafeArea(
top: false,
child: DecoratedBox(
decoration: BoxDecoration(
color: scheme.surfaceContainerLow,
border: Border(top: BorderSide(color: scheme.outlineVariant)),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SectionHeader(
l10n.mapCache_cacheArea,
padding: const EdgeInsets.fromLTRB(0, 12, 0, 8),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.mapCache_cacheArea,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.crop_free),
label: Text(l10n.mapCache_useCurrentView),
onPressed: _isDownloading
? null
: _setBoundsFromView,
),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.crop_free),
label: Text(l10n.mapCache_useCurrentView),
onPressed: _isDownloading ? null : _setBoundsFromView,
),
const SizedBox(width: 12),
TextButton(
onPressed: _isDownloading || selectedBounds == null
? null
: _clearBounds,
child: Text(l10n.common_clear),
),
],
),
const SizedBox(height: 12),
SectionHeader(
l10n.mapCache_zoomRange,
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
),
RangeSlider(
values: RangeValues(
_minZoom.toDouble(),
_maxZoom.toDouble(),
),
min: 3,
max: 18,
divisions: 15,
labels: RangeLabels('$_minZoom', '$_maxZoom'),
onChanged: _isDownloading
? null
: (values) {
setState(() {
_minZoom = values.start.round();
_maxZoom = values.end.round();
});
},
onChangeEnd: _isDownloading
? null
: (_) {
_saveZoomRange();
},
),
Text(
l10n.mapCache_estimatedTiles(_estimatedTiles),
style: MeshTheme.mono(
fontSize: 12,
color: scheme.onSurfaceVariant,
),
),
if (_isDownloading) ...[
const SizedBox(height: 8),
LinearProgressIndicator(
value: progressValue,
color: MeshPalette.blue,
backgroundColor: scheme.surfaceContainerHighest,
),
const SizedBox(height: 4),
Text(
l10n.mapCache_downloadedTiles(
_completedTiles,
_estimatedTiles,
),
style: MeshTheme.mono(
fontSize: 12,
color: scheme.onSurfaceVariant,
),
const SizedBox(width: 12),
TextButton(
onPressed: _isDownloading || selectedBounds == null
? null
: _clearBounds,
child: Text(l10n.common_clear),
),
],
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.download),
label: Text(l10n.mapCache_downloadTilesButton),
onPressed: _isDownloading || selectedBounds == null
? null
: _startDownload,
),
),
const SizedBox(width: 12),
OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: MeshPalette.alert,
side: const BorderSide(
color: MeshPalette.alertLine,
),
),
onPressed: _isDownloading ? null : _clearCache,
child: Text(l10n.mapCache_clearCacheButton),
),
],
),
const SizedBox(height: 12),
Text(
l10n.mapCache_zoomRange,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
if (_failedTiles > 0 && !_isDownloading)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
l10n.mapCache_failedDownloads(_failedTiles),
style: MeshTheme.mono(
fontSize: 12,
color: MeshPalette.alert,
),
),
RangeSlider(
values: RangeValues(
_minZoom.toDouble(),
_maxZoom.toDouble(),
),
min: 3,
max: 18,
divisions: 15,
labels: RangeLabels('$_minZoom', '$_maxZoom'),
onChanged: _isDownloading
? null
: (values) {
setState(() {
_minZoom = values.start.round();
_maxZoom = values.end.round();
});
},
onChangeEnd: _isDownloading
? null
: (_) {
_saveZoomRange();
},
),
Text(l10n.mapCache_estimatedTiles(_estimatedTiles)),
if (_isDownloading) ...[
const SizedBox(height: 8),
LinearProgressIndicator(value: progressValue),
const SizedBox(height: 4),
Text(
l10n.mapCache_downloadedTiles(
_completedTiles,
_estimatedTiles,
),
),
],
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.download),
label: Text(l10n.mapCache_downloadTilesButton),
onPressed: _isDownloading || selectedBounds == null
? null
: _startDownload,
),
),
],
),
const SizedBox(width: 12),
OutlinedButton(
onPressed: _isDownloading ? null : _clearCache,
child: Text(l10n.mapCache_clearCacheButton),
),
],
),
if (_failedTiles > 0 && !_isDownloading)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
l10n.mapCache_failedDownloads(_failedTiles),
style: TextStyle(color: Colors.orange[700]),
),
),
],
),
),
),
+780 -2175
View File
File diff suppressed because it is too large Load Diff
+143 -99
View File
@@ -9,10 +9,8 @@ import '../models/path_selection.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../services/repeater_command_service.dart';
import '../theme/mesh_theme.dart';
import '../widgets/empty_state.dart';
import '../widgets/mesh_ui.dart';
import '../widgets/routing_sheet.dart';
import '../widgets/path_management_dialog.dart';
import '../widgets/snr_indicator.dart';
import '../helpers/snack_bar_builder.dart';
class NeighborsScreen extends StatefulWidget {
@@ -169,7 +167,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.neighbors_receivedData),
backgroundColor: Theme.of(context).colorScheme.tertiary,
backgroundColor: Colors.green,
);
_statusTimeout?.cancel();
if (!mounted) return;
@@ -229,7 +227,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.neighbors_requestTimedOut),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
_recordStatusResult(false);
});
@@ -243,7 +241,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.neighbors_errorLoading(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
}
}
@@ -281,9 +279,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
children: [
Text(
l10n.neighbors_repeatersNeighbors,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
repeater.name,
@@ -291,18 +287,75 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
fontSize: 14,
fontWeight: FontWeight.normal,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
centerTitle: false,
actions: [
IconButton(
PopupMenuButton<String>(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onSelected: (mode) async {
if (mode == 'flood') {
await connector.setPathOverride(repeater, pathLen: -1);
} else {
await connector.setPathOverride(repeater, pathLen: null);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'auto',
child: Row(
children: [
Icon(
Icons.auto_mode,
size: 20,
color: !isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_autoUseSavedPath,
style: TextStyle(
fontWeight: !isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
PopupMenuItem(
value: 'flood',
child: Row(
children: [
Icon(
Icons.waves,
size: 20,
color: isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_forceFloodMode,
style: TextStyle(
fontWeight: isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
],
),
IconButton(
icon: const Icon(Icons.timeline),
tooltip: l10n.repeater_pathManagement,
onPressed: () =>
ContactRoutingSheet.show(context, contact: repeater),
PathManagementDialog.show(context, contact: repeater),
),
IconButton(
icon: _isLoading
@@ -322,16 +375,23 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
child: RefreshIndicator(
onRefresh: _loadNeighbors,
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
padding: const EdgeInsets.all(16),
children: [
if (!_isLoaded &&
!_hasData &&
(_parsedNeighbors == null || _parsedNeighbors!.isEmpty))
EmptyState(icon: Icons.wifi_find, title: l10n.neighbors_noData),
Center(
child: Text(
l10n.neighbors_noData,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
),
if (_isLoaded ||
_hasData &&
!(_parsedNeighbors == null || _parsedNeighbors!.isEmpty))
_buildNeighborsList(connector),
_buildNeighborsInfoCard(
"${l10n.repeater_neighbors} - $_neighborCount",
),
],
),
),
@@ -339,97 +399,81 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
);
}
Widget _buildNeighborsList(MeshCoreConnector connector) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionHeader(
'${l10n.repeater_neighbors}$_neighborCount',
padding: const EdgeInsets.fromLTRB(4, 8, 4, 10),
),
for (var i = 0; i < _parsedNeighbors!.length; i++)
ListEntrance(
index: i,
child: _buildNeighborRow(_parsedNeighbors![i], connector.currentSf),
),
],
);
}
Widget _buildNeighborRow(Map<String, dynamic> data, int? spreadingFactor) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final Contact? contact = data['contact'] as Contact?;
final double snr = data['snr'] as double;
final int lastHeardSeconds = data['lastHeard'] as int;
final name = contact != null
? contact.name
: l10n.neighbors_unknownContact(
'<${pubKeyToHex(data['publicKey'] as Uint8List)}>',
);
final snrColor = MeshTheme.snrColor(snr, blocked: false);
final heardLabel = l10n.neighbors_heardAgo(
fmtDuration(lastHeardSeconds + 0.0),
);
return MeshCard(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
margin: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
AvatarCircle(
name: name,
size: 40,
color: contact != null ? MeshPalette.warn : scheme.onSurfaceVariant,
icon: contact != null ? Icons.cell_tower : Icons.device_unknown,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
Widget _buildNeighborsInfoCard(String title) {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 15,
),
Icon(
Icons.info_outline,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
const SizedBox(height: 2),
const SizedBox(width: 8),
Text(
heardLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
SignalBars(snr: snr, height: 16),
const SizedBox(height: 4),
Text(
'${snr.toStringAsFixed(1)} dB',
style: MeshTheme.mono(
fontSize: 11,
fontWeight: FontWeight.w600,
color: snrColor,
const Divider(),
for (final entry in _parsedNeighbors!.asMap().entries)
_buildInfoRow(
entry.value['contact'] != null
? entry.value['contact'].name
: context.l10n.neighbors_unknownContact(
"<${pubKeyToHex(entry.value['publicKey'])}>",
),
context.l10n.neighbors_heardAgo(
fmtDuration(entry.value['lastHeard'] + 0.0),
),
entry.value['snr'],
connector.currentSf!,
),
],
],
),
),
);
}
Widget _buildInfoRow(
String label,
String value,
double snr,
int spreadingFactor,
) {
final snrUi = snrUiFromSNR(snr, spreadingFactor);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text(value),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(snrUi.icon, color: snrUi.color, size: 18.0),
Text(
snrUi.text,
style: TextStyle(fontSize: 10, color: snrUi.color),
),
],
),
),
),
],
),
File diff suppressed because it is too large Load Diff
+276 -259
View File
@@ -1,15 +1,14 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../theme/mesh_theme.dart';
import '../widgets/debug_frame_viewer.dart';
import '../services/repeater_command_service.dart';
import '../widgets/routing_sheet.dart';
import '../widgets/path_management_dialog.dart';
import '../helpers/snack_bar_builder.dart';
class RepeaterCliScreen extends StatefulWidget {
@@ -35,6 +34,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
StreamSubscription<Uint8List>? _frameSubscription;
RepeaterCommandService? _commandService;
// Common commands for quick access
late final List<Map<String, String>> _quickCommands = [
{'labelKey': 'advertise', 'command': 'advert'},
{'labelKey': 'getName', 'command': 'get name'},
@@ -67,8 +67,12 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
void _setupMessageListener() {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
// Listen for incoming text messages from the repeater
_frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return;
// Check if it's a text message response
if (frame[0] == respCodeContactMsgRecv ||
frame[0] == respCodeContactMsgRecvV3) {
_handleTextMessageResponse(frame);
@@ -98,7 +102,12 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
final parsed = parseContactMessageText(frame);
if (parsed == null) return;
if (!_matchesRepeaterPrefix(parsed.senderPrefix)) return;
// Notify command service of response (for retry handling)
_commandService?.handleResponse(widget.repeater, parsed.text);
// Note: The command service will handle the response via the Future
// We don't need to add it to history here anymore as _sendCommand will do it
}
bool _matchesRepeaterPrefix(Uint8List prefix) {
@@ -122,6 +131,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
});
});
// Show debug info if requested
if (showDebug && mounted) {
final frame = buildSendCliCommandFrame(
widget.repeater.publicKey,
@@ -134,6 +144,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
);
}
// Send CLI command to repeater with retry
try {
if (_commandService != null) {
final connector = Provider.of<MeshCoreConnector>(
@@ -146,6 +157,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
command,
retries: 1,
);
if (mounted) {
setState(() {
_commandHistory.add({
@@ -172,6 +184,7 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
_historyIndex = -1;
_commandFocusNode.requestFocus();
// Auto-scroll to bottom
Future.delayed(const Duration(milliseconds: 100), () {
if (_scrollController.hasClients) {
_scrollController.animateTo(
@@ -226,6 +239,161 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
});
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final connector = context.watch<MeshCoreConnector>();
final repeater = _resolveRepeater(connector);
final isFloodMode = repeater.pathOverride == -1;
return Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(l10n.repeater_cliTitle),
Text(
repeater.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
],
),
centerTitle: false,
actions: [
PopupMenuButton<String>(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onSelected: (mode) async {
if (mode == 'flood') {
await connector.setPathOverride(repeater, pathLen: -1);
} else {
await connector.setPathOverride(repeater, pathLen: null);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'auto',
child: Row(
children: [
Icon(
Icons.auto_mode,
size: 20,
color: !isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_autoUseSavedPath,
style: TextStyle(
fontWeight: !isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
PopupMenuItem(
value: 'flood',
child: Row(
children: [
Icon(
Icons.waves,
size: 20,
color: isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_forceFloodMode,
style: TextStyle(
fontWeight: isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
],
),
IconButton(
icon: const Icon(Icons.timeline),
tooltip: l10n.repeater_pathManagement,
onPressed: () =>
PathManagementDialog.show(context, contact: repeater),
),
IconButton(
icon: const Icon(Icons.bug_report),
tooltip: l10n.repeater_debugNextCommand,
onPressed: () {
// Set a flag or just send next command with debug
if (_commandController.text.trim().isNotEmpty) {
_sendCommand(showDebug: true);
} else {
showDismissibleSnackBar(
context,
content: Text(l10n.repeater_enterCommandFirst),
);
}
},
),
IconButton(
icon: const Icon(Icons.help_outline),
tooltip: l10n.repeater_commandHelp,
onPressed: () => _showCommandHelp(context),
),
IconButton(
icon: const Icon(Icons.clear_all),
tooltip: l10n.repeater_clearHistory,
onPressed: _commandHistory.isEmpty ? null : _clearHistory,
),
],
),
body: Column(
children: [
_buildQuickCommandsBar(),
const Divider(height: 1),
Expanded(
child: _commandHistory.isEmpty
? _buildEmptyState()
: _buildCommandHistory(),
),
const Divider(height: 1),
_buildCommandInput(),
],
),
);
}
Widget _buildQuickCommandsBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _quickCommands.map((cmd) {
final label = _quickCommandLabel(cmd['labelKey']!);
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ActionChip(
label: Text(label),
onPressed: () => _useQuickCommand(cmd['command']!),
avatar: const Icon(Icons.play_arrow, size: 16),
),
);
}).toList(),
),
),
);
}
String _quickCommandLabel(String key) {
final l10n = context.l10n;
switch (key) {
@@ -252,234 +420,22 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
}
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final connector = context.watch<MeshCoreConnector>();
final repeater = _resolveRepeater(connector);
final isFloodMode = repeater.pathOverride == -1;
return Scaffold(
backgroundColor: MeshPalette.bg,
appBar: AppBar(
backgroundColor: MeshPalette.bg1,
title: Text(l10n.repeater_cliTitle),
centerTitle: true,
actions: [
IconButton(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onPressed: () =>
ContactRoutingSheet.show(context, contact: repeater),
),
IconButton(
icon: const Icon(Icons.help_outline),
tooltip: l10n.repeater_commandHelp,
onPressed: () => _showCommandHelp(context),
),
IconButton(
icon: const Icon(Icons.clear_all),
tooltip: l10n.repeater_clearHistory,
onPressed: _commandHistory.isEmpty ? null : _clearHistory,
),
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert),
onSelected: (value) {
if (value == 'debug') {
if (_commandController.text.trim().isNotEmpty) {
_sendCommand(showDebug: true);
} else {
showDismissibleSnackBar(
context,
content: Text(l10n.repeater_enterCommandFirst),
);
}
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'debug',
child: Row(
children: [
const Icon(Icons.bug_report),
const SizedBox(width: 8),
Text(l10n.repeater_debugNextCommand),
],
),
),
],
),
],
),
body: Column(
children: [
// Quick commands bar
Container(
color: MeshPalette.bg1,
padding: const EdgeInsets.fromLTRB(8, 6, 8, 6),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _quickCommands.map((cmd) {
final label = _quickCommandLabel(cmd['labelKey']!);
return Padding(
padding: const EdgeInsets.only(right: 6),
child: ActionChip(
label: Text(
label,
style: MeshTheme.mono(
fontSize: 11,
fontWeight: FontWeight.w600,
color: MeshPalette.blue,
),
),
backgroundColor: MeshPalette.blueBg,
side: const BorderSide(color: MeshPalette.blueLine),
visualDensity: VisualDensity.compact,
onPressed: () => _useQuickCommand(cmd['command']!),
),
);
}).toList(),
),
),
),
Divider(height: 1, color: MeshPalette.line),
// Output area
Expanded(
child: _commandHistory.isEmpty
? _buildEmptyState()
: _buildCommandHistory(),
),
Divider(height: 1, color: MeshPalette.line),
// Command input
Container(
color: MeshPalette.bg1,
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
child: SafeArea(
child: Row(
children: [
IconButton(
icon: Icon(
Icons.arrow_upward,
size: 18,
color: scheme.onSurfaceVariant,
),
tooltip: l10n.repeater_previousCommand,
onPressed: () => _navigateHistory(true),
visualDensity: VisualDensity.compact,
),
IconButton(
icon: Icon(
Icons.arrow_downward,
size: 18,
color: scheme.onSurfaceVariant,
),
tooltip: l10n.repeater_nextCommand,
onPressed: () => _navigateHistory(false),
visualDensity: VisualDensity.compact,
),
const SizedBox(width: 4),
Expanded(
child: TextField(
controller: _commandController,
focusNode: _commandFocusNode,
style: MeshTheme.mono(
fontSize: 13,
color: MeshPalette.ink,
),
decoration: InputDecoration(
hintText: context.l10n.repeater_enterCommandHint,
hintStyle: MeshTheme.mono(
fontSize: 13,
color: MeshPalette.ink4,
),
prefixText: '> ',
prefixStyle: MeshTheme.mono(
fontSize: 13,
color: MeshPalette.blue,
fontWeight: FontWeight.w700,
),
filled: true,
fillColor: MeshPalette.bg2,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
borderSide: const BorderSide(
color: MeshPalette.line2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
borderSide: const BorderSide(
color: MeshPalette.line2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
borderSide: const BorderSide(
color: MeshPalette.blue,
width: 1.5,
),
),
),
textInputAction: TextInputAction.send,
onSubmitted: (_) => _sendCommand(),
),
),
const SizedBox(width: 6),
Material(
color: MeshPalette.blue.withValues(alpha: 0.15),
shape: const CircleBorder(
side: BorderSide(color: MeshPalette.blueLine),
),
child: InkWell(
customBorder: const CircleBorder(),
onTap: () {
HapticFeedback.lightImpact();
_sendCommand();
},
child: const Padding(
padding: EdgeInsets.all(10),
child: Icon(
Icons.send,
size: 18,
color: MeshPalette.blue,
),
),
),
),
],
),
),
),
],
),
);
}
Widget _buildEmptyState() {
final l10n = context.l10n;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.terminal, size: 48, color: MeshPalette.ink4),
const SizedBox(height: 12),
Icon(Icons.terminal, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
l10n.repeater_noCommandsSent,
style: MeshTheme.mono(fontSize: 13, color: MeshPalette.ink3),
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
const SizedBox(height: 4),
const SizedBox(height: 8),
Text(
l10n.repeater_typeCommandOrUseQuick,
style: const TextStyle(fontSize: 12, color: MeshPalette.ink4),
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
),
],
),
@@ -489,37 +445,49 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
Widget _buildCommandHistory() {
return ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
padding: const EdgeInsets.all(16),
itemCount: _commandHistory.length,
itemBuilder: (context, index) {
final entry = _commandHistory[index];
final isCommand = entry['type'] == 'command';
return Padding(
padding: const EdgeInsets.only(bottom: 2),
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Gutter prefix
SizedBox(
width: 20,
child: Text(
isCommand ? '>' : ' ',
style: MeshTheme.mono(
fontSize: 12,
fontWeight: FontWeight.w700,
color: isCommand ? MeshPalette.blue : MeshPalette.ink3,
),
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: isCommand
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(4),
),
child: Icon(
isCommand ? Icons.chevron_right : Icons.arrow_back,
size: 16,
color: isCommand
? Theme.of(context).colorScheme.onPrimaryContainer
: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
const SizedBox(width: 6),
const SizedBox(width: 12),
Expanded(
child: SelectableText(
entry['text']!,
style: MeshTheme.mono(
fontSize: 12.5,
color: isCommand ? MeshPalette.blue : MeshPalette.ink,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
entry['text']!,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 13,
color: isCommand
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
],
@@ -529,6 +497,54 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
);
}
Widget _buildCommandInput() {
final l10n = context.l10n;
return Container(
padding: const EdgeInsets.all(12),
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_upward, size: 20),
tooltip: l10n.repeater_previousCommand,
onPressed: () => _navigateHistory(true),
),
IconButton(
icon: const Icon(Icons.arrow_downward, size: 20),
tooltip: l10n.repeater_nextCommand,
onPressed: () => _navigateHistory(false),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _commandController,
focusNode: _commandFocusNode,
decoration: InputDecoration(
hintText: l10n.repeater_enterCommandHint,
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
prefixText: '> ',
),
style: const TextStyle(fontFamily: 'monospace'),
textInputAction: TextInputAction.send,
onSubmitted: (_) => _sendCommand(),
),
),
const SizedBox(width: 8),
IconButton.filled(
icon: const Icon(Icons.send),
onPressed: _sendCommand,
),
],
),
),
);
}
void _applyHelpCommand(String command) {
_commandController.text = command;
_commandController.selection = TextSelection.fromPosition(
@@ -1149,20 +1165,16 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
List<_CommandHelpEntry> commands, {
String? note,
}) {
final scheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
if (note != null) ...[
const SizedBox(height: 4),
Text(
note,
style: TextStyle(fontSize: 11, color: scheme.onSurfaceVariant),
),
const SizedBox(height: 6),
Text(note, style: const TextStyle(fontSize: 12)),
],
const SizedBox(height: 8),
...commands.map((entry) => _buildHelpCommandCard(context, entry)),
@@ -1171,35 +1183,39 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
}
Widget _buildHelpCommandCard(BuildContext context, _CommandHelpEntry entry) {
final scheme = Theme.of(context).colorScheme;
final colorScheme = Theme.of(context).colorScheme;
return Card(
elevation: 0,
margin: const EdgeInsets.only(bottom: 6),
color: scheme.surfaceContainerHighest,
margin: const EdgeInsets.only(bottom: 8),
color: colorScheme.surfaceContainerHighest,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.sm),
side: BorderSide(color: scheme.outlineVariant),
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: colorScheme.outlineVariant),
),
child: InkWell(
borderRadius: BorderRadius.circular(MeshRadii.sm),
borderRadius: BorderRadius.circular(8),
onTap: () => _applyHelpCommand(entry.command),
child: Padding(
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
entry.command,
style: MeshTheme.mono(
fontSize: 12,
fontWeight: FontWeight.w600,
color: MeshPalette.blue,
style: TextStyle(
fontFamily: 'monospace',
fontSize: 13,
fontWeight: FontWeight.bold,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
const SizedBox(height: 6),
Text(
entry.description,
style: TextStyle(fontSize: 12, color: scheme.onSurfaceVariant),
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
],
),
@@ -1212,5 +1228,6 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
class _CommandHelpEntry {
final String command;
final String description;
const _CommandHelpEntry({required this.command, required this.description});
}
+216 -215
View File
@@ -1,13 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../l10n/contact_localization.dart';
import '../services/app_settings_service.dart';
import '../theme/mesh_theme.dart';
import '../widgets/mesh_ui.dart';
import 'repeater_status_screen.dart';
import 'repeater_cli_screen.dart';
import 'repeater_settings_screen.dart';
@@ -29,157 +26,175 @@ class RepeaterHubScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final settingsService = context.watch<AppSettingsService>();
final chemistry = settingsService.batteryChemistryForRepeater(
repeater.publicKeyHex,
);
return Scaffold(
appBar: AppBar(
title: Text(
repeater.type == advTypeRepeater
? (isAdmin ? l10n.repeater_management : l10n.repeater_guest)
: (isAdmin ? l10n.room_management : l10n.room_guest),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (isAdmin)
Text(
repeater.type == advTypeRepeater
? l10n.repeater_management
: l10n.room_management,
),
if (!isAdmin)
Text(
repeater.type == advTypeRepeater
? l10n.repeater_guest
: l10n.room_guest,
),
Text(
repeater.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
],
),
centerTitle: true,
centerTitle: false,
),
body: SafeArea(
top: false,
child: ListView(
padding: const EdgeInsets.only(bottom: 24),
padding: const EdgeInsets.all(16),
children: [
// Identity card
Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 4),
child: MeshCard(
margin: EdgeInsets.zero,
padding: const EdgeInsets.all(20),
child: Row(
// Repeater info card
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
AvatarCircle(
name: repeater.name,
size: 52,
color: MeshPalette.warn,
icon: Icons.cell_tower,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
repeater.name,
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.w700),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
repeater.shortPubKeyHex,
style: MeshTheme.mono(
fontSize: 11,
color: scheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
Text(
repeater.pathLabel(l10n),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(color: scheme.onSurfaceVariant),
),
if (repeater.hasLocation) ...[
const SizedBox(height: 2),
Row(
children: [
Icon(
Icons.location_on,
size: 12,
color: scheme.onSurfaceVariant,
),
const SizedBox(width: 3),
Expanded(
child: Text(
'${repeater.latitude?.toStringAsFixed(4)}, '
'${repeater.longitude?.toStringAsFixed(4)}',
style: MeshTheme.mono(
fontSize: 10,
color: scheme.onSurfaceVariant,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
],
],
CircleAvatar(
radius: 40,
backgroundColor: Colors.orange,
child: const Icon(
Icons.cell_tower,
size: 40,
color: Colors.white,
),
),
StatusChip(
label: isAdmin ? 'ADMIN' : 'GUEST',
color: isAdmin
? MeshPalette.blue
: scheme.onSurfaceVariant,
const SizedBox(height: 16),
Text(
repeater.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
repeater.shortPubKeyHex,
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
const SizedBox(height: 8),
Text(
repeater.pathLabel(context.l10n),
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
if (repeater.hasLocation) ...[
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.location_on,
size: 14,
color: Colors.grey[600],
),
const SizedBox(width: 4),
Text(
'${repeater.latitude?.toStringAsFixed(4)}, ${repeater.longitude?.toStringAsFixed(4)}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
],
],
),
),
),
// Battery chemistry (admin only)
if (isAdmin) ...[
SectionHeader(l10n.appSettings_batteryChemistry),
MeshCard(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: const EdgeInsets.fromLTRB(14, 10, 14, 14),
child: DropdownButtonFormField<String>(
initialValue: chemistry,
isExpanded: true,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.battery_full, size: 18),
labelText: l10n.appSettings_batteryChemistry,
const SizedBox(height: 24),
if (isAdmin)
Card(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.battery_full),
const SizedBox(width: 10),
Expanded(
child: Text(
l10n.appSettings_batteryChemistry,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 12),
DropdownButtonFormField<String>(
initialValue: chemistry,
isExpanded: true,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
isDense: true,
),
onChanged: (value) {
if (value == null) return;
settingsService.setBatteryChemistryForRepeater(
repeater.publicKeyHex,
value,
);
},
items: [
DropdownMenuItem(
value: 'nmc',
child: Text(l10n.appSettings_batteryNmc),
),
DropdownMenuItem(
value: 'lifepo4',
child: Text(l10n.appSettings_batteryLifepo4),
),
DropdownMenuItem(
value: 'lipo',
child: Text(l10n.appSettings_batteryLipo),
),
],
),
],
),
onChanged: (value) {
if (value == null) return;
settingsService.setBatteryChemistryForRepeater(
repeater.publicKeyHex,
value,
);
},
items: [
DropdownMenuItem(
value: 'nmc',
child: Text(l10n.appSettings_batteryNmc),
),
DropdownMenuItem(
value: 'lifepo4',
child: Text(l10n.appSettings_batteryLifepo4),
),
DropdownMenuItem(
value: 'lipo',
child: Text(l10n.appSettings_batteryLipo),
),
],
),
),
],
// Tools
SectionHeader(
const SizedBox(height: 24),
Text(
isAdmin
? l10n.repeater_managementTools
: l10n.repeater_guestTools,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
_HubActionTile(
index: 0,
const SizedBox(height: 16),
// Status button
_buildManagementCard(
context,
icon: Icons.analytics,
title: l10n.repeater_status,
subtitle: l10n.repeater_statusSubtitle,
accentColor: MeshPalette.blue,
color: Colors.blue,
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
@@ -191,15 +206,15 @@ class RepeaterHubScreen extends StatelessWidget {
);
},
),
_HubActionTile(
index: 1,
const SizedBox(height: 16),
// Telemetry button
_buildManagementCard(
context,
icon: Icons.bar_chart_sharp,
title: l10n.repeater_telemetry,
subtitle: l10n.repeater_telemetrySubtitle,
accentColor: MeshPalette.magenta,
color: Colors.teal,
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
@@ -208,34 +223,16 @@ class RepeaterHubScreen extends StatelessWidget {
);
},
),
_HubActionTile(
index: 2,
icon: Icons.group,
title: l10n.repeater_neighbors,
subtitle: l10n.repeater_neighborsSubtitle,
accentColor: MeshPalette.signal,
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
NeighborsScreen(repeater: repeater, password: password),
),
);
},
),
if (isAdmin) ...[
_HubActionTile(
index: 3,
if (isAdmin) const SizedBox(height: 12),
// CLI button
if (isAdmin)
_buildManagementCard(
context,
icon: Icons.terminal,
title: l10n.repeater_cli,
subtitle: l10n.repeater_cliSubtitle,
accentColor: MeshPalette.warn,
color: Colors.green,
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
@@ -247,14 +244,34 @@ class RepeaterHubScreen extends StatelessWidget {
);
},
),
_HubActionTile(
index: 4,
const SizedBox(height: 12),
// Neighbors button
_buildManagementCard(
context,
icon: Icons.group,
title: l10n.repeater_neighbors,
subtitle: l10n.repeater_neighborsSubtitle,
color: Colors.orange,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
NeighborsScreen(repeater: repeater, password: password),
),
);
},
),
if (isAdmin) const SizedBox(height: 12),
// Settings button
if (isAdmin)
_buildManagementCard(
context,
icon: Icons.settings,
title: l10n.repeater_settings,
subtitle: l10n.repeater_settingsSubtitle,
accentColor: MeshPalette.alert,
color: Colors.deepOrange,
onTap: () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
@@ -266,76 +283,60 @@ class RepeaterHubScreen extends StatelessWidget {
);
},
),
],
],
),
),
);
}
}
class _HubActionTile extends StatelessWidget {
final int index;
final IconData icon;
final String title;
final String subtitle;
final Color accentColor;
final VoidCallback onTap;
const _HubActionTile({
required this.index,
required this.icon,
required this.title,
required this.subtitle,
required this.accentColor,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return ListEntrance(
index: index,
child: MeshCard(
Widget _buildManagementCard(
BuildContext context, {
required IconData icon,
required String title,
required String subtitle,
required Color color,
required VoidCallback onTap,
}) {
return Card(
elevation: 2,
child: InkWell(
onTap: onTap,
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: accentColor.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(MeshRadii.md),
border: Border.all(color: accentColor.withValues(alpha: 0.3)),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 32),
),
alignment: Alignment.center,
child: Icon(icon, size: 22, color: accentColor),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15,
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(
fontSize: 12.5,
color: scheme.onSurfaceVariant,
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
),
],
],
),
),
),
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
],
Icon(Icons.chevron_right, color: Colors.grey[400]),
],
),
),
),
);
File diff suppressed because it is too large Load Diff
+293 -228
View File
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
@@ -11,10 +10,8 @@ import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../services/app_settings_service.dart';
import '../services/repeater_command_service.dart';
import '../theme/mesh_theme.dart';
import '../utils/battery_utils.dart';
import '../widgets/mesh_ui.dart';
import '../widgets/routing_sheet.dart';
import '../widgets/path_management_dialog.dart';
import '../helpers/snack_bar_builder.dart';
class RepeaterStatusScreen extends StatefulWidget {
@@ -67,6 +64,8 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
_commandService = RepeaterCommandService(connector);
_setupMessageListener();
// Defer until after the first frame so any notifyListeners() triggered
// during preparePathForContactSend doesn't fire mid-build.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _loadStatus();
});
@@ -82,8 +81,12 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
void _setupMessageListener() {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
// Listen for incoming text messages from the repeater
_frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return;
// Check if it's a text message response
if (frame[0] == pushCodeStatusResponse) {
_handleStatusResponse(frame);
} else if (frame[0] == respCodeContactMsgRecv ||
@@ -115,7 +118,11 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
final parsed = parseContactMessageText(frame);
if (parsed == null) return;
if (!_matchesRepeaterPrefix(parsed.senderPrefix)) return;
// Notify command service of response (for retry handling)
_commandService?.handleResponse(widget.repeater, parsed.text);
// Parse status responses
_parseStatusResponse(parsed.text);
_recordStatusResult(true);
}
@@ -124,6 +131,7 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
if (frame.length < 8) return;
final prefix = frame.sublist(2, 8);
if (!_matchesRepeaterPrefix(prefix)) return;
if (frame.length < _statusResponseBytes) return;
final data = ByteData.sublistView(
@@ -246,9 +254,14 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
_dupFlood = _asInt(data['dup_flood']);
_dupDirect = _asInt(data['dup_direct']);
}
} catch (_) {}
} catch (_) {
// Ignore parse failures for non-JSON responses.
}
}
if (mounted) {
setState(() {});
}
if (mounted) setState(() {});
}
Future<void> _loadStatus() async {
@@ -289,7 +302,9 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
var messageBytes = frame.length >= _statusResponseBytes
? frame.length
: _statusResponseBytes;
if (messageBytes < maxFrameSize) messageBytes = maxFrameSize;
if (messageBytes < maxFrameSize) {
messageBytes = maxFrameSize;
}
final timeoutMs = connector.calculateTimeout(
pathLength: pathLengthValue,
messageBytes: messageBytes,
@@ -297,21 +312,26 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
_statusTimeout?.cancel();
_statusTimeout = Timer(Duration(milliseconds: timeoutMs), () {
if (!mounted) return;
setState(() => _isLoading = false);
setState(() {
_isLoading = false;
});
showDismissibleSnackBar(
context,
content: Text(context.l10n.repeater_statusRequestTimeout),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
_recordStatusResult(false);
});
} catch (e) {
if (mounted) {
setState(() => _isLoading = false);
setState(() {
_isLoading = false;
});
showDismissibleSnackBar(
context,
content: Text(context.l10n.repeater_errorLoadingStatus(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
}
_recordStatusResult(false);
@@ -327,6 +347,268 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
_pendingStatusSelection = null;
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final connector = context.watch<MeshCoreConnector>();
final repeater = _resolveRepeater(connector);
final isFloodMode = repeater.pathOverride == -1;
return Scaffold(
appBar: AppBar(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(l10n.repeater_statusTitle),
Text(
repeater.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
],
),
centerTitle: false,
actions: [
PopupMenuButton<String>(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onSelected: (mode) async {
if (mode == 'flood') {
await connector.setPathOverride(repeater, pathLen: -1);
} else {
await connector.setPathOverride(repeater, pathLen: null);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'auto',
child: Row(
children: [
Icon(
Icons.auto_mode,
size: 20,
color: !isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_autoUseSavedPath,
style: TextStyle(
fontWeight: !isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
PopupMenuItem(
value: 'flood',
child: Row(
children: [
Icon(
Icons.waves,
size: 20,
color: isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_forceFloodMode,
style: TextStyle(
fontWeight: isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
],
),
IconButton(
icon: const Icon(Icons.timeline),
tooltip: l10n.repeater_pathManagement,
onPressed: () =>
PathManagementDialog.show(context, contact: repeater),
),
IconButton(
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
onPressed: _isLoading ? null : _loadStatus,
tooltip: l10n.repeater_refresh,
),
],
),
body: SafeArea(
top: false,
child: RefreshIndicator(
onRefresh: _loadStatus,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSystemInfoCard(),
const SizedBox(height: 16),
_buildRadioStatsCard(),
const SizedBox(height: 16),
_buildPacketStatsCard(),
],
),
),
),
);
}
Widget _buildSystemInfoCard() {
final l10n = context.l10n;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
const SizedBox(width: 8),
Text(
l10n.repeater_systemInformation,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(),
_buildInfoRow(l10n.repeater_battery, _batteryText()),
_buildInfoRow(l10n.repeater_clockAtLogin, _clockText()),
_buildInfoRow(l10n.repeater_uptime, _formatDuration(_uptimeSecs)),
_buildInfoRow(l10n.repeater_queueLength, _formatValue(_queueLen)),
_buildInfoRow(l10n.repeater_debugFlags, _formatValue(_debugFlags)),
],
),
),
);
}
Widget _buildRadioStatsCard() {
final l10n = context.l10n;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.radio,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
const SizedBox(width: 8),
Text(
l10n.repeater_radioStatistics,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(),
_buildInfoRow(
l10n.repeater_lastRssi,
_formatValue(_lastRssi, suffix: ' dB'),
),
_buildInfoRow(l10n.repeater_lastSnr, _formatSnr(_lastSnr)),
_buildInfoRow(
l10n.repeater_noiseFloor,
_formatValue(_noiseFloor, suffix: ' dB'),
),
_buildInfoRow(l10n.repeater_txAirtime, _formatDuration(_txAirSecs)),
_buildInfoRow(l10n.repeater_rxAirtime, _formatDuration(_rxAirSecs)),
],
),
),
);
}
Widget _buildPacketStatsCard() {
final l10n = context.l10n;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.analytics,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
const SizedBox(width: 8),
Text(
l10n.repeater_packetStatistics,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(),
_buildInfoRow(l10n.repeater_sent, _packetTxText()),
_buildInfoRow(l10n.repeater_received, _packetRxText()),
_buildInfoRow(l10n.repeater_duplicates, _duplicateText()),
_buildInfoRow(l10n.repeater_chanUtil, _chanUtilText()),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 130,
child: Text(
label,
style: TextStyle(
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(fontWeight: FontWeight.w400),
),
),
],
),
);
}
int? _asInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
@@ -433,221 +715,4 @@ class _RepeaterStatusScreenState extends State<RepeaterStatusScreen> {
if (snr == null) return '';
return snr.toStringAsFixed(2);
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final connector = context.watch<MeshCoreConnector>();
final repeater = _resolveRepeater(connector);
final isFloodMode = repeater.pathOverride == -1;
return Scaffold(
appBar: AppBar(
title: Text(l10n.repeater_statusTitle),
centerTitle: true,
actions: [
IconButton(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onPressed: () =>
ContactRoutingSheet.show(context, contact: repeater),
),
IconButton(
icon: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
onPressed: _isLoading ? null : _loadStatus,
tooltip: l10n.repeater_refresh,
),
],
),
body: SafeArea(
top: false,
child: RefreshIndicator(
onRefresh: _loadStatus,
child: _isLoading && _batteryMv == null
? const Center(child: CircularProgressIndicator())
: _buildBody(l10n, repeater.name),
),
),
);
}
Widget _buildBody(dynamic l10n, String name) {
final scheme = Theme.of(context).colorScheme;
return ListView(
padding: const EdgeInsets.only(bottom: 24),
children: [
// System
SectionHeader(l10n.repeater_systemInformation),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _buildStatGrid([
_StatItem(
icon: Icons.battery_std,
label: l10n.repeater_battery,
value: _batteryText(),
color: _batteryColor(),
),
_StatItem(
icon: Icons.timer_outlined,
label: l10n.repeater_uptime,
value: _formatDuration(_uptimeSecs),
color: MeshPalette.blue,
),
_StatItem(
icon: Icons.schedule,
label: l10n.repeater_clockAtLogin,
value: _clockText(),
color: scheme.onSurfaceVariant,
),
_StatItem(
icon: Icons.inbox,
label: l10n.repeater_queueLength,
value: _formatValue(_queueLen),
color: scheme.onSurfaceVariant,
),
_StatItem(
icon: Icons.bug_report_outlined,
label: l10n.repeater_debugFlags,
value: _formatValue(_debugFlags),
color: _debugFlags != null && _debugFlags! > 0
? MeshPalette.warn
: scheme.onSurfaceVariant,
),
]),
),
// Radio
SectionHeader(l10n.repeater_radioStatistics),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _buildStatGrid([
_StatItem(
icon: Icons.signal_cellular_alt,
label: l10n.repeater_lastRssi,
value: _formatValue(_lastRssi, suffix: ' dB'),
color: MeshPalette.blue,
),
_StatItem(
icon: Icons.waves,
label: l10n.repeater_lastSnr,
value: _formatSnr(_lastSnr),
color: MeshTheme.snrColor(_lastSnr, blocked: false),
),
_StatItem(
icon: Icons.noise_control_off,
label: l10n.repeater_noiseFloor,
value: _formatValue(_noiseFloor, suffix: ' dB'),
color: scheme.onSurfaceVariant,
),
_StatItem(
icon: Icons.upload,
label: l10n.repeater_txAirtime,
value: _formatDuration(_txAirSecs),
color: MeshPalette.warn,
),
_StatItem(
icon: Icons.download,
label: l10n.repeater_rxAirtime,
value: _formatDuration(_rxAirSecs),
color: MeshPalette.signal,
),
]),
),
// Packets
SectionHeader(l10n.repeater_packetStatistics),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _buildStatGrid([
_StatItem(
icon: Icons.send,
label: l10n.repeater_sent,
value: _packetTxText(),
color: MeshPalette.blue,
),
_StatItem(
icon: Icons.call_received,
label: l10n.repeater_received,
value: _packetRxText(),
color: MeshPalette.signal,
),
_StatItem(
icon: Icons.content_copy,
label: l10n.repeater_duplicates,
value: _duplicateText(),
color: scheme.onSurfaceVariant,
),
_StatItem(
icon: Icons.percent,
label: l10n.repeater_chanUtil,
value: _chanUtilText(),
color: _chanUtil != null && _chanUtil! > 80
? MeshPalette.alert
: _chanUtil != null && _chanUtil! > 50
? MeshPalette.warn
: MeshPalette.signal,
),
]),
),
const SizedBox(height: 8),
],
);
}
Color _batteryColor() {
final connector = context.watch<MeshCoreConnector>();
final batteryMv =
connector.getRepeaterBatteryMillivolts(widget.repeater.publicKeyHex) ??
_batteryMv;
if (batteryMv == null)
return Theme.of(context).colorScheme.onSurfaceVariant;
final percent = estimateBatteryPercentFromMillivolts(
batteryMv,
_batteryChemistry(),
);
if (percent < 20) return MeshPalette.alert;
if (percent < 40) return MeshPalette.warn;
return MeshPalette.signal;
}
Widget _buildStatGrid(List<_StatItem> items) {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 2.2,
children: items
.map(
(item) => StatTile(
icon: item.icon,
label: item.label,
value: item.value,
color: item.color,
),
)
.toList(),
);
}
}
class _StatItem {
final IconData icon;
final String label;
final String value;
final Color color;
const _StatItem({
required this.icon,
required this.label,
required this.value,
required this.color,
});
}
+156 -221
View File
@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../utils/platform_info.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:provider/provider.dart';
@@ -8,14 +7,11 @@ import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
import '../services/linux_ble_error_classifier.dart';
import '../theme/mesh_theme.dart';
import '../utils/app_logger.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/device_tile.dart';
import '../widgets/empty_state.dart';
import '../widgets/mesh_ui.dart';
import '../helpers/snack_bar_builder.dart';
import 'channels_screen.dart';
import 'contacts_screen.dart';
import 'tcp_screen.dart';
import 'usb_screen.dart';
@@ -29,7 +25,6 @@ class ScannerScreen extends StatefulWidget {
class _ScannerScreenState extends State<ScannerScreen> {
bool _changedNavigation = false;
String? _connectingDeviceId;
late final MeshCoreConnector _connector;
late final VoidCallback _connectionListener;
BluetoothAdapterState _bluetoothState = BluetoothAdapterState.unknown;
@@ -51,7 +46,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
_changedNavigation = true;
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const ChannelsScreen()),
MaterialPageRoute(builder: (context) => const ContactsScreen()),
);
}
}
@@ -106,32 +101,6 @@ class _ScannerScreenState extends State<ScannerScreen> {
title: AdaptiveAppBarTitle(context.l10n.scanner_title),
centerTitle: true,
automaticallyImplyLeading: false,
actions: [
if (PlatformInfo.supportsUsbSerial)
IconButton(
icon: const Icon(Icons.usb),
tooltip: context.l10n.connectionChoiceUsbLabel,
onPressed: () {
appLogger.info(
'USB selected, opening UsbScreen',
tag: 'ScannerScreen',
);
Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => const UsbScreen()));
},
),
if (!PlatformInfo.isWeb)
IconButton(
icon: const Icon(Icons.lan),
tooltip: context.l10n.connectionChoiceTcpLabel,
onPressed: () {
Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => const TcpScreen()));
},
),
],
),
body: SafeArea(
top: false,
@@ -139,21 +108,12 @@ class _ScannerScreenState extends State<ScannerScreen> {
builder: (context, connector, child) {
return Column(
children: [
// Bluetooth off warning slides in/out with AnimatedSize
AnimatedSize(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: _bluetoothState == BluetoothAdapterState.off
? _BluetoothOffBanner(
onEnable: PlatformInfo.isAndroid
? () => FlutterBluePlus.turnOn()
: null,
)
: const SizedBox.shrink(),
),
// Bluetooth off warning
if (_bluetoothState == BluetoothAdapterState.off)
_bluetoothOffWarning(context),
// Connection status header
_ConnectionStatusHeader(connector: connector),
// Status bar
_buildStatusBar(context, connector),
// Device list
Expanded(child: _buildDeviceList(context, connector)),
@@ -162,43 +122,84 @@ class _ScannerScreenState extends State<ScannerScreen> {
},
),
),
floatingActionButton: Consumer<MeshCoreConnector>(
bottomNavigationBar: Consumer<MeshCoreConnector>(
builder: (context, connector, child) {
final isScanning =
connector.state == MeshCoreConnectionState.scanning;
final isBluetoothOff = _bluetoothState == BluetoothAdapterState.off;
final usbSupported = PlatformInfo.supportsUsbSerial;
final tcpSupported = !PlatformInfo.isWeb;
return FloatingActionButton.extended(
heroTag: 'scanner_ble_action',
onPressed: isBluetoothOff
? null
: () {
HapticFeedback.lightImpact();
_toggleScan(connector);
},
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 220),
transitionBuilder: (child, anim) =>
ScaleTransition(scale: anim, child: child),
child: isScanning
? SizedBox(
key: const ValueKey('scanning'),
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Theme.of(context).colorScheme.onPrimary,
),
)
: const Icon(
Icons.bluetooth_searching,
key: ValueKey('idle'),
return SafeArea(
top: false,
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerRight,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (usbSupported)
FloatingActionButton.extended(
onPressed: () {
appLogger.info(
'USB selected, opening UsbScreen',
tag: 'ScannerScreen',
);
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const UsbScreen()),
);
},
heroTag: 'scanner_usb_action',
icon: const Icon(Icons.usb),
label: Text(context.l10n.connectionChoiceUsbLabel),
),
),
label: Text(
isScanning
? context.l10n.scanner_stop
: context.l10n.scanner_scan,
if (usbSupported) const SizedBox(width: 12),
if (tcpSupported)
FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const TcpScreen()),
);
},
heroTag: 'scanner_tcp_action',
icon: const Icon(Icons.lan),
label: Text(context.l10n.connectionChoiceTcpLabel),
),
if (tcpSupported) const SizedBox(width: 12),
FloatingActionButton.extended(
heroTag: 'scanner_ble_action',
onPressed: isBluetoothOff
? null
: () {
if (isScanning) {
connector.stopScan();
} else {
unawaited(
connector.startScan().catchError((e) {
appLogger.warn(
'startScan error: $e',
tag: 'ScannerScreen',
);
}),
);
}
},
icon: isScanning
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.bluetooth_searching),
label: Text(
isScanning
? context.l10n.scanner_stop
: context.l10n.scanner_scan,
),
),
],
),
),
);
},
@@ -206,70 +207,79 @@ class _ScannerScreenState extends State<ScannerScreen> {
);
}
void _toggleScan(MeshCoreConnector connector) {
if (PlatformInfo.isWeb) {
// flutter_blue_plus has no web backend, so a BLE scan silently no-ops in
// the browser. Tell the user instead of leaving them staring at a button.
showDismissibleSnackBar(
context,
content: Text(context.l10n.scanner_bluetoothWebUnsupported),
);
return;
}
if (connector.state == MeshCoreConnectionState.scanning) {
connector.stopScan();
} else {
unawaited(
connector.startScan().catchError((e) {
appLogger.warn('startScan error: $e', tag: 'ScannerScreen');
}),
);
Widget _buildStatusBar(BuildContext context, MeshCoreConnector connector) {
String statusText;
Color statusColor;
final l10n = context.l10n;
switch (connector.state) {
case MeshCoreConnectionState.scanning:
statusText = l10n.scanner_scanning;
statusColor = Colors.blue;
break;
case MeshCoreConnectionState.connecting:
statusText = l10n.scanner_connecting;
statusColor = Colors.orange;
break;
case MeshCoreConnectionState.connected:
statusText = l10n.scanner_connectedTo(connector.deviceDisplayName);
statusColor = Colors.green;
break;
case MeshCoreConnectionState.disconnecting:
statusText = l10n.scanner_disconnecting;
statusColor = Colors.orange;
break;
case MeshCoreConnectionState.disconnected:
statusText = l10n.scanner_notConnected;
statusColor = Colors.grey;
break;
}
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: statusColor.withValues(alpha: 0.1),
child: Row(
children: [
Icon(Icons.circle, size: 12, color: statusColor),
const SizedBox(width: 8),
Text(
statusText,
style: TextStyle(color: statusColor, fontWeight: FontWeight.w500),
),
],
),
);
}
Widget _buildDeviceList(BuildContext context, MeshCoreConnector connector) {
if (connector.scanResults.isEmpty) {
final isBluetoothOff = _bluetoothState == BluetoothAdapterState.off;
final isScanning = connector.state == MeshCoreConnectionState.scanning;
return EmptyState(
icon: isBluetoothOff ? Icons.bluetooth_disabled : Icons.bluetooth,
title: isBluetoothOff
? context.l10n.scanner_bluetoothOff
: isScanning
? context.l10n.scanner_searchingDevices
: context.l10n.scanner_tapToScan,
subtitle: isBluetoothOff
? context.l10n.scanner_bluetoothOffMessage
: null,
action: (isBluetoothOff || isScanning)
? null
: FilledButton.icon(
onPressed: () {
HapticFeedback.lightImpact();
_toggleScan(connector);
},
icon: const Icon(Icons.bluetooth_searching),
label: Text(context.l10n.scanner_scan),
),
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.bluetooth, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
connector.state == MeshCoreConnectionState.scanning
? context.l10n.scanner_searchingDevices
: context.l10n.scanner_tapToScan,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
],
),
);
}
final isConnecting = connector.state == MeshCoreConnectionState.connecting;
return ListView.builder(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 96),
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: connector.scanResults.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final result = connector.scanResults[index];
final deviceId = result.device.remoteId.toString();
return ListEntrance(
index: index,
child: DeviceTile(
scanResult: result,
isConnecting: isConnecting && _connectingDeviceId == deviceId,
onTap: isConnecting
? null
: () => _connectToDevice(context, connector, result),
),
return DeviceTile(
scanResult: result,
onTap: () => _connectToDevice(context, connector, result),
);
},
);
@@ -283,9 +293,6 @@ class _ScannerScreenState extends State<ScannerScreen> {
final name = result.device.platformName.isNotEmpty
? result.device.platformName
: result.advertisementData.advName;
setState(() {
_connectingDeviceId = result.device.remoteId.toString();
});
try {
await connector.connect(
result.device,
@@ -314,15 +321,9 @@ class _ScannerScreenState extends State<ScannerScreen> {
showDismissibleSnackBar(
context,
content: Text(context.l10n.scanner_connectionFailed(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
}
} finally {
if (mounted) {
setState(() {
_connectingDeviceId = null;
});
}
}
}
@@ -411,113 +412,47 @@ class _ScannerScreenState extends State<ScannerScreen> {
);
return pin;
}
}
// Private sub-widgets
/// Bluetooth-off warning banner styled as an alert MeshCard.
class _BluetoothOffBanner extends StatelessWidget {
final VoidCallback? onEnable;
const _BluetoothOffBanner({this.onEnable});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return MeshCard(
color: scheme.error.withValues(alpha: 0.08),
borderColor: scheme.error.withValues(alpha: 0.35),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
Widget _bluetoothOffWarning(BuildContext context) {
final errorColor = Theme.of(context).colorScheme.error;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
color: errorColor.withValues(alpha: 0.15),
child: Row(
children: [
Icon(Icons.bluetooth_disabled, size: 20, color: scheme.error),
const SizedBox(width: 10),
Icon(Icons.bluetooth_disabled, size: 24, color: errorColor),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.scanner_bluetoothOff,
style: TextStyle(
color: scheme.error,
color: errorColor,
fontWeight: FontWeight.w600,
fontSize: 13.5,
fontSize: 14,
),
),
const SizedBox(height: 2),
const SizedBox(height: 4),
Text(
context.l10n.scanner_bluetoothOffMessage,
style: TextStyle(
color: scheme.error.withValues(alpha: 0.8),
color: errorColor.withValues(alpha: 0.85),
fontSize: 12,
),
),
],
),
),
if (onEnable != null) ...[
const SizedBox(width: 8),
if (PlatformInfo.isAndroid)
TextButton(
onPressed: onEnable,
onPressed: () => FlutterBluePlus.turnOn(),
child: Text(context.l10n.scanner_enableBluetooth),
),
],
],
),
);
}
}
/// Connection status header with AnimatedSwitcher between states.
class _ConnectionStatusHeader extends StatelessWidget {
final MeshCoreConnector connector;
const _ConnectionStatusHeader({required this.connector});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final (String label, Color color, bool pulse) = switch (connector.state) {
MeshCoreConnectionState.scanning => (
l10n.scanner_scanning,
MeshPalette.blue,
true,
),
MeshCoreConnectionState.connecting => (
l10n.scanner_connecting,
MeshPalette.warn,
true,
),
MeshCoreConnectionState.connected => (
l10n.scanner_connectedTo(connector.deviceDisplayName),
MeshPalette.signal,
false,
),
MeshCoreConnectionState.disconnecting => (
l10n.scanner_disconnecting,
MeshPalette.warn,
true,
),
MeshCoreConnectionState.disconnected => (
l10n.scanner_notConnected,
scheme.onSurfaceVariant,
false,
),
};
return Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Align(
key: ValueKey(connector.state),
alignment: Alignment.centerLeft,
child: StatusChip(label: label, color: color, pulse: pulse),
),
),
);
}
}
File diff suppressed because it is too large Load Diff
+81 -112
View File
@@ -1,18 +1,15 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
import '../services/app_settings_service.dart';
import '../theme/mesh_theme.dart';
import '../utils/platform_info.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/mesh_ui.dart';
import '../helpers/snack_bar_builder.dart';
import 'channels_screen.dart';
import 'contacts_screen.dart';
import 'usb_screen.dart';
class TcpScreen extends StatefulWidget {
@@ -27,7 +24,7 @@ class _TcpScreenState extends State<TcpScreen> {
late final TextEditingController _portController;
late final MeshCoreConnector _connector;
late final VoidCallback _connectionListener;
bool _navigatedToChannels = false;
bool _navigatedToContacts = false;
@override
void initState() {
@@ -45,20 +42,20 @@ class _TcpScreenState extends State<TcpScreen> {
_connectionListener = () {
if (!mounted) return;
if (_connector.state == MeshCoreConnectionState.disconnected) {
_navigatedToChannels = false;
_navigatedToContacts = false;
}
if (_connector.state == MeshCoreConnectionState.connected &&
_connector.isTcpTransportConnected &&
!_navigatedToChannels) {
!_navigatedToContacts) {
context.read<AppSettingsService>().setTcpServerAddress(
_hostController.text,
);
context.read<AppSettingsService>().setTcpServerPort(
int.tryParse(_portController.text) ?? 0,
);
_navigatedToChannels = true;
_navigatedToContacts = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const ChannelsScreen()),
MaterialPageRoute(builder: (_) => const ContactsScreen()),
);
}
};
@@ -70,7 +67,7 @@ class _TcpScreenState extends State<TcpScreen> {
_hostController.dispose();
_portController.dispose();
_connector.removeListener(_connectionListener);
if (!_navigatedToChannels &&
if (!_navigatedToContacts &&
_connector.activeTransport == MeshCoreTransportType.tcp &&
_connector.state != MeshCoreConnectionState.disconnected) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -98,32 +95,13 @@ class _TcpScreenState extends State<TcpScreen> {
final isConnecting =
connector.state == MeshCoreConnectionState.connecting &&
connector.activeTransport == MeshCoreTransportType.tcp;
// Connect is only available from a fully disconnected state
// scanning, connecting, or an active session must settle first.
final isButtonDisabled =
connector.state != MeshCoreConnectionState.disconnected;
return ListView(
padding: const EdgeInsets.only(bottom: 32),
isConnecting ||
connector.state == MeshCoreConnectionState.scanning;
return Column(
children: [
// Status header
_buildStatusBar(context, connector),
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Align(
key: ValueKey(connector.state),
alignment: Alignment.centerLeft,
child: _buildStatusChip(context, connector),
),
),
),
// Transport switcher
_buildTransportLinks(context),
// Connection form
const SectionHeader('TCP / IP'),
MeshCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -133,6 +111,7 @@ class _TcpScreenState extends State<TcpScreen> {
decoration: InputDecoration(
labelText: context.l10n.tcpHostLabel,
hintText: context.l10n.tcpHostHint,
border: const OutlineInputBorder(),
),
enabled: !isConnecting,
keyboardType: TextInputType.url,
@@ -143,6 +122,7 @@ class _TcpScreenState extends State<TcpScreen> {
decoration: InputDecoration(
labelText: context.l10n.tcpPortLabel,
hintText: context.l10n.tcpPortHint,
border: const OutlineInputBorder(),
),
enabled: !isConnecting,
keyboardType: TextInputType.number,
@@ -150,12 +130,7 @@ class _TcpScreenState extends State<TcpScreen> {
const SizedBox(height: 16),
FilledButton.icon(
key: const Key('tcp_connect_button'),
onPressed: isButtonDisabled
? null
: () {
HapticFeedback.lightImpact();
_connectTcp();
},
onPressed: isButtonDisabled ? null : _connectTcp,
icon: isConnecting
? const SizedBox(
width: 18,
@@ -174,100 +149,94 @@ class _TcpScreenState extends State<TcpScreen> {
],
),
),
// Last used endpoint
if (connector.activeTcpEndpoint != null &&
connector.isTcpTransportConnected) ...[
const SectionHeader('CONNECTED TO'),
MeshCard(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
child: Row(
children: [
Icon(
Icons.lan,
size: 16,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 10),
Expanded(
child: Text(
connector.activeTcpEndpoint!,
style: MeshTheme.mono(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
],
],
);
},
),
),
bottomNavigationBar: SafeArea(
top: false,
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerRight,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (PlatformInfo.supportsUsbSerial)
FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const UsbScreen()),
);
},
heroTag: 'tcp_usb_action',
extendedPadding: const EdgeInsets.symmetric(horizontal: 12),
icon: const Icon(Icons.usb),
label: Text(context.l10n.connectionChoiceUsbLabel),
),
if (PlatformInfo.supportsUsbSerial) const SizedBox(width: 12),
FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).maybePop();
},
heroTag: 'tcp_ble_action',
extendedPadding: const EdgeInsets.symmetric(horizontal: 12),
icon: const Icon(Icons.bluetooth),
label: Text(context.l10n.connectionChoiceBluetoothLabel),
),
],
),
),
),
);
}
Widget _buildStatusChip(BuildContext context, MeshCoreConnector connector) {
Widget _buildStatusBar(BuildContext context, MeshCoreConnector connector) {
final l10n = context.l10n;
String statusText;
Color statusColor;
if (connector.isTcpTransportConnected) {
return StatusChip(
label: l10n.scanner_connectedTo(connector.activeTcpEndpoint ?? 'TCP'),
color: MeshPalette.signal,
statusText = l10n.scanner_connectedTo(
connector.activeTcpEndpoint ?? 'TCP',
);
statusColor = Colors.green;
} else if (connector.state == MeshCoreConnectionState.connecting &&
connector.activeTransport == MeshCoreTransportType.tcp) {
return StatusChip(
label: l10n.tcpStatus_connectingTo(
'${_hostController.text}:${_portController.text}',
),
color: MeshPalette.warn,
pulse: true,
statusText = l10n.tcpStatus_connectingTo(
'${_hostController.text}:${_portController.text}',
);
statusColor = Colors.orange;
} else if (connector.state == MeshCoreConnectionState.disconnecting &&
connector.activeTransport == MeshCoreTransportType.tcp) {
return StatusChip(
label: l10n.scanner_disconnecting,
color: MeshPalette.warn,
pulse: true,
);
statusText = l10n.scanner_disconnecting;
statusColor = Colors.orange;
} else {
return StatusChip(
label: l10n.tcpStatus_notConnected,
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
statusText = l10n.tcpStatus_notConnected;
statusColor = Colors.grey;
}
}
Widget _buildTransportLinks(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Wrap(
spacing: 12,
runSpacing: 8,
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: statusColor.withValues(alpha: 0.1),
child: Row(
children: [
if (PlatformInfo.supportsUsbSerial)
OutlinedButton.icon(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const UsbScreen()),
);
},
icon: const Icon(Icons.usb),
label: Text(context.l10n.connectionChoiceUsbLabel),
Icon(Icons.circle, size: 12, color: statusColor),
const SizedBox(width: 8),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.w500,
),
),
),
OutlinedButton.icon(
onPressed: () => Navigator.of(context).maybePop(),
icon: const Icon(Icons.bluetooth),
label: Text(context.l10n.connectionChoiceBluetoothLabel),
),
],
),
@@ -305,7 +274,7 @@ class _TcpScreenState extends State<TcpScreen> {
showDismissibleSnackBar(
context,
content: Text(message),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
}
+139 -626
View File
@@ -1,26 +1,20 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../l10n/l10n.dart';
import '../models/contact.dart';
import '../models/path_selection.dart';
import '../models/app_settings.dart';
import '../storage/prefs_manager.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../services/app_settings_service.dart';
import '../services/repeater_command_service.dart';
import '../utils/app_logger.dart';
import '../widgets/routing_sheet.dart';
import '../widgets/path_management_dialog.dart';
import '../helpers/cayenne_lpp.dart';
import '../utils/battery_utils.dart';
import '../helpers/snack_bar_builder.dart';
import '../widgets/sync_progress_overlay.dart';
import '../widgets/telemetry_location_map.dart';
import '../theme/mesh_theme.dart';
import '../widgets/mesh_ui.dart';
class TelemetryScreen extends StatefulWidget {
final Contact contact;
@@ -32,13 +26,6 @@ class TelemetryScreen extends StatefulWidget {
}
class _TelemetryScreenState extends State<TelemetryScreen> {
static const int _autoRefreshDefaultIntervalSeconds = 20;
static const int _autoRefreshDefaultQuantity = 10;
static const int _autoRefreshMinIntervalSeconds = 10;
static const int _autoRefreshMaxIntervalSeconds = 300;
static const int _autoRefreshMinQuantity = 1;
static const int _autoRefreshMaxQuantity = 10;
int _tagData = 0;
bool _isLoading = false;
@@ -49,17 +36,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
RepeaterCommandService? _commandService;
PathSelection? _pendingStatusSelection;
List<Map<String, dynamic>>? _parsedTelemetry;
final TextEditingController _autoRefreshIntervalController =
TextEditingController(text: '$_autoRefreshDefaultIntervalSeconds');
final TextEditingController _autoRefreshQuantityController =
TextEditingController(text: '$_autoRefreshDefaultQuantity');
Timer? _autoRefreshTimer;
bool _isAutoRefreshEnabled = false;
bool _activeTelemetryRequestIsAutoRefresh = false;
bool _autoRefreshLastAttemptFailed = false;
int _autoRefreshCurrentAttempt = 0;
int _autoRefreshTotalAttempts = 0;
int _autoRefreshIntervalSeconds = _autoRefreshDefaultIntervalSeconds;
int _tripTime = 0;
@@ -86,7 +62,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
super.initState();
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
_commandService = RepeaterCommandService(connector);
_loadAutoRefreshSettings();
_setupMessageListener();
_loadTelemetry();
_hasData = false;
@@ -106,26 +81,17 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
_tagData = reader.readUInt32LE();
_tripTime = reader.readUInt32LE();
_statusTimeout?.cancel();
final isAutoRefreshRequest = _activeTelemetryRequestIsAutoRefresh;
_statusTimeout = Timer(Duration(milliseconds: _tripTime), () {
if (!mounted) return;
setState(() {
_isLoading = false;
_isLoaded = false;
if (isAutoRefreshRequest && _isAutoRefreshEnabled) {
_autoRefreshLastAttemptFailed = true;
}
});
if (!isAutoRefreshRequest) {
showDismissibleSnackBar(
context,
content: Text(context.l10n.telemetry_requestTimeout),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
if (isAutoRefreshRequest && _isAutoRefreshEnabled) {
_scheduleNextAutoRefreshAttempt();
}
showDismissibleSnackBar(
context,
content: Text(context.l10n.telemetry_requestTimeout),
backgroundColor: Colors.red,
);
_recordTelemetryResult(false);
});
}
@@ -167,21 +133,15 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
);
}
if (!mounted) return;
final isAutoRefreshRequest = _activeTelemetryRequestIsAutoRefresh;
setState(() {
_parsedTelemetry = parsedTelemetry;
if (isAutoRefreshRequest) {
_autoRefreshLastAttemptFailed = false;
}
_activeTelemetryRequestIsAutoRefresh = false;
});
if (!isAutoRefreshRequest) {
showDismissibleSnackBar(
context,
content: Text(context.l10n.telemetry_receivedData),
);
}
showDismissibleSnackBar(
context,
content: Text(context.l10n.telemetry_receivedData),
backgroundColor: Colors.green,
);
_statusTimeout?.cancel();
if (!mounted) return;
setState(() {
@@ -189,18 +149,14 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
_isLoaded = true;
_hasData = true;
});
if (isAutoRefreshRequest) {
_scheduleNextAutoRefreshAttempt();
}
}
Future<void> _loadTelemetry({bool isAutoRefresh = false}) async {
Future<void> _loadTelemetry() async {
if (_commandService == null) return;
setState(() {
_isLoading = true;
_isLoaded = false;
_activeTelemetryRequestIsAutoRefresh = isAutoRefresh;
});
try {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
@@ -212,7 +168,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
if (widget.contact.type != advTypeChat) {
frame = buildSendBinaryReq(
widget.contact.publicKey,
payload: buildTelemetryBinaryPayload(),
payload: Uint8List.fromList([reqTypeGetTelemetry]),
);
} else {
frame = buildSendTelemetryReq(widget.contact.publicKey);
@@ -223,76 +179,17 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
setState(() {
_isLoading = false;
_isLoaded = false;
if (isAutoRefresh) {
_autoRefreshLastAttemptFailed = true;
}
_activeTelemetryRequestIsAutoRefresh = false;
});
if (isAutoRefresh) {
_scheduleNextAutoRefreshAttempt();
}
if (!isAutoRefresh) {
showDismissibleSnackBar(
context,
content: Text(context.l10n.telemetry_errorLoading(e.toString())),
backgroundColor: Theme.of(context).colorScheme.error,
);
}
showDismissibleSnackBar(
context,
content: Text(context.l10n.telemetry_errorLoading(e.toString())),
backgroundColor: Colors.red,
);
}
}
}
void _loadAutoRefreshSettings() {
final prefs = PrefsManager.instance;
final contactKey = widget.contact.publicKeyHex;
final interval =
(prefs.getInt(_autoRefreshIntervalKey(contactKey)) ??
_autoRefreshDefaultIntervalSeconds)
.clamp(
_autoRefreshMinIntervalSeconds,
_autoRefreshMaxIntervalSeconds,
)
.toInt();
final quantity =
(prefs.getInt(_autoRefreshQuantityKey(contactKey)) ??
_autoRefreshDefaultQuantity)
.clamp(_autoRefreshMinQuantity, _autoRefreshMaxQuantity)
.toInt();
_autoRefreshIntervalSeconds = interval;
_autoRefreshIntervalController.text = interval.toString();
_autoRefreshQuantityController.text = quantity.toString();
}
Future<void> _saveAutoRefreshSettings() async {
final contactKey = widget.contact.publicKeyHex;
final interval = _clampControllerValue(
controller: _autoRefreshIntervalController,
min: _autoRefreshMinIntervalSeconds,
max: _autoRefreshMaxIntervalSeconds,
fallback: _autoRefreshIntervalSeconds,
);
final quantity = _clampControllerValue(
controller: _autoRefreshQuantityController,
min: _autoRefreshMinQuantity,
max: _autoRefreshMaxQuantity,
fallback: _autoRefreshDefaultQuantity,
);
final prefs = PrefsManager.instance;
await prefs.setInt(_autoRefreshIntervalKey(contactKey), interval);
await prefs.setInt(_autoRefreshQuantityKey(contactKey), quantity);
}
String _autoRefreshIntervalKey(String contactKey) {
return 'telemetry_auto_refresh_interval_$contactKey';
}
String _autoRefreshQuantityKey(String contactKey) {
return 'telemetry_auto_refresh_quantity_$contactKey';
}
void _recordTelemetryResult(bool success) {
final selection = _pendingStatusSelection;
if (selection == null) return;
@@ -308,28 +205,19 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
@override
void dispose() {
unawaited(_saveAutoRefreshSettings());
_frameSubscription?.cancel();
_commandService?.dispose();
_statusTimeout?.cancel();
_autoRefreshTimer?.cancel();
_autoRefreshIntervalController.dispose();
_autoRefreshQuantityController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final connector = context.watch<MeshCoreConnector>();
final settings = context.watch<AppSettingsService>().settings;
final isImperialUnits = settings.unitSystem == UnitSystem.imperial;
final contact = connector.contacts.firstWhere(
(c) => c.publicKeyHex == widget.contact.publicKeyHex,
orElse: () => widget.contact,
);
final isFloodMode = contact.pathOverride == -1;
final isFloodMode = widget.contact.pathOverride == -1;
return Scaffold(
appBar: AppBar(
@@ -351,13 +239,71 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
],
),
centerTitle: false,
bottom: const SyncProgressAppBarBottom(),
actions: [
IconButton(
PopupMenuButton<String>(
icon: Icon(isFloodMode ? Icons.waves : Icons.route),
tooltip: l10n.repeater_routingMode,
onSelected: (mode) async {
if (mode == 'flood') {
await connector.setPathOverride(widget.contact, pathLen: -1);
} else {
await connector.setPathOverride(widget.contact, pathLen: null);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'auto',
child: Row(
children: [
Icon(
Icons.auto_mode,
size: 20,
color: !isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_autoUseSavedPath,
style: TextStyle(
fontWeight: !isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
PopupMenuItem(
value: 'flood',
child: Row(
children: [
Icon(
Icons.waves,
size: 20,
color: isFloodMode
? Theme.of(context).primaryColor
: null,
),
const SizedBox(width: 8),
Text(
l10n.repeater_forceFloodMode,
style: TextStyle(
fontWeight: isFloodMode
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
],
),
IconButton(
icon: const Icon(Icons.timeline),
tooltip: l10n.repeater_pathManagement,
onPressed: () =>
ContactRoutingSheet.show(context, contact: widget.contact),
PathManagementDialog.show(context, contact: widget.contact),
),
IconButton(
icon: _isLoading
@@ -367,9 +313,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh),
onPressed: (_isLoading || _isAutoRefreshEnabled)
? null
: () => _loadTelemetry(),
onPressed: _isLoading ? null : _loadTelemetry,
tooltip: l10n.repeater_refresh,
),
],
@@ -377,8 +321,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
body: SafeArea(
top: false,
child: RefreshIndicator(
onRefresh: () =>
_isAutoRefreshEnabled ? Future.value() : _loadTelemetry(),
onRefresh: _loadTelemetry,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
@@ -388,10 +331,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
Center(
child: Text(
l10n.telemetry_noData,
style: TextStyle(
fontSize: 16,
color: scheme.onSurfaceVariant,
),
style: const TextStyle(fontSize: 16, color: Colors.grey),
),
),
if ((_isLoaded || _hasData) &&
@@ -404,7 +344,6 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
entry['channel'],
isImperialUnits,
),
_buildAutoRefreshCard(),
],
),
),
@@ -417,505 +356,86 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
String title,
int channel,
bool isImperialUnits,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionHeader(title, padding: const EdgeInsets.fromLTRB(16, 16, 16, 8)),
MeshCard(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final entry in channelData.entries)
_buildTelemetryField(entry, channel, isImperialUnits),
],
),
),
],
);
}
Widget _buildTelemetryField(
MapEntry<String, dynamic> entry,
int channel,
bool isImperialUnits,
) {
if (entry.key == 'gps') {
return _buildGpsInfo(entry.value);
}
final display = _formatTelemetryField(
entry.key,
entry.value,
channel,
isImperialUnits,
);
return _buildInfoRow(display.label, display.value);
}
_TelemetryFieldDisplay _formatTelemetryField(
String key,
dynamic value,
int channel,
bool isImperialUnits,
) {
final l10n = context.l10n;
final text = _telemetryValueText(value);
switch (key) {
case 'digitalInput':
return _TelemetryFieldDisplay(l10n.telemetry_digitalInputLabel, text);
case 'digitalOutput':
return _TelemetryFieldDisplay(l10n.telemetry_digitalOutputLabel, text);
case 'analogInput':
return _TelemetryFieldDisplay(
l10n.telemetry_analogInputLabel,
l10n.telemetry_analogValue(text),
);
case 'analogOutput':
return _TelemetryFieldDisplay(
l10n.telemetry_analogOutputLabel,
l10n.telemetry_analogValue(text),
);
case 'generic':
return _TelemetryFieldDisplay(l10n.telemetry_genericLabel, text);
case 'luminosity':
return _TelemetryFieldDisplay(
l10n.telemetry_luminosityLabel,
l10n.telemetry_luminosityValue(text),
);
case 'presence':
return _TelemetryFieldDisplay(l10n.telemetry_presenceLabel, text);
case 'temperature':
return _TelemetryFieldDisplay(
channel == 1
? l10n.telemetry_mcuTemperatureLabel
: l10n.telemetry_temperatureLabel,
_temperatureText(value, isImperialUnits),
);
case 'humidity':
return _TelemetryFieldDisplay(l10n.telemetry_humidityLabel, text);
case 'accelerometer':
return _TelemetryFieldDisplay(
l10n.telemetry_accelerometerLabel,
_telemetryAxisText(value),
);
case 'pressure':
return _TelemetryFieldDisplay(
l10n.telemetry_pressureLabel,
l10n.telemetry_pressureValue(text),
);
case 'altitude':
return _TelemetryFieldDisplay(
l10n.telemetry_altitudeLabel,
l10n.telemetry_altitudeValue(text),
);
case 'voltage':
return _TelemetryFieldDisplay(
channel == 1
? l10n.telemetry_batteryLabel
: l10n.telemetry_voltageLabel,
channel == 1
? _batteryText(value)
: l10n.telemetry_voltageValue(text),
);
case 'current':
return _TelemetryFieldDisplay(
l10n.telemetry_currentLabel,
l10n.telemetry_currentValue(text),
);
case 'frequency':
return _TelemetryFieldDisplay(
l10n.telemetry_frequencyLabel,
l10n.telemetry_frequencyValue(text),
);
case 'percentage':
return _TelemetryFieldDisplay(
l10n.telemetry_percentageLabel,
l10n.telemetry_percentageValue(text),
);
case 'concentration':
return _TelemetryFieldDisplay(
l10n.telemetry_concentrationLabel,
l10n.telemetry_concentrationValue(text),
);
case 'power':
return _TelemetryFieldDisplay(
l10n.telemetry_powerLabel,
l10n.telemetry_powerValue(text),
);
case 'distance':
return _TelemetryFieldDisplay(
l10n.telemetry_distanceLabel,
l10n.telemetry_distanceValue(text),
);
case 'energy':
return _TelemetryFieldDisplay(
l10n.telemetry_energyLabel,
l10n.telemetry_energyValue(text),
);
case 'direction':
return _TelemetryFieldDisplay(
l10n.telemetry_directionLabel,
l10n.telemetry_directionValue(text),
);
case 'time':
return _TelemetryFieldDisplay(
l10n.telemetry_timeLabel,
_telemetryTimeText(value),
);
case 'gyrometer':
return _TelemetryFieldDisplay(
l10n.telemetry_gyrometerLabel,
_telemetryAxisText(value),
);
case 'colour':
return _TelemetryFieldDisplay(
l10n.telemetry_colourLabel,
_telemetryColorText(value),
);
case 'switch':
return _TelemetryFieldDisplay(l10n.telemetry_switchLabel, text);
case 'polyline':
return _TelemetryFieldDisplay(
l10n.telemetry_polylineLabel,
_telemetryMapText(value),
);
default:
return _TelemetryFieldDisplay(key, text);
}
}
Widget _buildAutoRefreshCard() {
final l10n = context.l10n;
final counterText = _autoRefreshCounterText();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SectionHeader(
l10n.common_autoRefresh,
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
),
MeshCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildAutoRefreshNumberField(
controller: _autoRefreshIntervalController,
label: l10n.common_interval,
min: _autoRefreshMinIntervalSeconds,
max: _autoRefreshMaxIntervalSeconds,
fallback: _autoRefreshIntervalSeconds,
),
const SizedBox(height: 12),
_buildAutoRefreshNumberField(
controller: _autoRefreshQuantityController,
label: l10n.telemetry_autoFetchQuantity,
min: _autoRefreshMinQuantity,
max: _autoRefreshMaxQuantity,
fallback: _autoRefreshDefaultQuantity,
),
if (counterText != null) ...[
const SizedBox(height: 12),
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.info_outline,
color: Theme.of(context).textTheme.headlineSmall?.color,
),
const SizedBox(width: 8),
Text(
counterText,
textAlign: TextAlign.center,
style: TextStyle(
color: _autoRefreshLastAttemptFailed
? Theme.of(context).colorScheme.error
: null,
fontWeight: FontWeight.w600,
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
const SizedBox(height: 12),
FilledButton(
onPressed: _isLoading && !_isAutoRefreshEnabled
? null
: _toggleAutoRefresh,
child: _isAutoRefreshEnabled
? SizedBox(
width: double.infinity,
height: 20,
child: Stack(
alignment: Alignment.center,
children: [
Center(child: Text(l10n.common_disable)),
Positioned(
right: 0,
child: SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Theme.of(
context,
).colorScheme.onPrimary,
),
),
),
],
),
)
: Text(l10n.common_enable),
),
],
),
),
const Divider(),
for (final entry in channelData.entries)
if (entry.key == 'voltage' && channel == 1)
_buildInfoRow(
l10n.telemetry_batteryLabel,
_batteryText(entry.value),
)
else if (entry.key == 'voltage')
_buildInfoRow(
l10n.telemetry_voltageLabel,
l10n.telemetry_voltageValue(entry.value.toString()),
)
else if (entry.key == 'temperature' && channel == 1)
_buildInfoRow(
l10n.telemetry_mcuTemperatureLabel,
_temperatureText(entry.value, isImperialUnits),
)
else if (entry.key == 'temperature')
_buildInfoRow(
l10n.telemetry_temperatureLabel,
_temperatureText(entry.value, isImperialUnits),
)
else if (entry.key == 'current' && channel == 1)
_buildInfoRow(
l10n.telemetry_currentLabel,
l10n.telemetry_currentValue(entry.value.toString()),
)
else
_buildInfoRow(entry.key, entry.value.toString()),
],
),
],
);
}
Widget _buildAutoRefreshNumberField({
required TextEditingController controller,
required String label,
required int min,
required int max,
required int fallback,
}) {
return TextField(
controller: controller,
enabled: !_isAutoRefreshEnabled,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
isDense: true,
),
onEditingComplete: () {
_clampControllerValue(
controller: controller,
min: min,
max: max,
fallback: fallback,
);
unawaited(_saveAutoRefreshSettings());
FocusScope.of(context).unfocus();
},
onSubmitted: (_) => unawaited(_saveAutoRefreshSettings()),
onTapOutside: (_) {
unawaited(_saveAutoRefreshSettings());
FocusScope.of(context).unfocus();
},
);
}
String? _autoRefreshCounterText() {
if (!_isAutoRefreshEnabled && _autoRefreshCurrentAttempt == 0) return null;
final counter = '$_autoRefreshCurrentAttempt/$_autoRefreshTotalAttempts';
if (_autoRefreshLastAttemptFailed) {
return '${context.l10n.telemetry_error}: $counter';
}
return counter;
}
void _toggleAutoRefresh() {
if (_isAutoRefreshEnabled) {
_stopAutoRefresh();
return;
}
_startAutoRefresh();
}
void _startAutoRefresh() {
final interval = _clampControllerValue(
controller: _autoRefreshIntervalController,
min: _autoRefreshMinIntervalSeconds,
max: _autoRefreshMaxIntervalSeconds,
fallback: _autoRefreshIntervalSeconds,
);
final quantity = _clampControllerValue(
controller: _autoRefreshQuantityController,
min: _autoRefreshMinQuantity,
max: _autoRefreshMaxQuantity,
fallback: _autoRefreshDefaultQuantity,
);
unawaited(_saveAutoRefreshSettings());
setState(() {
_isAutoRefreshEnabled = true;
_autoRefreshIntervalSeconds = interval;
_autoRefreshTotalAttempts = quantity;
_autoRefreshCurrentAttempt = 0;
_autoRefreshLastAttemptFailed = false;
});
_runAutoRefreshAttempt();
}
void _stopAutoRefresh() {
_autoRefreshTimer?.cancel();
_autoRefreshTimer = null;
if (!mounted) return;
setState(() {
_isAutoRefreshEnabled = false;
});
}
Future<void> _runAutoRefreshAttempt() async {
if (!_isAutoRefreshEnabled || !mounted) return;
if (_autoRefreshCurrentAttempt >= _autoRefreshTotalAttempts) {
_stopAutoRefresh();
return;
}
setState(() {
_autoRefreshCurrentAttempt += 1;
});
await _loadTelemetry(isAutoRefresh: true);
}
void _scheduleNextAutoRefreshAttempt() {
if (!_isAutoRefreshEnabled || !mounted) return;
_autoRefreshTimer?.cancel();
if (_autoRefreshCurrentAttempt >= _autoRefreshTotalAttempts) {
_stopAutoRefresh();
return;
}
// Start the interval only after the current request has finished: after a
// telemetry response, timeout, or send error. This keeps slow replies from
// shortening the intended pause between requests.
_autoRefreshTimer = Timer(
Duration(seconds: _autoRefreshIntervalSeconds),
_runAutoRefreshAttempt,
);
}
int _clampControllerValue({
required TextEditingController controller,
required int min,
required int max,
required int fallback,
}) {
final parsed = int.tryParse(controller.text);
final value = (parsed ?? fallback).clamp(min, max).toInt();
controller.text = value.toString();
controller.selection = TextSelection.collapsed(
offset: controller.text.length,
);
return value;
}
Widget _buildGpsInfo(dynamic value) {
final latitude = _readGpsValue(value, 'latitude');
final longitude = _readGpsValue(value, 'longitude');
final altitude = _readGpsValue(value, 'altitude');
final isValidPosition = _isValidGpsPosition(latitude, longitude);
final gpsText = isValidPosition
? [
latitude!.toStringAsFixed(5),
longitude!.toStringAsFixed(5),
if (altitude != null) '${altitude.toStringAsFixed(1)} m',
].join(', ')
: value.toString();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(context.l10n.telemetry_gpsLabel, gpsText),
if (isValidPosition)
TelemetryLocationMap(
// The map renders only after bounds validation, keeping malformed
// Cayenne payloads from creating an invalid FlutterMap center.
latitude: latitude!,
longitude: longitude!,
label: widget.contact.name,
contactType: widget.contact.type,
contactPublicKeyHex: widget.contact.publicKeyHex,
),
],
);
}
double? _readGpsValue(dynamic value, String key) {
if (value is! Map) return null;
final rawValue = value[key];
if (rawValue is num) return rawValue.toDouble();
return null;
}
bool _isValidGpsPosition(double? latitude, double? longitude) {
if (latitude == null || longitude == null) return false;
const double epsilon = 1e-6;
return (latitude.abs() > epsilon || longitude.abs() > epsilon) &&
latitude >= -90.0 &&
latitude <= 90.0 &&
longitude >= -180.0 &&
longitude <= 180.0;
}
String _telemetryValueText(dynamic value) {
if (value == null) return context.l10n.common_notAvailable;
if (value is double) {
return value.toStringAsFixed(value.truncateToDouble() == value ? 0 : 2);
}
if (value is num) {
return value.toString();
}
return value.toString();
}
String _telemetryAxisText(dynamic value) {
if (value is! Map) return _telemetryValueText(value);
final x = _telemetryValueText(value['x']);
final y = _telemetryValueText(value['y']);
final z = _telemetryValueText(value['z']);
return 'X: $x, Y: $y, Z: $z';
}
String _telemetryColorText(dynamic value) {
if (value is! Map) return _telemetryValueText(value);
final red = _telemetryValueText(value['red']);
final green = _telemetryValueText(value['green']);
final blue = _telemetryValueText(value['blue']);
return 'R: $red, G: $green, B: $blue';
}
String _telemetryMapText(dynamic value) {
if (value is! Map) return _telemetryValueText(value);
return value.entries
.map((entry) => '${entry.key}: ${entry.value}')
.join(', ');
}
String _telemetryTimeText(dynamic value) {
if (value is! num || value <= 0) return _telemetryValueText(value);
final dateTime = DateTime.fromMillisecondsSinceEpoch(
value.toInt() * 1000,
isUtc: true,
).toLocal();
final localizations = MaterialLocalizations.of(context);
final time = localizations.formatTimeOfDay(
TimeOfDay.fromDateTime(dateTime),
);
return '${localizations.formatFullDate(dateTime)} $time';
}
Widget _buildInfoRow(String label, String value) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
SizedBox(
width: 130,
child: Text(
label,
style: TextStyle(
color: scheme.onSurfaceVariant,
fontSize: 13,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 8),
Text(
value,
style: MeshTheme.mono(fontSize: 13, color: scheme.onSurface),
textAlign: TextAlign.end,
Expanded(
child: Text(
value,
style: const TextStyle(fontWeight: FontWeight.w400),
),
),
],
),
@@ -963,10 +483,3 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
return '${tempC.toStringAsFixed(1)}°C';
}
}
class _TelemetryFieldDisplay {
final String label;
final String value;
const _TelemetryFieldDisplay(this.label, this.value);
}
+149 -157
View File
@@ -6,15 +6,13 @@ import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
import '../theme/mesh_theme.dart';
import '../utils/app_logger.dart';
import '../utils/platform_info.dart';
import '../utils/usb_port_labels.dart';
import '../widgets/adaptive_app_bar_title.dart';
import '../widgets/empty_state.dart';
import '../widgets/mesh_ui.dart';
import '../helpers/snack_bar_builder.dart';
import 'channels_screen.dart';
import 'contacts_screen.dart';
import 'scanner_screen.dart';
import 'tcp_screen.dart';
class UsbScreen extends StatefulWidget {
@@ -27,7 +25,7 @@ class UsbScreen extends StatefulWidget {
class _UsbScreenState extends State<UsbScreen> {
final List<String> _ports = <String>[];
bool _isLoadingPorts = true;
bool _navigatedToChannels = false;
bool _navigatedToContacts = false;
bool _didScheduleInitialLoad = false;
Timer? _hotPlugTimer;
late final MeshCoreConnector _connector;
@@ -43,14 +41,14 @@ class _UsbScreenState extends State<UsbScreen> {
_connectionListener = () {
if (!mounted) return;
if (_connector.state == MeshCoreConnectionState.disconnected) {
_navigatedToChannels = false;
_navigatedToContacts = false;
}
if (_connector.state == MeshCoreConnectionState.connected &&
_connector.isUsbTransportConnected &&
!_navigatedToChannels) {
_navigatedToChannels = true;
!_navigatedToContacts) {
_navigatedToContacts = true;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const ChannelsScreen()),
MaterialPageRoute(builder: (_) => const ContactsScreen()),
);
}
};
@@ -74,7 +72,7 @@ class _UsbScreenState extends State<UsbScreen> {
_hotPlugTimer?.cancel();
_hotPlugTimer = null;
_connector.removeListener(_connectionListener);
if (!_navigatedToChannels &&
if (!_navigatedToContacts &&
_connector.activeTransport == MeshCoreTransportType.usb &&
_connector.state != MeshCoreConnectionState.disconnected) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -100,124 +98,138 @@ class _UsbScreenState extends State<UsbScreen> {
child: Consumer<MeshCoreConnector>(
builder: (context, connector, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Status header
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Align(
key: ValueKey('${connector.state}_$_isLoadingPorts'),
alignment: Alignment.centerLeft,
child: _buildStatusChip(context, connector),
),
),
),
// Transport switcher
_buildTransportLinks(context),
// Port list
_buildStatusBar(context, connector),
Expanded(child: _buildPortList(context, connector)),
],
);
},
),
),
bottomNavigationBar: _supportsHotPlug
? null
: SafeArea(
top: false,
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
bottomNavigationBar: Consumer<MeshCoreConnector>(
builder: (context, connector, child) {
final isLoading = _isLoadingPorts;
final showBle = true;
final showTcp = !PlatformInfo.isWeb;
return SafeArea(
top: false,
minimum: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerRight,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.extended(
onPressed: _isLoadingPorts ? null : _loadPorts,
heroTag: 'usb_refresh_action',
icon: _isLoadingPorts
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.usb),
label: Text(context.l10n.scanner_scan),
),
if (showTcp)
FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const TcpScreen()),
);
},
heroTag: 'usb_tcp_action',
extendedPadding: const EdgeInsets.symmetric(
horizontal: 12,
),
icon: const Icon(Icons.lan),
label: Text(context.l10n.connectionChoiceTcpLabel),
),
if (showTcp && showBle) const SizedBox(width: 12),
if (showBle)
FloatingActionButton.extended(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const ScannerScreen(),
),
);
},
heroTag: 'usb_ble_action',
extendedPadding: const EdgeInsets.symmetric(
horizontal: 12,
),
icon: const Icon(Icons.bluetooth),
label: Text(context.l10n.connectionChoiceBluetoothLabel),
),
if ((showTcp || showBle) && !_supportsHotPlug)
const SizedBox(width: 12),
if (!_supportsHotPlug)
FloatingActionButton.extended(
onPressed: isLoading ? null : _loadPorts,
heroTag: 'usb_refresh_action',
extendedPadding: const EdgeInsets.symmetric(
horizontal: 12,
),
icon: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.usb),
label: Text(context.l10n.scanner_scan),
),
],
),
),
);
},
),
);
}
Widget _buildStatusChip(BuildContext context, MeshCoreConnector connector) {
Widget _buildStatusBar(BuildContext context, MeshCoreConnector connector) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
String statusText;
Color statusColor;
if (_isLoadingPorts) {
return StatusChip(
label: l10n.usbStatus_searching,
color: scheme.primary,
pulse: true,
);
statusText = l10n.usbStatus_searching;
statusColor = Colors.blue;
} else if (connector.isUsbTransportConnected) {
switch (connector.state) {
case MeshCoreConnectionState.connected:
return StatusChip(
label: l10n.scanner_connectedTo(
connector.activeUsbPortDisplayLabel ?? 'USB',
),
color: MeshPalette.signal,
statusText = l10n.scanner_connectedTo(
connector.activeUsbPortDisplayLabel ?? 'USB',
);
statusColor = Colors.green;
case MeshCoreConnectionState.disconnecting:
return StatusChip(
label: l10n.scanner_disconnecting,
color: MeshPalette.warn,
pulse: true,
);
statusText = l10n.scanner_disconnecting;
statusColor = Colors.orange;
default:
return StatusChip(
label: l10n.usbStatus_notConnected,
color: scheme.onSurfaceVariant,
);
statusText = l10n.usbStatus_notConnected;
statusColor = Colors.grey;
}
} else if (connector.state == MeshCoreConnectionState.connecting &&
connector.activeTransport == MeshCoreTransportType.usb) {
return StatusChip(
label: l10n.usbStatus_connecting,
color: MeshPalette.warn,
pulse: true,
);
statusText = l10n.usbStatus_connecting;
statusColor = Colors.orange;
} else {
return StatusChip(
label: l10n.usbStatus_notConnected,
color: scheme.onSurfaceVariant,
);
statusText = l10n.usbStatus_notConnected;
statusColor = Colors.grey;
}
}
Widget _buildTransportLinks(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Wrap(
spacing: 12,
runSpacing: 8,
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: statusColor.withValues(alpha: 0.1),
child: Row(
children: [
if (!PlatformInfo.isWeb)
OutlinedButton.icon(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const TcpScreen()),
);
},
icon: const Icon(Icons.lan),
label: Text(context.l10n.connectionChoiceTcpLabel),
Icon(Icons.circle, size: 12, color: statusColor),
const SizedBox(width: 8),
Expanded(
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.w500,
),
),
),
OutlinedButton.icon(
onPressed: () => Navigator.of(context).maybePop(),
icon: const Icon(Icons.bluetooth),
label: Text(context.l10n.connectionChoiceBluetoothLabel),
),
],
),
@@ -228,20 +240,46 @@ class _UsbScreenState extends State<UsbScreen> {
final l10n = context.l10n;
if (_isLoadingPorts) {
return EmptyState(icon: Icons.usb, title: l10n.usbStatus_searching);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.usb, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
l10n.usbStatus_searching,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
],
),
);
}
if (_ports.isEmpty) {
return EmptyState(icon: Icons.usb, title: l10n.usbScreenEmptyState);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.usb, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
l10n.usbScreenEmptyState,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
],
),
);
}
final isConnecting =
connector.state == MeshCoreConnectionState.connecting &&
connector.activeTransport == MeshCoreTransportType.usb;
return ListView.builder(
padding: const EdgeInsets.only(bottom: 32),
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: _ports.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final port = _ports[index];
final displayName = friendlyUsbPortName(port);
@@ -249,50 +287,18 @@ class _UsbScreenState extends State<UsbScreen> {
final showRawName =
rawName != displayName && !rawName.startsWith('web:');
return ListEntrance(
index: index,
child: MeshCard(
padding: EdgeInsets.zero,
child: ListTile(
onTap: isConnecting
? null
: () {
HapticFeedback.selectionClick();
_connectPort(port);
},
leading: AvatarCircle(
name: displayName,
size: 40,
icon: Icons.usb,
color: Theme.of(context).colorScheme.primary,
),
title: Text(
displayName,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: showRawName
? Text(
rawName,
style: MeshTheme.mono(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
: null,
trailing: Icon(
Icons.chevron_right,
size: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
return ListTile(
leading: const Icon(Icons.usb),
title: Text(
displayName,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: showRawName ? Text(rawName) : null,
trailing: ElevatedButton(
onPressed: isConnecting ? null : () => _connectPort(port),
child: Text(l10n.common_connect),
),
onTap: isConnecting ? null : () => _connectPort(port),
);
},
);
@@ -378,27 +384,13 @@ class _UsbScreenState extends State<UsbScreen> {
void _showError(Object error) {
if (!mounted) return;
// Cancelling the browser's serial port picker is a normal user action, not
// an error don't show a scary red toast (and never leak the raw
// DOMException text).
if (_isUserCancelledPortPicker(error)) return;
showDismissibleSnackBar(
context,
content: Text(_friendlyErrorMessage(error)),
backgroundColor: Theme.of(context).colorScheme.error,
backgroundColor: Colors.red,
);
}
bool _isUserCancelledPortPicker(Object error) {
if (error is StateError &&
error.message.contains('No USB serial device selected')) {
return true;
}
final text = error.toString();
return text.contains('No port selected by the user') ||
text.contains("Failed to execute 'requestPort'");
}
String _friendlyErrorMessage(Object error) {
final l10n = context.l10n;
-6
View File
@@ -235,12 +235,6 @@ class AppSettingsService extends ChangeNotifier {
await updateSettings(_settings.copyWith(translationEnabled: value));
}
Future<void> setAutoTranslateIncomingMessages(bool value) async {
await updateSettings(
_settings.copyWith(autoTranslateIncomingMessages: value),
);
}
Future<void> setTranslationTargetLanguageCode(String? value) async {
await updateSettings(
_settings.copyWith(translationTargetLanguageCode: value),
+7 -52
View File
@@ -39,12 +39,7 @@ class RetryServiceConfig {
final void Function(Message) updateMessage;
final Function(Contact)? clearContactPath;
final Function(Contact, Uint8List, int)? setContactPath;
final int Function(
int pathLength,
int messageBytes, {
String? contactKey,
int? deviceTimeoutMs,
})?
final int Function(int pathLength, int messageBytes, {String? contactKey})?
calculateTimeout;
final Uint8List? Function()? getSelfPublicKey;
final String Function(Contact, String)? prepareContactOutboundText;
@@ -79,12 +74,6 @@ class RetryServiceConfig {
class MessageRetryService extends ChangeNotifier {
static const int maxAckHistorySize = 100;
/// Global cap on concurrent in-flight messages across ALL contacts.
/// The firmware's expected_ack_table is a single 8-entry circular buffer
/// shared globally; cap at 6 to leave two slots of headroom.
static const int _maxGlobalInFlight = 6;
int _maxRetries = 5;
int get maxRetries => _maxRetries;
@@ -181,9 +170,8 @@ class MessageRetryService extends ChangeNotifier {
_config?.addMessage(contact.publicKeyHex, message);
// Queue per contact one message in-flight per contact at a time, and
// bounded globally by _maxGlobalInFlight across all contacts so we never
// overflow the firmware's 8-entry global expected_ack_table.
// Queue per contact only one message in-flight at a time to avoid
// overflowing the firmware's 8-entry expected_ack_table.
final contactKey = contact.publicKeyHex;
_sendQueue[contactKey] ??= [];
_sendQueue[contactKey]!.add(messageId);
@@ -196,11 +184,6 @@ class MessageRetryService extends ChangeNotifier {
}
void _sendNextForContact(String contactKey) {
// Enforce the global in-flight cap before starting a new send.
// The firmware's expected_ack_table is a single 8-entry circular buffer
// shared across all contacts; exceeding it silently evicts an older slot.
if (_activeMessages.length >= _maxGlobalInFlight) return;
final queue = _sendQueue[contactKey];
if (queue == null) return;
@@ -228,16 +211,7 @@ class MessageRetryService extends ChangeNotifier {
if (_resolvedMessages.contains(messageId)) return;
_resolvedMessages.add(messageId);
_activeMessages.remove(messageId);
// Pump this contact's queue first, then any other contacts that are waiting.
_sendNextForContact(contactKey);
for (final key in _sendQueue.keys) {
if (key == contactKey) continue;
if (_activeMessages.length >= _maxGlobalInFlight) break;
final queue = _sendQueue[key];
if (queue != null && queue.isNotEmpty) {
_sendNextForContact(key);
}
}
}
PathSelection? _selectPathForAttempt(Message message, Contact contact) {
@@ -378,10 +352,6 @@ class MessageRetryService extends ChangeNotifier {
}
bool updateMessageFromSent(int ackHash, int timeoutMs) {
// Firmware sets expected_ack = 0 for CLI/command sends (TXT_TYPE_CLI_DATA).
// No ACK will ever be issued for these, so arming a retry timer is wrong.
if (ackHash == 0) return false;
final config = _config;
if (config == null) return false;
@@ -434,18 +404,13 @@ class MessageRetryService extends ChangeNotifier {
// Calculate timeout: prefer ML prediction, then device-provided, then physics fallback
final pathLengthValue = message.pathLength ?? contact.pathLength;
final outboundTextForTimeout =
config.prepareContactOutboundText?.call(contact, message.text) ??
message.text;
final messageBytesForTimeout = utf8.encode(outboundTextForTimeout).length;
int actualTimeout = timeoutMs;
if (config.calculateTimeout != null) {
actualTimeout = config.calculateTimeout!(
pathLengthValue,
messageBytesForTimeout,
message.text.length,
contactKey: contact.publicKeyHex,
deviceTimeoutMs: timeoutMs > 0 ? timeoutMs : null,
);
}
@@ -484,11 +449,6 @@ class MessageRetryService extends ChangeNotifier {
});
}
void untrack(String messageId) {
_timeoutTimers[messageId]?.cancel();
_cleanupMessage(messageId);
}
void _cleanupMessage(String messageId) {
_moveAckHashesToHistory(messageId);
_ackHashToMessageId.removeWhere(
@@ -652,6 +612,7 @@ class MessageRetryService extends ChangeNotifier {
for (final expectedHash in expectedHashes) {
if (expectedHash == ackHash) {
matchedMessageId = messageId;
matchedAttemptIndex = expectedHashes.indexOf(expectedHash);
break;
}
}
@@ -703,16 +664,10 @@ class MessageRetryService extends ChangeNotifier {
if (config?.onDeliveryObserved != null &&
tripTimeMs > 0 &&
message.pathLength != null) {
final outboundTextForObserved =
config!.prepareContactOutboundText?.call(contact, message.text) ??
message.text;
final messageBytesForObserved = utf8
.encode(outboundTextForObserved)
.length;
config.onDeliveryObserved!(
config!.onDeliveryObserved!(
contact.publicKeyHex,
message.pathLength!,
messageBytesForObserved,
message.text.length,
tripTimeMs,
);
}
+8 -42
View File
@@ -114,36 +114,6 @@ class NotificationService {
return _isInitialized;
}
// Cached "are we allowed to post notifications" result. Null = not yet
// determined. Avoids calling _notifications.show() when it would only throw
// "You must request notifications permissions first" (every web build, and
// Android 13+ before the user grants the permission).
bool? _canNotify;
Future<bool> _ensureCanNotify() async {
if (!await _ensureInitialized()) return false;
final cached = _canNotify;
if (cached != null) return cached;
// flutter_local_notifications has no web backend, so show() always throws.
// Skip silently instead of logging an error per incoming message.
if (kIsWeb) return _canNotify = false;
// On Android 13+ notifications require an explicit grant; reflect the real
// OS state so we don't spam failed show() calls when denied.
final androidPlugin = _notifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>();
if (androidPlugin != null) {
final enabled = await androidPlugin.areNotificationsEnabled();
return _canNotify = enabled ?? false;
}
// iOS/macOS request permission during initialize(); desktop has no gate.
return _canNotify = true;
}
Future<bool> requestPermissions() async {
if (!_isInitialized) {
await initialize();
@@ -156,8 +126,7 @@ class NotificationService {
>();
if (androidPlugin != null) {
final granted = await androidPlugin.requestNotificationsPermission();
_canNotify = granted ?? false;
return _canNotify!;
return granted ?? false;
}
// iOS permissions are requested during initialization
@@ -171,8 +140,7 @@ class NotificationService {
badge: true,
sound: true,
);
_canNotify = granted ?? false;
return _canNotify!;
return granted ?? false;
}
return true;
@@ -197,7 +165,7 @@ class NotificationService {
String? contactId,
int? badgeCount,
}) async {
if (!await _ensureCanNotify()) return;
if (!await _ensureInitialized()) return;
final androidDetails = AndroidNotificationDetails(
'messages',
@@ -247,7 +215,7 @@ class NotificationService {
required String contactType,
String? contactId,
}) async {
if (!await _ensureCanNotify()) return;
if (!await _ensureInitialized()) return;
const androidDetails = AndroidNotificationDetails(
'adverts',
@@ -280,7 +248,7 @@ class NotificationService {
await _notifications.show(
id: contactId != null
? 'advert:$contactId'.hashCode
: DateTime.now().millisecondsSinceEpoch & 0x7FFFFFFF,
: DateTime.now().millisecondsSinceEpoch,
title: _l10n.notification_newTypeDiscovered(contactType),
body: contactName,
notificationDetails: notificationDetails,
@@ -297,7 +265,7 @@ class NotificationService {
int? channelIndex,
int? badgeCount,
}) async {
if (!await _ensureCanNotify()) return;
if (!await _ensureInitialized()) return;
final androidDetails = AndroidNotificationDetails(
'channel_messages',
@@ -336,9 +304,7 @@ class NotificationService {
try {
await _notifications.show(
id:
channelIndex?.hashCode ??
DateTime.now().millisecondsSinceEpoch & 0x7FFFFFFF,
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: channelName,
body: body,
notificationDetails: notificationDetails,
@@ -577,7 +543,7 @@ class NotificationService {
}
Future<void> _showBatchSummary(List<_PendingNotification> batch) async {
if (!await _ensureCanNotify()) return;
if (!await _ensureInitialized()) return;
// Group by type
final messages = batch
+1 -3
View File
@@ -134,12 +134,10 @@ class PathHistoryService extends ChangeNotifier {
newWeight = (currentWeight + successIncrement).clamp(0.0, maxWeight);
} else {
newWeight = currentWeight - failureDecrement;
if (newWeight <= 0 && failureCount >= 3) {
if (newWeight <= 0) {
removePathRecord(contactPubKeyHex, selection.pathBytes);
return;
}
// Keep the record with a small floor weight until we have enough evidence
newWeight = newWeight.clamp(0.1, maxWeight);
}
_addPathRecord(
+8 -20
View File
@@ -63,15 +63,12 @@ class TimeoutPredictionService extends ChangeNotifier {
required int tripTimeMs,
int secondsSinceLastRx = 0,
}) {
final isFlood = pathLength < 0;
final observation = DeliveryObservation(
contactKey: contactKey,
// Clamp to 0 for flood so the hop-count slope is learned from direct paths
// only; isFlood carries the flood signal as a separate feature.
pathLength: isFlood ? 0 : pathLength,
pathLength: pathLength,
messageBytes: messageBytes,
secondsSinceLastRx: secondsSinceLastRx,
isFlood: isFlood,
isFlood: pathLength < 0,
deliveryMs: tripTimeMs,
timestamp: DateTime.now(),
);
@@ -79,12 +76,11 @@ class TimeoutPredictionService extends ChangeNotifier {
_observations.add(observation);
if (_observations.length > maxObservations) {
_observations.removeAt(0);
_rebuildContactStats();
} else {
_contactStats.putIfAbsent(contactKey, () => _ContactStats());
_contactStats[contactKey]!.add(tripTimeMs.toDouble());
}
_contactStats.putIfAbsent(contactKey, () => _ContactStats());
_contactStats[contactKey]!.add(tripTimeMs.toDouble());
_observationsSinceLastTrain++;
if (_observationsSinceLastTrain >= _retrainInterval &&
_observations.length >= minObservations) {
@@ -112,14 +108,11 @@ class TimeoutPredictionService extends ChangeNotifier {
try {
if (_activeFeatures.isEmpty) return null;
final flood = pathLength < 0;
final allFeatures = {
// Clamp to 0 for flood mirrors recordObservation so training and
// prediction see the same pathLength values; isFlood carries the signal.
'pathLength': flood ? 0.0 : pathLength.toDouble(),
'pathLength': pathLength.toDouble(),
'messageBytes': messageBytes.toDouble(),
'secSinceRx': secondsSinceLastRx.toDouble(),
'isFlood': flood ? 1.0 : 0.0,
'isFlood': pathLength < 0 ? 1.0 : 0.0,
};
final row = _activeFeatures.map((f) => allFeatures[f]!).toList();
@@ -171,9 +164,7 @@ class TimeoutPredictionService extends ChangeNotifier {
// (ml_algo's OLS produces all-zero coefficients for singular matrices)
final allNames = ['pathLength', 'messageBytes', 'secSinceRx', 'isFlood'];
final allExtractors = <double Function(DeliveryObservation)>[
// pathLength is already clamped to >=0 in recordObservation, but guard
// here as well for any observations loaded from older persisted data.
(o) => o.pathLength < 0 ? 0.0 : o.pathLength.toDouble(),
(o) => o.pathLength.toDouble(),
(o) => o.messageBytes.toDouble(),
(o) => o.secondsSinceLastRx.toDouble(),
(o) => o.isFlood ? 1.0 : 0.0,
@@ -224,9 +215,6 @@ class TimeoutPredictionService extends ChangeNotifier {
@override
void dispose() {
if (_persistTimer?.isActive == true) {
_storage?.saveDeliveryObservations(_observations);
}
_persistTimer?.cancel();
super.dispose();
}
+79 -69
View File
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:llamadart/llamadart.dart';
import 'package:flutter_langdetect/flutter_langdetect.dart';
import '../models/app_settings.dart';
import '../models/translation_support.dart';
@@ -42,12 +41,8 @@ class TranslationService extends ChangeNotifier {
TranslationService(
this._appSettingsService, {
TranslationFileStore? fileStore,
}) : _fileStore = fileStore ?? TranslationFileStore() {
// Initialize langdetect once at service construction.
_langDetectInit = initLangDetect();
}
}) : _fileStore = fileStore ?? TranslationFileStore();
bool _disposed = false;
bool _isBusy = false;
bool _isDownloading = false;
bool _cancelDownloadRequested = false;
@@ -56,7 +51,6 @@ class TranslationService extends ChangeNotifier {
LlamaEngine? _engine;
String? _loadedModelPath;
String? _failedModelPath;
Future<void>? _langDetectInit;
int _downloadedBytes = 0;
int? _downloadTotalBytes;
String? _downloadFileName;
@@ -90,22 +84,7 @@ class TranslationService extends ChangeNotifier {
'en';
}
bool shouldAutoTranslateIncoming({
required String text,
required bool isCli,
required bool isOutgoing,
}) {
if (!_settings.autoTranslateIncomingMessages) {
return false;
}
return canTranslateIncoming(
text: text,
isCli: isCli,
isOutgoing: isOutgoing,
);
}
bool canTranslateIncoming({
bool shouldTranslateIncoming({
required String text,
required bool isCli,
required bool isOutgoing,
@@ -216,7 +195,7 @@ class TranslationService extends ChangeNotifier {
}
_downloadTotalBytes = totalSize;
_notify();
notifyListeners();
DownloadedModelFile downloaded;
if (supportsRange &&
@@ -269,7 +248,7 @@ class TranslationService extends ChangeNotifier {
throw StateError('Model download failed: HTTP ${response.statusCode}');
}
_downloadTotalBytes ??= response.contentLength;
_notify();
notifyListeners();
final trackedStream = _trackDownloadProgress(response.stream);
return await _fileStore.writeModelBytes(
fileName: fileName,
@@ -314,7 +293,7 @@ class TranslationService extends ChangeNotifier {
throw const TranslationDownloadCancelled();
}
_downloadFileName = 'Merging chunks...';
_notify();
notifyListeners();
combineReached = true;
return await _fileStore.combineChunks(
fileName: fileName,
@@ -362,7 +341,7 @@ class TranslationService extends ChangeNotifier {
}
_cancelDownloadRequested = true;
_lastError = 'Download stopped.';
_notify();
notifyListeners();
}
Future<void> removeModel(TranslationModelRecord model) async {
@@ -389,9 +368,7 @@ class TranslationService extends ChangeNotifier {
if (targetLanguageCode == null || !_isPlainTextEligible(text)) {
return null;
}
final detectedLanguageCode = await detectLanguage(
_stripReplyInfoForDetection(text),
);
final detectedLanguageCode = await detectLanguage(text);
if (detectedLanguageCode != null &&
detectedLanguageCode == targetLanguageCode) {
return const TranslationResult(
@@ -432,9 +409,7 @@ class TranslationService extends ChangeNotifier {
if (targetLanguageCode == null || !_isPlainTextEligible(text)) {
return null;
}
final detectedLanguageCode = await detectLanguage(
_stripReplyInfoForDetection(text),
);
final detectedLanguageCode = await detectLanguage(text);
if (detectedLanguageCode != null &&
detectedLanguageCode == targetLanguageCode) {
return const TranslationResult(
@@ -461,26 +436,7 @@ class TranslationService extends ChangeNotifier {
}
Future<String?> detectLanguage(String text) async {
try {
// Ensure the detector is initialized (constructor starts init).
await (_langDetectInit ??= initLangDetect());
final code = detect(text);
if (code.isEmpty) return null;
return code;
} catch (error) {
_lastError = error.toString();
appLogger.warn('Language detection failed: $error');
_notify();
return null;
}
}
String _stripReplyInfoForDetection(String text) {
final match = RegExp(
r'@\[([^\]]+)\]\s+(.+)$',
dotAll: true,
).firstMatch(text);
return match?.group(2) ?? text;
return _heuristicLanguageCode(text);
}
Future<String?> _translateText({
@@ -539,7 +495,7 @@ class TranslationService extends ChangeNotifier {
} catch (error) {
_lastError = error.toString();
appLogger.warn('Translation request failed: $error');
_notify();
notifyListeners();
return null;
}
}
@@ -562,6 +518,72 @@ class TranslationService extends ChangeNotifier {
trimmed.startsWith('r:'));
}
String? _heuristicLanguageCode(String text) {
final trimmed = text.trim();
if (trimmed.isEmpty) {
return null;
}
if (RegExp(r'[ぁ-んァ-ン]').hasMatch(text)) {
return 'ja';
}
if (RegExp(r'[가-힣]').hasMatch(text)) {
return 'ko';
}
if (RegExp(r'[\u4e00-\u9fff]').hasMatch(text)) {
return 'zh';
}
final lower = trimmed.toLowerCase();
final patterns = <String, String>{
'uk': r'\b(привіт|дякую|будь|ласка|як|де|не|так|це|є|най|ще|може|для)\b',
'ru':
r'\b(что|это|как|не|да|нет|он|она|они|быть|есть|для|сегодня|если|уже|может)\b',
'bg': r'\b(ще|няма|благодаря|моля|това|какво|тук|ние|вие|не|със|за)\b',
'de':
r'\b(der|die|das|und|ist|nicht|ein|eine|ich|für|mit|auf|zu|auch|als|an|im|am|es|dem|den|sich|von)\b',
'en':
r'\b(the|and|is|you|for|with|from|not|that|this|have|be|are|was|were|but|can|will|your|what|when|how|they)\b',
'es':
r'\b(el|la|los|las|es|que|de|en|con|por|para|no|un|una|se|como|su|al|del|está)\b',
'fr':
r'\b(le|la|les|un|une|et|est|que|qui|pour|dans|pas|avec|sur|ne|vous|il|elle|des|ce|cette|je|tu|nous|vous)\b',
'it':
r'\b(il|la|lo|un|una|che|di|da|in|per|con|non|si|mi|ti|noi|voi|lui|lei)\b',
'pt':
r'\b(os|as|que|de|do|da|em|para|com|por|não|uma|um|se|você|também)\b',
'nl':
r'\b(de|het|een|en|is|niet|dat|wat|je|ik|op|aan|voor|met|als|nog|zijn)\b',
'sv':
r'\b(och|är|det|att|som|en|på|inte|har|var|men|du|jag|vi|ni|den|detta)\b',
'pl':
r'\b(na|się|nie|jest|to|że|do|od|dla|czy|tak|ale|ma|jak|on|ona|my)\b',
'sk': r'\b(je|na|so|že|do|od|za|si|to|ten|tá|tí|ako|má|nie|som|sa)\b',
'sl': r'\b(in|je|na|se|da|za|od|ne|to|ta|so|kako|bo|sem|si)\b',
'hu':
r'\b(az|és|nem|van|volt|hogy|mit|mire|ki|mi|ez|azért|is|de|ha|te|ő|mi|itt)\b',
};
final scores = <String, int>{};
for (final entry in patterns.entries) {
scores[entry.key] = RegExp(
entry.value,
caseSensitive: false,
).allMatches(lower).length;
}
final sorted = scores.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
if (sorted.isEmpty || sorted.first.value == 0) {
return null;
}
if (sorted.length > 1 && sorted.first.value == sorted[1].value) {
return null;
}
return sorted.first.key;
}
String _languageLabel(String code) {
for (final option in supportedTranslationLanguages) {
if (option.code == code) {
@@ -632,10 +654,6 @@ class TranslationService extends ChangeNotifier {
final completer = Completer<T>();
_setBusy(true);
_queue = _queue.then((_) async {
if (_disposed) {
completer.completeError(StateError('TranslationService disposed.'));
return;
}
try {
completer.complete(await action());
} catch (error, stackTrace) {
@@ -653,24 +671,17 @@ class TranslationService extends ChangeNotifier {
throw const TranslationDownloadCancelled();
}
_downloadedBytes += chunk.length;
_notify();
notifyListeners();
yield chunk;
}
}
void _notify() {
if (_disposed) {
return;
}
notifyListeners();
}
void _setBusy(bool value) {
if (_isBusy == value) {
return;
}
_isBusy = value;
_notify();
notifyListeners();
}
void _setDownloading(bool value) {
@@ -681,12 +692,11 @@ class TranslationService extends ChangeNotifier {
_downloadTotalBytes = null;
_downloadFileName = null;
}
_notify();
notifyListeners();
}
@override
void dispose() {
_disposed = true;
final engine = _engine;
_engine = null;
_loadedModelPath = null;
@@ -33,14 +33,12 @@ class UsbSerialService {
String? _connectedPortLabel;
FlSerial? _serial;
AppDebugLogService? _debugLogService;
Object? _lastError;
UsbSerialStatus get status => _status;
String? get activePortKey => _connectedPortKey;
String? get activePortDisplayLabel =>
_connectedPortLabel ?? _connectedPortKey;
Stream<Uint8List> get frameStream => _frameController.stream;
Object? get lastError => _lastError;
bool get _useAndroidUsbHost =>
!kIsWeb && defaultTargetPlatform == TargetPlatform.android;
bool get _useDesktopFlSerial =>
@@ -436,7 +434,6 @@ class UsbSerialService {
}
void _addFrameError(Object error, [StackTrace? stackTrace]) {
_lastError = error;
if (_frameController.isClosed) {
return;
}
+11 -48
View File
@@ -15,18 +15,6 @@ class UsbSerialService {
static const Map<String, String> _knownUsbNames = <String, String>{
'2886:1667': 'Seeed Wio Tracker L1',
};
/// USB-to-UART bridge chips whose hardware auto-reset circuit requires DTR
/// to be held asserted after open (otherwise the MCU resets). Native-USB-CDC
/// boards (nRF52840/Adafruit 0x239A, Espressif native 0x303A, Seeed 0x2886)
/// tie DTR to the bootloader/reset line, so asserting it re-enumerates and
/// drops the device ("The device has been lost"); they must be left alone.
static const Set<int> _uartBridgeVendorIds = <int>{
0x10C4, // Silicon Labs CP210x
0x1A86, // QinHeng CH340 / CH9102
0x0403, // FTDI
0x067B, // Prolific PL2303
};
static final Map<String, String> _deviceNamesByPortKey = <String, String>{};
static final Map<String, String> _baseLabelsByPortKey = <String, String>{};
static final Map<String, JSObject> _authorizedPortsByKey =
@@ -46,14 +34,12 @@ class UsbSerialService {
String _requestPortLabel = 'Choose USB Device';
String _fallbackDeviceName = 'Web Serial Device';
AppDebugLogService? _debugLogService;
Object? _lastError;
UsbSerialStatus get status => _status;
String? get activePortKey => _connectedPortKey;
String? get activePortDisplayLabel => _connectedPortName ?? _connectedPortKey;
Stream<Uint8List> get frameStream => _frameController.stream;
bool get isConnected => _status == UsbSerialStatus.connected;
Object? get lastError => _lastError;
JSObject get _navigator => JSObject.fromInteropObject(web.window.navigator);
bool get _isSupported => _navigator.has('serial');
@@ -88,7 +74,6 @@ class UsbSerialService {
}
_status = UsbSerialStatus.connecting;
_lastError = null;
_frameDecoder.reset();
try {
@@ -297,30 +282,16 @@ class UsbSerialService {
..['flowControl'] = 'none'.toJS;
await port.callMethod<JSPromise<JSAny?>>('open'.toJS, options).toDart;
// Only UART-bridge chips (CP210x/CH340/FTDI/PL2303) need DTR held high to
// avoid the auto-reset circuit firing on open. Native-USB-CDC boards
// (e.g. nRF52840/Adafruit) tie DTR to the reset line toggling it there
// re-enumerates the device and Web Serial reports "The device has been
// lost". Leave their signals untouched.
final vendorId = _portInfo(port)?.usbVendorId;
final isUartBridge =
vendorId != null && _uartBridgeVendorIds.contains(vendorId);
_debugLogService?.info(
'Open: vendorId=${vendorId == null ? 'unknown' : '0x${vendorId.toRadixString(16)}'} '
'uartBridge=$isUartBridge (DTR ${isUartBridge ? 'asserted' : 'left default'})',
tag: 'USB Serial',
);
if (isUartBridge) {
try {
final signals = JSObject()
..['dataTerminalReady'] = true.toJS
..['requestToSend'] = false.toJS;
await port
.callMethod<JSPromise<JSAny?>>('setSignals'.toJS, signals)
.toDart;
} catch (_) {
// setSignals may not be supported on all browsers/devices.
}
// Prevent ESP32 USB-CDC reset: hold DTR=true, RTS=false after open.
try {
final signals = JSObject()
..['dataTerminalReady'] = true.toJS
..['requestToSend'] = false.toJS;
await port
.callMethod<JSPromise<JSAny?>>('setSignals'.toJS, signals)
.toDart;
} catch (_) {
// setSignals may not be supported on all browsers/devices.
}
}
@@ -413,21 +384,13 @@ class UsbSerialService {
} catch (error, stackTrace) {
_debugLogService?.error('_pumpReads error: $error', tag: 'USB Serial');
if (_status == UsbSerialStatus.connected) {
// The transport is dead reflect that in status immediately so a
// concurrent connect handshake fails fast instead of waiting for a
// SELF_INFO that can never arrive.
_status = UsbSerialStatus.disconnected;
_lastError = error;
_addFrameError(error, stackTrace);
}
} finally {
_debugLogService?.info('_pumpReads: ended', tag: 'USB Serial');
_releaseLock(reader);
if (_status == UsbSerialStatus.connected && identical(reader, _reader)) {
_status = UsbSerialStatus.disconnected;
final closedError = StateError('USB serial connection closed');
_lastError = closedError;
_addFrameError(closedError);
_addFrameError(StateError('USB serial connection closed'));
}
}
}
+10 -1
View File
@@ -42,9 +42,18 @@ class ChannelStore {
try {
final jsonList = jsonDecode(jsonString) as List<dynamic>;
return jsonList
final channels = jsonList
.map((entry) => _fromJson(entry as Map<String, dynamic>))
.toList();
// Deduplicate: keep the last entry per channel index
final seen = <int>{};
final deduped = <Channel>[];
for (final channel in channels.reversed) {
if (seen.add(channel.index)) {
deduped.add(channel);
}
}
return deduped.reversed.toList();
} catch (_) {
return [];
}
+69 -238
View File
@@ -1,113 +1,70 @@
import 'package:flutter/cupertino.dart' show CupertinoPageTransitionsBuilder;
import 'package:flutter/material.dart';
/// MeshCore palette high-contrast slate surfaces with sky-blue accents.
/// MeshCore redesign palette warm field-journal dark theme with
/// phosphor-green signal accents. Mirrors values from the redesign spec.
class MeshPalette {
MeshPalette._();
// Surfaces shared with the map overlays and navigation.
static const bg = Color(0xFF0B1220);
static const bg1 = Color(0xFF0F172A);
static const bg2 = Color(0xFF162033);
static const bg3 = Color(0xFF1E293B);
static const bg4 = Color(0xFF334155);
// Surfaces (warm near-black, olive undertone)
static const bg = Color(0xFF0F1412);
static const bg1 = Color(0xFF161C19);
static const bg2 = Color(0xFF1D2521);
static const bg3 = Color(0xFF28322D);
static const bg4 = Color(0xFF34403A);
// Lines
static const line = Color(0xFF1E293B);
static const line2 = Color(0xFF334155);
static const line3 = Color(0xFF475569);
static const line = Color(0xFF232C28);
static const line2 = Color(0xFF34403A);
static const line3 = Color(0xFF48564F);
// Ink
static const ink = Color(0xFFF8FAFC);
static const ink2 = Color(0xFFCBD5E1);
static const ink3 = Color(0xFF94A3B8);
static const ink4 = Color(0xFF64748B);
static const ink = Color(0xFFEFF3E8);
static const ink2 = Color(0xFFBAC4B5);
static const ink3 = Color(0xFF7C8B82);
static const ink4 = Color(0xFF55635B);
// Signal-quality green (used only for SNR coloring, not UI chrome)
static const signal = Color(0xFF22C55E);
static const signalDim = Color(0xFF16A34A);
// Signal (phosphor)
static const signal = Color(0xFF7BEFA8);
static const signalDim = Color(0xFF4DC580);
static const signalBg = Color(0x177BEFA8); // ~9% alpha
static const signalLine = Color(0x427BEFA8); // ~26%
static const signalGlow = Color(0x597BEFA8); // ~35%
// Warn
static const warn = Color(0xFFF59E0B);
static const warnDim = Color(0xFFD97706);
static const warnBg = Color(0x1FF59E0B);
static const warnLine = Color(0x66F59E0B);
// Warn (ember)
static const warn = Color(0xFFFFA552);
static const warnDim = Color(0xFFC27E3C);
static const warnBg = Color(0x1CFFA552);
static const warnLine = Color(0x4DFFA552);
// Alert
static const alert = Color(0xFFEF4444);
static const alertBg = Color(0x1FEF4444);
static const alertLine = Color(0x66EF4444);
// Alert (coral)
static const alert = Color(0xFFFF6A5C);
static const alertBg = Color(0x1CFF6A5C);
static const alertLine = Color(0x52FF6A5C);
// Blue primary map/app accent
static const blue = Color(0xFF0EA5E9);
static const blueDim = Color(0xFF0284C7);
static const blueBg = Color(0x290EA5E9);
static const blueLine = Color(0x800EA5E9);
// Blue (dusk sky)
static const blue = Color(0xFF7FCBF5);
static const blueBg = Color(0x1C7FCBF5);
static const blueLine = Color(0x477FCBF5);
// Magenta
static const magenta = Color(0xFFDE7FDB);
static const magentaBg = Color(0x1CDE7FDB);
static const magentaLine = Color(0x47DE7FDB);
// Me bubble (dusk blue)
static const me = Color(0xFF0C4A6E);
static const meBorder = Color(0xFF0369A1);
static const meInk = Color(0xFFF0F9FF);
// Me bubble (mossy)
static const me = Color(0xFF1E3527);
static const meBorder = Color(0xFF2D5039);
static const meInk = Color(0xFFDEF0DC);
// Light variant (used when user explicitly picks light theme)
static const lightBg = Color(0xFFF4F6F8);
static const lightBg1 = Color(0xFFEAEEF2);
static const lightBg2 = Color(0xFFDFE5EA);
static const lightLine = Color(0xFFC3CCD4);
static const lightInk = Color(0xFF10161B);
static const lightInk2 = Color(0xFF3C4853);
static const lightInk3 = Color(0xFF69767F);
static const lightBlue = Color(0xFF2F6EA8);
}
/// High-contrast semantic colors for UI rendered over variable map tiles.
class MapPalette {
MapPalette._();
static const online = Color(0xFF22C55E);
static const offline = Color(0xFF6B7280);
static const stale = Color(0xFFF59E0B);
static const repeater = Color(0xFF2563EB);
static const router = Color(0xFF7C3AED);
static const batteryLow = Color(0xFFEF4444);
static const cluster = Color(0xFFF97316);
static const selected = Color(0xFF0EA5E9);
static const sensor = Color(0xFF0F766E);
static const shared = Color(0xFF0369A1);
static const panelLight = Color(0xF0FFFFFF);
static const panelDark = Color(0xF50B1220);
static const textPrimary = Color(0xFFF8FAFC);
static const textSecondary = Color(0xFFCBD5E1);
static const textMuted = Color(0xFF94A3B8);
static const border = Color(0x5264758B);
static const markerOutline = Colors.white;
static const markerShadow = Color(0xB3000000);
}
/// High-contrast colors for line-of-sight maps and elevation profiles.
class LosPalette {
LosPalette._();
static const terrain = Color(0xFFA3E635);
static const beam = Color(0xFF38BDF8);
static const horizon = Color(0xFFFBBF24);
static const blocked = Color(0xFFEF4444);
static const marginal = Color(0xFFF59E0B);
static const clear = Color(0xFF22C55E);
static const selected = Color(0xFF0EA5E9);
static const chartBackground = Color(0xFF0B1220);
static const panelDark = Color(0xF00F172A);
static const panelLight = Color(0xF5FFFFFF);
static const text = Color(0xFFF8FAFC);
static const textMuted = Color(0xFFCBD5E1);
static const border = Color(0x5264758B);
static const shadow = Color(0x99000000);
static const lightBg = Color(0xFFF5F3EC);
static const lightBg1 = Color(0xFFECE9DF);
static const lightBg2 = Color(0xFFE2DED2);
static const lightLine = Color(0xFFCAC5B4);
static const lightInk = Color(0xFF0F1410);
static const lightInk2 = Color(0xFF3D463E);
static const lightInk3 = Color(0xFF6A756D);
static const lightSignal = Color(0xFF1A7A44);
}
/// Named font stacks Flutter falls back to system fonts when the named
@@ -118,7 +75,6 @@ class MeshFonts {
static const sans = 'Inter';
static const mono = 'JetBrains Mono';
static const display = 'Instrument Serif';
static const emoji = 'Noto Color Emoji';
static const List<String> sansFallback = [
'system-ui',
@@ -140,11 +96,6 @@ class MeshFonts {
'Times New Roman',
'serif',
];
static const List<String> emojiFallback = [
'Apple Color Emoji',
'Segoe UI Emoji',
'Noto Emoji',
];
}
/// Radii used consistently across the app.
@@ -164,22 +115,18 @@ class MeshTheme {
static ThemeData dark() {
const scheme = ColorScheme.dark(
primary: MeshPalette.blue,
onPrimary: Colors.white,
primaryContainer: Color(0xFF075985),
onPrimaryContainer: Colors.white,
secondary: MeshPalette.magenta,
onSecondary: Colors.white,
secondaryContainer: Color(0xFF331A33),
onSecondaryContainer: Colors.white,
tertiary: MeshPalette.warn,
onTertiary: Color(0xFF0B1220),
tertiaryContainer: Color(0xFF78350F),
onTertiaryContainer: Colors.white,
primary: MeshPalette.signal,
onPrimary: Color(0xFF0A1810),
primaryContainer: MeshPalette.signalBg,
onPrimaryContainer: MeshPalette.signal,
secondary: MeshPalette.blue,
onSecondary: Color(0xFF0A1520),
tertiary: MeshPalette.magenta,
onTertiary: Color(0xFF201020),
error: MeshPalette.alert,
onError: Colors.white,
errorContainer: Color(0xFF7F1D1D),
onErrorContainer: Colors.white,
onError: Color(0xFF1A0A08),
errorContainer: MeshPalette.alertBg,
onErrorContainer: MeshPalette.alert,
surface: MeshPalette.bg,
onSurface: MeshPalette.ink,
surfaceContainerLowest: MeshPalette.bg,
@@ -194,39 +141,33 @@ class MeshTheme {
scrim: Colors.black54,
inverseSurface: MeshPalette.ink,
onInverseSurface: MeshPalette.bg,
inversePrimary: MeshPalette.blueDim,
inversePrimary: MeshPalette.signalDim,
);
return _build(scheme, Brightness.dark);
}
static ThemeData light() {
const scheme = ColorScheme.light(
primary: MeshPalette.lightBlue,
primary: MeshPalette.lightSignal,
onPrimary: Colors.white,
primaryContainer: Color(0xFFD3E4F5),
onPrimaryContainer: Color(0xFF12354F),
secondary: Color(0xFF8C4A8A),
primaryContainer: Color(0xFFD4E8D8),
onPrimaryContainer: MeshPalette.lightSignal,
secondary: Color(0xFF2F6EA8),
onSecondary: Colors.white,
secondaryContainer: Color(0xFFEFD6EE),
onSecondaryContainer: Color(0xFF3D1A3C),
tertiary: Color(0xFF9A5B16),
tertiary: Color(0xFF8C4A8A),
onTertiary: Colors.white,
tertiaryContainer: Color(0xFFF8E3C9),
onTertiaryContainer: Color(0xFF4A2A05),
error: Color(0xFFB53D2F),
onError: Colors.white,
errorContainer: Color(0xFFF6D9D4),
onErrorContainer: Color(0xFF5C1A12),
surface: MeshPalette.lightBg,
onSurface: MeshPalette.lightInk,
surfaceContainerLowest: MeshPalette.lightBg,
surfaceContainerLow: MeshPalette.lightBg1,
surfaceContainer: MeshPalette.lightBg1,
surfaceContainerHigh: MeshPalette.lightBg2,
surfaceContainerHighest: Color(0xFFD2DAE1),
surfaceContainerHighest: Color(0xFFD5D0C0),
onSurfaceVariant: MeshPalette.lightInk2,
outline: MeshPalette.lightLine,
outlineVariant: Color(0xFFD8DEE5),
outlineVariant: Color(0xFFDBD6C6),
);
return _build(scheme, Brightness.light);
}
@@ -386,9 +327,9 @@ class MeshTheme {
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: scheme.surface,
backgroundColor: scheme.surfaceContainerLow,
surfaceTintColor: Colors.transparent,
indicatorColor: scheme.primary,
indicatorColor: scheme.primary.withValues(alpha: 0.14),
indicatorShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.md),
),
@@ -400,13 +341,13 @@ class MeshTheme {
fontSize: 10,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
letterSpacing: 0.1,
color: selected ? scheme.onPrimary : scheme.onSurfaceVariant,
color: selected ? scheme.primary : scheme.onSurfaceVariant,
);
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
final selected = states.contains(WidgetState.selected);
return IconThemeData(
color: selected ? scheme.onPrimary : scheme.onSurfaceVariant,
color: selected ? scheme.primary : scheme.onSurfaceVariant,
size: 22,
);
}),
@@ -445,106 +386,6 @@ class MeshTheme {
),
iconTheme: IconThemeData(color: scheme.onSurfaceVariant, size: 22),
splashFactory: InkSparkle.splashFactory,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: FadeForwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: FadeForwardsPageTransitionsBuilder(),
TargetPlatform.macOS: FadeForwardsPageTransitionsBuilder(),
TargetPlatform.windows: FadeForwardsPageTransitionsBuilder(),
},
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: SegmentedButton.styleFrom(
selectedBackgroundColor: scheme.primary.withValues(alpha: 0.16),
selectedForegroundColor: scheme.primary,
side: BorderSide(color: scheme.outlineVariant),
textStyle: const TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected)
? scheme.onPrimary
: scheme.onSurfaceVariant,
),
trackColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected)
? scheme.primary
: scheme.surfaceContainerHighest,
),
trackOutlineColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected)
? Colors.transparent
: scheme.outline,
),
),
sliderTheme: SliderThemeData(
activeTrackColor: scheme.primary,
inactiveTrackColor: scheme.surfaceContainerHighest,
thumbColor: scheme.primary,
overlayColor: scheme.primary.withValues(alpha: 0.12),
valueIndicatorColor: scheme.surfaceContainerHighest,
valueIndicatorTextStyle: TextStyle(
fontFamily: MeshFonts.mono,
fontFamilyFallback: MeshFonts.monoFallback,
color: scheme.onSurface,
fontSize: 12,
),
trackHeight: 3,
),
tabBarTheme: TabBarThemeData(
labelColor: scheme.primary,
unselectedLabelColor: scheme.onSurfaceVariant,
indicatorColor: scheme.primary,
dividerColor: scheme.outlineVariant,
labelStyle: const TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontSize: 13.5,
fontWeight: FontWeight.w700,
),
unselectedLabelStyle: const TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontSize: 13.5,
fontWeight: FontWeight.w500,
),
),
progressIndicatorTheme: ProgressIndicatorThemeData(
color: scheme.primary,
linearTrackColor: scheme.surfaceContainerHigh,
circularTrackColor: Colors.transparent,
),
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(MeshRadii.sm),
border: Border.all(color: scheme.outline),
),
textStyle: TextStyle(color: scheme.onSurface, fontSize: 12),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: scheme.primary,
foregroundColor: scheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(MeshRadii.pill),
),
textStyle: const TextStyle(
fontFamily: MeshFonts.sans,
fontFamilyFallback: MeshFonts.sansFallback,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
);
}
@@ -595,16 +436,6 @@ class MeshTheme {
);
}
/// Color-emoji style with platform fallbacks and stable vertical metrics.
static TextStyle emoji({double fontSize = 28}) {
return TextStyle(
fontFamily: MeshFonts.emoji,
fontFamilyFallback: MeshFonts.emojiFallback,
fontSize: fontSize,
height: 1,
);
}
/// Color-code an SNR value for consistency across the app.
static Color snrColor(num? snr, {required bool blocked}) {
if (blocked) return MeshPalette.alert;
+15
View File
@@ -31,6 +31,21 @@ Future<bool> showDisconnectDialog(
if (confirmed == true) {
appLogger.info('Disconnect confirmed from popup', tag: 'Connection');
await connector.disconnect();
if (context.mounted) {
await showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: Text(context.l10n.dialog_disconnectedTitle),
content: Text(context.l10n.dialog_disconnectedMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.l10n.common_ok),
),
],
),
);
}
return true;
}
return false;
@@ -1,20 +0,0 @@
import 'package:flutter/material.dart';
import '../connector/meshcore_connector.dart';
/// Mixin that automatically navigates back to scanner when disconnected.
/// Use in State classes for screens that require active connection.
mixin DisconnectNavigationMixin<T extends StatefulWidget> on State<T> {
/// Call this in your Widget build method to enable auto-navigation.
/// Returns true if still connected, false if navigation was triggered.
bool checkConnectionAndNavigate(MeshCoreConnector connector) {
if (!connector.isConnected) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
}
});
return false;
}
return true;
}
}
+2 -1
View File
@@ -69,7 +69,8 @@ class AppBarTitle extends StatelessWidget {
if (showBattery) BatteryIndicator(connector: connector),
if (showSnr) SNRIndicator(connector: connector),
if (connector.supportsCompanionRadioStats)
const RadioStatsIconButton(compact: true),
if (connector.isConnected)
const RadioStatsIconButton(compact: true),
],
),
trailing ?? const SizedBox.shrink(),
+11 -12
View File
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import '../connector/meshcore_connector.dart';
import '../theme/mesh_theme.dart';
class BatteryUi {
final IconData icon;
@@ -11,19 +10,19 @@ class BatteryUi {
BatteryUi batteryUiForPercent(int? percent) {
if (percent == null) {
return const BatteryUi(Icons.battery_unknown, null);
return const BatteryUi(Icons.battery_unknown, Colors.grey);
}
final p = percent.clamp(0, 100);
return switch (p) {
<= 5 => const BatteryUi(Icons.battery_alert, MeshPalette.alert),
<= 15 => const BatteryUi(Icons.battery_0_bar, MeshPalette.alert),
<= 30 => const BatteryUi(Icons.battery_1_bar, MeshPalette.warn),
<= 45 => const BatteryUi(Icons.battery_2_bar, MeshPalette.warn),
<= 60 => const BatteryUi(Icons.battery_3_bar, null),
<= 80 => const BatteryUi(Icons.battery_5_bar, null),
_ => const BatteryUi(Icons.battery_full, MeshPalette.signal),
<= 5 => const BatteryUi(Icons.battery_alert, Colors.redAccent),
<= 15 => const BatteryUi(Icons.battery_0_bar, Colors.redAccent),
<= 30 => const BatteryUi(Icons.battery_1_bar, Colors.orange),
<= 45 => const BatteryUi(Icons.battery_2_bar, Colors.amber),
<= 60 => const BatteryUi(Icons.battery_3_bar, Colors.lightGreen),
<= 80 => const BatteryUi(Icons.battery_5_bar, Colors.green),
_ => const BatteryUi(Icons.battery_full, Colors.green),
};
}
@@ -77,9 +76,9 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
Flexible(
child: Text(
displayText,
style: MeshTheme.mono(
fontSize: 11,
fontWeight: FontWeight.w600,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: batteryUi.color,
),
maxLines: 1,
+3 -5
View File
@@ -86,7 +86,7 @@ class ByteCountedTextField extends StatelessWidget {
final counterColor = ratio > errorThreshold
? Theme.of(context).colorScheme.error
: ratio > warningThreshold
? Theme.of(context).colorScheme.tertiary
? Colors.orange
: Theme.of(context).colorScheme.onSurfaceVariant;
return Column(
@@ -118,9 +118,8 @@ class ByteCountedTextField extends StatelessWidget {
textInputAction: textInputAction,
onSubmitted: onSubmitted,
),
Opacity(
opacity: showCounter ? 1 : 0,
child: Padding(
if (showCounter)
Padding(
padding: const EdgeInsets.only(top: 4, right: 4),
child: Align(
alignment: Alignment.centerRight,
@@ -130,7 +129,6 @@ class ByteCountedTextField extends StatelessWidget {
),
),
),
),
],
);
},
+28 -85
View File
@@ -1,27 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import '../l10n/l10n.dart';
import '../theme/mesh_theme.dart';
import 'mesh_ui.dart';
import 'signal_ui.dart';
/// A MeshCard-based row for displaying a scanned BLE device.
/// Shows an AvatarCircle (router icon, deterministic hue from device name),
/// device name, mono MAC address, mono RSSI dBm, and SignalBars on the right.
/// While connecting, shows a small progress ring instead of signal bars.
/// A reusable tile widget for displaying a MeshCore device in a list
class DeviceTile extends StatelessWidget {
final ScanResult scanResult;
final VoidCallback? onTap;
final bool isConnecting;
final VoidCallback onTap;
const DeviceTile({
super.key,
required this.scanResult,
required this.onTap,
this.isConnecting = false,
});
const DeviceTile({super.key, required this.scanResult, required this.onTap});
@override
Widget build(BuildContext context) {
@@ -30,12 +17,23 @@ class DeviceTile extends StatelessWidget {
final name = device.platformName.isNotEmpty
? device.platformName
: scanResult.advertisementData.advName;
final displayName = name.isNotEmpty
? name
: context.l10n.common_unknownDevice;
final mac = device.remoteId.toString();
final scheme = Theme.of(context).colorScheme;
return ListTile(
leading: _buildSignalIcon(rssi),
title: Text(
name.isNotEmpty ? name : context.l10n.common_unknownDevice,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text(device.remoteId.toString()),
trailing: ElevatedButton(
onPressed: onTap,
child: Text(context.l10n.common_connect),
),
onTap: onTap,
);
}
Widget _buildSignalIcon(int rssi) {
final tier = rssi >= -60
? 0
: rssi >= -70
@@ -47,70 +45,15 @@ class DeviceTile extends StatelessWidget {
: 4;
final signalUi = signalUiForStrengthTier(tier);
return MeshCard(
onTap: onTap == null
? null
: () {
HapticFeedback.selectionClick();
onTap!();
},
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
child: Row(
children: [
AvatarCircle(name: displayName, size: 42, icon: Icons.router),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
displayName,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: scheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 3),
Text(
mac,
style: MeshTheme.mono(
fontSize: 11,
color: scheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 12),
if (isConnecting)
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: scheme.primary,
),
)
else
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Icon(signalUi.icon, size: 16, color: signalUi.color),
const SizedBox(height: 3),
Text(
'$rssi dBm',
style: MeshTheme.mono(fontSize: 10, color: signalUi.color),
),
],
),
],
),
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(signalUi.icon, color: signalUi.color),
Text(
'$rssi dBm',
style: TextStyle(fontSize: 10, color: signalUi.color),
),
],
);
}
}
+23 -57
View File
@@ -29,65 +29,31 @@ class FeatureToggleRow extends StatefulWidget {
class _FeatureToggleRow extends State<FeatureToggleRow> {
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.title,
style: textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
widget.subtitle,
style: textTheme.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
],
),
return Row(
children: [
Expanded(
child: SwitchListTile(
title: Text(widget.title),
subtitle: Text(widget.subtitle),
value: widget.value,
onChanged: widget.onChanged,
contentPadding: EdgeInsets.zero,
),
const SizedBox(width: 8),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Switch(value: widget.value, onChanged: widget.onChanged),
if (widget.hasRefreshing) ...[
const SizedBox(width: 4),
widget.isRefreshing
? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 1.8,
color: scheme.primary,
),
)
: IconButton(
icon: const Icon(Icons.refresh, size: 18),
onPressed: widget.onRefresh,
tooltip: widget.refreshTooltip,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
),
],
],
),
if (widget.hasRefreshing)
IconButton(
icon: widget.isRefreshing
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.refresh, size: 20),
onPressed: widget.isRefreshing ? null : widget.onRefresh,
tooltip: widget.refreshTooltip,
visualDensity: VisualDensity.compact,
),
],
),
],
);
}
}
+2 -12
View File
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import '../l10n/app_localizations.dart';
import '../l10n/l10n.dart';
import '../theme/mesh_theme.dart';
class EmojiPicker extends StatelessWidget {
final Function(String) onEmojiSelected;
@@ -258,11 +257,7 @@ class EmojiPicker extends StatelessWidget {
),
child: Text(
emoji,
style: MeshTheme.emoji(),
textHeightBehavior: const TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
),
style: const TextStyle(fontSize: 28),
),
),
),
@@ -303,12 +298,7 @@ class EmojiPicker extends StatelessWidget {
child: Center(
child: Text(
emojis[index],
style: MeshTheme.emoji(),
textHeightBehavior:
const TextHeightBehavior(
applyHeightToFirstAscent: false,
applyHeightToLastDescent: false,
),
style: const TextStyle(fontSize: 28),
),
),
),
+17 -91
View File
@@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
/// A centered empty state display with icon, title, and optional subtitle/action.
/// Features a tinted icon circle, fade+slide entrance animation, and clear
/// typography hierarchy using the MeshCore design system.
class EmptyState extends StatefulWidget {
class EmptyState extends StatelessWidget {
final IconData icon;
final String title;
final String? subtitle;
@@ -17,97 +15,25 @@ class EmptyState extends StatefulWidget {
this.action,
});
@override
State<EmptyState> createState() => _EmptyStateState();
}
class _EmptyStateState extends State<EmptyState>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 420),
);
late final CurvedAnimation _curve = CurvedAnimation(
parent: _controller,
curve: Curves.easeOutCubic,
);
@override
void initState() {
super.initState();
_controller.forward();
}
@override
void dispose() {
_curve.dispose();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return FadeTransition(
opacity: _curve,
child: SlideTransition(
position: Tween(
begin: const Offset(0, 0.06),
end: Offset.zero,
).animate(_curve),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: scheme.primary.withValues(alpha: 0.08),
border: Border.all(
color: scheme.primary.withValues(alpha: 0.18),
width: 1.5,
),
),
child: Icon(
widget.icon,
size: 36,
color: scheme.onSurfaceVariant,
),
),
const SizedBox(height: 20),
Text(
widget.title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
color: scheme.onSurface,
letterSpacing: -0.1,
),
textAlign: TextAlign.center,
),
if (widget.subtitle != null) ...[
const SizedBox(height: 8),
Text(
widget.subtitle!,
style: TextStyle(
fontSize: 13.5,
color: scheme.onSurfaceVariant,
height: 1.45,
),
textAlign: TextAlign.center,
),
],
if (widget.action != null) ...[
const SizedBox(height: 28),
widget.action!,
],
],
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(title, style: TextStyle(fontSize: 16, color: Colors.grey[600])),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle!,
style: TextStyle(fontSize: 14, color: Colors.grey[500]),
textAlign: TextAlign.center,
),
),
),
],
if (action != null) ...[const SizedBox(height: 24), action!],
],
),
);
}
+5 -22
View File
@@ -180,10 +180,7 @@ class _GifPickerState extends State<GifPicker> {
const SizedBox(height: 8),
Text(
context.l10n.gifPicker_poweredBy,
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
),
],
),
@@ -200,18 +197,11 @@ class _GifPickerState extends State<GifPicker> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
Icon(Icons.error_outline, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
_error!,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
const SizedBox(height: 16),
ElevatedButton.icon(
@@ -229,18 +219,11 @@ class _GifPickerState extends State<GifPicker> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off,
size: 64,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
Icon(Icons.search_off, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
context.l10n.gifPicker_noGifsFound,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
],
),
+3 -30
View File
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import '../helpers/chat_scroll_controller.dart';
import '../theme/mesh_theme.dart';
class JumpToBottomButton extends StatelessWidget {
final ChatScrollController scrollController;
@@ -10,7 +8,6 @@ class JumpToBottomButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return ValueListenableBuilder<bool>(
valueListenable: scrollController.showJumpToBottom,
builder: (context, show, _) {
@@ -18,33 +15,9 @@ class JumpToBottomButton extends StatelessWidget {
return Positioned(
right: 16,
bottom: 16,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: scrollController.jumpToBottom,
borderRadius: BorderRadius.circular(MeshRadii.pill),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: scheme.surfaceContainerHigh.withValues(alpha: 0.92),
border: Border.all(color: scheme.outlineVariant, width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.18),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Icon(
Icons.keyboard_arrow_down,
size: 22,
color: scheme.primary,
),
),
),
child: FloatingActionButton.small(
onPressed: scrollController.jumpToBottom,
child: const Icon(Icons.keyboard_arrow_down),
),
);
},

Some files were not shown because too many files have changed in this diff Show More