diff --git a/README.md b/README.md
index 2acb390..984e6ba 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,10 @@ Open-source Flutter client for MeshCore LoRa mesh networking devices.
MeshCore Open is a cross-platform mobile application for communicating with MeshCore LoRa mesh network devices via Bluetooth Low Energy (BLE). The app enables long-range, off-grid communication through peer-to-peer messaging, public channels, and mesh networking capabilities.
+
+
+
+
## Screenshots
@@ -21,6 +25,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
## Features
### Core Functionality
+
- **Direct Messaging**: Private encrypted conversations with individual contacts
- **Public Channels**: Broadcast messages to channel subscribers on the mesh network
- **Contact Management**: Organize contacts, track last seen times, and manage conversation history
@@ -29,6 +34,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **Message Replies**: Thread conversations with inline reply functionality
### Mesh Network
+
- **Path Visualization**: View routing paths and signal quality for each contact
- **Route Management**: Manual path overriding and automatic route rotation
- **Signal Metrics**: Real-time SNR (Signal-to-Noise Ratio) tracking
@@ -36,6 +42,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **Repeater Support**: Connect to and manage repeater nodes for extended range
### Map & Location
+
- **Live Map View**: Real-time visualization of mesh network nodes on an interactive map
- **Node Filtering**: Filter by node type (chat, repeater, sensor) and time range
- **Location Sharing**: Share GPS coordinates and custom markers with contacts
@@ -43,12 +50,14 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **MGRS Coordinates**: Support for Military Grid Reference System coordinate format
### Device Management
+
- **BLE Connection**: Scan and connect to MeshCore devices via Bluetooth
- **Device Settings**: Configure radio parameters, power settings, and network options
- **Battery Monitoring**: Real-time battery status with chemistry-specific voltage curves
- **Firmware Updates**: Over-the-air firmware updates via BLE (coming soon)
### Repeater Hub
+
- **CLI Access**: Full command-line interface to repeater nodes
- **Settings Management**: Configure repeater behavior, power limits, and network settings
- **Statistics Dashboard**: View repeater traffic, connected clients, and system health
@@ -57,6 +66,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
## Technical Details
### Architecture
+
- **Framework**: Flutter 3.38.5 / Dart 3.10.4
- **State Management**: Provider pattern with ChangeNotifier
- **BLE Protocol**: Nordic UART Service (NUS) over Bluetooth Low Energy
@@ -64,11 +74,13 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
- **Encryption**: End-to-end encryption for private messages using the MeshCore protocol
### Platform Support
+
- ✅ **Android**: Full support (API 21+)
- ✅ **iOS**: Full support (iOS 12+)
- 🚧 **Desktop**: Limited support (macOS/Linux/Windows)
### Dependencies
+
| Package | Purpose |
|---------|---------|
| flutter_blue_plus | Bluetooth Low Energy communication |
@@ -84,6 +96,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
## Getting Started
### Prerequisites
+
- Flutter SDK 3.38.5 or later
- Android Studio / Xcode (for mobile development)
- A MeshCore-compatible LoRa device
@@ -91,17 +104,20 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
### Installation
1. **Clone the repository**
+
```bash
git clone https://github.com/zjs81/meshcore-open.git
cd meshcore-open
```
2. **Install dependencies**
+
```bash
flutter pub get
```
3. **Run the app**
+
```bash
flutter run
```
@@ -109,11 +125,13 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
### Building for Release
**Android APK:**
+
```bash
flutter build apk --release
```
**iOS:**
+
```bash
flutter build ios --release
```
@@ -152,25 +170,30 @@ lib/
## BLE Protocol
### Nordic UART Service (NUS)
+
- **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e`
- **RX Characteristic**: `6e400002-b5a3-f393-e0a9-e50e24dcca9e` (Write to device)
- **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device)
### Device Discovery
+
Devices are discovered by scanning for BLE advertisements with the name prefix `MeshCore-`
### Message Format
+
Messages are transmitted as binary frames using a custom protocol optimized for LoRa transmission. See `meshcore_protocol.dart` for frame structure definitions.
## Configuration
### App Settings
+
- **Theme**: System default, light, or dark mode
- **Notifications**: Configurable for messages, channels, and node advertisements
- **Battery Chemistry**: Support for NMC, LiFePO4, and LiPo battery types
- **Message Retry**: Automatic retry with configurable path clearing
### Device Settings
+
- **Radio Power**: Transmit power adjustment (10-30 dBm)
- **Frequency**: LoRa frequency configuration
- **Bandwidth**: Channel bandwidth selection
@@ -182,22 +205,23 @@ Messages are transmitted as binary frames using a custom protocol optimized for
This is an open-source project. Contributions are welcome!
### Development Guidelines
+
- Follow the Flutter style guide
- Use Material 3 design components
- Write clear commit messages
- Test on both Android and iOS before submitting PRs
### Code Style
+
- Prefer `StatelessWidget` with `Consumer` for reactive UI
- Use `const` constructors where possible
- Keep functions small and focused
- Avoid premature abstractions
-
## Support
For issues, questions, or feature requests, please open an issue on GitHub:
-https://github.com/zjs81/meshcore-open/issues
+
## Donate
diff --git a/assets/badges/badge_obtainium.png b/assets/badges/badge_obtainium.png
new file mode 100644
index 0000000..cc3a0ed
Binary files /dev/null and b/assets/badges/badge_obtainium.png differ
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index aef2502..cf8bbca 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -57,9 +57,6 @@ PODS:
- nanopb/encode (3.30910.0)
- package_info_plus (0.4.5):
- Flutter
- - path_provider_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- PromisesObjC (2.4.0)
- shared_preferences_foundation (0.0.1):
- Flutter
@@ -79,7 +76,6 @@ DEPENDENCIES:
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@@ -112,8 +108,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/mobile_scanner/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
- path_provider_foundation:
- :path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
@@ -140,7 +134,6 @@ SPEC CHECKSUMS:
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
- path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart
index 1bab130..3a36a92 100644
--- a/lib/connector/meshcore_connector.dart
+++ b/lib/connector/meshcore_connector.dart
@@ -706,7 +706,7 @@ class MeshCoreConnector extends ChangeNotifier {
try {
_connectionSubscription = device.connectionState.listen((state) {
- if (state == BluetoothConnectionState.disconnected) {
+ if (state == BluetoothConnectionState.disconnected && isConnected) {
_handleDisconnection();
}
});
@@ -959,12 +959,7 @@ class MeshCoreConnector extends ChangeNotifier {
if (!isConnected) return;
if (_batteryRequested && !force) return;
_batteryRequested = true;
- try {
- await sendFrame(buildGetBattAndStorageFrame());
- } catch (e) {
- // Connection likely lost - trigger disconnection handling
- _handleDisconnection();
- }
+ await sendFrame(buildGetBattAndStorageFrame());
}
void _startBatteryPolling() {
@@ -995,6 +990,7 @@ class MeshCoreConnector extends ChangeNotifier {
}
Future _requestDeviceInfo() async {
+ if (!isConnected || _awaitingSelfInfo) return;
_awaitingSelfInfo = true;
await sendFrame(buildDeviceQueryFrame());
await sendFrame(buildAppStartFrame());
diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart
index 470b795..dfe787e 100644
--- a/lib/connector/meshcore_protocol.dart
+++ b/lib/connector/meshcore_protocol.dart
@@ -102,6 +102,23 @@ class BufferWriter {
}
writeBytes(bytes);
}
+
+ void writeHex(String hex) {
+ // Validate hex string length is even and not empty
+ if (hex.isEmpty || hex.length % 2 != 0) {
+ throw FormatException('Invalid hex string length: ${hex.length}');
+ }
+ List result = [];
+ for (int i = 0; i < hex.length ~/ 2; i++) {
+ final hexByte = hex.substring(i * 2, i * 2 + 2);
+ final byte = int.tryParse(hexByte, radix: 16);
+ if (byte == null) {
+ throw FormatException('Invalid hex characters at position $i: $hexByte');
+ }
+ result.add(byte);
+ }
+ writeBytes(Uint8List.fromList(result));
+ }
}
// Command codes (to device)
@@ -164,6 +181,7 @@ const int respCodeContactMsgRecv = 7;
const int respCodeChannelMsgRecv = 8;
const int respCodeCurrTime = 9;
const int respCodeNoMoreMessages = 10;
+const int respCodeExportContact = 11;
const int respCodeBattAndStorage = 12;
const int respCodeDeviceInfo = 13;
const int respCodeContactMsgRecvV3 = 16;
@@ -728,4 +746,31 @@ Uint8List buildTraceReq(int tag, int auth, int flag, {Uint8List? payload})
writer.writeBytes(payload);
}
return writer.toBytes();
-}
\ No newline at end of file
+}
+
+// Build a export contact frame
+// [cmd][pub_key x32 / if empty exports your contact info]
+Uint8List buildExportContactFrame(Uint8List pubKey) {
+ final writer = BufferWriter();
+ writer.writeByte(cmdExportContact);
+ writer.writeBytes(pubKey);
+ return writer.toBytes();
+}
+
+// Build a import contact frame
+// [cmd][contact_frame x98+]
+Uint8List buildImportContactFrame(String contactFrame) {
+ final writer = BufferWriter();
+ writer.writeByte(cmdImportContact);
+ writer.writeHex(contactFrame);
+ return writer.toBytes();
+}
+
+// Build a export contact frame
+// [cmd][pub_key x32]
+Uint8List buildZeroHopContact(Uint8List pubKey) {
+ final writer = BufferWriter();
+ writer.writeByte(cmdShareContact);
+ writer.writeBytes(pubKey);
+ return writer.toBytes();
+}
diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb
index 958d963..460bc9d 100644
--- a/lib/l10n/app_bg.arb
+++ b/lib/l10n/app_bg.arb
@@ -1552,5 +1552,21 @@
"contacts_chatTraceRoute": "Трасиране на път",
"contacts_roomPathTrace": "Трасиране на път до съ",
"contacts_roomPing": "Ping на сървъра на стаята",
- "contacts_pathTraceTo": "Проследи маршрут към {name}"
+ "contacts_pathTraceTo": "Проследи маршрут към {name}",
+ "appSettings_languageUk": "Украински",
+ "contacts_clipboardEmpty": "Клипборда е празна.",
+ "contacts_invalidAdvertFormat": "Невалидни данни за контакт",
+ "appSettings_languageRu": "Руски",
+ "contacts_contactImported": "Контактът е импортиран.",
+ "contacts_zeroHopAdvert": "Реклама без скок",
+ "contacts_contactImportFailed": "Контактът не е успешно импортиран.",
+ "contacts_floodAdvert": "Потопна реклама",
+ "contacts_addContactFromClipboard": "Добави контакт от клипборда",
+ "contacts_copyAdvertToClipboard": "Копирай обявата в клипборда",
+ "contacts_ShareContact": "Копирай контакт в клипборда",
+ "contacts_ShareContactZeroHop": "Сподели контакт чрез обява",
+ "contacts_contactAdvertCopied": "Рекламата е копирана в клипборда.",
+ "contacts_zeroHopContactAdvertFailed": "Неуспешно изпращане на контакт.",
+ "contacts_zeroHopContactAdvertSent": "Изпратен контакт по обява.",
+ "contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя."
}
diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb
index 2586877..0a72559 100644
--- a/lib/l10n/app_de.arb
+++ b/lib/l10n/app_de.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Pfadverfolgung zum Raumserver",
"contacts_roomPing": "Raumserver anpingen",
"contacts_pathTraceTo": "Route nach {name} verfolgen",
- "contacts_chatTraceRoute": "Pfadverfolgungsroute"
+ "contacts_chatTraceRoute": "Pfadverfolgungsroute",
+ "appSettings_languageRu": "Russisch",
+ "contacts_invalidAdvertFormat": "Ungültige Kontaktdaten",
+ "contacts_clipboardEmpty": "Die Zwischenablage ist leer.",
+ "appSettings_languageUk": "Ukrainisch",
+ "contacts_contactImported": "Kontakt wurde importiert.",
+ "contacts_contactImportFailed": "Kontakt konnte nicht importiert werden",
+ "contacts_zeroHopAdvert": "Zero-Hop-Anzeige",
+ "contacts_floodAdvert": "Überflutungsanzeige",
+ "contacts_addContactFromClipboard": "Kontakt aus Zwischenablage hinzufügen",
+ "contacts_ShareContactZeroHop": "Kontakt über Anzeige teilen",
+ "contacts_copyAdvertToClipboard": "Werbung in die Zwischenablage kopieren",
+ "contacts_ShareContact": "Kontakt in die Zwischenablage kopieren",
+ "contacts_zeroHopContactAdvertFailed": "Kontakt konnte nicht gesendet werden.",
+ "contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet",
+ "contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.",
+ "contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen."
}
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index cb7b95e..ee5cf7d 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -1328,6 +1328,20 @@
"placeholders": {
"name": {"type": "String"}
}
- }
+ },
+ "contacts_clipboardEmpty": "Clipboard is empty.",
+ "contacts_invalidAdvertFormat": "Invalid contact data",
+ "contacts_contactImported": "Contact has been imported.",
+ "contacts_contactImportFailed": "Failed to import contact.",
+ "contacts_zeroHopAdvert":"Zero Hop Advert",
+ "contacts_floodAdvert":"Flood Advert",
+ "contacts_copyAdvertToClipboard":"Copy Advert to Clipboard",
+ "contacts_addContactFromClipboard":"Add Contact from Clipboard",
+ "contacts_ShareContact": "Copy contact to Clipboard",
+ "contacts_ShareContactZeroHop": "Share contact by advert",
+ "contacts_zeroHopContactAdvertSent": "Sent contact by advert.",
+ "contacts_zeroHopContactAdvertFailed": "Failed to send contact.",
+ "contacts_contactAdvertCopied": "Advert copied to Clipboard.",
+ "contacts_contactAdvertCopyFailed": "Copying advert to Clipboard failed."
}
diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb
index 1cdfb7b..c6dad1f 100644
--- a/lib/l10n/app_es.arb
+++ b/lib/l10n/app_es.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPing": "Pingar servidor de sala",
"contacts_roomPathTrace": "Rastreo de ruta al servidor de la habitación",
"contacts_pathTraceTo": "Rastrear ruta a {name}",
- "contacts_chatTraceRoute": "Ruta de trazado"
+ "contacts_chatTraceRoute": "Ruta de trazado",
+ "appSettings_languageUk": "Ucraniano",
+ "contacts_clipboardEmpty": "El portapapeles está vacío.",
+ "appSettings_languageRu": "Ruso",
+ "contacts_invalidAdvertFormat": "Datos de contacto no válidos",
+ "contacts_floodAdvert": "Anuncio de inundación",
+ "contacts_contactImported": "El contacto ha sido importado.",
+ "contacts_contactImportFailed": "Contacto no se importó correctamente.",
+ "contacts_zeroHopAdvert": "Anuncio de Zero Hop",
+ "contacts_ShareContactZeroHop": "Compartir contacto por anuncio",
+ "contacts_ShareContact": "Copiar contacto al Portapapeles",
+ "contacts_copyAdvertToClipboard": "Copiar anuncio al portapapeles",
+ "contacts_addContactFromClipboard": "Agregar contacto desde el portapapeles",
+ "contacts_zeroHopContactAdvertFailed": "No se pudo enviar el contacto.",
+ "contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.",
+ "contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.",
+ "contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado."
}
diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb
index 88c65d6..c1157ed 100644
--- a/lib/l10n/app_fr.arb
+++ b/lib/l10n/app_fr.arb
@@ -1552,5 +1552,21 @@
"contacts_chatTraceRoute": "Tracer le chemin",
"contacts_pathTraceTo": "Tracer l'itinéraire vers {name}",
"contacts_ping": "Ping",
- "contacts_roomPing": "Pinguer le serveur de la salle"
+ "contacts_roomPing": "Pinguer le serveur de la salle",
+ "contacts_invalidAdvertFormat": "Données de contact non valides",
+ "appSettings_languageUk": "Ukrainien",
+ "appSettings_languageRu": "Russe",
+ "contacts_clipboardEmpty": "Le presse-papiers est vide.",
+ "contacts_contactImported": "Le contact a été importé.",
+ "contacts_floodAdvert": "Annonce de crue",
+ "contacts_contactImportFailed": "Échec de l'importation du contact.",
+ "contacts_zeroHopAdvert": "Annonce Zero Hop",
+ "contacts_copyAdvertToClipboard": "Copier l'annonce dans le presse-papiers",
+ "contacts_addContactFromClipboard": "Ajouter un contact depuis le presse-papiers",
+ "contacts_ShareContact": "Copier le contact dans le presse-papiers",
+ "contacts_ShareContactZeroHop": "Partager un contact par annonce",
+ "contacts_contactAdvertCopied": "Annonce copiée dans le presse-papiers.",
+ "contacts_contactAdvertCopyFailed": "La copie de l'annonce vers le presse-papiers a échoué.",
+ "contacts_zeroHopContactAdvertSent": "Envoyer un contact par annonce.",
+ "contacts_zeroHopContactAdvertFailed": "Échec de l'envoi du contact."
}
diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb
index acd440b..c32e863 100644
--- a/lib/l10n/app_it.arb
+++ b/lib/l10n/app_it.arb
@@ -1552,5 +1552,21 @@
"contacts_repeaterPing": "Ripetitore ping",
"contacts_pathTraceTo": "Traccia percorso verso {name}",
"contacts_roomPing": "Ping al server della stanza",
- "contacts_chatTraceRoute": "Traccia percorso path"
+ "contacts_chatTraceRoute": "Traccia percorso path",
+ "appSettings_languageRu": "Russo",
+ "contacts_invalidAdvertFormat": "Dati di contatto non validi",
+ "appSettings_languageUk": "Ucraino",
+ "contacts_zeroHopAdvert": "Annuncio Zero Hop",
+ "contacts_floodAdvert": "Annuncio alluvionale",
+ "contacts_copyAdvertToClipboard": "Copia Annuncio negli Appunti",
+ "contacts_addContactFromClipboard": "Aggiungere contatto dalla clipboard",
+ "contacts_clipboardEmpty": "La clipboard è vuota.",
+ "contacts_ShareContact": "Copia contatto negli Appunti",
+ "contacts_contactImported": "Il contatto è stato importato.",
+ "contacts_contactImportFailed": "Contatto non importato con successo.",
+ "contacts_zeroHopContactAdvertSent": "Inviato contatto tramite annuncio.",
+ "contacts_contactAdvertCopyFailed": "Copia dell'annuncio nella Clipboard non riuscita.",
+ "contacts_ShareContactZeroHop": "Condividi contatto tramite annuncio",
+ "contacts_zeroHopContactAdvertFailed": "Invio del contatto non riuscito.",
+ "contacts_contactAdvertCopied": "Annuncio copiato negli Appunti."
}
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index ac3eb99..055667f 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -4771,6 +4771,90 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Trace route to {name}'**
String contacts_pathTraceTo(String name);
+
+ /// No description provided for @contacts_clipboardEmpty.
+ ///
+ /// In en, this message translates to:
+ /// **'Clipboard is empty.'**
+ String get contacts_clipboardEmpty;
+
+ /// No description provided for @contacts_invalidAdvertFormat.
+ ///
+ /// In en, this message translates to:
+ /// **'Invalid contact data'**
+ String get contacts_invalidAdvertFormat;
+
+ /// No description provided for @contacts_contactImported.
+ ///
+ /// In en, this message translates to:
+ /// **'Contact has been imported.'**
+ String get contacts_contactImported;
+
+ /// No description provided for @contacts_contactImportFailed.
+ ///
+ /// In en, this message translates to:
+ /// **'Failed to import contact.'**
+ String get contacts_contactImportFailed;
+
+ /// No description provided for @contacts_zeroHopAdvert.
+ ///
+ /// In en, this message translates to:
+ /// **'Zero Hop Advert'**
+ String get contacts_zeroHopAdvert;
+
+ /// No description provided for @contacts_floodAdvert.
+ ///
+ /// In en, this message translates to:
+ /// **'Flood Advert'**
+ String get contacts_floodAdvert;
+
+ /// No description provided for @contacts_copyAdvertToClipboard.
+ ///
+ /// In en, this message translates to:
+ /// **'Copy Advert to Clipboard'**
+ String get contacts_copyAdvertToClipboard;
+
+ /// No description provided for @contacts_addContactFromClipboard.
+ ///
+ /// In en, this message translates to:
+ /// **'Add Contact from Clipboard'**
+ String get contacts_addContactFromClipboard;
+
+ /// No description provided for @contacts_ShareContact.
+ ///
+ /// In en, this message translates to:
+ /// **'Copy contact to Clipboard'**
+ String get contacts_ShareContact;
+
+ /// No description provided for @contacts_ShareContactZeroHop.
+ ///
+ /// In en, this message translates to:
+ /// **'Share contact by advert'**
+ String get contacts_ShareContactZeroHop;
+
+ /// No description provided for @contacts_zeroHopContactAdvertSent.
+ ///
+ /// In en, this message translates to:
+ /// **'Sent contact by advert.'**
+ String get contacts_zeroHopContactAdvertSent;
+
+ /// No description provided for @contacts_zeroHopContactAdvertFailed.
+ ///
+ /// In en, this message translates to:
+ /// **'Failed to send contact.'**
+ String get contacts_zeroHopContactAdvertFailed;
+
+ /// No description provided for @contacts_contactAdvertCopied.
+ ///
+ /// In en, this message translates to:
+ /// **'Advert copied to Clipboard.'**
+ String get contacts_contactAdvertCopied;
+
+ /// No description provided for @contacts_contactAdvertCopyFailed.
+ ///
+ /// In en, this message translates to:
+ /// **'Copying advert to Clipboard failed.'**
+ String get contacts_contactAdvertCopyFailed;
}
class _AppLocalizationsDelegate
diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart
index 27b2007..701429e 100644
--- a/lib/l10n/app_localizations_bg.dart
+++ b/lib/l10n/app_localizations_bg.dart
@@ -451,10 +451,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Руски';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Украински';
@override
String get appSettings_notifications => 'Уведомления';
@@ -2720,4 +2720,50 @@ class AppLocalizationsBg extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Проследи маршрут към $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Клипборда е празна.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Невалидни данни за контакт';
+
+ @override
+ String get contacts_contactImported => 'Контактът е импортиран.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Контактът не е успешно импортиран.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Реклама без скок';
+
+ @override
+ String get contacts_floodAdvert => 'Потопна реклама';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Копирай обявата в клипборда';
+
+ @override
+ String get contacts_addContactFromClipboard => 'Добави контакт от клипборда';
+
+ @override
+ String get contacts_ShareContact => 'Копирай контакт в клипборда';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Сподели контакт чрез обява';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Изпратен контакт по обява.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Неуспешно изпращане на контакт.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Рекламата е копирана в клипборда.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Копирането на обявата в клипборда не успя.';
}
diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart
index 69e6a59..514a7a1 100644
--- a/lib/l10n/app_localizations_de.dart
+++ b/lib/l10n/app_localizations_de.dart
@@ -445,10 +445,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Russisch';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ukrainisch';
@override
String get appSettings_notifications => 'Benachrichtigungen';
@@ -2724,4 +2724,53 @@ class AppLocalizationsDe extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Route nach $name verfolgen';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Die Zwischenablage ist leer.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Ungültige Kontaktdaten';
+
+ @override
+ String get contacts_contactImported => 'Kontakt wurde importiert.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Kontakt konnte nicht importiert werden';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Zero-Hop-Anzeige';
+
+ @override
+ String get contacts_floodAdvert => 'Überflutungsanzeige';
+
+ @override
+ String get contacts_copyAdvertToClipboard =>
+ 'Werbung in die Zwischenablage kopieren';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Kontakt aus Zwischenablage hinzufügen';
+
+ @override
+ String get contacts_ShareContact => 'Kontakt in die Zwischenablage kopieren';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Kontakt über Anzeige teilen';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Kontakt über Anzeige gesendet';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Kontakt konnte nicht gesendet werden.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Anzeige in die Zwischenablage kopiert.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.';
}
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index a609dd8..040d809 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -2680,4 +2680,47 @@ class AppLocalizationsEn extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Trace route to $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Clipboard is empty.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Invalid contact data';
+
+ @override
+ String get contacts_contactImported => 'Contact has been imported.';
+
+ @override
+ String get contacts_contactImportFailed => 'Failed to import contact.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Zero Hop Advert';
+
+ @override
+ String get contacts_floodAdvert => 'Flood Advert';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Copy Advert to Clipboard';
+
+ @override
+ String get contacts_addContactFromClipboard => 'Add Contact from Clipboard';
+
+ @override
+ String get contacts_ShareContact => 'Copy contact to Clipboard';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Share contact by advert';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Sent contact by advert.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed => 'Failed to send contact.';
+
+ @override
+ String get contacts_contactAdvertCopied => 'Advert copied to Clipboard.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Copying advert to Clipboard failed.';
}
diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart
index 28d3e9d..e65cbcd 100644
--- a/lib/l10n/app_localizations_es.dart
+++ b/lib/l10n/app_localizations_es.dart
@@ -448,10 +448,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Ruso';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ucraniano';
@override
String get appSettings_notifications => 'Notificaciones';
@@ -2720,4 +2720,50 @@ class AppLocalizationsEs extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Rastrear ruta a $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'El portapapeles está vacío.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Datos de contacto no válidos';
+
+ @override
+ String get contacts_contactImported => 'El contacto ha sido importado.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Contacto no se importó correctamente.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Anuncio de Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Anuncio de inundación';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Copiar anuncio al portapapeles';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Agregar contacto desde el portapapeles';
+
+ @override
+ String get contacts_ShareContact => 'Copiar contacto al Portapapeles';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Compartir contacto por anuncio';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Envió contacto por anuncio.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'No se pudo enviar el contacto.';
+
+ @override
+ String get contacts_contactAdvertCopied => 'Anuncio copiado al Portapapeles.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Copiar anuncio al Portapapeles ha fallado.';
}
diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart
index ce6f6a9..4496fc8 100644
--- a/lib/l10n/app_localizations_fr.dart
+++ b/lib/l10n/app_localizations_fr.dart
@@ -449,10 +449,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Russe';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ukrainien';
@override
String get appSettings_notifications => 'Notifications';
@@ -2737,4 +2737,54 @@ class AppLocalizationsFr extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Tracer l\'itinéraire vers $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Le presse-papiers est vide.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Données de contact non valides';
+
+ @override
+ String get contacts_contactImported => 'Le contact a été importé.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Échec de l\'importation du contact.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Annonce Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Annonce de crue';
+
+ @override
+ String get contacts_copyAdvertToClipboard =>
+ 'Copier l\'annonce dans le presse-papiers';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Ajouter un contact depuis le presse-papiers';
+
+ @override
+ String get contacts_ShareContact =>
+ 'Copier le contact dans le presse-papiers';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Partager un contact par annonce';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Envoyer un contact par annonce.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Échec de l\'envoi du contact.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Annonce copiée dans le presse-papiers.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'La copie de l\'annonce vers le presse-papiers a échoué.';
}
diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart
index a7ac6a6..02345c4 100644
--- a/lib/l10n/app_localizations_it.dart
+++ b/lib/l10n/app_localizations_it.dart
@@ -447,10 +447,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Russo';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ucraino';
@override
String get appSettings_notifications => 'Notifiche';
@@ -2721,4 +2721,52 @@ class AppLocalizationsIt extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Traccia percorso verso $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'La clipboard è vuota.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Dati di contatto non validi';
+
+ @override
+ String get contacts_contactImported => 'Il contatto è stato importato.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Contatto non importato con successo.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Annuncio Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Annuncio alluvionale';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Copia Annuncio negli Appunti';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Aggiungere contatto dalla clipboard';
+
+ @override
+ String get contacts_ShareContact => 'Copia contatto negli Appunti';
+
+ @override
+ String get contacts_ShareContactZeroHop =>
+ 'Condividi contatto tramite annuncio';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Inviato contatto tramite annuncio.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Invio del contatto non riuscito.';
+
+ @override
+ String get contacts_contactAdvertCopied => 'Annuncio copiato negli Appunti.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Copia dell\'annuncio nella Clipboard non riuscita.';
}
diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart
index b55dc41..292181f 100644
--- a/lib/l10n/app_localizations_nl.dart
+++ b/lib/l10n/app_localizations_nl.dart
@@ -445,10 +445,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Russisch';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Oekraïens';
@override
String get appSettings_notifications => 'Notificaties';
@@ -2710,4 +2710,52 @@ class AppLocalizationsNl extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Trace route to $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Knipbord is leeg.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Ongeldige contactgegevens';
+
+ @override
+ String get contacts_contactImported => 'Contact is geïmporteerd.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Contact kon niet geïmporteerd worden.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Zero Hop Reclame';
+
+ @override
+ String get contacts_floodAdvert => 'Overstromingsadvertentie';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Advert naar klembord kopiëren';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Contact uit klembord toevoegen';
+
+ @override
+ String get contacts_ShareContact => 'Kontakt naar Klembord kopiëren';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Contact delen via advertentie';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Contact verzonden via advertentie';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Mislukt om contact te verzenden';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Reclame gekopieerd naar Klembord.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Kopiëren van advertentie naar Clipboard is mislukt.';
}
diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart
index 0f7a704..0832329 100644
--- a/lib/l10n/app_localizations_pl.dart
+++ b/lib/l10n/app_localizations_pl.dart
@@ -449,10 +449,10 @@ class AppLocalizationsPl extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Rosyjski';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ukraińska';
@override
String get appSettings_notifications => 'Powiadomienia';
@@ -2719,4 +2719,51 @@ class AppLocalizationsPl extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Śledź trasę do $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Schowek jest pusty.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Nieprawidłowe dane kontaktowe';
+
+ @override
+ String get contacts_contactImported => 'Kontakt został zaimportowany.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Kontakt nie został zaimportowany.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Reklama Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Reklama powodziowa';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Kopiuj ogłoszenie do schowka';
+
+ @override
+ String get contacts_addContactFromClipboard => 'Dodaj kontakt z schowka';
+
+ @override
+ String get contacts_ShareContact => 'Kopiuj kontakt do schowka';
+
+ @override
+ String get contacts_ShareContactZeroHop =>
+ 'Udostępnij kontakt przez ogłoszenie';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Wysłano kontakt przez ogłoszenie.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Nie udało się wysłać kontaktu.';
+
+ @override
+ String get contacts_contactAdvertCopied => 'Reklama skopiowana do schowka.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Kopiowanie ogłoszenia do schowka nie powiodło się.';
}
diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart
index 5c25276..eadea3b 100644
--- a/lib/l10n/app_localizations_pt.dart
+++ b/lib/l10n/app_localizations_pt.dart
@@ -449,10 +449,10 @@ class AppLocalizationsPt extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Russo';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ucraniano';
@override
String get appSettings_notifications => 'Notificações';
@@ -2721,4 +2721,51 @@ class AppLocalizationsPt extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Rastrear rota para $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Área de Transferência Está Vazia.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Dados de Contato Inválidos';
+
+ @override
+ String get contacts_contactImported => 'Contato foi importado.';
+
+ @override
+ String get contacts_contactImportFailed => 'Contato falhou ao ser importado.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Anúncio Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Anúncio de Inundação';
+
+ @override
+ String get contacts_copyAdvertToClipboard =>
+ 'Copiar Anúncio para Área de Transferência';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Adicionar Contato da Área de Transferência';
+
+ @override
+ String get contacts_ShareContact =>
+ 'Copiar contato para Área de Transferência';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Compartilhar contato por anúncio';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Enviou contato por anúncio.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed => 'Falha ao enviar contato.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Anúncio copiado para a Área de Transferência.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Cópia do anúncio para a Área de Transferência falhou.';
}
diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart
index a944fab..ec0f1ba 100644
--- a/lib/l10n/app_localizations_ru.dart
+++ b/lib/l10n/app_localizations_ru.dart
@@ -2723,4 +2723,54 @@ class AppLocalizationsRu extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Показать маршрут к $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Буфер обмена пуст.';
+
+ @override
+ String get contacts_invalidAdvertFormat =>
+ 'Недействительные контактные данные';
+
+ @override
+ String get contacts_contactImported => 'Контакт был импортирован';
+
+ @override
+ String get contacts_contactImportFailed => 'Контакт не удалось импортировать';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Реклама Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Рекламный поток';
+
+ @override
+ String get contacts_copyAdvertToClipboard =>
+ 'Копировать рекламу в буфер обмена';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Добавить контакт из буфера обмена';
+
+ @override
+ String get contacts_ShareContact => 'Копировать контакт в буфер обмена';
+
+ @override
+ String get contacts_ShareContactZeroHop =>
+ 'Поделиться контактом по объявлению';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Отправлено сообщение по объявлению.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Не удалось отправить контакт.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Реклама скопирована в буфер обмена.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Копирование рекламы в буфер обмена не удалось.';
}
diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart
index 02f2b62..346047b 100644
--- a/lib/l10n/app_localizations_sk.dart
+++ b/lib/l10n/app_localizations_sk.dart
@@ -445,10 +445,10 @@ class AppLocalizationsSk extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Ruština';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ukrajinská';
@override
String get appSettings_notifications => 'Upozornenia';
@@ -2706,4 +2706,50 @@ class AppLocalizationsSk extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Sledovať trasu k $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Schránka je prázdna.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Neplatné kontaktné údaje';
+
+ @override
+ String get contacts_contactImported => 'Kontakt bol importovaný.';
+
+ @override
+ String get contacts_contactImportFailed =>
+ 'Kontakt sa nepodarilo importovať.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Inzerát Zero Hop';
+
+ @override
+ String get contacts_floodAdvert => 'Inzerát povodní';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Kopírovať reklamu do schránky';
+
+ @override
+ String get contacts_addContactFromClipboard => 'Pridať kontakt z schránky';
+
+ @override
+ String get contacts_ShareContact => 'Kopírovať kontakt do schránky';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Zdieľať kontakt cez inzerát';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Poslal kontakt cez inzerát.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Zlyhalo odoslanie kontaktu.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Inzerát bol skopírovaný do schránky.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Kopírovanie inzerátu do schránky zlyhalo.';
}
diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart
index 21d7b6f..ed71122 100644
--- a/lib/l10n/app_localizations_sl.dart
+++ b/lib/l10n/app_localizations_sl.dart
@@ -444,10 +444,10 @@ class AppLocalizationsSl extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Ruščina';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ukrajinsko';
@override
String get appSettings_notifications => 'Obvestila';
@@ -2709,4 +2709,49 @@ class AppLocalizationsSl extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Trace route to $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Odložišče je prazno.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Neveljavni kontaktne podatke';
+
+ @override
+ String get contacts_contactImported => 'Kontakt je bil uvožen.';
+
+ @override
+ String get contacts_contactImportFailed => 'Kontakt ni bil uspešno uvožen.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Reklama brez posrednikov';
+
+ @override
+ String get contacts_floodAdvert => 'Poplavna oglás';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Kopiraj oglas v odložišče';
+
+ @override
+ String get contacts_addContactFromClipboard => 'Dodaj stik iz odložišča';
+
+ @override
+ String get contacts_ShareContact => 'Kopiraj stik v Odložišče';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Deliti kontakt prek oglasa';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Poslano po oglasu.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Pošiljanje kontakta ni uspelo.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Oglas je bil kopiran v odložišče.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Kopiranje oglasa v odložišče je spodletelo.';
}
diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart
index a96d7dc..97b849f 100644
--- a/lib/l10n/app_localizations_sv.dart
+++ b/lib/l10n/app_localizations_sv.dart
@@ -442,10 +442,10 @@ class AppLocalizationsSv extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Ryska';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => 'Ukrainska';
@override
String get appSettings_notifications => 'Meddelanden';
@@ -2694,4 +2694,49 @@ class AppLocalizationsSv extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Spåra rutt till $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Urklipp är tomt.';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Ogiltiga kontaktuppgifter';
+
+ @override
+ String get contacts_contactImported => 'Kontakt har importerats.';
+
+ @override
+ String get contacts_contactImportFailed => 'Kontakt kunde inte importeras.';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Reklam med nollhopp';
+
+ @override
+ String get contacts_floodAdvert => 'Översvämningsannons';
+
+ @override
+ String get contacts_copyAdvertToClipboard => 'Kopiera annons till urklipp';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Lägg till kontakt från urklipp';
+
+ @override
+ String get contacts_ShareContact => 'Kopiera kontakt till Urklipp';
+
+ @override
+ String get contacts_ShareContactZeroHop => 'Dela kontakt via annons';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => 'Skickat kontakt via annons.';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Misslyckades med att skicka kontakt.';
+
+ @override
+ String get contacts_contactAdvertCopied => 'Annons kopierad till Urklipp.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Kopiering av annons till Urklipp misslyckades.';
}
diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart
index 6107c5b..899d540 100644
--- a/lib/l10n/app_localizations_uk.dart
+++ b/lib/l10n/app_localizations_uk.dart
@@ -447,7 +447,7 @@ class AppLocalizationsUk extends AppLocalizations {
String get appSettings_languageBg => 'Български';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => 'Російська';
@override
String get appSettings_languageUk => 'Українська';
@@ -2730,4 +2730,53 @@ class AppLocalizationsUk extends AppLocalizations {
String contacts_pathTraceTo(String name) {
return 'Відстежити маршрут до $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => 'Буфер обміну порожній';
+
+ @override
+ String get contacts_invalidAdvertFormat => 'Недійсні контактні дані';
+
+ @override
+ String get contacts_contactImported => 'Контакт було імпортовано.';
+
+ @override
+ String get contacts_contactImportFailed => 'Контакт не вдалося імпортувати';
+
+ @override
+ String get contacts_zeroHopAdvert => 'Реклама без перехоплення';
+
+ @override
+ String get contacts_floodAdvert => 'Залив реклами';
+
+ @override
+ String get contacts_copyAdvertToClipboard =>
+ 'Копіювати оголошення в буфер обміну';
+
+ @override
+ String get contacts_addContactFromClipboard =>
+ 'Додати контакт з буфера обміну';
+
+ @override
+ String get contacts_ShareContact => 'Копіювати контакт у буфер обміну';
+
+ @override
+ String get contacts_ShareContactZeroHop =>
+ 'Поділитися контактом за оголошенням';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent =>
+ 'Відправлено контакт за оголошенням';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed =>
+ 'Не вдалося надіслати контакт.';
+
+ @override
+ String get contacts_contactAdvertCopied =>
+ 'Рекламу скопійовано до буфера обміну.';
+
+ @override
+ String get contacts_contactAdvertCopyFailed =>
+ 'Копіювання оголошення в буфер обміну завершилося невдало';
}
diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart
index c10a745..7746792 100644
--- a/lib/l10n/app_localizations_zh.dart
+++ b/lib/l10n/app_localizations_zh.dart
@@ -12,7 +12,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get appTitle => 'MeshCore Open';
@override
- String get nav_contacts => '联系人';
+ String get nav_contacts => '联系方式';
@override
String get nav_channels => '频道';
@@ -54,13 +54,13 @@ class AppLocalizationsZh extends AppLocalizations {
String get common_disconnect => '断开';
@override
- String get common_connected => '已连接';
+ String get common_connected => '连接';
@override
String get common_disconnected => '断开';
@override
- String get common_create => '创建';
+ String get common_create => '创造';
@override
String get common_continue => '继续';
@@ -78,7 +78,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get common_hide => '隐藏';
@override
- String get common_remove => '删除';
+ String get common_remove => '移除';
@override
String get common_enable => '启用';
@@ -87,7 +87,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get common_disable => '禁用';
@override
- String get common_reboot => '重启';
+ String get common_reboot => '重新启动';
@override
String get common_loading => '正在加载...';
@@ -106,34 +106,34 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get scanner_title => 'MeshCore Open';
+ String get scanner_title => 'MeshCore 开放';
@override
- String get scanner_scanning => '扫描设备…';
+ String get scanner_scanning => '正在搜索设备...';
@override
- String get scanner_connecting => '连接中...';
+ String get scanner_connecting => '正在连接...';
@override
- String get scanner_disconnecting => '断开中...';
+ String get scanner_disconnecting => '断开连接...';
@override
String get scanner_notConnected => '未连接';
@override
String scanner_connectedTo(String deviceName) {
- return '已连接至 $deviceName';
+ return '已连接到 $deviceName';
}
@override
- String get scanner_searchingDevices => '搜索 MeshCore 设备...';
+ String get scanner_searchingDevices => '正在搜索 MeshCore 设备...';
@override
- String get scanner_tapToScan => '点击扫描以查找MeshCore设备';
+ String get scanner_tapToScan => '点击“扫描”功能,以查找 MeshCore 设备。';
@override
String scanner_connectionFailed(String error) {
- return '连接失败:$error';
+ return 'Connection failed: $error';
}
@override
@@ -146,7 +146,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get device_quickSwitch => '快速切换';
@override
- String get device_meshcore => 'MeshCore';
+ String get device_meshcore => '网格核心';
@override
String get settings_title => '设置';
@@ -176,40 +176,40 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_nodeNameUpdated => '姓名已更新';
@override
- String get settings_radioSettings => '无线设置';
+ String get settings_radioSettings => '收音机设置';
@override
- String get settings_radioSettingsSubtitle => '频率,功率,扩展因子';
+ String get settings_radioSettingsSubtitle => '频率、功率、扩频因子';
@override
- String get settings_radioSettingsUpdated => '射频设置已更新';
+ String get settings_radioSettingsUpdated => '收音机设置已更新';
@override
- String get settings_location => '位置';
+ String get settings_location => '地点';
@override
- String get settings_locationSubtitle => 'GPS坐标';
+ String get settings_locationSubtitle => 'GPS 坐标';
@override
- String get settings_locationUpdated => '位置已更新';
+ String get settings_locationUpdated => '位置和 GPS 设置已更新';
@override
- String get settings_locationBothRequired => '请输入纬度和经度。';
+ String get settings_locationBothRequired => '请输入经度和纬度。';
@override
- String get settings_locationInvalid => '无效的纬度或经度。';
+ String get settings_locationInvalid => '无效的经度和纬度。';
@override
- String get settings_locationGPSEnable => '启用GPS';
+ String get settings_locationGPSEnable => '开启 GPS 功能';
@override
- String get settings_locationGPSEnableSubtitle => '启用GPS自动更新位置。';
+ String get settings_locationGPSEnableSubtitle => '使 GPS 能够自动更新位置。';
@override
- String get settings_locationIntervalSec => 'GPS 间隔(秒)';
+ String get settings_locationIntervalSec => 'GPS 间隔时间(秒)';
@override
- String get settings_locationIntervalInvalid => '时间间隔必须至少为60秒,且小于86400秒。';
+ String get settings_locationIntervalInvalid => '间隔时间必须至少为 60 秒,但不超过 86400 秒。';
@override
String get settings_latitude => '纬度';
@@ -221,34 +221,34 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_privacyMode => '隐私模式';
@override
- String get settings_privacyModeSubtitle => '隐藏在广告中的姓名/位置';
+ String get settings_privacyModeSubtitle => '在广告中隐藏姓名/位置';
@override
- String get settings_privacyModeToggle => '开启隐私模式以隐藏您的姓名和位置在广告中的显示。';
+ String get settings_privacyModeToggle => '切换隐私模式,以隐藏您的姓名和位置,从而在广告中保护您的个人信息。';
@override
String get settings_privacyModeEnabled => '隐私模式已启用';
@override
- String get settings_privacyModeDisabled => '隐私模式已禁用';
+ String get settings_privacyModeDisabled => '隐私模式已关闭';
@override
- String get settings_actions => '操作';
+ String get settings_actions => '行动';
@override
- String get settings_sendAdvertisement => '发送广告';
+ String get settings_sendAdvertisement => '发布广告';
@override
- String get settings_sendAdvertisementSubtitle => '现在已广播';
+ String get settings_sendAdvertisementSubtitle => '现已开始进行广播节目';
@override
- String get settings_advertisementSent => '广告已发送';
+ String get settings_advertisementSent => '已发送广告';
@override
String get settings_syncTime => '同步时间';
@override
- String get settings_syncTimeSubtitle => '将设备时钟设置为手机时间';
+ String get settings_syncTimeSubtitle => '将设备时钟设置为与手机时间一致';
@override
String get settings_timeSynchronized => '时间同步';
@@ -257,31 +257,31 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_refreshContacts => '刷新联系人';
@override
- String get settings_refreshContactsSubtitle => '从设备重新加载联系人列表';
+ String get settings_refreshContactsSubtitle => '从设备中重新加载联系人列表';
@override
String get settings_rebootDevice => '重启设备';
@override
- String get settings_rebootDeviceSubtitle => '重启 MeshCore 设备';
+ String get settings_rebootDeviceSubtitle => '重新启动 MeshCore 设备';
@override
- String get settings_rebootDeviceConfirm => '您确定要重启设备吗?您将会断开连接。';
+ String get settings_rebootDeviceConfirm => '您确定要重启设备吗?这将导致您与设备断开连接。';
@override
String get settings_debug => '调试';
@override
- String get settings_bleDebugLog => '蓝牙调试日志';
+ String get settings_bleDebugLog => 'BLE 调试日志';
@override
- String get settings_bleDebugLogSubtitle => '蓝牙命令、响应和原始数据';
+ String get settings_bleDebugLogSubtitle => 'BLE 命令、响应和原始数据';
@override
- String get settings_appDebugLog => '应用调试日志';
+ String get settings_appDebugLog => '应用程序调试日志';
@override
- String get settings_appDebugLogSubtitle => '应用调试消息';
+ String get settings_appDebugLogSubtitle => '应用程序调试消息';
@override
String get settings_about => '关于';
@@ -292,11 +292,11 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get settings_aboutLegalese => '2024 MeshCore 开放源代码项目';
+ String get settings_aboutLegalese => '2026 MeshCore 开源项目';
@override
String get settings_aboutDescription =>
- '一个开源的 Flutter 客户端,用于 MeshCore LoRa 网状网络设备。';
+ '一个开源的 Flutter 客户端,用于 MeshCore LoRa 无线网络设备。';
@override
String get settings_infoName => '姓名';
@@ -317,19 +317,19 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_infoContactsCount => '联系人数量';
@override
- String get settings_infoChannelCount => '频道数量';
+ String get settings_infoChannelCount => '通道数量';
@override
String get settings_presets => '预设';
@override
- String get settings_preset915Mhz => '915 MHz';
+ String get settings_preset915Mhz => '915 兆赫';
@override
- String get settings_preset868Mhz => '868 MHz';
+ String get settings_preset868Mhz => '868 兆赫';
@override
- String get settings_preset433Mhz => '433 MHz';
+ String get settings_preset433Mhz => '433 兆赫';
@override
String get settings_frequency => '频率 (MHz)';
@@ -338,35 +338,35 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_frequencyHelper => '300.0 - 2500.0';
@override
- String get settings_frequencyInvalid => '无效频率 (300-2500 MHz)';
+ String get settings_frequencyInvalid => '无效频率(300-2500 MHz)';
@override
String get settings_bandwidth => '带宽';
@override
- String get settings_spreadingFactor => '扩散因子';
+ String get settings_spreadingFactor => '传播系数';
@override
String get settings_codingRate => '编码速率';
@override
- String get settings_txPower => 'TX Power (dBm)';
+ String get settings_txPower => 'TX 功率(dBm)';
@override
String get settings_txPowerHelper => '0 - 22';
@override
- String get settings_txPowerInvalid => '无效的 TX 电功率 (0-22 dBm)';
+ String get settings_txPowerInvalid => '无效的发射功率(0-22 dBm)';
@override
String get settings_longRange => '远距离';
@override
- String get settings_fastSpeed => '快速速度';
+ String get settings_fastSpeed => '高速';
@override
String settings_error(String message) {
- return '错误:$message';
+ return '[保存:$message]\n错误:$message';
}
@override
@@ -379,64 +379,64 @@ class AppLocalizationsZh extends AppLocalizations {
String get appSettings_theme => '主题';
@override
- String get appSettings_themeSystem => '系统默认';
+ String get appSettings_themeSystem => '系统默认设置';
@override
String get appSettings_themeLight => '光';
@override
- String get appSettings_themeDark => '深色';
+ String get appSettings_themeDark => '黑暗';
@override
String get appSettings_language => '语言';
@override
- String get appSettings_languageSystem => '系统默认';
+ String get appSettings_languageSystem => '系统默认设置';
@override
- String get appSettings_languageEn => 'English';
+ String get appSettings_languageEn => '英语';
@override
- String get appSettings_languageFr => 'Français';
+ String get appSettings_languageFr => '法语';
@override
- String get appSettings_languageEs => 'Español';
+ String get appSettings_languageEs => '西班牙语';
@override
- String get appSettings_languageDe => 'Deutsch';
+ String get appSettings_languageDe => '德语';
@override
- String get appSettings_languagePl => 'Polski';
+ String get appSettings_languagePl => '波兰语';
@override
- String get appSettings_languageSl => 'Slovenščina';
+ String get appSettings_languageSl => '斯洛文语';
@override
- String get appSettings_languagePt => 'Português';
+ String get appSettings_languagePt => '葡萄牙语';
@override
- String get appSettings_languageIt => 'Italiano';
+ String get appSettings_languageIt => '意大利语';
@override
String get appSettings_languageZh => '中文';
@override
- String get appSettings_languageSv => 'Svenska';
+ String get appSettings_languageSv => '瑞典语';
@override
- String get appSettings_languageNl => 'Nederlands';
+ String get appSettings_languageNl => '荷兰语';
@override
- String get appSettings_languageSk => 'Slovenčina';
+ String get appSettings_languageSk => '斯洛伐克语';
@override
- String get appSettings_languageBg => 'Български';
+ String get appSettings_languageBg => '保加利亚';
@override
- String get appSettings_languageRu => 'Русский';
+ String get appSettings_languageRu => '俄语';
@override
- String get appSettings_languageUk => 'Українська';
+ String get appSettings_languageUk => '乌克兰';
@override
String get appSettings_notifications => '通知';
@@ -448,7 +448,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get appSettings_enableNotificationsSubtitle => '接收消息和广告的通知';
@override
- String get appSettings_notificationPermissionDenied => '通知权限被拒绝';
+ String get appSettings_notificationPermissionDenied => '权限被拒绝';
@override
String get appSettings_notificationsEnabled => '通知已启用';
@@ -460,40 +460,41 @@ class AppLocalizationsZh extends AppLocalizations {
String get appSettings_messageNotifications => '消息通知';
@override
- String get appSettings_messageNotificationsSubtitle => '显示收到新消息时的通知';
+ String get appSettings_messageNotificationsSubtitle => '在收到新消息时显示通知';
@override
String get appSettings_channelMessageNotifications => '频道消息通知';
@override
- String get appSettings_channelMessageNotificationsSubtitle => '显示接收频道消息时的通知';
+ String get appSettings_channelMessageNotificationsSubtitle =>
+ '在收到频道消息时,显示通知。';
@override
String get appSettings_advertisementNotifications => '广告通知';
@override
- String get appSettings_advertisementNotificationsSubtitle => '显示当新节点被发现时通知';
+ String get appSettings_advertisementNotificationsSubtitle => '在发现新的节点时,显示通知。';
@override
- String get appSettings_messaging => '消息';
+ String get appSettings_messaging => '信息传递';
@override
- String get appSettings_clearPathOnMaxRetry => '清除最大重试路径';
+ String get appSettings_clearPathOnMaxRetry => '关于“最大重试”的清晰说明';
@override
- String get appSettings_clearPathOnMaxRetrySubtitle => '重置联系人路径,在5次发送失败尝试后';
+ String get appSettings_clearPathOnMaxRetrySubtitle => '在尝试发送失败后 5 次,重置联系路径。';
@override
- String get appSettings_pathsWillBeCleared => '路径将在5次失败重试后清除';
+ String get appSettings_pathsWillBeCleared => '如果尝试 5 次后仍然失败,则将重新规划路径。';
@override
- String get appSettings_pathsWillNotBeCleared => '路径不会自动清理';
+ String get appSettings_pathsWillNotBeCleared => '路径不会自动清除。';
@override
- String get appSettings_autoRouteRotation => '自动路径旋转';
+ String get appSettings_autoRouteRotation => '自动路径轮换';
@override
- String get appSettings_autoRouteRotationSubtitle => '在最佳路径和洪水模式之间切换';
+ String get appSettings_autoRouteRotationSubtitle => '在最佳路径和防洪模式之间切换';
@override
String get appSettings_autoRouteRotationEnabled => '自动路径轮换已启用';
@@ -509,26 +510,26 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String appSettings_batteryChemistryPerDevice(String deviceName) {
- return '设置每个设备 ($deviceName)';
+ return '为每个设备设置 ($deviceName)';
}
@override
- String get appSettings_batteryChemistryConnectFirst => '连接设备以选择';
+ String get appSettings_batteryChemistryConnectFirst => '连接到设备以进行选择';
@override
- String get appSettings_batteryNmc => '18650 NMC (3.0-4.2V)';
+ String get appSettings_batteryNmc => '18650 型号,NMC 电池(3.0-4.2V)';
@override
String get appSettings_batteryLifepo4 => '磷酸铁锂 (2.6-3.65V)';
@override
- String get appSettings_batteryLipo => 'LiPo (3.0-4.2V)';
+ String get appSettings_batteryLipo => '锂离子电池 (3.0-4.2V)';
@override
- String get appSettings_mapDisplay => '地图显示';
+ String get appSettings_mapDisplay => '地图展示';
@override
- String get appSettings_showRepeaters => '显示循环器';
+ String get appSettings_showRepeaters => '显示重复';
@override
String get appSettings_showRepeatersSubtitle => '在地图上显示重复节点';
@@ -543,36 +544,36 @@ class AppLocalizationsZh extends AppLocalizations {
String get appSettings_showOtherNodes => '显示其他节点';
@override
- String get appSettings_showOtherNodesSubtitle => '显示其他节点类型在地图上';
+ String get appSettings_showOtherNodesSubtitle => '在地图上显示其他节点类型';
@override
- String get appSettings_timeFilter => '时间筛选';
+ String get appSettings_timeFilter => '时间过滤器';
@override
String get appSettings_timeFilterShowAll => '显示所有节点';
@override
String appSettings_timeFilterShowLast(int hours) {
- return '显示来自过去 $hours 小时的节点';
+ return 'Show nodes from last $hours hours';
}
@override
String get appSettings_mapTimeFilter => '地图时间筛选';
@override
- String get appSettings_showNodesDiscoveredWithin => '显示发现的节点在:';
+ String get appSettings_showNodesDiscoveredWithin => '显示在以下范围内发现的节点:';
@override
String get appSettings_allTime => '所有时间';
@override
- String get appSettings_lastHour => '最后小时';
+ String get appSettings_lastHour => '过去一小时';
@override
- String get appSettings_last6Hours => '最后6小时';
+ String get appSettings_last6Hours => '过去6小时';
@override
- String get appSettings_last24Hours => '最后24小时';
+ String get appSettings_last24Hours => '过去24小时';
@override
String get appSettings_lastWeek => '上周';
@@ -585,38 +586,38 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String appSettings_areaSelectedZoom(int minZoom, int maxZoom) {
- return '选中的区域(缩放至 $minZoom - $maxZoom)';
+ return '已选择区域(缩放至 $minZoom - $maxZoom)';
}
@override
String get appSettings_debugCard => '调试';
@override
- String get appSettings_appDebugLogging => '应用调试日志';
+ String get appSettings_appDebugLogging => '应用程序调试日志';
@override
- String get appSettings_appDebugLoggingSubtitle => '记录应用调试消息以供故障排除';
+ String get appSettings_appDebugLoggingSubtitle => '用于故障排除的日志应用程序调试消息';
@override
- String get appSettings_appDebugLoggingEnabled => '应用调试日志已启用';
+ String get appSettings_appDebugLoggingEnabled => '调试日志已启用';
@override
- String get appSettings_appDebugLoggingDisabled => '应用调试日志已禁用';
+ String get appSettings_appDebugLoggingDisabled => '应用程序调试日志已禁用';
@override
- String get contacts_title => '联系人';
+ String get contacts_title => '联系方式';
@override
- String get contacts_noContacts => '还没有联系人';
+ String get contacts_noContacts => '目前还没有联系人';
@override
- String get contacts_contactsWillAppear => '设备会广播时,联系人会显示';
+ String get contacts_contactsWillAppear => '当设备发布广告时,联系方式会显示。';
@override
String get contacts_searchContacts => '搜索联系人...';
@override
- String get contacts_noUnreadContacts => '未读联系人';
+ String get contacts_noUnreadContacts => '没有未读通讯';
@override
String get contacts_noContactsFound => '未找到任何联系人或群组';
@@ -626,26 +627,26 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String contacts_removeConfirm(String contactName) {
- return '从联系人中删除 $contactName 吗?';
+ return 'Remove $contactName from contacts?';
}
@override
- String get contacts_manageRepeater => '管理重复项';
+ String get contacts_manageRepeater => '管理重复器';
@override
String get contacts_manageRoom => '管理房间服务器';
@override
- String get contacts_roomLogin => '房间登录';
+ String get contacts_roomLogin => '服务器登录';
@override
- String get contacts_openChat => '打开聊天';
+ String get contacts_openChat => '开放聊天';
@override
- String get contacts_editGroup => '编辑组';
+ String get contacts_editGroup => '编辑小组';
@override
- String get contacts_deleteGroup => '删除分组';
+ String get contacts_deleteGroup => '删除群组';
@override
String contacts_deleteGroupConfirm(String groupName) {
@@ -653,50 +654,50 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get contacts_newGroup => '新组';
+ String get contacts_newGroup => '新的团体';
@override
- String get contacts_groupName => '组名';
+ String get contacts_groupName => '团体名称';
@override
- String get contacts_groupNameRequired => '组名不能为空';
+ String get contacts_groupNameRequired => '需要提供组名称';
@override
String contacts_groupAlreadyExists(String name) {
- return '组“$name”已存在';
+ return '名为\"$name\"的组已经存在';
}
@override
String get contacts_filterContacts => '筛选联系人...';
@override
- String get contacts_noContactsMatchFilter => '未找到匹配您的筛选条件的结果';
+ String get contacts_noContactsMatchFilter => '未找到符合您筛选条件的联系人';
@override
String get contacts_noMembers => '没有会员';
@override
- String get contacts_lastSeenNow => '最后一次登录时间现在';
+ String get contacts_lastSeenNow => '最后一次被看到的时间';
@override
String contacts_lastSeenMinsAgo(int minutes) {
- return '最后一次出现 $minutes 分前';
+ return 'Last seen $minutes mins ago';
}
@override
- String get contacts_lastSeenHourAgo => '最后一次出现前1小时';
+ String get contacts_lastSeenHourAgo => '最后一次被看到的时间:1小时前';
@override
String contacts_lastSeenHoursAgo(int hours) {
- return '最后一次出现 $hours 小时前';
+ return 'Last seen $hours hours ago';
}
@override
- String get contacts_lastSeenDayAgo => '最后一次登录前一天';
+ String get contacts_lastSeenDayAgo => '最后一次被看到的时间是1天前';
@override
String contacts_lastSeenDaysAgo(int days) {
- return '最后一次出现 $days 天前';
+ return 'Last seen $days days ago';
}
@override
@@ -706,7 +707,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get channels_noChannelsConfigured => '未配置任何频道';
@override
- String get channels_addPublicChannel => '添加公开频道';
+ String get channels_addPublicChannel => '添加公共频道';
@override
String get channels_searchChannels => '搜索频道...';
@@ -720,19 +721,19 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get channels_hashtagChannel => '话题频道';
+ String get channels_hashtagChannel => '话题标签频道';
@override
- String get channels_public => '公开';
+ String get channels_public => '公众';
@override
- String get channels_private => '私有';
+ String get channels_private => '私人';
@override
- String get channels_publicChannel => '公开频道';
+ String get channels_publicChannel => '公共频道';
@override
- String get channels_privateChannel => '私聊频道';
+ String get channels_privateChannel => '私密频道';
@override
String get channels_editChannel => '编辑频道';
@@ -742,12 +743,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String channels_deleteChannelConfirm(String name) {
- return '删除\"$name\"?此操作无法撤销。';
+ return 'Delete \"$name\"? This cannot be undone.';
}
@override
String channels_channelDeleted(String name) {
- return '频道“$name”已删除';
+ return '删除频道 \"$name\"';
}
@override
@@ -763,23 +764,23 @@ class AppLocalizationsZh extends AppLocalizations {
String get channels_usePublicChannel => '使用公共频道';
@override
- String get channels_standardPublicPsk => '标准公钥共享密钥';
+ String get channels_standardPublicPsk => '标准公共PSK';
@override
String get channels_pskHex => 'PSK (十六进制)';
@override
- String get channels_generateRandomPsk => '生成随机PSK';
+ String get channels_generateRandomPsk => '生成随机的PSK(正交相移键控)';
@override
- String get channels_enterChannelName => '请输入频道名称';
+ String get channels_enterChannelName => '请在此处输入频道名称';
@override
- String get channels_pskMustBe32Hex => 'PSK 必须是 32 个十六进制字符';
+ String get channels_pskMustBe32Hex => 'PSK 必须包含 32 个十六进制字符。';
@override
String channels_channelAdded(String name) {
- return '频道“$name”已添加';
+ return '添加频道 \"$name\"';
}
@override
@@ -792,20 +793,20 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String channels_channelUpdated(String name) {
- return '频道“$name”已更新';
+ return '频道 \"$name\" 已更新';
}
@override
- String get channels_publicChannelAdded => '公共频道已添加';
+ String get channels_publicChannelAdded => '已添加公共频道';
@override
- String get channels_sortBy => '按类型排序';
+ String get channels_sortBy => '按排序';
@override
- String get channels_sortManual => '手动';
+ String get channels_sortManual => '手册';
@override
- String get channels_sortAZ => 'A-Z';
+ String get channels_sortAZ => 'A 到 Z';
@override
String get channels_sortLatestMessages => '最新消息';
@@ -814,10 +815,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get channels_sortUnread => '未读';
@override
- String get channels_createPrivateChannel => '创建私聊频道';
+ String get channels_createPrivateChannel => '创建私密频道';
@override
- String get channels_createPrivateChannelDesc => '使用密钥保护。';
+ String get channels_createPrivateChannelDesc => '使用秘密密钥进行保护。';
@override
String get channels_joinPrivateChannel => '加入私密频道';
@@ -832,48 +833,48 @@ class AppLocalizationsZh extends AppLocalizations {
String get channels_joinPublicChannelDesc => '任何人都可以加入这个频道。';
@override
- String get channels_joinHashtagChannel => '加入标签频道';
+ String get channels_joinHashtagChannel => '加入一个带有特定标签的频道';
@override
- String get channels_joinHashtagChannelDesc => '任何人都可以加入话题频道。';
+ String get channels_joinHashtagChannelDesc => '任何人都可以加入带有特定标签的频道。';
@override
String get channels_scanQrCode => '扫描二维码';
@override
- String get channels_scanQrCodeComingSoon => '即将到来';
+ String get channels_scanQrCodeComingSoon => '即将发布';
@override
String get channels_enterHashtag => '输入标签';
@override
- String get channels_hashtagHint => '例如 #团队';
+ String get channels_hashtagHint => '例如:#团队';
@override
- String get chat_noMessages => '目前还没有消息';
+ String get chat_noMessages => '目前还没有收到任何消息。';
@override
- String get chat_sendMessageToStart => '发送消息开始';
+ String get chat_sendMessageToStart => '发送消息以开始';
@override
- String get chat_originalMessageNotFound => '找不到原始消息';
+ String get chat_originalMessageNotFound => '无法找到原始消息';
@override
String chat_replyingTo(String name) {
- return '回复 $name';
+ return 'Replying to $name';
}
@override
String chat_replyTo(String name) {
- return '回复 $name';
+ return 'Reply to $name';
}
@override
- String get chat_location => '位置';
+ String get chat_location => '地点';
@override
String chat_sendMessageTo(String contactName) {
- return '向$contactName发送消息';
+ return 'Send a message to $contactName';
}
@override
@@ -881,7 +882,7 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String chat_messageTooLong(int maxBytes) {
- return '消息太长了(最大 $maxBytes 字节)。';
+ return '消息内容过长(最大 $maxBytes 字节)。';
}
@override
@@ -891,21 +892,21 @@ class AppLocalizationsZh extends AppLocalizations {
String get chat_messageDeleted => '消息已删除';
@override
- String get chat_retryingMessage => '重试';
+ String get chat_retryingMessage => '重试消息';
@override
String chat_retryCount(int current, int max) {
- return '重试 $current/$max';
+ return 'Retry $current/$max';
}
@override
- String get chat_sendGif => '发送GIF';
+ String get chat_sendGif => '发送 GIF 动画';
@override
String get chat_reply => '回复';
@override
- String get chat_addReaction => '添加反应';
+ String get chat_addReaction => '添加评论';
@override
String get chat_me => '我';
@@ -917,16 +918,16 @@ class AppLocalizationsZh extends AppLocalizations {
String get emojiCategoryGestures => '手势';
@override
- String get emojiCategoryHearts => '心';
+ String get emojiCategoryHearts => '心脏';
@override
- String get emojiCategoryObjects => '对象';
+ String get emojiCategoryObjects => '物体';
@override
- String get gifPicker_title => '选择一个 GIF';
+ String get gifPicker_title => '选择一个 GIF 动画';
@override
- String get gifPicker_searchHint => '搜索GIF...';
+ String get gifPicker_searchHint => '搜索 GIF 动画...';
@override
String get gifPicker_poweredBy => '由 GIPHY 提供支持';
@@ -935,46 +936,46 @@ class AppLocalizationsZh extends AppLocalizations {
String get gifPicker_noGifsFound => '未找到 GIF 动画';
@override
- String get gifPicker_failedLoad => 'GIF 加载失败';
+ String get gifPicker_failedLoad => '无法加载 GIF 动画';
@override
- String get gifPicker_failedSearch => '搜索GIF失败';
+ String get gifPicker_failedSearch => '未能搜索 GIF 动画';
@override
- String get gifPicker_noInternet => '无网络连接';
+ String get gifPicker_noInternet => '没有互联网连接';
@override
- String get debugLog_appTitle => '应用调试日志';
+ String get debugLog_appTitle => '应用程序调试日志';
@override
- String get debugLog_bleTitle => '蓝牙调试日志';
+ String get debugLog_bleTitle => 'BLE 调试日志';
@override
String get debugLog_copyLog => '复制日志';
@override
- String get debugLog_clearLog => '清除日志';
+ String get debugLog_clearLog => '清晰的日志';
@override
String get debugLog_copied => '调试日志已复制';
@override
- String get debugLog_bleCopied => '蓝牙日志复制';
+ String get debugLog_bleCopied => 'BLE 日志已复制';
@override
- String get debugLog_noEntries => '尚未生成调试日志';
+ String get debugLog_noEntries => '目前还没有调试日志';
@override
- String get debugLog_enableInSettings => '启用应用调试日志记录设置';
+ String get debugLog_enableInSettings => '在设置中启用应用程序调试日志功能。';
@override
- String get debugLog_frames => '帧';
+ String get debugLog_frames => '框架';
@override
String get debugLog_rawLogRx => '原始日志-RX';
@override
- String get debugLog_noBleActivity => '目前还没有蓝牙活动。';
+ String get debugLog_noBleActivity => '目前尚未有蓝牙低功耗(BLE)活动。';
@override
String debugFrame_length(int count) {
@@ -987,16 +988,16 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get debugFrame_textMessageHeader => '短信框';
+ String get debugFrame_textMessageHeader => '短信模板:';
@override
String debugFrame_destinationPubKey(String pubKey) {
- return '- 目的地公钥:$pubKey';
+ return '- 目标公钥:$pubKey';
}
@override
String debugFrame_timestamp(int timestamp) {
- return '- 时间戳:$timestamp';
+ return '- Timestamp: $timestamp';
}
@override
@@ -1006,22 +1007,22 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String debugFrame_textType(int type, String label) {
- return '- 文本类型:$type ($label)';
+ return '- Text Type: $type ($label)';
}
@override
- String get debugFrame_textTypeCli => 'CLI';
+ String get debugFrame_textTypeCli => '命令行界面';
@override
- String get debugFrame_textTypePlain => '简洁';
+ String get debugFrame_textTypePlain => '简单';
@override
String debugFrame_text(String text) {
- return '- 文本:\"$text\"';
+ return '- 文本:“$text”';
}
@override
- String get debugFrame_hexDump => '十六进制数据';
+ String get debugFrame_hexDump => '十六进制数据:';
@override
String get chat_pathManagement => '路径管理';
@@ -1030,30 +1031,30 @@ class AppLocalizationsZh extends AppLocalizations {
String get chat_routingMode => '路由模式';
@override
- String get chat_autoUseSavedPath => '自动(使用已保存路径)';
+ String get chat_autoUseSavedPath => '自动(使用已保存的路径)';
@override
String get chat_forceFloodMode => '强制洪水模式';
@override
- String get chat_recentAckPaths => '最近的 ACK 路径 (点击以使用):';
+ String get chat_recentAckPaths => '最近使用的 ACK 路径(点击使用):';
@override
- String get chat_pathHistoryFull => '路径历史已满。删除条目以添加新条目。';
+ String get chat_pathHistoryFull => '路径历史已满。删除条目以添加新的条目。';
@override
- String get chat_hopSingular => '跳转';
+ String get chat_hopSingular => '跳跃';
@override
- String get chat_hopPlural => '跳跃';
+ String get chat_hopPlural => '啤酒花';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
- other: '跳跃',
- one: '跳跃',
+ other: 'hops',
+ one: 'hop',
);
return '$count $_temp0';
}
@@ -1065,7 +1066,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get chat_removePath => '删除路径';
@override
- String get chat_noPathHistoryYet => '还没有历史记录。\n发送消息以发现路径。';
+ String get chat_noPathHistoryYet => '目前还没有历史记录。\n发送消息以查找路径。';
@override
String get chat_pathActions => '路径操作:';
@@ -1077,25 +1078,25 @@ class AppLocalizationsZh extends AppLocalizations {
String get chat_setCustomPathSubtitle => '手动指定路由路径';
@override
- String get chat_clearPath => '清除路径';
+ String get chat_clearPath => '明确的道路';
@override
- String get chat_clearPathSubtitle => '强制下次发送时重新发现';
+ String get chat_clearPathSubtitle => '在下一次发送时,重新尝试。';
@override
- String get chat_pathCleared => '路径已清除。下一条消息将重新发现路线。';
+ String get chat_pathCleared => '路径已清理。下一条消息将重新确定路线。';
@override
- String get chat_floodModeSubtitle => '使用应用栏中的路由切换开关';
+ String get chat_floodModeSubtitle => '使用应用程序栏中的路由切换功能';
@override
- String get chat_floodModeEnabled => '防洪模式已启用。通过应用程序栏中的路由图标进行反转。';
+ String get chat_floodModeEnabled => '防洪模式已启用。通过应用程序栏中的路由图标进行切换。';
@override
String get chat_fullPath => '完整路径';
@override
- String get chat_pathDetailsNotAvailable => '路径详情尚未获取。请尝试发送消息以刷新。';
+ String get chat_pathDetailsNotAvailable => '路径信息尚未提供。请尝试发送消息以刷新。';
@override
String chat_pathSetHops(int hopCount, String status) {
@@ -1105,20 +1106,20 @@ class AppLocalizationsZh extends AppLocalizations {
other: 'hops',
one: 'hop',
);
- return '路径设置:$hopCount $_temp0 - $status';
+ return 'Path set: $hopCount $_temp0 - $status';
}
@override
- String get chat_pathSavedLocally => '已本地保存。连接以同步。';
+ String get chat_pathSavedLocally => '已本地保存。连接以进行同步。';
@override
String get chat_pathDeviceConfirmed => '设备已确认。';
@override
- String get chat_pathDeviceNotConfirmed => '设备尚未确认。';
+ String get chat_pathDeviceNotConfirmed => '该设备尚未得到确认。';
@override
- String get chat_type => '输入';
+ String get chat_type => '类型';
@override
String get chat_path => '路径';
@@ -1130,64 +1131,64 @@ class AppLocalizationsZh extends AppLocalizations {
String get chat_compressOutgoingMessages => '压缩发送的消息';
@override
- String get chat_floodForced => '强制溢出';
+ String get chat_floodForced => '洪水(被迫)';
@override
- String get chat_directForced => '强制直接';
+ String get chat_directForced => '直接(强制性的)';
@override
String chat_hopsForced(int count) {
- return '$count 次跳跃 (强制)';
+ return '$count 根啤酒花(人工种植)';
}
@override
- String get chat_floodAuto => '自动防洪';
+ String get chat_floodAuto => '自动洪水';
@override
String get chat_direct => '直接';
@override
- String get chat_poiShared => '共享位置信息';
+ String get chat_poiShared => '共享位置';
@override
String chat_unread(int count) {
- return '未读:$count';
+ return 'Unread: $count';
}
@override
String get chat_openLink => '打开链接?';
@override
- String get chat_openLinkConfirmation => '您想在浏览器中打开此链接吗?';
+ String get chat_openLinkConfirmation => '您想用浏览器打开这个链接吗?';
@override
- String get chat_open => '打开';
+ String get chat_open => '开放';
@override
String chat_couldNotOpenLink(String url) {
- return '无法打开链接:$url';
+ return '[保存:$url]\n无法打开链接:$url';
}
@override
- String get chat_invalidLink => '链接格式无效';
+ String get chat_invalidLink => '无效的链接格式';
@override
- String get map_title => '节点地图';
+ String get map_title => '节点图';
@override
- String get map_noNodesWithLocation => '没有具有位置数据的节点';
+ String get map_noNodesWithLocation => '没有包含位置信息的节点';
@override
- String get map_nodesNeedGps => '节点需要共享它们的 GPS 坐标\n才能在地图上显示';
+ String get map_nodesNeedGps => '节点需要共享其 GPS 坐标,以便在地图上显示';
@override
String map_nodesCount(int count) {
- return '节点:$count';
+ return 'Nodes: $count';
}
@override
String map_pinsCount(int count) {
- return '针:$count';
+ return 'Pins: $count';
}
@override
@@ -1203,16 +1204,16 @@ class AppLocalizationsZh extends AppLocalizations {
String get map_sensor => '传感器';
@override
- String get map_pinDm => '私信 (DM)';
+ String get map_pinDm => 'PIN (直接消息)';
@override
- String get map_pinPrivate => '私密模式';
+ String get map_pinPrivate => '私密';
@override
- String get map_pinPublic => '公开(公版)';
+ String get map_pinPublic => '公开';
@override
- String get map_lastSeen => '最后一次登录';
+ String get map_lastSeen => '最后一次被看到';
@override
String get map_disconnectConfirm => '您确定要断开与此设备的连接吗?';
@@ -1227,19 +1228,19 @@ class AppLocalizationsZh extends AppLocalizations {
String get map_flags => '旗帜';
@override
- String get map_shareMarkerHere => '分享标记在此';
+ String get map_shareMarkerHere => '在此分享标记';
@override
- String get map_pinLabel => '固定标签';
+ String get map_pinLabel => '标签';
@override
String get map_label => '标签';
@override
- String get map_pointOfInterest => '兴趣点';
+ String get map_pointOfInterest => '值得参观的地方';
@override
- String get map_sendToContact => '发送给联系人';
+ String get map_sendToContact => '发送给联系';
@override
String get map_sendToChannel => '发送到频道';
@@ -1248,18 +1249,18 @@ class AppLocalizationsZh extends AppLocalizations {
String get map_noChannelsAvailable => '没有可用的频道';
@override
- String get map_publicLocationShare => '公共位置共享';
+ String get map_publicLocationShare => '公共场所共享';
@override
String map_publicLocationShareConfirm(String channelLabel) {
- return '您即将分享一个位置在 $channelLabel。此频道公开,任何拥有 PSK 的人都可以看到它。';
+ return '[保存:$channelLabel]\n您即将分享一个位置,该位置位于 $channelLabel。 此频道是公开的,任何拥有 PSK 的人都可以看到它。';
}
@override
String get map_connectToShareMarkers => '连接设备以共享标记';
@override
- String get map_filterNodes => '筛选节点';
+ String get map_filterNodes => '过滤节点';
@override
String get map_nodeTypes => '节点类型';
@@ -1274,10 +1275,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get map_otherNodes => '其他节点';
@override
- String get map_keyPrefix => '键前缀';
+ String get map_keyPrefix => '关键前缀';
@override
- String get map_filterByKeyPrefix => '按关键词前缀筛选';
+ String get map_filterByKeyPrefix => '按关键前缀筛选';
@override
String get map_publicKeyPrefix => '公钥前缀';
@@ -1289,32 +1290,32 @@ class AppLocalizationsZh extends AppLocalizations {
String get map_showSharedMarkers => '显示共享标记';
@override
- String get map_lastSeenTime => '最后一次查看时间';
+ String get map_lastSeenTime => '最后一次被看到的时间';
@override
- String get map_sharedPin => '共享 PIN';
+ String get map_sharedPin => '共享密码';
@override
String get map_joinRoom => '加入房间';
@override
- String get map_manageRepeater => '管理重复项';
+ String get map_manageRepeater => '管理重复器';
@override
String get mapCache_title => '离线地图缓存';
@override
- String get mapCache_selectAreaFirst => '选择一个区域进行缓存';
+ String get mapCache_selectAreaFirst => '选择一个用于缓存的区域';
@override
- String get mapCache_noTilesToDownload => '该区域没有可下载的瓦片。';
+ String get mapCache_noTilesToDownload => '此区域没有可下载的瓦片。';
@override
- String get mapCache_downloadTilesTitle => '下载瓦片';
+ String get mapCache_downloadTilesTitle => '下载瓷砖';
@override
String mapCache_downloadTilesPrompt(int count) {
- return '下载 $count 个瓦片用于离线使用?';
+ return '[保存:$count]\n下载 $count 个图片用于离线使用?';
}
@override
@@ -1322,19 +1323,19 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String mapCache_cachedTiles(int count) {
- return '已缓存 $count 个瓦片';
+ return '缓存 $count 个瓦片';
}
@override
String mapCache_cachedTilesWithFailed(int downloaded, int failed) {
- return '已缓存 $downloaded 个瓦片 ($failed 失败)';
+ return 'Cached $downloaded tiles ($failed failed)';
}
@override
String get mapCache_clearOfflineCacheTitle => '清除离线缓存';
@override
- String get mapCache_clearOfflineCachePrompt => '删除所有缓存地图瓦片?';
+ String get mapCache_clearOfflineCachePrompt => '清除所有缓存的地图瓦片';
@override
String get mapCache_offlineCacheCleared => '离线缓存已清除';
@@ -1349,27 +1350,27 @@ class AppLocalizationsZh extends AppLocalizations {
String get mapCache_useCurrentView => '使用当前视图';
@override
- String get mapCache_zoomRange => '缩放范围';
+ String get mapCache_zoomRange => '变焦范围';
@override
String mapCache_estimatedTiles(int count) {
- return '预计瓦片数量:$count';
+ return 'Estimated tiles: $count';
}
@override
String mapCache_downloadedTiles(int completed, int total) {
- return '已下载 $completed / $total';
+ return 'Downloaded $completed / $total';
}
@override
- String get mapCache_downloadTilesButton => '下载瓦片';
+ String get mapCache_downloadTilesButton => '下载瓷砖';
@override
String get mapCache_clearCacheButton => '清除缓存';
@override
String mapCache_failedDownloads(int count) {
- return '下载失败:$count';
+ return 'Failed downloads: $count';
}
@override
@@ -1379,7 +1380,7 @@ class AppLocalizationsZh extends AppLocalizations {
String east,
String west,
) {
- return '北 $north, 南 $south, 东 $east, 西 $west';
+ return 'N $north, S $south, E $east, W $west';
}
@override
@@ -1387,17 +1388,17 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String time_minutesAgo(int minutes) {
- return '$minutes分钟前';
+ return '${minutes}m ago';
}
@override
String time_hoursAgo(int hours) {
- return '$hours小时前';
+ return '${hours}h ago';
}
@override
String time_daysAgo(int days) {
- return '$days 天前';
+ return '$days天前';
}
@override
@@ -1407,16 +1408,16 @@ class AppLocalizationsZh extends AppLocalizations {
String get time_hours => '小时';
@override
- String get time_day => '今天';
+ String get time_day => '一天';
@override
String get time_days => '天';
@override
- String get time_week => '本周';
+ String get time_week => '一周';
@override
- String get time_weeks => '几周';
+ String get time_weeks => '周';
@override
String get time_month => '月份';
@@ -1440,7 +1441,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get login_repeaterLogin => '重复登录';
@override
- String get login_roomLogin => '房间登录';
+ String get login_roomLogin => '服务器登录';
@override
String get login_password => '密码';
@@ -1452,13 +1453,13 @@ class AppLocalizationsZh extends AppLocalizations {
String get login_savePassword => '保存密码';
@override
- String get login_savePasswordSubtitle => '密码将安全地存储在这个设备上';
+ String get login_savePasswordSubtitle => '密码将安全地存储在 данном设备上';
@override
- String get login_repeaterDescription => '输入重复密码以访问设置和状态。';
+ String get login_repeaterDescription => '输入重复器密码,即可访问设置和状态。';
@override
- String get login_roomDescription => '输入房间密码以访问设置和状态。';
+ String get login_roomDescription => '输入密码进入房间,即可访问设置和状态。';
@override
String get login_routing => '路由';
@@ -1467,7 +1468,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get login_routingMode => '路由模式';
@override
- String get login_autoUseSavedPath => '自动(使用已保存路径)';
+ String get login_autoUseSavedPath => '自动(使用已保存的路径)';
@override
String get login_forceFloodMode => '强制洪水模式';
@@ -1480,26 +1481,26 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String login_attempt(int current, int max) {
- return '尝试 $current/$max';
+ return 'Attempt $current/$max';
}
@override
String login_failed(String error) {
- return '登录失败:$error';
+ return 'Login failed: $error';
}
@override
- String get login_failedMessage => '登录失败。密码不正确或中继器不可达。';
+ String get login_failedMessage => '登录失败。可能是密码错误,也可能是无法连接到服务器。';
@override
String get common_reload => '重新加载';
@override
- String get common_clear => '清除';
+ String get common_clear => '清晰';
@override
String path_currentPath(String path) {
- return '当前路径:$path';
+ return 'Current path: $path';
}
@override
@@ -1510,7 +1511,7 @@ class AppLocalizationsZh extends AppLocalizations {
other: 'hops',
one: 'hop',
);
- return '使用 $count $_temp0 路径';
+ return '使用 $count $_temp0 条路径';
}
@override
@@ -1520,29 +1521,29 @@ class AppLocalizationsZh extends AppLocalizations {
String get path_currentPathLabel => '当前路径';
@override
- String get path_hexPrefixInstructions => '输入2个字符的十六进制前缀,每个前缀之间用逗号分隔。';
+ String get path_hexPrefixInstructions => '请输入每个跳跃步骤的 2 个字符的十六进制前缀,用逗号分隔。';
@override
- String get path_hexPrefixExample => 'A1,F2,3C (每个节点使用其公钥的第一字节)';
+ String get path_hexPrefixExample => '例如:A1, F2, 3C (每个节点使用其公钥的第一字节)';
@override
- String get path_labelHexPrefixes => '十六进制前缀';
+ String get path_labelHexPrefixes => '路径(十六进制前缀)';
@override
- String get path_helperMaxHops => '最大 64 步跳。每个前缀是 2 个十六进制字符(1 字节)';
+ String get path_helperMaxHops => '最大 64 个“hop”(跳跃)。每个前缀由 2 个十六进制字符(1 字节)组成。';
@override
- String get path_selectFromContacts => '或从联系人中选择:';
+ String get path_selectFromContacts => '或者从联系人列表中选择:';
@override
- String get path_noRepeatersFound => '未找到任何重复器或房间服务器。';
+ String get path_noRepeatersFound => '未找到任何重复设备或房间服务器。';
@override
- String get path_customPathsRequire => '自定义路径需要中间跳转,这些跳转可以传递消息。';
+ String get path_customPathsRequire => '自定义路径需要中间节点,这些节点可以转发消息。';
@override
String path_invalidHexPrefixes(String prefixes) {
- return '无效的十六进制前缀:$prefixes';
+ return 'Invalid hex prefixes: $prefixes';
}
@override
@@ -1555,7 +1556,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_management => '重复器管理';
@override
- String get room_management => '房间服务器管理';
+ String get room_management => '服务器管理';
@override
String get repeater_managementTools => '管理工具';
@@ -1567,22 +1568,22 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_statusSubtitle => '查看重复器状态、统计信息和邻居';
@override
- String get repeater_telemetry => '遥测';
+ String get repeater_telemetry => '远程监控';
@override
- String get repeater_telemetrySubtitle => '查看传感器和系统状态的Telemetry数据';
+ String get repeater_telemetrySubtitle => '查看传感器和系统状态的数据。';
@override
- String get repeater_cli => 'CLI';
+ String get repeater_cli => '命令行界面';
@override
- String get repeater_cliSubtitle => '发送命令到重复器';
+ String get repeater_cliSubtitle => '向复用器发送指令';
@override
String get repeater_neighbours => '邻居';
@override
- String get repeater_neighboursSubtitle => '查看零跳邻居。';
+ String get repeater_neighboursSubtitle => '查看邻居节点(无需中间节点)。';
@override
String get repeater_settings => '设置';
@@ -1597,7 +1598,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_routingMode => '路由模式';
@override
- String get repeater_autoUseSavedPath => '自动(使用已保存路径)';
+ String get repeater_autoUseSavedPath => '自动(使用已保存的路径)';
@override
String get repeater_forceFloodMode => '强制洪水模式';
@@ -1606,14 +1607,14 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_pathManagement => '路径管理';
@override
- String get repeater_refresh => '刷新';
+ String get repeater_refresh => '更新';
@override
String get repeater_statusRequestTimeout => '状态请求超时。';
@override
String repeater_errorLoadingStatus(String error) {
- return '错误加载状态:$error';
+ return 'Error loading status: $error';
}
@override
@@ -1623,10 +1624,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_battery => '电池';
@override
- String get repeater_clockAtLogin => '时间 (登录时)';
+ String get repeater_clockAtLogin => '登录时的时间';
@override
- String get repeater_uptime => '可用时间';
+ String get repeater_uptime => '正常运行时间';
@override
String get repeater_queueLength => '排队长度';
@@ -1635,28 +1636,28 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_debugFlags => '调试标志';
@override
- String get repeater_radioStatistics => '无线电统计';
+ String get repeater_radioStatistics => '广播统计';
@override
- String get repeater_lastRssi => '上次RSSI';
+ String get repeater_lastRssi => '上次的 RSSI 值';
@override
- String get repeater_lastSnr => '最后 SNR';
+ String get repeater_lastSnr => '最后一次信噪比';
@override
- String get repeater_noiseFloor => '噪声地板';
+ String get repeater_noiseFloor => '噪声水平';
@override
- String get repeater_txAirtime => 'TX Airtime';
+ String get repeater_txAirtime => 'TX 频道预留时间';
@override
- String get repeater_rxAirtime => 'RX Airtime';
+ String get repeater_rxAirtime => 'RX 空时';
@override
String get repeater_packetStatistics => '数据包统计';
@override
- String get repeater_sent => '已发送';
+ String get repeater_sent => '发送';
@override
String get repeater_received => '已收到';
@@ -1676,26 +1677,26 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String repeater_packetTxTotal(int total, String flood, String direct) {
- return '总计:$total, 洪流:$flood, 直连:$direct';
+ return 'Total: $total, Flood: $flood, Direct: $direct';
}
@override
String repeater_packetRxTotal(int total, String flood, String direct) {
- return '总计:$total, 洪流:$flood, 直连:$direct';
+ return 'Total: $total, Flood: $flood, Direct: $direct';
}
@override
String repeater_duplicatesFloodDirect(String flood, String direct) {
- return '洪水:$flood, 直通:$direct';
+ return 'Flood: $flood, Direct: $direct';
}
@override
String repeater_duplicatesTotal(int total) {
- return '总计:$total';
+ return 'Total: $total';
}
@override
- String get repeater_settingsTitle => '重复设置';
+ String get repeater_settingsTitle => '重复器设置';
@override
String get repeater_basicSettings => '基本设置';
@@ -1704,7 +1705,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_repeaterName => '重复器名称';
@override
- String get repeater_repeaterNameHelper => '显示此重复器的名称';
+ String get repeater_repeaterNameHelper => '此复播器的显示名称';
@override
String get repeater_adminPassword => '管理员密码';
@@ -1719,16 +1720,16 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_guestPasswordHelper => '只读访问密码';
@override
- String get repeater_radioSettings => '射频设置';
+ String get repeater_radioSettings => '收音机设置';
@override
String get repeater_frequencyMhz => '频率 (MHz)';
@override
- String get repeater_frequencyHelper => '300-2500 MHz';
+ String get repeater_frequencyHelper => '300-2500 兆赫';
@override
- String get repeater_txPower => 'TX Power';
+ String get repeater_txPower => 'TX 功率';
@override
String get repeater_txPowerHelper => '1-30 dBm';
@@ -1737,7 +1738,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_bandwidth => '带宽';
@override
- String get repeater_spreadingFactor => '扩散因子';
+ String get repeater_spreadingFactor => '传播系数';
@override
String get repeater_codingRate => '编码速率';
@@ -1749,56 +1750,56 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_latitude => '纬度';
@override
- String get repeater_latitudeHelper => '十进度的数字(例如:37.7749)';
+ String get repeater_latitudeHelper => '十进制度(例如:37.7749)';
@override
String get repeater_longitude => '经度';
@override
- String get repeater_longitudeHelper => '十进度的数字(例如:-122.4194)';
+ String get repeater_longitudeHelper => '十进制度(例如:-122.4194)';
@override
- String get repeater_features => '功能';
+ String get repeater_features => '特点';
@override
String get repeater_packetForwarding => '数据包转发';
@override
- String get repeater_packetForwardingSubtitle => '启用重复器以转发数据包';
+ String get repeater_packetForwardingSubtitle => '启用重复器,使其能够转发数据包';
@override
String get repeater_guestAccess => '访客访问';
@override
- String get repeater_guestAccessSubtitle => '允许访客仅读访问';
+ String get repeater_guestAccessSubtitle => '允许访客仅限读取权限';
@override
String get repeater_privacyMode => '隐私模式';
@override
- String get repeater_privacyModeSubtitle => '隐藏在广告中的姓名/位置';
+ String get repeater_privacyModeSubtitle => '在广告中隐藏姓名/位置';
@override
String get repeater_advertisementSettings => '广告设置';
@override
- String get repeater_localAdvertInterval => '本地广告间隔';
+ String get repeater_localAdvertInterval => '本地广告投放时间段';
@override
String repeater_localAdvertIntervalMinutes(int minutes) {
- return '$minutes 分钟';
+ return '$minutes minutes';
}
@override
- String get repeater_floodAdvertInterval => '洪水广告间隔';
+ String get repeater_floodAdvertInterval => '洪水广告播放间隔';
@override
String repeater_floodAdvertIntervalHours(int hours) {
- return '$hours 小时';
+ return '$hours hours';
}
@override
- String get repeater_encryptedAdvertInterval => '加密广告间隔';
+ String get repeater_encryptedAdvertInterval => '加密的广告投放时间段';
@override
String get repeater_dangerZone => '危险区域';
@@ -1807,10 +1808,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_rebootRepeater => '重启重复器';
@override
- String get repeater_rebootRepeaterSubtitle => '重启重复器设备';
+ String get repeater_rebootRepeaterSubtitle => '重新启动重复器设备';
@override
- String get repeater_rebootRepeaterConfirm => '您确定要重启这个中继器吗?';
+ String get repeater_rebootRepeaterConfirm => '您确定要重新启动这个中继器吗?';
@override
String get repeater_regenerateIdentityKey => '重新生成身份密钥';
@@ -1819,7 +1820,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_regenerateIdentityKeySubtitle => '生成新的公钥/私钥对';
@override
- String get repeater_regenerateIdentityKeyConfirm => '这将生成一个重复器的新身份。继续吗?';
+ String get repeater_regenerateIdentityKeyConfirm => '这将为复用器生成一个新的身份。继续吗?';
@override
String get repeater_eraseFileSystem => '删除文件系统';
@@ -1828,109 +1829,109 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_eraseFileSystemSubtitle => '格式化重复文件系统';
@override
- String get repeater_eraseFileSystemConfirm => '警告:这将擦除重复器上的所有数据。 这无法撤销!';
+ String get repeater_eraseFileSystemConfirm => '警告:此操作将清除复用器上的所有数据。 无法恢复!';
@override
- String get repeater_eraseSerialOnly => '通过串行控制台才能删除。';
+ String get repeater_eraseSerialOnly => '“Erase”功能仅可通过串行控制台使用。';
@override
String repeater_commandSent(String command) {
- return '命令已发送:$command';
+ return 'Command sent: $command';
}
@override
String repeater_errorSendingCommand(String error) {
- return '发送命令时出错:$error';
+ return 'Error sending command: $error';
}
@override
String get repeater_confirm => '确认';
@override
- String get repeater_settingsSaved => '设置已保存成功';
+ String get repeater_settingsSaved => '设置已成功保存';
@override
String repeater_errorSavingSettings(String error) {
- return '保存设置出错:$error';
+ return 'Error saving settings: $error';
}
@override
- String get repeater_refreshBasicSettings => '刷新基本设置';
+ String get repeater_refreshBasicSettings => '重置基本设置';
@override
- String get repeater_refreshRadioSettings => '刷新无线电设置';
+ String get repeater_refreshRadioSettings => '重置收音机设置';
@override
- String get repeater_refreshTxPower => '刷新 TX 电量';
+ String get repeater_refreshTxPower => '重置 TX 电源';
@override
- String get repeater_refreshLocationSettings => '刷新位置设置';
+ String get repeater_refreshLocationSettings => '重置位置设置';
@override
String get repeater_refreshPacketForwarding => '刷新包转发';
@override
- String get repeater_refreshGuestAccess => '刷新访客访问';
+ String get repeater_refreshGuestAccess => '重新获取访客访问权限';
@override
- String get repeater_refreshPrivacyMode => '刷新隐私模式';
+ String get repeater_refreshPrivacyMode => '重置隐私模式';
@override
- String get repeater_refreshAdvertisementSettings => '刷新广告设置';
+ String get repeater_refreshAdvertisementSettings => '重置广告设置';
@override
String repeater_refreshed(String label) {
- return '$label 已刷新';
+ return '$label refreshed';
}
@override
String repeater_errorRefreshing(String label) {
- return '刷新 $label 时出错';
+ return '[保存:$label]\n刷新 $label 时出错';
}
@override
- String get repeater_cliTitle => '重复器命令行工具';
+ String get repeater_cliTitle => '重复器命令行界面';
@override
- String get repeater_debugNextCommand => '调试下一步命令';
+ String get repeater_debugNextCommand => '调试下一条命令';
@override
String get repeater_commandHelp => '帮助';
@override
- String get repeater_clearHistory => '清除历史';
+ String get repeater_clearHistory => '清晰的历史';
@override
- String get repeater_noCommandsSent => '尚未发送任何命令';
+ String get repeater_noCommandsSent => '尚未发送任何指令';
@override
- String get repeater_typeCommandOrUseQuick => '输入命令或使用快捷命令';
+ String get repeater_typeCommandOrUseQuick => '在下方输入命令,或使用快捷命令。';
@override
String get repeater_enterCommandHint => '输入命令...';
@override
- String get repeater_previousCommand => '上一个命令';
+ String get repeater_previousCommand => '之前的命令';
@override
- String get repeater_nextCommand => '下一步命令';
+ String get repeater_nextCommand => '下一个指令';
@override
- String get repeater_enterCommandFirst => '请输入一个命令';
+ String get repeater_enterCommandFirst => '首先输入一个命令';
@override
- String get repeater_cliCommandFrameTitle => 'CLI 命令窗口';
+ String get repeater_cliCommandFrameTitle => 'CLI 命令框架';
@override
String repeater_cliCommandError(String error) {
- return '错误:$error';
+ return 'Error: $error';
}
@override
String get repeater_cliQuickGetName => '获取姓名';
@override
- String get repeater_cliQuickGetRadio => '获取收音机';
+ String get repeater_cliQuickGetRadio => '收听广播';
@override
String get repeater_cliQuickGetTx => '获取 TX';
@@ -1942,196 +1943,199 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_cliQuickVersion => '版本';
@override
- String get repeater_cliQuickAdvertise => '发布';
+ String get repeater_cliQuickAdvertise => '发布广告';
@override
String get repeater_cliQuickClock => '时钟';
@override
- String get repeater_cliHelpAdvert => '发送广告包';
+ String get repeater_cliHelpAdvert => '发送广告资料包';
@override
- String get repeater_cliHelpReboot => '重启设备。(请注意,可能会出现“超时”现象,这是正常现象)';
+ String get repeater_cliHelpReboot => '重置设备。 (请注意,您可能会收到“超时”错误,这是正常的现象)';
@override
String get repeater_cliHelpClock => '显示每个设备的当前时间。';
@override
- String get repeater_cliHelpPassword => '设置设备的新管理员密码。';
+ String get repeater_cliHelpPassword => '为设备设置新的管理员密码。';
@override
String get repeater_cliHelpVersion => '显示设备版本和固件构建日期。';
@override
- String get repeater_cliHelpClearStats => '重置各种统计数值为零。';
+ String get repeater_cliHelpClearStats => '重置各种统计指标,将其设置为零。';
@override
- String get repeater_cliHelpSetAf => '设置空闲时间因子。';
+ String get repeater_cliHelpSetAf => '设置时间因素。';
@override
- String get repeater_cliHelpSetTx => '设置 LoRa 传输功率 (重置生效)';
+ String get repeater_cliHelpSetTx =>
+ '设置 LoRa 传输功率,单位为 dBm (相对于参考值)。 (重启以应用更改)';
@override
- String get repeater_cliHelpSetRepeat => '启用或禁用此节点的重复器角色。';
+ String get repeater_cliHelpSetRepeat => '启用或禁用此节点的重复器功能。';
@override
String get repeater_cliHelpSetAllowReadOnly =>
- '(房间服务器) 如果“开”了,则空密码登录将被允许,但不能向房间发布内容。(仅限读取)';
+ '(房间服务器)如果设置为“开启”,则允许使用空密码登录,但无法向房间发送消息(只能进行读取)。';
@override
- String get repeater_cliHelpSetFloodMax => '设置最大换路包数量(如果 >= 最大,则不转发包)。';
+ String get repeater_cliHelpSetFloodMax => '设置最大传入数据包的跳数(如果大于或等于最大值,则不进行转发)。';
@override
String get repeater_cliHelpSetIntThresh =>
- '设置干扰阈值(以 dB 为单位)。默认值为 14。将设置为 0 以禁用通道干扰检测。';
+ '设置干扰阈值(以dB为单位)。默认值为14。将设置为0以禁用频道干扰检测。';
@override
String get repeater_cliHelpSetAgcResetInterval =>
- '设置间隔以重置自动增益控制器。将设置为 0 以禁用。';
+ '设置间隔时间,用于重置自动增益控制器。设置为 0 以禁用。';
@override
- String get repeater_cliHelpSetMultiAcks => '启用或禁用“双 ACKs”功能。';
+ String get repeater_cliHelpSetMultiAcks => '启用或禁用“双重确认”功能。';
@override
String get repeater_cliHelpSetAdvertInterval =>
- '设置定时器间隔时间为分钟,以发送本地(零跳)广告包。将设置为0以禁用。';
+ '设置定时器间隔,单位为分钟,用于发送本地(无中继)的广告数据包。 将设置为 0 以禁用。';
@override
String get repeater_cliHelpSetFloodAdvertInterval =>
- '设置定时器间隔时间为小时,以发送洪水广告包。将设置为 0 以禁用。';
+ '设置定时器间隔时间为小时,以便发送广告信息包。将设置为 0 以禁用。';
@override
String get repeater_cliHelpSetGuestPassword =>
- '设置/更新客人密码。(对于重复器,客人在登录时可以发送“获取统计”请求)';
+ '设置/更新访客密码。 (对于访客,登录请求可以发送“获取统计”请求)';
@override
String get repeater_cliHelpSetName => '设置广告名称。';
@override
- String get repeater_cliHelpSetLat => '设置广告地图纬度(十进制度)';
+ String get repeater_cliHelpSetLat => '设置广告地图的纬度。(以十进制表示)';
@override
- String get repeater_cliHelpSetLon => '设置广告地图经度 (十进位)';
+ String get repeater_cliHelpSetLon => '设置广告地图的经度。 (十进制度)';
@override
- String get repeater_cliHelpSetRadio => '设置全新的无线电参数,并保存到偏好设置。需要执行“重启”命令才能应用。';
+ String get repeater_cliHelpSetRadio => '完全重新设置无线电参数,并保存到偏好设置。需要执行“重启”命令才能生效。';
@override
String get repeater_cliHelpSetRxDelay =>
- '设置(实验性)的基础(必须大于 1 才能生效)是用于对接收到的数据包应用轻微延迟,基于信号强度/得分。将设置为 0 以禁用。';
+ '设置(实验性):设置一个基础值(必须大于1才能生效),用于对接收到的数据包进行轻微延迟处理,该延迟值基于信号强度/评分。将该值设置为0以禁用。';
@override
String get repeater_cliHelpSetTxDelay =>
- '设置一个与时间-在空气中(time-on-air)的系数,用于洪水模式的数据包,并结合随机插槽系统,以延迟其转发。(以降低碰撞的可能性)';
+ '通过将一个因子与“浮动模式”数据包的时间在空中停留时间相乘,并结合随机的“时隙”系统,来延迟其转发,从而降低数据包冲突的概率。';
@override
String get repeater_cliHelpSetDirectTxDelay =>
- '与txdelay相同,但用于为直接模式包的转发应用随机延迟。';
+ '与txdelay相同,但用于对直接模式数据包的转发进行随机延迟。';
@override
- String get repeater_cliHelpSetBridgeEnabled => '启用/禁用桥梁';
+ String get repeater_cliHelpSetBridgeEnabled => '启用/禁用桥接。';
@override
- String get repeater_cliHelpSetBridgeDelay => '设置在重新发送数据包之前延迟时间。';
+ String get repeater_cliHelpSetBridgeDelay => '在重新发送数据包之前,设置延迟时间。';
@override
- String get repeater_cliHelpSetBridgeSource => '选择桥梁是否会重传接收到的数据包或发送的数据包。';
+ String get repeater_cliHelpSetBridgeSource => '选择桥接器是否会转发收到的数据包,还是转发发送的数据包。';
@override
- String get repeater_cliHelpSetBridgeBaud => '设置rs232桥接的串口链路波特率。';
+ String get repeater_cliHelpSetBridgeBaud => '为 RS232 桥接设置串行链路的波特率。';
@override
- String get repeater_cliHelpSetBridgeSecret => '设置 espnow 桥的秘密。';
+ String get repeater_cliHelpSetBridgeSecret => '设置 ESPNOW 桥的秘密。';
@override
- String get repeater_cliHelpSetAdcMultiplier => '设置自定义因子以调整报告的电池电压(仅限部分板卡支持)。';
+ String get repeater_cliHelpSetAdcMultiplier =>
+ '设置自定义因子,用于调整报告的电池电压(仅在特定板上支持)。';
@override
String get repeater_cliHelpTempRadio =>
- '设置临时无线电参数,持续指定的分钟数,之后恢复为原始无线电参数。(不保存到偏好设置)。';
+ '设置临时收音机参数,持续指定分钟数,之后恢复到原始收音机参数。(不保存到偏好设置)。';
@override
String get repeater_cliHelpSetPerm =>
- '修改ACL。如果“权限”为零,则删除匹配的条目(通过pubkey前缀)。如果pubkey-hex的完整长度且当前不在ACL中,则添加新条目。通过匹配pubkey前缀更新条目。权限位因固件角色而异,但低2位为:0(Guest)、1(只读)、2(读写)、3(Admin)';
+ '修改 ACL。如果 \"permissions\" 的值为 0,则删除与 pubkey 相关的条目。如果 pubkey-hex 完整且当前不在 ACL 中,则添加新的条目。通过匹配 pubkey 相关的前缀来更新条目。不同固件角色的权限位有所不同,但低 2 位分别对应:0 (访客)、1 (只读)、2 (读写)、3 (管理员)。';
@override
- String get repeater_cliHelpGetBridgeType => '获取桥接类型:无,RS232,ESPNow';
+ String get repeater_cliHelpGetBridgeType => '支持桥接模式、RS232、ESPNOW。';
@override
String get repeater_cliHelpLogStart => '开始将数据包记录到文件系统。';
@override
- String get repeater_cliHelpLogStop => '停止将数据包记录到文件系统。';
+ String get repeater_cliHelpLogStop => '停止将数据包记录写入文件系统。';
@override
- String get repeater_cliHelpLogErase => '删除文件系统中的包日志。';
+ String get repeater_cliHelpLogErase => '从文件系统中删除所有已记录的包信息。';
@override
String get repeater_cliHelpNeighbors =>
- '显示通过零跳广告收听的其他重复节点列表。 每行是 id-prefix-hex:时间戳:snr-times-4';
+ '显示了通过零跳广告收到的其他复用节点列表。 每行包含:id-前缀-十六进制:时间戳:信噪比(4次)';
@override
String get repeater_cliHelpNeighborRemove =>
- '移除邻居列表中第一个匹配的条目(通过十六进制 pubkey 前缀)。';
+ '从邻居列表中删除第一个匹配项(通过十六进制的 pubkey 前缀)。';
@override
- String get repeater_cliHelpRegion => '(仅显示区域) 列出所有已定义的区域和当前的防洪权限。';
+ String get repeater_cliHelpRegion => '(仅限序列)列出所有已定义的区域以及当前的防洪许可。';
@override
String get repeater_cliHelpRegionLoad =>
- '注意:这是一个特殊的多命令调用。 随后的每个命令都是一个区域名称(用空格缩进以指示父级层次结构,至少需要一个空格)。 以发送一个空行/命令结束。';
+ '请注意:这是一个特殊的、包含多个命令的调用方式。 之后的每个命令都是一个区域名称(使用空格进行缩进,以表示父级关系,至少需要一个空格)。 结束方式是通过发送一个空行/命令。';
@override
String get repeater_cliHelpRegionGet =>
- '搜索具有给定名称前缀的区域(或“”用于全局范围)。回复为“-> region-name (parent-name) ‘F’”';
+ '搜索具有指定名称前缀的区域(或使用“*”表示全局范围)。 返回结果为“-> region-name (parent-name) \'F\'”';
@override
- String get repeater_cliHelpRegionPut => '添加或更新区域定义,使用指定名称。';
+ String get repeater_cliHelpRegionPut => '添加或更新一个区域定义,并指定其名称。';
@override
- String get repeater_cliHelpRegionRemove => '删除指定名称的区域定义。(必须没有子区域)';
+ String get repeater_cliHelpRegionRemove =>
+ '删除具有指定名称的区域定义。 (必须与指定名称完全匹配,且不能有子区域)';
@override
- String get repeater_cliHelpRegionAllowf => '设置指定区域的“洪水”权限。(“”代表全局/遗留范围)';
+ String get repeater_cliHelpRegionAllowf => '为指定区域设置“洪水”权限。(“*”表示全局/旧版本范围)';
@override
String get repeater_cliHelpRegionDenyf =>
- '移除指定区域的‘F’lood权限。 (注意:目前阶段不建议在此范围内使用,尤其是全局/旧版范围!!)';
+ '移除指定区域的“洪水”权限。(请注意:目前不建议在全局/旧版本中使用此功能!!)';
@override
- String get repeater_cliHelpRegionHome => '回复当前“主页”区域。 (注意尚未应用,保留用于未来)';
+ String get repeater_cliHelpRegionHome => '回复当前“主区域”。(此功能尚未应用,仅供未来使用)';
@override
- String get repeater_cliHelpRegionHomeSet => '设置‘主页’区域。';
+ String get repeater_cliHelpRegionHomeSet => '设置“主”区域。';
@override
- String get repeater_cliHelpRegionSave => '保存区域列表/地图到存储。';
+ String get repeater_cliHelpRegionSave => '将区域列表/地图保存到存储中。';
@override
String get repeater_cliHelpGps =>
- '显示GPS状态。当GPS关闭时,回复仅为“关闭”,如果已开启,则回复为“开启”、“状态”、“定位”和卫星数量。';
+ '显示 GPS 状态。当 GPS 处于关闭状态时,它只会显示“关闭”;当 GPS 处于开启状态时,它会显示“开启”、“状态”、“定位”、“卫星数量”等信息。';
@override
- String get repeater_cliHelpGpsOnOff => '切换 GPS 开启状态。';
+ String get repeater_cliHelpGpsOnOff => '切换 GPS 设备的电源状态。';
@override
- String get repeater_cliHelpGpsSync => '同步节点时间与 GPS 钟。';
+ String get repeater_cliHelpGpsSync => '将节点时间与 GPS 钟同步。';
@override
- String get repeater_cliHelpGpsSetLoc => '设置节点位置至 GPS 坐标并保存偏好设置。';
+ String get repeater_cliHelpGpsSetLoc => '将节点的坐标设置为 GPS 坐标,并保存设置。';
@override
String get repeater_cliHelpGpsAdvert =>
- '提供节点广告配置位置:\n- none:不包含位置在广告中\n- share:分享 GPS 位置(来自 SensorManager)\n- prefs:在偏好设置中投放位置';
+ '设置节点的位置广告配置:\n- none:不将位置信息包含在广告中\n- share:共享 GPS 位置(从 SensorManager 获取)\n- prefs:在偏好设置中展示的位置';
@override
- String get repeater_cliHelpGpsAdvertSet => '设置广告位置配置。';
+ String get repeater_cliHelpGpsAdvertSet => '设置广告的位置配置。';
@override
String get repeater_commandsListTitle => '命令列表';
@override
- String get repeater_commandsListNote => '注意:对于各种“设置...”命令,也存在“获取...”命令。';
+ String get repeater_commandsListNote => '请注意:对于各种“set ...”命令,也存在“get ...”命令。';
@override
String get repeater_general => '通用';
@@ -2146,29 +2150,29 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_logging => '记录';
@override
- String get repeater_neighborsRepeaterOnly => '邻居(仅限重复器)';
+ String get repeater_neighborsRepeaterOnly => '邻居(仅限重复功能)';
@override
- String get repeater_regionManagementRepeaterOnly => '区域管理(仅限重复器)';
+ String get repeater_regionManagementRepeaterOnly => '区域管理(仅限重复站点)';
@override
- String get repeater_regionNote => '区域命令已推出,用于管理区域定义和权限。';
+ String get repeater_regionNote => '区域命令已引入,用于管理区域定义和权限。';
@override
- String get repeater_gpsManagement => 'GPS管理';
+ String get repeater_gpsManagement => 'GPS 管理';
@override
- String get repeater_gpsNote => 'GPS 命令已引入用于管理与位置相关的主题。';
+ String get repeater_gpsNote => '已引入 GPS 命令,用于管理与位置相关的任务。';
@override
- String get telemetry_receivedData => '接收遥测数据';
+ String get telemetry_receivedData => '接收到的遥测数据';
@override
String get telemetry_requestTimeout => '遥测请求超时。';
@override
String telemetry_errorLoading(String error) {
- return '错误加载遥测数据:$error';
+ return 'Error loading telemetry: $error';
}
@override
@@ -2186,7 +2190,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get telemetry_voltageLabel => '电压';
@override
- String get telemetry_mcuTemperatureLabel => 'MCU 温度';
+ String get telemetry_mcuTemperatureLabel => 'MCU 的温度';
@override
String get telemetry_temperatureLabel => '温度';
@@ -2215,30 +2219,30 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get neighbors_receivedData => '收到邻居数据';
+ String get neighbors_receivedData => '已收到邻居信息';
@override
- String get neighbors_requestTimedOut => '邻居请求超时处理。';
+ String get neighbors_requestTimedOut => '邻居要求停止干扰。';
@override
String neighbors_errorLoading(String error) {
- return '加载邻居时出错:$error';
+ return 'Error loading neighbors: $error';
}
@override
- String get neighbors_repeatersNeighbours => '重复器邻居';
+ String get neighbors_repeatersNeighbours => '重复使用的邻居';
@override
- String get neighbors_noData => '没有可用的邻居数据。';
+ String get neighbors_noData => '没有可用的邻居信息。';
@override
String neighbors_unknownContact(String pubkey) {
- return '未知$pubkey';
+ return 'Unknown $pubkey';
}
@override
String neighbors_heardAgo(String time) {
- return '听到的时间:$time前';
+ return 'Heard: $time ago';
}
@override
@@ -2251,10 +2255,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get channelPath_otherObservedPaths => '其他观察到的路径';
@override
- String get channelPath_repeaterHops => '重复跳跃';
+ String get channelPath_repeaterHops => '复用跳跃';
@override
- String get channelPath_noHopDetails => '此包的详细信息未提供。';
+ String get channelPath_noHopDetails => '对于此包,未提供详细信息。';
@override
String get channelPath_messageDetails => '消息详情';
@@ -2274,15 +2278,15 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get channelPath_observedLabel => '已观察';
+ String get channelPath_observedLabel => '观察到的';
@override
String channelPath_observedPathTitle(int index, String hops) {
- return '观察路径 $index • $hops';
+ return 'Observed path $index • $hops';
}
@override
- String get channelPath_noLocationData => '没有位置数据';
+ String get channelPath_noLocationData => '没有位置信息';
@override
String channelPath_timeWithDate(int day, int month, String time) {
@@ -2305,30 +2309,30 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String channelPath_observedZeroOf(int total) {
- return '0 of $total 跳跃';
+ return '0 of $total hops';
}
@override
String channelPath_observedSomeOf(int observed, int total) {
- return '已观察到 $observed 步中的 $total 步';
+ return '$observed of $total hops';
}
@override
- String get channelPath_mapTitle => '路径地图';
+ String get channelPath_mapTitle => '路线图';
@override
- String get channelPath_noRepeaterLocations => '此路径没有可用的重复器位置。';
+ String get channelPath_noRepeaterLocations => '这条路径上没有可用的中继器位置。';
@override
String channelPath_primaryPath(int index) {
- return '路径 $index (主)';
+ return '路径 $index (主要路径)';
}
@override
String get channelPath_pathLabelTitle => '路径';
@override
- String get channelPath_observedPathHeader => '已观察路径';
+ String get channelPath_observedPathHeader => '观察路径';
@override
String channelPath_selectedPathLabel(String label, String prefixes) {
@@ -2336,19 +2340,19 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
- String get channelPath_noHopDetailsAvailable => '此包的跳跃详情不可用。';
+ String get channelPath_noHopDetailsAvailable => '对于此包裹,尚无详细信息。';
@override
- String get channelPath_unknownRepeater => '未知重复器';
+ String get channelPath_unknownRepeater => '未知的重复设备';
@override
String get community_title => '社区';
@override
- String get community_create => '创建社区';
+ String get community_create => '建立社区';
@override
- String get community_createDesc => '创建新的社区并可通过二维码分享。';
+ String get community_createDesc => '创建一个新的社群,并通过二维码进行分享。';
@override
String get community_join => '加入';
@@ -2358,20 +2362,20 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String community_joinConfirmation(String name) {
- return '您想加入社区 \"$name\" 吗?';
+ return 'Do you want to join the community \"$name\"?';
}
@override
String get community_scanQr => '扫描社区二维码';
@override
- String get community_scanInstructions => '将相机对准社区二维码';
+ String get community_scanInstructions => '将相机对准社区的二维码。';
@override
String get community_showQr => '显示二维码';
@override
- String get community_publicChannel => '社区公开';
+ String get community_publicChannel => '社区公共';
@override
String get community_hashtagChannel => '社区标签';
@@ -2384,12 +2388,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String community_created(String name) {
- return '社区“$name”已创建';
+ return 'Community \"$name\" created';
}
@override
String community_joined(String name) {
- return '加入社区 \"$name\"';
+ return 'Joined community \"$name\"';
}
@override
@@ -2397,128 +2401,128 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String community_qrInstructions(String name) {
- return '扫描此二维码加入$name';
+ return 'Scan this QR code to join \"$name\"';
}
@override
- String get community_hashtagPrivacyHint => '社区标签频道仅社区成员可加入';
+ String get community_hashtagPrivacyHint => '仅社区成员才能加入社区话题标签的频道。';
@override
String get community_invalidQrCode => '无效的社区二维码';
@override
- String get community_alreadyMember => '已经是会员了';
+ String get community_alreadyMember => '已经是会员';
@override
String community_alreadyMemberMessage(String name) {
- return '您已经是 \"$name\" 的会员。';
+ return 'You are already a member of \"$name\".';
}
@override
- String get community_addPublicChannel => '添加社区公共频道';
+ String get community_addPublicChannel => '添加公共频道';
@override
String get community_addPublicChannelHint => '自动添加该社区的公共频道';
@override
- String get community_noCommunities => '尚未加入任何社区';
+ String get community_noCommunities => '目前还没有任何社区加入。';
@override
- String get community_scanOrCreate => '扫描二维码或创建社区开始';
+ String get community_scanOrCreate => '扫描二维码或创建社群,即可开始。';
@override
- String get community_manageCommunities => '管理社群';
+ String get community_manageCommunities => '管理社区';
@override
String get community_delete => '退出社区';
@override
String community_deleteConfirm(String name) {
- return '退出 \"$name\"?';
+ return '是否要删除\"$name\"?';
}
@override
String community_deleteChannelsWarning(int count) {
- return '这也将删除 $count 个频道及其消息。';
+ return '这将同时删除 $count 个频道及其所有消息。';
}
@override
String community_deleted(String name) {
- return '已退出社区 \"$name\"';
+ return 'Left community \"$name\"';
}
@override
- String get community_regenerateSecret => '重新生成密钥';
+ String get community_regenerateSecret => '恢复秘密';
@override
String community_regenerateSecretConfirm(String name) {
- return '重新生成“$name”的秘密密钥?所有成员将需要扫描新的二维码才能继续沟通。';
+ return '[保存:$name]\n是否需要重新生成\"$name\"的密钥?所有成员都需要扫描新的二维码才能继续进行通信。';
}
@override
- String get community_regenerate => '重新生成';
+ String get community_regenerate => '再生';
@override
String community_secretRegenerated(String name) {
- return '密码已重置为“$name”';
+ return '[保护对象:$name]\n秘密已恢复至\"$name\"';
}
@override
- String get community_updateSecret => '更新密钥';
+ String get community_updateSecret => '更新秘密';
@override
String community_secretUpdated(String name) {
- return '密码已更新为“$name”';
+ return '“$name”的秘密已更新';
}
@override
String community_scanToUpdateSecret(String name) {
- return '扫描新的二维码更新\"$name\"的密码';
+ return 'Scan the new QR code to update the secret for \"$name\"';
}
@override
String get community_addHashtagChannel => '添加社区标签';
@override
- String get community_addHashtagChannelDesc => '添加一个话题频道给此社区';
+ String get community_addHashtagChannelDesc => '为这个社区创建一个带有话题标签的频道';
@override
String get community_selectCommunity => '选择社区';
@override
- String get community_regularHashtag => '常规话题标签';
+ String get community_regularHashtag => '常用标签';
@override
- String get community_regularHashtagDesc => '公共话题(任何人都可以加入)';
+ String get community_regularHashtagDesc => '公共话题标签(任何人都可以参与)';
@override
String get community_communityHashtag => '社区标签';
@override
- String get community_communityHashtagDesc => '仅限社区成员使用';
+ String get community_communityHashtagDesc => '仅限社区成员';
@override
String community_forCommunity(String name) {
- return '对于 $name';
+ return 'For $name';
}
@override
String get listFilter_tooltip => '筛选和排序';
@override
- String get listFilter_sortBy => '按类型排序';
+ String get listFilter_sortBy => '按排序';
@override
String get listFilter_latestMessages => '最新消息';
@override
- String get listFilter_heardRecently => '最近听说';
+ String get listFilter_heardRecently => '最近听到的';
@override
- String get listFilter_az => 'A-Z';
+ String get listFilter_az => 'A 到 Z';
@override
- String get listFilter_filters => '筛选';
+ String get listFilter_filters => '过滤器';
@override
String get listFilter_all => '全部';
@@ -2533,46 +2537,88 @@ class AppLocalizationsZh extends AppLocalizations {
String get listFilter_roomServers => '房间服务器';
@override
- String get listFilter_unreadOnly => '未读消息';
+ String get listFilter_unreadOnly => '仅显示未读消息';
@override
- String get listFilter_newGroup => '新组';
+ String get listFilter_newGroup => '新的团体';
@override
- String get pathTrace_you => '你';
+ String get pathTrace_you => '您';
@override
String get pathTrace_failed => '路径追踪失败。';
@override
- String get pathTrace_notAvailable => '路径追踪不可用';
+ String get pathTrace_notAvailable => '无法获取路径信息。';
@override
- String get pathTrace_refreshTooltip => '刷新路径追踪';
+ String get pathTrace_refreshTooltip => '重新绘制路径。';
@override
String get contacts_pathTrace => '路径追踪';
@override
- String get contacts_ping => 'ping';
+ String get contacts_ping => '乒';
@override
- String get contacts_repeaterPathTrace => '路径追踪到中继器';
+ String get contacts_repeaterPathTrace => '追踪路径至中继器';
@override
- String get contacts_repeaterPing => 'Ping 中继器';
+ String get contacts_repeaterPing => '中继器';
@override
- String get contacts_roomPathTrace => '路径追踪至房间服务器';
+ String get contacts_roomPathTrace => '追踪到房间服务器';
@override
- String get contacts_roomPing => 'Ping 房间服务器';
+ String get contacts_roomPing => '会议室服务器';
@override
- String get contacts_chatTraceRoute => '路径追踪';
+ String get contacts_chatTraceRoute => '路径跟踪路线';
@override
String contacts_pathTraceTo(String name) {
- return '追踪路由到 $name';
+ return '追踪路径至 $name';
}
+
+ @override
+ String get contacts_clipboardEmpty => '剪贴板为空。';
+
+ @override
+ String get contacts_invalidAdvertFormat => '无效的联系信息';
+
+ @override
+ String get contacts_contactImported => '已建立联系。';
+
+ @override
+ String get contacts_contactImportFailed => '未能导入联系人。';
+
+ @override
+ String get contacts_zeroHopAdvert => '零跳广告';
+
+ @override
+ String get contacts_floodAdvert => '防洪广告';
+
+ @override
+ String get contacts_copyAdvertToClipboard => '复制广告到剪贴板';
+
+ @override
+ String get contacts_addContactFromClipboard => '从剪贴板添加联系人';
+
+ @override
+ String get contacts_ShareContact => '复制联系方式到剪贴板';
+
+ @override
+ String get contacts_ShareContactZeroHop => '通过广告分享联系方式';
+
+ @override
+ String get contacts_zeroHopContactAdvertSent => '通过广告获取联系方式。';
+
+ @override
+ String get contacts_zeroHopContactAdvertFailed => '发送联系方式失败。';
+
+ @override
+ String get contacts_contactAdvertCopied => '广告内容已复制到剪贴板。';
+
+ @override
+ String get contacts_contactAdvertCopyFailed => '将广告复制到剪贴板操作失败。';
}
diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb
index b28d668..e94deb3 100644
--- a/lib/l10n/app_nl.arb
+++ b/lib/l10n/app_nl.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Padtrace naar room server",
"contacts_roomPing": "Ping kamer server",
"contacts_chatTraceRoute": "Route traceren",
- "contacts_pathTraceTo": "Trace route to {name}"
+ "contacts_pathTraceTo": "Trace route to {name}",
+ "appSettings_languageUk": "Oekraïens",
+ "contacts_invalidAdvertFormat": "Ongeldige contactgegevens",
+ "contacts_contactImportFailed": "Contact kon niet geïmporteerd worden.",
+ "contacts_zeroHopAdvert": "Zero Hop Reclame",
+ "contacts_floodAdvert": "Overstromingsadvertentie",
+ "contacts_copyAdvertToClipboard": "Advert naar klembord kopiëren",
+ "appSettings_languageRu": "Russisch",
+ "contacts_clipboardEmpty": "Knipbord is leeg.",
+ "contacts_addContactFromClipboard": "Contact uit klembord toevoegen",
+ "contacts_contactImported": "Contact is geïmporteerd.",
+ "contacts_zeroHopContactAdvertSent": "Contact verzonden via advertentie",
+ "contacts_contactAdvertCopied": "Reclame gekopieerd naar Klembord.",
+ "contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.",
+ "contacts_ShareContact": "Kontakt naar Klembord kopiëren",
+ "contacts_ShareContactZeroHop": "Contact delen via advertentie",
+ "contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden"
}
diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb
index 8070ac3..44552c3 100644
--- a/lib/l10n/app_pl.arb
+++ b/lib/l10n/app_pl.arb
@@ -1552,5 +1552,21 @@
"pathTrace_refreshTooltip": "Odśwież ścieżkę.",
"contacts_repeaterPing": "Repeater pingowy",
"contacts_pathTraceTo": "Śledź trasę do {name}",
- "contacts_chatTraceRoute": "Śledź trasę promienia"
+ "contacts_chatTraceRoute": "Śledź trasę promienia",
+ "appSettings_languageRu": "Rosyjski",
+ "appSettings_languageUk": "Ukraińska",
+ "contacts_contactImportFailed": "Kontakt nie został zaimportowany.",
+ "contacts_zeroHopAdvert": "Reklama Zero Hop",
+ "contacts_floodAdvert": "Reklama powodziowa",
+ "contacts_copyAdvertToClipboard": "Kopiuj ogłoszenie do schowka",
+ "contacts_clipboardEmpty": "Schowek jest pusty.",
+ "contacts_invalidAdvertFormat": "Nieprawidłowe dane kontaktowe",
+ "contacts_addContactFromClipboard": "Dodaj kontakt z schowka",
+ "contacts_contactImported": "Kontakt został zaimportowany.",
+ "contacts_zeroHopContactAdvertSent": "Wysłano kontakt przez ogłoszenie.",
+ "contacts_contactAdvertCopied": "Reklama skopiowana do schowka.",
+ "contacts_contactAdvertCopyFailed": "Kopiowanie ogłoszenia do schowka nie powiodło się.",
+ "contacts_ShareContactZeroHop": "Udostępnij kontakt przez ogłoszenie",
+ "contacts_ShareContact": "Kopiuj kontakt do schowka",
+ "contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu."
}
diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb
index 6994bea..56a7f2b 100644
--- a/lib/l10n/app_pt.arb
+++ b/lib/l10n/app_pt.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Traçar caminho para o servidor da sala",
"contacts_roomPing": "Pingar servidor da sala",
"contacts_chatTraceRoute": "Rastrear rota do caminho",
- "contacts_pathTraceTo": "Rastrear rota para {name}"
+ "contacts_pathTraceTo": "Rastrear rota para {name}",
+ "contacts_invalidAdvertFormat": "Dados de Contato Inválidos",
+ "contacts_clipboardEmpty": "Área de Transferência Está Vazia.",
+ "appSettings_languageUk": "Ucraniano",
+ "contacts_contactImported": "Contato foi importado.",
+ "contacts_zeroHopAdvert": "Anúncio Zero Hop",
+ "contacts_copyAdvertToClipboard": "Copiar Anúncio para Área de Transferência",
+ "contacts_addContactFromClipboard": "Adicionar Contato da Área de Transferência",
+ "appSettings_languageRu": "Russo",
+ "contacts_ShareContact": "Copiar contato para Área de Transferência",
+ "contacts_contactImportFailed": "Contato falhou ao ser importado.",
+ "contacts_zeroHopContactAdvertSent": "Enviou contato por anúncio.",
+ "contacts_contactAdvertCopied": "Anúncio copiado para a Área de Transferência.",
+ "contacts_floodAdvert": "Anúncio de Inundação",
+ "contacts_contactAdvertCopyFailed": "Cópia do anúncio para a Área de Transferência falhou.",
+ "contacts_ShareContactZeroHop": "Compartilhar contato por anúncio",
+ "contacts_zeroHopContactAdvertFailed": "Falha ao enviar contato."
}
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
index f007aa7..0bca5ef 100644
--- a/lib/l10n/app_ru.arb
+++ b/lib/l10n/app_ru.arb
@@ -793,5 +793,20 @@
"contacts_roomPathTrace": "Трассировка пути к серверу комнаты",
"contacts_roomPing": "Пинговать сервер комнаты",
"contacts_chatTraceRoute": "Трассировка маршрута",
- "contacts_pathTraceTo": "Показать маршрут к {name}"
+ "contacts_pathTraceTo": "Показать маршрут к {name}",
+ "contacts_contactImported": "Контакт был импортирован",
+ "contacts_contactImportFailed": "Контакт не удалось импортировать",
+ "contacts_invalidAdvertFormat": "Недействительные контактные данные",
+ "contacts_zeroHopAdvert": "Реклама Zero Hop",
+ "appSettings_languageUk": "Українська",
+ "contacts_floodAdvert": "Рекламный поток",
+ "contacts_clipboardEmpty": "Буфер обмена пуст.",
+ "contacts_copyAdvertToClipboard": "Копировать рекламу в буфер обмена",
+ "contacts_ShareContact": "Копировать контакт в буфер обмена",
+ "contacts_zeroHopContactAdvertFailed": "Не удалось отправить контакт.",
+ "contacts_contactAdvertCopied": "Реклама скопирована в буфер обмена.",
+ "contacts_contactAdvertCopyFailed": "Копирование рекламы в буфер обмена не удалось.",
+ "contacts_addContactFromClipboard": "Добавить контакт из буфера обмена",
+ "contacts_ShareContactZeroHop": "Поделиться контактом по объявлению",
+ "contacts_zeroHopContactAdvertSent": "Отправлено сообщение по объявлению."
}
diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb
index 4e66af0..d61cca6 100644
--- a/lib/l10n/app_sk.arb
+++ b/lib/l10n/app_sk.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Sledovanie cesty k serveru miestnosti",
"contacts_roomPing": "Ping server miestnosti",
"contacts_chatTraceRoute": "Sledovať trasu lúča",
- "contacts_pathTraceTo": "Sledovať trasu k {name}"
+ "contacts_pathTraceTo": "Sledovať trasu k {name}",
+ "contacts_clipboardEmpty": "Schránka je prázdna.",
+ "appSettings_languageUk": "Ukrajinská",
+ "contacts_contactImportFailed": "Kontakt sa nepodarilo importovať.",
+ "contacts_zeroHopAdvert": "Inzerát Zero Hop",
+ "contacts_floodAdvert": "Inzerát povodní",
+ "contacts_copyAdvertToClipboard": "Kopírovať reklamu do schránky",
+ "contacts_invalidAdvertFormat": "Neplatné kontaktné údaje",
+ "appSettings_languageRu": "Ruština",
+ "contacts_addContactFromClipboard": "Pridať kontakt z schránky",
+ "contacts_contactImported": "Kontakt bol importovaný.",
+ "contacts_zeroHopContactAdvertSent": "Poslal kontakt cez inzerát.",
+ "contacts_contactAdvertCopied": "Inzerát bol skopírovaný do schránky.",
+ "contacts_contactAdvertCopyFailed": "Kopírovanie inzerátu do schránky zlyhalo.",
+ "contacts_zeroHopContactAdvertFailed": "Zlyhalo odoslanie kontaktu.",
+ "contacts_ShareContactZeroHop": "Zdieľať kontakt cez inzerát",
+ "contacts_ShareContact": "Kopírovať kontakt do schránky"
}
diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb
index 805621b..cbc4e3f 100644
--- a/lib/l10n/app_sl.arb
+++ b/lib/l10n/app_sl.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Sledenje poti do strežnika sobe",
"contacts_roomPing": "Ping strežnik sobe",
"contacts_chatTraceRoute": "Slediti poti žarkov",
- "contacts_pathTraceTo": "Trace route to {name}"
+ "contacts_pathTraceTo": "Trace route to {name}",
+ "appSettings_languageRu": "Ruščina",
+ "appSettings_languageUk": "Ukrajinsko",
+ "contacts_contactImported": "Kontakt je bil uvožen.",
+ "contacts_contactImportFailed": "Kontakt ni bil uspešno uvožen.",
+ "contacts_zeroHopAdvert": "Reklama brez posrednikov",
+ "contacts_floodAdvert": "Poplavna oglás",
+ "contacts_invalidAdvertFormat": "Neveljavni kontaktne podatke",
+ "contacts_clipboardEmpty": "Odložišče je prazno.",
+ "contacts_copyAdvertToClipboard": "Kopiraj oglas v odložišče",
+ "contacts_addContactFromClipboard": "Dodaj stik iz odložišča",
+ "contacts_zeroHopContactAdvertSent": "Poslano po oglasu.",
+ "contacts_zeroHopContactAdvertFailed": "Pošiljanje kontakta ni uspelo.",
+ "contacts_contactAdvertCopied": "Oglas je bil kopiran v odložišče.",
+ "contacts_contactAdvertCopyFailed": "Kopiranje oglasa v odložišče je spodletelo.",
+ "contacts_ShareContactZeroHop": "Deliti kontakt prek oglasa",
+ "contacts_ShareContact": "Kopiraj stik v Odložišče"
}
diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb
index da017be..05f77cb 100644
--- a/lib/l10n/app_sv.arb
+++ b/lib/l10n/app_sv.arb
@@ -1552,5 +1552,21 @@
"contacts_roomPathTrace": "Vägspårning till rumserver",
"contacts_roomPing": "Ping rumsserver",
"contacts_chatTraceRoute": "Spåra rutt",
- "contacts_pathTraceTo": "Spåra rutt till {name}"
+ "contacts_pathTraceTo": "Spåra rutt till {name}",
+ "contacts_clipboardEmpty": "Urklipp är tomt.",
+ "appSettings_languageRu": "Ryska",
+ "contacts_contactImportFailed": "Kontakt kunde inte importeras.",
+ "contacts_zeroHopAdvert": "Reklam med nollhopp",
+ "contacts_floodAdvert": "Översvämningsannons",
+ "contacts_copyAdvertToClipboard": "Kopiera annons till urklipp",
+ "contacts_invalidAdvertFormat": "Ogiltiga kontaktuppgifter",
+ "appSettings_languageUk": "Ukrainska",
+ "contacts_addContactFromClipboard": "Lägg till kontakt från urklipp",
+ "contacts_contactImported": "Kontakt har importerats.",
+ "contacts_zeroHopContactAdvertSent": "Skickat kontakt via annons.",
+ "contacts_contactAdvertCopied": "Annons kopierad till Urklipp.",
+ "contacts_contactAdvertCopyFailed": "Kopiering av annons till Urklipp misslyckades.",
+ "contacts_ShareContact": "Kopiera kontakt till Urklipp",
+ "contacts_zeroHopContactAdvertFailed": "Misslyckades med att skicka kontakt.",
+ "contacts_ShareContactZeroHop": "Dela kontakt via annons"
}
diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb
index 85ce4a2..3362d40 100644
--- a/lib/l10n/app_uk.arb
+++ b/lib/l10n/app_uk.arb
@@ -1553,5 +1553,20 @@
"contacts_roomPathTrace": "Трасування шляху до серверу кімнати",
"contacts_roomPing": "Пінг сервера кімнати",
"contacts_chatTraceRoute": "Трасування шляху",
- "contacts_pathTraceTo": "Відстежити маршрут до {name}"
+ "contacts_pathTraceTo": "Відстежити маршрут до {name}",
+ "contacts_invalidAdvertFormat": "Недійсні контактні дані",
+ "contacts_contactImported": "Контакт було імпортовано.",
+ "contacts_contactImportFailed": "Контакт не вдалося імпортувати",
+ "contacts_zeroHopAdvert": "Реклама без перехоплення",
+ "contacts_floodAdvert": "Залив реклами",
+ "contacts_copyAdvertToClipboard": "Копіювати оголошення в буфер обміну",
+ "contacts_clipboardEmpty": "Буфер обміну порожній",
+ "appSettings_languageRu": "Російська",
+ "contacts_ShareContact": "Копіювати контакт у буфер обміну",
+ "contacts_zeroHopContactAdvertFailed": "Не вдалося надіслати контакт.",
+ "contacts_contactAdvertCopied": "Рекламу скопійовано до буфера обміну.",
+ "contacts_contactAdvertCopyFailed": "Копіювання оголошення в буфер обміну завершилося невдало",
+ "contacts_zeroHopContactAdvertSent": "Відправлено контакт за оголошенням",
+ "contacts_addContactFromClipboard": "Додати контакт з буфера обміну",
+ "contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням"
}
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
index 5f0c797..c941461 100644
--- a/lib/l10n/app_zh.arb
+++ b/lib/l10n/app_zh.arb
@@ -1,10 +1,11 @@
{
"@@locale": "zh",
"appTitle": "MeshCore Open",
- "nav_contacts": "联系人",
+ "nav_contacts": "联系方式",
"nav_channels": "频道",
"nav_map": "地图",
"common_cancel": "取消",
+ "common_ok": "好的",
"common_connect": "连接",
"common_unknownDevice": "未知设备",
"common_save": "保存",
@@ -14,18 +15,18 @@
"common_add": "添加",
"common_settings": "设置",
"common_disconnect": "断开",
- "common_connected": "已连接",
+ "common_connected": "连接",
"common_disconnected": "断开",
- "common_create": "创建",
+ "common_create": "创造",
"common_continue": "继续",
"common_share": "分享",
"common_copy": "复制",
"common_retry": "重试",
"common_hide": "隐藏",
- "common_remove": "删除",
+ "common_remove": "移除",
"common_enable": "启用",
"common_disable": "禁用",
- "common_reboot": "重启",
+ "common_reboot": "重新启动",
"common_loading": "正在加载...",
"common_notAvailable": "—",
"common_voltageValue": "{volts} V",
@@ -44,12 +45,12 @@
}
}
},
- "scanner_title": "MeshCore Open",
- "scanner_scanning": "扫描设备…",
- "scanner_connecting": "连接中...",
- "scanner_disconnecting": "断开中...",
+ "scanner_title": "MeshCore 开放",
+ "scanner_scanning": "正在搜索设备...",
+ "scanner_connecting": "正在连接...",
+ "scanner_disconnecting": "断开连接...",
"scanner_notConnected": "未连接",
- "scanner_connectedTo": "已连接至 {deviceName}",
+ "scanner_connectedTo": "已连接到 {deviceName}",
"@scanner_connectedTo": {
"placeholders": {
"deviceName": {
@@ -57,9 +58,9 @@
}
}
},
- "scanner_searchingDevices": "搜索 MeshCore 设备...",
- "scanner_tapToScan": "点击扫描以查找MeshCore设备",
- "scanner_connectionFailed": "连接失败:{error}",
+ "scanner_searchingDevices": "正在搜索 MeshCore 设备...",
+ "scanner_tapToScan": "点击“扫描”功能,以查找 MeshCore 设备。",
+ "scanner_connectionFailed": "Connection failed: {error}",
"@scanner_connectionFailed": {
"placeholders": {
"error": {
@@ -70,7 +71,7 @@
"scanner_stop": "停止",
"scanner_scan": "扫描",
"device_quickSwitch": "快速切换",
- "device_meshcore": "MeshCore",
+ "device_meshcore": "网格核心",
"settings_title": "设置",
"settings_deviceInfo": "设备信息",
"settings_appSettings": "应用设置",
@@ -80,38 +81,42 @@
"settings_nodeNameNotSet": "未设置",
"settings_nodeNameHint": "请输入节点名称",
"settings_nodeNameUpdated": "姓名已更新",
- "settings_radioSettings": "无线设置",
- "settings_radioSettingsSubtitle": "频率,功率,扩展因子",
- "settings_radioSettingsUpdated": "射频设置已更新",
- "settings_location": "位置",
- "settings_locationSubtitle": "GPS坐标",
- "settings_locationUpdated": "位置已更新",
- "settings_locationBothRequired": "请输入纬度和经度。",
- "settings_locationInvalid": "无效的纬度或经度。",
+ "settings_radioSettings": "收音机设置",
+ "settings_radioSettingsSubtitle": "频率、功率、扩频因子",
+ "settings_radioSettingsUpdated": "收音机设置已更新",
+ "settings_location": "地点",
+ "settings_locationSubtitle": "GPS 坐标",
+ "settings_locationUpdated": "位置和 GPS 设置已更新",
+ "settings_locationBothRequired": "请输入经度和纬度。",
+ "settings_locationInvalid": "无效的经度和纬度。",
+ "settings_locationGPSEnable": "开启 GPS 功能",
+ "settings_locationGPSEnableSubtitle": "使 GPS 能够自动更新位置。",
+ "settings_locationIntervalSec": "GPS 间隔时间(秒)",
+ "settings_locationIntervalInvalid": "间隔时间必须至少为 60 秒,但不超过 86400 秒。",
"settings_latitude": "纬度",
"settings_longitude": "经度",
"settings_privacyMode": "隐私模式",
- "settings_privacyModeSubtitle": "隐藏在广告中的姓名/位置",
- "settings_privacyModeToggle": "开启隐私模式以隐藏您的姓名和位置在广告中的显示。",
+ "settings_privacyModeSubtitle": "在广告中隐藏姓名/位置",
+ "settings_privacyModeToggle": "切换隐私模式,以隐藏您的姓名和位置,从而在广告中保护您的个人信息。",
"settings_privacyModeEnabled": "隐私模式已启用",
- "settings_privacyModeDisabled": "隐私模式已禁用",
- "settings_actions": "操作",
- "settings_sendAdvertisement": "发送广告",
- "settings_sendAdvertisementSubtitle": "现在已广播",
- "settings_advertisementSent": "广告已发送",
+ "settings_privacyModeDisabled": "隐私模式已关闭",
+ "settings_actions": "行动",
+ "settings_sendAdvertisement": "发布广告",
+ "settings_sendAdvertisementSubtitle": "现已开始进行广播节目",
+ "settings_advertisementSent": "已发送广告",
"settings_syncTime": "同步时间",
- "settings_syncTimeSubtitle": "将设备时钟设置为手机时间",
+ "settings_syncTimeSubtitle": "将设备时钟设置为与手机时间一致",
"settings_timeSynchronized": "时间同步",
"settings_refreshContacts": "刷新联系人",
- "settings_refreshContactsSubtitle": "从设备重新加载联系人列表",
+ "settings_refreshContactsSubtitle": "从设备中重新加载联系人列表",
"settings_rebootDevice": "重启设备",
- "settings_rebootDeviceSubtitle": "重启 MeshCore 设备",
- "settings_rebootDeviceConfirm": "您确定要重启设备吗?您将会断开连接。",
+ "settings_rebootDeviceSubtitle": "重新启动 MeshCore 设备",
+ "settings_rebootDeviceConfirm": "您确定要重启设备吗?这将导致您与设备断开连接。",
"settings_debug": "调试",
- "settings_bleDebugLog": "蓝牙调试日志",
- "settings_bleDebugLogSubtitle": "蓝牙命令、响应和原始数据",
- "settings_appDebugLog": "应用调试日志",
- "settings_appDebugLogSubtitle": "应用调试消息",
+ "settings_bleDebugLog": "BLE 调试日志",
+ "settings_bleDebugLogSubtitle": "BLE 命令、响应和原始数据",
+ "settings_appDebugLog": "应用程序调试日志",
+ "settings_appDebugLogSubtitle": "应用程序调试消息",
"settings_about": "关于",
"settings_aboutVersion": "MeshCore Open v{version}",
"@settings_aboutVersion": {
@@ -121,31 +126,31 @@
}
}
},
- "settings_aboutLegalese": "2024 MeshCore 开放源代码项目",
- "settings_aboutDescription": "一个开源的 Flutter 客户端,用于 MeshCore LoRa 网状网络设备。",
+ "settings_aboutLegalese": "2026 MeshCore 开源项目",
+ "settings_aboutDescription": "一个开源的 Flutter 客户端,用于 MeshCore LoRa 无线网络设备。",
"settings_infoName": "姓名",
"settings_infoId": "ID",
"settings_infoStatus": "状态",
"settings_infoBattery": "电池",
"settings_infoPublicKey": "公钥",
"settings_infoContactsCount": "联系人数量",
- "settings_infoChannelCount": "频道数量",
+ "settings_infoChannelCount": "通道数量",
"settings_presets": "预设",
- "settings_preset915Mhz": "915 MHz",
- "settings_preset868Mhz": "868 MHz",
- "settings_preset433Mhz": "433 MHz",
+ "settings_preset915Mhz": "915 兆赫",
+ "settings_preset868Mhz": "868 兆赫",
+ "settings_preset433Mhz": "433 兆赫",
"settings_frequency": "频率 (MHz)",
"settings_frequencyHelper": "300.0 - 2500.0",
- "settings_frequencyInvalid": "无效频率 (300-2500 MHz)",
+ "settings_frequencyInvalid": "无效频率(300-2500 MHz)",
"settings_bandwidth": "带宽",
- "settings_spreadingFactor": "扩散因子",
+ "settings_spreadingFactor": "传播系数",
"settings_codingRate": "编码速率",
- "settings_txPower": "TX Power (dBm)",
+ "settings_txPower": "TX 功率(dBm)",
"settings_txPowerHelper": "0 - 22",
- "settings_txPowerInvalid": "无效的 TX 电功率 (0-22 dBm)",
+ "settings_txPowerInvalid": "无效的发射功率(0-22 dBm)",
"settings_longRange": "远距离",
- "settings_fastSpeed": "快速速度",
- "settings_error": "错误:{message}",
+ "settings_fastSpeed": "高速",
+ "settings_error": "[保存:{message}]\n错误:{message}",
"@settings_error": {
"placeholders": {
"message": {
@@ -156,48 +161,50 @@
"appSettings_title": "应用设置",
"appSettings_appearance": "外观",
"appSettings_theme": "主题",
- "appSettings_themeSystem": "系统默认",
+ "appSettings_themeSystem": "系统默认设置",
"appSettings_themeLight": "光",
- "appSettings_themeDark": "深色",
+ "appSettings_themeDark": "黑暗",
"appSettings_language": "语言",
- "appSettings_languageSystem": "系统默认",
- "appSettings_languageEn": "English",
- "appSettings_languageFr": "Français",
- "appSettings_languageEs": "Español",
- "appSettings_languageDe": "Deutsch",
- "appSettings_languagePl": "Polski",
- "appSettings_languageSl": "Slovenščina",
- "appSettings_languagePt": "Português",
- "appSettings_languageIt": "Italiano",
+ "appSettings_languageSystem": "系统默认设置",
+ "appSettings_languageEn": "英语",
+ "appSettings_languageFr": "法语",
+ "appSettings_languageEs": "西班牙语",
+ "appSettings_languageDe": "德语",
+ "appSettings_languagePl": "波兰语",
+ "appSettings_languageSl": "斯洛文语",
+ "appSettings_languagePt": "葡萄牙语",
+ "appSettings_languageIt": "意大利语",
"appSettings_languageZh": "中文",
- "appSettings_languageSv": "Svenska",
- "appSettings_languageNl": "Nederlands",
- "appSettings_languageSk": "Slovenčina",
- "appSettings_languageBg": "Български",
+ "appSettings_languageSv": "瑞典语",
+ "appSettings_languageNl": "荷兰语",
+ "appSettings_languageSk": "斯洛伐克语",
+ "appSettings_languageBg": "保加利亚",
+ "appSettings_languageRu": "俄语",
+ "appSettings_languageUk": "乌克兰",
"appSettings_notifications": "通知",
"appSettings_enableNotifications": "启用通知",
"appSettings_enableNotificationsSubtitle": "接收消息和广告的通知",
- "appSettings_notificationPermissionDenied": "通知权限被拒绝",
+ "appSettings_notificationPermissionDenied": "权限被拒绝",
"appSettings_notificationsEnabled": "通知已启用",
"appSettings_notificationsDisabled": "通知已关闭",
"appSettings_messageNotifications": "消息通知",
- "appSettings_messageNotificationsSubtitle": "显示收到新消息时的通知",
+ "appSettings_messageNotificationsSubtitle": "在收到新消息时显示通知",
"appSettings_channelMessageNotifications": "频道消息通知",
- "appSettings_channelMessageNotificationsSubtitle": "显示接收频道消息时的通知",
+ "appSettings_channelMessageNotificationsSubtitle": "在收到频道消息时,显示通知。",
"appSettings_advertisementNotifications": "广告通知",
- "appSettings_advertisementNotificationsSubtitle": "显示当新节点被发现时通知",
- "appSettings_messaging": "消息",
- "appSettings_clearPathOnMaxRetry": "清除最大重试路径",
- "appSettings_clearPathOnMaxRetrySubtitle": "重置联系人路径,在5次发送失败尝试后",
- "appSettings_pathsWillBeCleared": "路径将在5次失败重试后清除",
- "appSettings_pathsWillNotBeCleared": "路径不会自动清理",
- "appSettings_autoRouteRotation": "自动路径旋转",
- "appSettings_autoRouteRotationSubtitle": "在最佳路径和洪水模式之间切换",
+ "appSettings_advertisementNotificationsSubtitle": "在发现新的节点时,显示通知。",
+ "appSettings_messaging": "信息传递",
+ "appSettings_clearPathOnMaxRetry": "关于“最大重试”的清晰说明",
+ "appSettings_clearPathOnMaxRetrySubtitle": "在尝试发送失败后 5 次,重置联系路径。",
+ "appSettings_pathsWillBeCleared": "如果尝试 5 次后仍然失败,则将重新规划路径。",
+ "appSettings_pathsWillNotBeCleared": "路径不会自动清除。",
+ "appSettings_autoRouteRotation": "自动路径轮换",
+ "appSettings_autoRouteRotationSubtitle": "在最佳路径和防洪模式之间切换",
"appSettings_autoRouteRotationEnabled": "自动路径轮换已启用",
"appSettings_autoRouteRotationDisabled": "自动路径轮换已禁用",
"appSettings_battery": "电池",
"appSettings_batteryChemistry": "电池化学",
- "appSettings_batteryChemistryPerDevice": "设置每个设备 ({deviceName})",
+ "appSettings_batteryChemistryPerDevice": "为每个设备设置 ({deviceName})",
"@appSettings_batteryChemistryPerDevice": {
"placeholders": {
"deviceName": {
@@ -205,20 +212,20 @@
}
}
},
- "appSettings_batteryChemistryConnectFirst": "连接设备以选择",
- "appSettings_batteryNmc": "18650 NMC (3.0-4.2V)",
+ "appSettings_batteryChemistryConnectFirst": "连接到设备以进行选择",
+ "appSettings_batteryNmc": "18650 型号,NMC 电池(3.0-4.2V)",
"appSettings_batteryLifepo4": "磷酸铁锂 (2.6-3.65V)",
- "appSettings_batteryLipo": "LiPo (3.0-4.2V)",
- "appSettings_mapDisplay": "地图显示",
- "appSettings_showRepeaters": "显示循环器",
+ "appSettings_batteryLipo": "锂离子电池 (3.0-4.2V)",
+ "appSettings_mapDisplay": "地图展示",
+ "appSettings_showRepeaters": "显示重复",
"appSettings_showRepeatersSubtitle": "在地图上显示重复节点",
"appSettings_showChatNodes": "显示聊天节点",
"appSettings_showChatNodesSubtitle": "在地图上显示聊天节点",
"appSettings_showOtherNodes": "显示其他节点",
- "appSettings_showOtherNodesSubtitle": "显示其他节点类型在地图上",
- "appSettings_timeFilter": "时间筛选",
+ "appSettings_showOtherNodesSubtitle": "在地图上显示其他节点类型",
+ "appSettings_timeFilter": "时间过滤器",
"appSettings_timeFilterShowAll": "显示所有节点",
- "appSettings_timeFilterShowLast": "显示来自过去 {hours} 小时的节点",
+ "appSettings_timeFilterShowLast": "Show nodes from last {hours} hours",
"@appSettings_timeFilterShowLast": {
"placeholders": {
"hours": {
@@ -227,15 +234,15 @@
}
},
"appSettings_mapTimeFilter": "地图时间筛选",
- "appSettings_showNodesDiscoveredWithin": "显示发现的节点在:",
+ "appSettings_showNodesDiscoveredWithin": "显示在以下范围内发现的节点:",
"appSettings_allTime": "所有时间",
- "appSettings_lastHour": "最后小时",
- "appSettings_last6Hours": "最后6小时",
- "appSettings_last24Hours": "最后24小时",
+ "appSettings_lastHour": "过去一小时",
+ "appSettings_last6Hours": "过去6小时",
+ "appSettings_last24Hours": "过去24小时",
"appSettings_lastWeek": "上周",
"appSettings_offlineMapCache": "离线地图缓存",
"appSettings_noAreaSelected": "未选择任何区域",
- "appSettings_areaSelectedZoom": "选中的区域(缩放至 {minZoom} - {maxZoom})",
+ "appSettings_areaSelectedZoom": "已选择区域(缩放至 {minZoom} - {maxZoom})",
"@appSettings_areaSelectedZoom": {
"placeholders": {
"minZoom": {
@@ -247,18 +254,18 @@
}
},
"appSettings_debugCard": "调试",
- "appSettings_appDebugLogging": "应用调试日志",
- "appSettings_appDebugLoggingSubtitle": "记录应用调试消息以供故障排除",
- "appSettings_appDebugLoggingEnabled": "应用调试日志已启用",
- "appSettings_appDebugLoggingDisabled": "应用调试日志已禁用",
- "contacts_title": "联系人",
- "contacts_noContacts": "还没有联系人",
- "contacts_contactsWillAppear": "设备会广播时,联系人会显示",
+ "appSettings_appDebugLogging": "应用程序调试日志",
+ "appSettings_appDebugLoggingSubtitle": "用于故障排除的日志应用程序调试消息",
+ "appSettings_appDebugLoggingEnabled": "调试日志已启用",
+ "appSettings_appDebugLoggingDisabled": "应用程序调试日志已禁用",
+ "contacts_title": "联系方式",
+ "contacts_noContacts": "目前还没有联系人",
+ "contacts_contactsWillAppear": "当设备发布广告时,联系方式会显示。",
"contacts_searchContacts": "搜索联系人...",
- "contacts_noUnreadContacts": "未读联系人",
+ "contacts_noUnreadContacts": "没有未读通讯",
"contacts_noContactsFound": "未找到任何联系人或群组",
"contacts_deleteContact": "删除联系人",
- "contacts_removeConfirm": "从联系人中删除 {contactName} 吗?",
+ "contacts_removeConfirm": "Remove {contactName} from contacts?",
"@contacts_removeConfirm": {
"placeholders": {
"contactName": {
@@ -266,11 +273,12 @@
}
}
},
- "contacts_manageRepeater": "管理重复项",
- "contacts_roomLogin": "房间登录",
- "contacts_openChat": "打开聊天",
- "contacts_editGroup": "编辑组",
- "contacts_deleteGroup": "删除分组",
+ "contacts_manageRepeater": "管理重复器",
+ "contacts_manageRoom": "管理房间服务器",
+ "contacts_roomLogin": "服务器登录",
+ "contacts_openChat": "开放聊天",
+ "contacts_editGroup": "编辑小组",
+ "contacts_deleteGroup": "删除群组",
"contacts_deleteGroupConfirm": "删除\"{groupName}\"?",
"@contacts_deleteGroupConfirm": {
"placeholders": {
@@ -279,10 +287,10 @@
}
}
},
- "contacts_newGroup": "新组",
- "contacts_groupName": "组名",
- "contacts_groupNameRequired": "组名不能为空",
- "contacts_groupAlreadyExists": "组“{name}”已存在",
+ "contacts_newGroup": "新的团体",
+ "contacts_groupName": "团体名称",
+ "contacts_groupNameRequired": "需要提供组名称",
+ "contacts_groupAlreadyExists": "名为\"{name}\"的组已经存在",
"@contacts_groupAlreadyExists": {
"placeholders": {
"name": {
@@ -291,10 +299,10 @@
}
},
"contacts_filterContacts": "筛选联系人...",
- "contacts_noContactsMatchFilter": "未找到匹配您的筛选条件的结果",
+ "contacts_noContactsMatchFilter": "未找到符合您筛选条件的联系人",
"contacts_noMembers": "没有会员",
- "contacts_lastSeenNow": "最后一次登录时间现在",
- "contacts_lastSeenMinsAgo": "最后一次出现 {minutes} 分前",
+ "contacts_lastSeenNow": "最后一次被看到的时间",
+ "contacts_lastSeenMinsAgo": "Last seen {minutes} mins ago",
"@contacts_lastSeenMinsAgo": {
"placeholders": {
"minutes": {
@@ -302,8 +310,8 @@
}
}
},
- "contacts_lastSeenHourAgo": "最后一次出现前1小时",
- "contacts_lastSeenHoursAgo": "最后一次出现 {hours} 小时前",
+ "contacts_lastSeenHourAgo": "最后一次被看到的时间:1小时前",
+ "contacts_lastSeenHoursAgo": "Last seen {hours} hours ago",
"@contacts_lastSeenHoursAgo": {
"placeholders": {
"hours": {
@@ -311,8 +319,8 @@
}
}
},
- "contacts_lastSeenDayAgo": "最后一次登录前一天",
- "contacts_lastSeenDaysAgo": "最后一次出现 {days} 天前",
+ "contacts_lastSeenDayAgo": "最后一次被看到的时间是1天前",
+ "contacts_lastSeenDaysAgo": "Last seen {days} days ago",
"@contacts_lastSeenDaysAgo": {
"placeholders": {
"days": {
@@ -322,7 +330,7 @@
},
"channels_title": "频道",
"channels_noChannelsConfigured": "未配置任何频道",
- "channels_addPublicChannel": "添加公开频道",
+ "channels_addPublicChannel": "添加公共频道",
"channels_searchChannels": "搜索频道...",
"channels_noChannelsFound": "未找到任何频道",
"channels_channelIndex": "频道 {index}",
@@ -333,14 +341,14 @@
}
}
},
- "channels_hashtagChannel": "话题频道",
- "channels_public": "公开",
- "channels_private": "私有",
- "channels_publicChannel": "公开频道",
- "channels_privateChannel": "私聊频道",
+ "channels_hashtagChannel": "话题标签频道",
+ "channels_public": "公众",
+ "channels_private": "私人",
+ "channels_publicChannel": "公共频道",
+ "channels_privateChannel": "私密频道",
"channels_editChannel": "编辑频道",
"channels_deleteChannel": "删除频道",
- "channels_deleteChannelConfirm": "删除\"{name}\"?此操作无法撤销。",
+ "channels_deleteChannelConfirm": "Delete \"{name}\"? This cannot be undone.",
"@channels_deleteChannelConfirm": {
"placeholders": {
"name": {
@@ -348,7 +356,7 @@
}
}
},
- "channels_channelDeleted": "频道“{name}”已删除",
+ "channels_channelDeleted": "删除频道 \"{name}\"",
"@channels_channelDeleted": {
"placeholders": {
"name": {
@@ -360,12 +368,12 @@
"channels_channelIndexLabel": "频道索引",
"channels_channelName": "频道名称",
"channels_usePublicChannel": "使用公共频道",
- "channels_standardPublicPsk": "标准公钥共享密钥",
+ "channels_standardPublicPsk": "标准公共PSK",
"channels_pskHex": "PSK (十六进制)",
- "channels_generateRandomPsk": "生成随机PSK",
- "channels_enterChannelName": "请输入频道名称",
- "channels_pskMustBe32Hex": "PSK 必须是 32 个十六进制字符",
- "channels_channelAdded": "频道“{name}”已添加",
+ "channels_generateRandomPsk": "生成随机的PSK(正交相移键控)",
+ "channels_enterChannelName": "请在此处输入频道名称",
+ "channels_pskMustBe32Hex": "PSK 必须包含 32 个十六进制字符。",
+ "channels_channelAdded": "添加频道 \"{name}\"",
"@channels_channelAdded": {
"placeholders": {
"name": {
@@ -382,7 +390,7 @@
}
},
"channels_smazCompression": "SMAZ 压缩",
- "channels_channelUpdated": "频道“{name}”已更新",
+ "channels_channelUpdated": "频道 \"{name}\" 已更新",
"@channels_channelUpdated": {
"placeholders": {
"name": {
@@ -390,16 +398,28 @@
}
}
},
- "channels_publicChannelAdded": "公共频道已添加",
- "channels_sortBy": "按类型排序",
- "channels_sortManual": "手动",
- "channels_sortAZ": "A-Z",
+ "channels_publicChannelAdded": "已添加公共频道",
+ "channels_sortBy": "按排序",
+ "channels_sortManual": "手册",
+ "channels_sortAZ": "A 到 Z",
"channels_sortLatestMessages": "最新消息",
"channels_sortUnread": "未读",
- "chat_noMessages": "目前还没有消息",
- "chat_sendMessageToStart": "发送消息开始",
- "chat_originalMessageNotFound": "找不到原始消息",
- "chat_replyingTo": "回复 {name}",
+ "channels_createPrivateChannel": "创建私密频道",
+ "channels_createPrivateChannelDesc": "使用秘密密钥进行保护。",
+ "channels_joinPrivateChannel": "加入私密频道",
+ "channels_joinPrivateChannelDesc": "手动输入密钥。",
+ "channels_joinPublicChannel": "加入公共频道",
+ "channels_joinPublicChannelDesc": "任何人都可以加入这个频道。",
+ "channels_joinHashtagChannel": "加入一个带有特定标签的频道",
+ "channels_joinHashtagChannelDesc": "任何人都可以加入带有特定标签的频道。",
+ "channels_scanQrCode": "扫描二维码",
+ "channels_scanQrCodeComingSoon": "即将发布",
+ "channels_enterHashtag": "输入标签",
+ "channels_hashtagHint": "例如:#团队",
+ "chat_noMessages": "目前还没有收到任何消息。",
+ "chat_sendMessageToStart": "发送消息以开始",
+ "chat_originalMessageNotFound": "无法找到原始消息",
+ "chat_replyingTo": "Replying to {name}",
"@chat_replyingTo": {
"placeholders": {
"name": {
@@ -407,7 +427,7 @@
}
}
},
- "chat_replyTo": "回复 {name}",
+ "chat_replyTo": "Reply to {name}",
"@chat_replyTo": {
"placeholders": {
"name": {
@@ -415,8 +435,8 @@
}
}
},
- "chat_location": "位置",
- "chat_sendMessageTo": "向{contactName}发送消息",
+ "chat_location": "地点",
+ "chat_sendMessageTo": "Send a message to {contactName}",
"@chat_sendMessageTo": {
"placeholders": {
"contactName": {
@@ -425,7 +445,7 @@
}
},
"chat_typeMessage": "输入消息...",
- "chat_messageTooLong": "消息太长了(最大 {maxBytes} 字节)。",
+ "chat_messageTooLong": "消息内容过长(最大 {maxBytes} 字节)。",
"@chat_messageTooLong": {
"placeholders": {
"maxBytes": {
@@ -435,8 +455,8 @@
},
"chat_messageCopied": "消息已复制",
"chat_messageDeleted": "消息已删除",
- "chat_retryingMessage": "重试",
- "chat_retryCount": "重试 {current}/{max}",
+ "chat_retryingMessage": "重试消息",
+ "chat_retryCount": "Retry {current}/{max}",
"@chat_retryCount": {
"placeholders": {
"current": {
@@ -447,32 +467,32 @@
}
}
},
- "chat_sendGif": "发送GIF",
+ "chat_sendGif": "发送 GIF 动画",
"chat_reply": "回复",
- "chat_addReaction": "添加反应",
+ "chat_addReaction": "添加评论",
"chat_me": "我",
"emojiCategorySmileys": "表情符号",
"emojiCategoryGestures": "手势",
- "emojiCategoryHearts": "心",
- "emojiCategoryObjects": "对象",
- "gifPicker_title": "选择一个 GIF",
- "gifPicker_searchHint": "搜索GIF...",
+ "emojiCategoryHearts": "心脏",
+ "emojiCategoryObjects": "物体",
+ "gifPicker_title": "选择一个 GIF 动画",
+ "gifPicker_searchHint": "搜索 GIF 动画...",
"gifPicker_poweredBy": "由 GIPHY 提供支持",
"gifPicker_noGifsFound": "未找到 GIF 动画",
- "gifPicker_failedLoad": "GIF 加载失败",
- "gifPicker_failedSearch": "搜索GIF失败",
- "gifPicker_noInternet": "无网络连接",
- "debugLog_appTitle": "应用调试日志",
- "debugLog_bleTitle": "蓝牙调试日志",
+ "gifPicker_failedLoad": "无法加载 GIF 动画",
+ "gifPicker_failedSearch": "未能搜索 GIF 动画",
+ "gifPicker_noInternet": "没有互联网连接",
+ "debugLog_appTitle": "应用程序调试日志",
+ "debugLog_bleTitle": "BLE 调试日志",
"debugLog_copyLog": "复制日志",
- "debugLog_clearLog": "清除日志",
+ "debugLog_clearLog": "清晰的日志",
"debugLog_copied": "调试日志已复制",
- "debugLog_bleCopied": "蓝牙日志复制",
- "debugLog_noEntries": "尚未生成调试日志",
- "debugLog_enableInSettings": "启用应用调试日志记录设置",
- "debugLog_frames": "帧",
+ "debugLog_bleCopied": "BLE 日志已复制",
+ "debugLog_noEntries": "目前还没有调试日志",
+ "debugLog_enableInSettings": "在设置中启用应用程序调试日志功能。",
+ "debugLog_frames": "框架",
"debugLog_rawLogRx": "原始日志-RX",
- "debugLog_noBleActivity": "目前还没有蓝牙活动。",
+ "debugLog_noBleActivity": "目前尚未有蓝牙低功耗(BLE)活动。",
"debugFrame_length": "帧长度:{count} 字节",
"@debugFrame_length": {
"placeholders": {
@@ -489,8 +509,8 @@
}
}
},
- "debugFrame_textMessageHeader": "短信框",
- "debugFrame_destinationPubKey": "- 目的地公钥:{pubKey}",
+ "debugFrame_textMessageHeader": "短信模板:",
+ "debugFrame_destinationPubKey": "- 目标公钥:{pubKey}",
"@debugFrame_destinationPubKey": {
"placeholders": {
"pubKey": {
@@ -498,7 +518,7 @@
}
}
},
- "debugFrame_timestamp": "- 时间戳:{timestamp}",
+ "debugFrame_timestamp": "- Timestamp: {timestamp}",
"@debugFrame_timestamp": {
"placeholders": {
"timestamp": {
@@ -514,7 +534,7 @@
}
}
},
- "debugFrame_textType": "- 文本类型:{type} ({label})",
+ "debugFrame_textType": "- Text Type: {type} ({label})",
"@debugFrame_textType": {
"placeholders": {
"type": {
@@ -525,9 +545,9 @@
}
}
},
- "debugFrame_textTypeCli": "CLI",
- "debugFrame_textTypePlain": "简洁",
- "debugFrame_text": "- 文本:\"{text}\"",
+ "debugFrame_textTypeCli": "命令行界面",
+ "debugFrame_textTypePlain": "简单",
+ "debugFrame_text": "- 文本:“{text}”",
"@debugFrame_text": {
"placeholders": {
"text": {
@@ -535,16 +555,16 @@
}
}
},
- "debugFrame_hexDump": "十六进制数据",
+ "debugFrame_hexDump": "十六进制数据:",
"chat_pathManagement": "路径管理",
"chat_routingMode": "路由模式",
- "chat_autoUseSavedPath": "自动(使用已保存路径)",
+ "chat_autoUseSavedPath": "自动(使用已保存的路径)",
"chat_forceFloodMode": "强制洪水模式",
- "chat_recentAckPaths": "最近的 ACK 路径 (点击以使用):",
- "chat_pathHistoryFull": "路径历史已满。删除条目以添加新条目。",
- "chat_hopSingular": "跳转",
- "chat_hopPlural": "跳跃",
- "chat_hopsCount": "{count} {count, plural, =1{跳跃} other{跳跃}}",
+ "chat_recentAckPaths": "最近使用的 ACK 路径(点击使用):",
+ "chat_pathHistoryFull": "路径历史已满。删除条目以添加新的条目。",
+ "chat_hopSingular": "跳跃",
+ "chat_hopPlural": "啤酒花",
+ "chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
"@chat_hopsCount": {
"placeholders": {
"count": {
@@ -554,18 +574,18 @@
},
"chat_successes": "成功",
"chat_removePath": "删除路径",
- "chat_noPathHistoryYet": "还没有历史记录。\n发送消息以发现路径。",
+ "chat_noPathHistoryYet": "目前还没有历史记录。\n发送消息以查找路径。",
"chat_pathActions": "路径操作:",
"chat_setCustomPath": "设置自定义路径",
"chat_setCustomPathSubtitle": "手动指定路由路径",
- "chat_clearPath": "清除路径",
- "chat_clearPathSubtitle": "强制下次发送时重新发现",
- "chat_pathCleared": "路径已清除。下一条消息将重新发现路线。",
- "chat_floodModeSubtitle": "使用应用栏中的路由切换开关",
- "chat_floodModeEnabled": "防洪模式已启用。通过应用程序栏中的路由图标进行反转。",
+ "chat_clearPath": "明确的道路",
+ "chat_clearPathSubtitle": "在下一次发送时,重新尝试。",
+ "chat_pathCleared": "路径已清理。下一条消息将重新确定路线。",
+ "chat_floodModeSubtitle": "使用应用程序栏中的路由切换功能",
+ "chat_floodModeEnabled": "防洪模式已启用。通过应用程序栏中的路由图标进行切换。",
"chat_fullPath": "完整路径",
- "chat_pathDetailsNotAvailable": "路径详情尚未获取。请尝试发送消息以刷新。",
- "chat_pathSetHops": "路径设置:{hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
+ "chat_pathDetailsNotAvailable": "路径信息尚未提供。请尝试发送消息以刷新。",
+ "chat_pathSetHops": "Path set: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
"@chat_pathSetHops": {
"placeholders": {
"hopCount": {
@@ -576,16 +596,16 @@
}
}
},
- "chat_pathSavedLocally": "已本地保存。连接以同步。",
+ "chat_pathSavedLocally": "已本地保存。连接以进行同步。",
"chat_pathDeviceConfirmed": "设备已确认。",
- "chat_pathDeviceNotConfirmed": "设备尚未确认。",
- "chat_type": "输入",
+ "chat_pathDeviceNotConfirmed": "该设备尚未得到确认。",
+ "chat_type": "类型",
"chat_path": "路径",
"chat_publicKey": "公钥",
"chat_compressOutgoingMessages": "压缩发送的消息",
- "chat_floodForced": "强制溢出",
- "chat_directForced": "强制直接",
- "chat_hopsForced": "{count} 次跳跃 (强制)",
+ "chat_floodForced": "洪水(被迫)",
+ "chat_directForced": "直接(强制性的)",
+ "chat_hopsForced": "{count} 根啤酒花(人工种植)",
"@chat_hopsForced": {
"placeholders": {
"count": {
@@ -593,10 +613,10 @@
}
}
},
- "chat_floodAuto": "自动防洪",
+ "chat_floodAuto": "自动洪水",
"chat_direct": "直接",
- "chat_poiShared": "共享位置信息",
- "chat_unread": "未读:{count}",
+ "chat_poiShared": "共享位置",
+ "chat_unread": "Unread: {count}",
"@chat_unread": {
"placeholders": {
"count": {
@@ -605,9 +625,9 @@
}
},
"chat_openLink": "打开链接?",
- "chat_openLinkConfirmation": "您想在浏览器中打开此链接吗?",
- "chat_open": "打开",
- "chat_couldNotOpenLink": "无法打开链接:{url}",
+ "chat_openLinkConfirmation": "您想用浏览器打开这个链接吗?",
+ "chat_open": "开放",
+ "chat_couldNotOpenLink": "[保存:{url}]\n无法打开链接:{url}",
"@chat_couldNotOpenLink": {
"placeholders": {
"url": {
@@ -615,11 +635,11 @@
}
}
},
- "chat_invalidLink": "链接格式无效",
- "map_title": "节点地图",
- "map_noNodesWithLocation": "没有具有位置数据的节点",
- "map_nodesNeedGps": "节点需要共享它们的 GPS 坐标\n才能在地图上显示",
- "map_nodesCount": "节点:{count}",
+ "chat_invalidLink": "无效的链接格式",
+ "map_title": "节点图",
+ "map_noNodesWithLocation": "没有包含位置信息的节点",
+ "map_nodesNeedGps": "节点需要共享其 GPS 坐标,以便在地图上显示",
+ "map_nodesCount": "Nodes: {count}",
"@map_nodesCount": {
"placeholders": {
"count": {
@@ -627,7 +647,7 @@
}
}
},
- "map_pinsCount": "针:{count}",
+ "map_pinsCount": "Pins: {count}",
"@map_pinsCount": {
"placeholders": {
"count": {
@@ -639,23 +659,23 @@
"map_repeater": "重复器",
"map_room": "房间",
"map_sensor": "传感器",
- "map_pinDm": "私信 (DM)",
- "map_pinPrivate": "私密模式",
- "map_pinPublic": "公开(公版)",
- "map_lastSeen": "最后一次登录",
+ "map_pinDm": "PIN (直接消息)",
+ "map_pinPrivate": "私密",
+ "map_pinPublic": "公开",
+ "map_lastSeen": "最后一次被看到",
"map_disconnectConfirm": "您确定要断开与此设备的连接吗?",
"map_from": "从",
"map_source": "来源",
"map_flags": "旗帜",
- "map_shareMarkerHere": "分享标记在此",
- "map_pinLabel": "固定标签",
+ "map_shareMarkerHere": "在此分享标记",
+ "map_pinLabel": "标签",
"map_label": "标签",
- "map_pointOfInterest": "兴趣点",
- "map_sendToContact": "发送给联系人",
+ "map_pointOfInterest": "值得参观的地方",
+ "map_sendToContact": "发送给联系",
"map_sendToChannel": "发送到频道",
"map_noChannelsAvailable": "没有可用的频道",
- "map_publicLocationShare": "公共位置共享",
- "map_publicLocationShareConfirm": "您即将分享一个位置在 {channelLabel}。此频道公开,任何拥有 PSK 的人都可以看到它。",
+ "map_publicLocationShare": "公共场所共享",
+ "map_publicLocationShareConfirm": "[保存:{channelLabel}]\n您即将分享一个位置,该位置位于 {channelLabel}。 此频道是公开的,任何拥有 PSK 的人都可以看到它。",
"@map_publicLocationShareConfirm": {
"placeholders": {
"channelLabel": {
@@ -664,25 +684,25 @@
}
},
"map_connectToShareMarkers": "连接设备以共享标记",
- "map_filterNodes": "筛选节点",
+ "map_filterNodes": "过滤节点",
"map_nodeTypes": "节点类型",
"map_chatNodes": "聊天节点",
"map_repeaters": "重复器",
"map_otherNodes": "其他节点",
- "map_keyPrefix": "键前缀",
- "map_filterByKeyPrefix": "按关键词前缀筛选",
+ "map_keyPrefix": "关键前缀",
+ "map_filterByKeyPrefix": "按关键前缀筛选",
"map_publicKeyPrefix": "公钥前缀",
"map_markers": "标记",
"map_showSharedMarkers": "显示共享标记",
- "map_lastSeenTime": "最后一次查看时间",
- "map_sharedPin": "共享 PIN",
+ "map_lastSeenTime": "最后一次被看到的时间",
+ "map_sharedPin": "共享密码",
"map_joinRoom": "加入房间",
- "map_manageRepeater": "管理重复项",
+ "map_manageRepeater": "管理重复器",
"mapCache_title": "离线地图缓存",
- "mapCache_selectAreaFirst": "选择一个区域进行缓存",
- "mapCache_noTilesToDownload": "该区域没有可下载的瓦片。",
- "mapCache_downloadTilesTitle": "下载瓦片",
- "mapCache_downloadTilesPrompt": "下载 {count} 个瓦片用于离线使用?",
+ "mapCache_selectAreaFirst": "选择一个用于缓存的区域",
+ "mapCache_noTilesToDownload": "此区域没有可下载的瓦片。",
+ "mapCache_downloadTilesTitle": "下载瓷砖",
+ "mapCache_downloadTilesPrompt": "[保存:{count}]\n下载 {count} 个图片用于离线使用?",
"@mapCache_downloadTilesPrompt": {
"placeholders": {
"count": {
@@ -691,7 +711,7 @@
}
},
"mapCache_downloadAction": "下载",
- "mapCache_cachedTiles": "已缓存 {count} 个瓦片",
+ "mapCache_cachedTiles": "缓存 {count} 个瓦片",
"@mapCache_cachedTiles": {
"placeholders": {
"count": {
@@ -699,7 +719,7 @@
}
}
},
- "mapCache_cachedTilesWithFailed": "已缓存 {downloaded} 个瓦片 ({failed} 失败)",
+ "mapCache_cachedTilesWithFailed": "Cached {downloaded} tiles ({failed} failed)",
"@mapCache_cachedTilesWithFailed": {
"placeholders": {
"downloaded": {
@@ -711,13 +731,13 @@
}
},
"mapCache_clearOfflineCacheTitle": "清除离线缓存",
- "mapCache_clearOfflineCachePrompt": "删除所有缓存地图瓦片?",
+ "mapCache_clearOfflineCachePrompt": "清除所有缓存的地图瓦片",
"mapCache_offlineCacheCleared": "离线缓存已清除",
"mapCache_noAreaSelected": "未选择任何区域",
"mapCache_cacheArea": "缓存区域",
"mapCache_useCurrentView": "使用当前视图",
- "mapCache_zoomRange": "缩放范围",
- "mapCache_estimatedTiles": "预计瓦片数量:{count}",
+ "mapCache_zoomRange": "变焦范围",
+ "mapCache_estimatedTiles": "Estimated tiles: {count}",
"@mapCache_estimatedTiles": {
"placeholders": {
"count": {
@@ -725,7 +745,7 @@
}
}
},
- "mapCache_downloadedTiles": "已下载 {completed} / {total}",
+ "mapCache_downloadedTiles": "Downloaded {completed} / {total}",
"@mapCache_downloadedTiles": {
"placeholders": {
"completed": {
@@ -736,9 +756,9 @@
}
}
},
- "mapCache_downloadTilesButton": "下载瓦片",
+ "mapCache_downloadTilesButton": "下载瓷砖",
"mapCache_clearCacheButton": "清除缓存",
- "mapCache_failedDownloads": "下载失败:{count}",
+ "mapCache_failedDownloads": "Failed downloads: {count}",
"@mapCache_failedDownloads": {
"placeholders": {
"count": {
@@ -746,7 +766,7 @@
}
}
},
- "mapCache_boundsLabel": "北 {north}, 南 {south}, 东 {east}, 西 {west}",
+ "mapCache_boundsLabel": "N {north}, S {south}, E {east}, W {west}",
"@mapCache_boundsLabel": {
"placeholders": {
"north": {
@@ -764,7 +784,7 @@
}
},
"time_justNow": "刚才",
- "time_minutesAgo": "{minutes}分钟前",
+ "time_minutesAgo": "{minutes}m ago",
"@time_minutesAgo": {
"placeholders": {
"minutes": {
@@ -772,7 +792,7 @@
}
}
},
- "time_hoursAgo": "{hours}小时前",
+ "time_hoursAgo": "{hours}h ago",
"@time_hoursAgo": {
"placeholders": {
"hours": {
@@ -780,7 +800,7 @@
}
}
},
- "time_daysAgo": "{days} 天前",
+ "time_daysAgo": "{days}天前",
"@time_daysAgo": {
"placeholders": {
"days": {
@@ -790,10 +810,10 @@
},
"time_hour": "小时",
"time_hours": "小时",
- "time_day": "今天",
+ "time_day": "一天",
"time_days": "天",
- "time_week": "本周",
- "time_weeks": "几周",
+ "time_week": "一周",
+ "time_weeks": "周",
"time_month": "月份",
"time_months": "月份",
"time_minutes": "分钟",
@@ -801,20 +821,20 @@
"dialog_disconnect": "断开",
"dialog_disconnectConfirm": "您确定要断开与此设备的连接吗?",
"login_repeaterLogin": "重复登录",
- "login_roomLogin": "房间登录",
+ "login_roomLogin": "服务器登录",
"login_password": "密码",
"login_enterPassword": "请输入密码",
"login_savePassword": "保存密码",
- "login_savePasswordSubtitle": "密码将安全地存储在这个设备上",
- "login_repeaterDescription": "输入重复密码以访问设置和状态。",
- "login_roomDescription": "输入房间密码以访问设置和状态。",
+ "login_savePasswordSubtitle": "密码将安全地存储在 данном设备上",
+ "login_repeaterDescription": "输入重复器密码,即可访问设置和状态。",
+ "login_roomDescription": "输入密码进入房间,即可访问设置和状态。",
"login_routing": "路由",
"login_routingMode": "路由模式",
- "login_autoUseSavedPath": "自动(使用已保存路径)",
+ "login_autoUseSavedPath": "自动(使用已保存的路径)",
"login_forceFloodMode": "强制洪水模式",
"login_managePaths": "管理路径",
"login_login": "登录",
- "login_attempt": "尝试 {current}/{max}",
+ "login_attempt": "Attempt {current}/{max}",
"@login_attempt": {
"placeholders": {
"current": {
@@ -825,7 +845,7 @@
}
}
},
- "login_failed": "登录失败:{error}",
+ "login_failed": "Login failed: {error}",
"@login_failed": {
"placeholders": {
"error": {
@@ -833,10 +853,10 @@
}
}
},
- "login_failedMessage": "登录失败。密码不正确或中继器不可达。",
+ "login_failedMessage": "登录失败。可能是密码错误,也可能是无法连接到服务器。",
"common_reload": "重新加载",
- "common_clear": "清除",
- "path_currentPath": "当前路径:{path}",
+ "common_clear": "清晰",
+ "path_currentPath": "Current path: {path}",
"@path_currentPath": {
"placeholders": {
"path": {
@@ -844,7 +864,7 @@
}
}
},
- "path_usingHopsPath": "使用 {count} {count, plural, =1{hop} other{hops}} 路径",
+ "path_usingHopsPath": "使用 {count} {count, plural, =1{hop} other{hops}} 条路径",
"@path_usingHopsPath": {
"placeholders": {
"count": {
@@ -854,14 +874,14 @@
},
"path_enterCustomPath": "输入自定义路径",
"path_currentPathLabel": "当前路径",
- "path_hexPrefixInstructions": "输入2个字符的十六进制前缀,每个前缀之间用逗号分隔。",
- "path_hexPrefixExample": "A1,F2,3C (每个节点使用其公钥的第一字节)",
- "path_labelHexPrefixes": "十六进制前缀",
- "path_helperMaxHops": "最大 64 步跳。每个前缀是 2 个十六进制字符(1 字节)",
- "path_selectFromContacts": "或从联系人中选择:",
- "path_noRepeatersFound": "未找到任何重复器或房间服务器。",
- "path_customPathsRequire": "自定义路径需要中间跳转,这些跳转可以传递消息。",
- "path_invalidHexPrefixes": "无效的十六进制前缀:{prefixes}",
+ "path_hexPrefixInstructions": "请输入每个跳跃步骤的 2 个字符的十六进制前缀,用逗号分隔。",
+ "path_hexPrefixExample": "例如:A1, F2, 3C (每个节点使用其公钥的第一字节)",
+ "path_labelHexPrefixes": "路径(十六进制前缀)",
+ "path_helperMaxHops": "最大 64 个“hop”(跳跃)。每个前缀由 2 个十六进制字符(1 字节)组成。",
+ "path_selectFromContacts": "或者从联系人列表中选择:",
+ "path_noRepeatersFound": "未找到任何重复设备或房间服务器。",
+ "path_customPathsRequire": "自定义路径需要中间节点,这些节点可以转发消息。",
+ "path_invalidHexPrefixes": "Invalid hex prefixes: {prefixes}",
"@path_invalidHexPrefixes": {
"placeholders": {
"prefixes": {
@@ -872,23 +892,26 @@
"path_tooLong": "路径太长。允许的最大跳跃次数为 64 次。",
"path_setPath": "设置路径",
"repeater_management": "重复器管理",
+ "room_management": "服务器管理",
"repeater_managementTools": "管理工具",
"repeater_status": "状态",
"repeater_statusSubtitle": "查看重复器状态、统计信息和邻居",
- "repeater_telemetry": "遥测",
- "repeater_telemetrySubtitle": "查看传感器和系统状态的Telemetry数据",
- "repeater_cli": "CLI",
- "repeater_cliSubtitle": "发送命令到重复器",
+ "repeater_telemetry": "远程监控",
+ "repeater_telemetrySubtitle": "查看传感器和系统状态的数据。",
+ "repeater_cli": "命令行界面",
+ "repeater_cliSubtitle": "向复用器发送指令",
+ "repeater_neighbours": "邻居",
+ "repeater_neighboursSubtitle": "查看邻居节点(无需中间节点)。",
"repeater_settings": "设置",
"repeater_settingsSubtitle": "配置重复器参数",
"repeater_statusTitle": "重复器状态",
"repeater_routingMode": "路由模式",
- "repeater_autoUseSavedPath": "自动(使用已保存路径)",
+ "repeater_autoUseSavedPath": "自动(使用已保存的路径)",
"repeater_forceFloodMode": "强制洪水模式",
"repeater_pathManagement": "路径管理",
- "repeater_refresh": "刷新",
+ "repeater_refresh": "更新",
"repeater_statusRequestTimeout": "状态请求超时。",
- "repeater_errorLoadingStatus": "错误加载状态:{error}",
+ "repeater_errorLoadingStatus": "Error loading status: {error}",
"@repeater_errorLoadingStatus": {
"placeholders": {
"error": {
@@ -898,18 +921,18 @@
},
"repeater_systemInformation": "系统信息",
"repeater_battery": "电池",
- "repeater_clockAtLogin": "时间 (登录时)",
- "repeater_uptime": "可用时间",
+ "repeater_clockAtLogin": "登录时的时间",
+ "repeater_uptime": "正常运行时间",
"repeater_queueLength": "排队长度",
"repeater_debugFlags": "调试标志",
- "repeater_radioStatistics": "无线电统计",
- "repeater_lastRssi": "上次RSSI",
- "repeater_lastSnr": "最后 SNR",
- "repeater_noiseFloor": "噪声地板",
- "repeater_txAirtime": "TX Airtime",
- "repeater_rxAirtime": "RX Airtime",
+ "repeater_radioStatistics": "广播统计",
+ "repeater_lastRssi": "上次的 RSSI 值",
+ "repeater_lastSnr": "最后一次信噪比",
+ "repeater_noiseFloor": "噪声水平",
+ "repeater_txAirtime": "TX 频道预留时间",
+ "repeater_rxAirtime": "RX 空时",
"repeater_packetStatistics": "数据包统计",
- "repeater_sent": "已发送",
+ "repeater_sent": "发送",
"repeater_received": "已收到",
"repeater_duplicates": "重复",
"repeater_daysHoursMinsSecs": "{days}天 {hours}小时 {minutes}分 {seconds}秒",
@@ -929,7 +952,7 @@
}
}
},
- "repeater_packetTxTotal": "总计:{total}, 洪流:{flood}, 直连:{direct}",
+ "repeater_packetTxTotal": "Total: {total}, Flood: {flood}, Direct: {direct}",
"@repeater_packetTxTotal": {
"placeholders": {
"total": {
@@ -943,7 +966,7 @@
}
}
},
- "repeater_packetRxTotal": "总计:{total}, 洪流:{flood}, 直连:{direct}",
+ "repeater_packetRxTotal": "Total: {total}, Flood: {flood}, Direct: {direct}",
"@repeater_packetRxTotal": {
"placeholders": {
"total": {
@@ -957,7 +980,7 @@
}
}
},
- "repeater_duplicatesFloodDirect": "洪水:{flood}, 直通:{direct}",
+ "repeater_duplicatesFloodDirect": "Flood: {flood}, Direct: {direct}",
"@repeater_duplicatesFloodDirect": {
"placeholders": {
"flood": {
@@ -968,7 +991,7 @@
}
}
},
- "repeater_duplicatesTotal": "总计:{total}",
+ "repeater_duplicatesTotal": "Total: {total}",
"@repeater_duplicatesTotal": {
"placeholders": {
"total": {
@@ -976,37 +999,37 @@
}
}
},
- "repeater_settingsTitle": "重复设置",
+ "repeater_settingsTitle": "重复器设置",
"repeater_basicSettings": "基本设置",
"repeater_repeaterName": "重复器名称",
- "repeater_repeaterNameHelper": "显示此重复器的名称",
+ "repeater_repeaterNameHelper": "此复播器的显示名称",
"repeater_adminPassword": "管理员密码",
"repeater_adminPasswordHelper": "完整访问密码",
"repeater_guestPassword": "访客密码",
"repeater_guestPasswordHelper": "只读访问密码",
- "repeater_radioSettings": "射频设置",
+ "repeater_radioSettings": "收音机设置",
"repeater_frequencyMhz": "频率 (MHz)",
- "repeater_frequencyHelper": "300-2500 MHz",
- "repeater_txPower": "TX Power",
+ "repeater_frequencyHelper": "300-2500 兆赫",
+ "repeater_txPower": "TX 功率",
"repeater_txPowerHelper": "1-30 dBm",
"repeater_bandwidth": "带宽",
- "repeater_spreadingFactor": "扩散因子",
+ "repeater_spreadingFactor": "传播系数",
"repeater_codingRate": "编码速率",
"repeater_locationSettings": "位置设置",
"repeater_latitude": "纬度",
- "repeater_latitudeHelper": "十进度的数字(例如:37.7749)",
+ "repeater_latitudeHelper": "十进制度(例如:37.7749)",
"repeater_longitude": "经度",
- "repeater_longitudeHelper": "十进度的数字(例如:-122.4194)",
- "repeater_features": "功能",
+ "repeater_longitudeHelper": "十进制度(例如:-122.4194)",
+ "repeater_features": "特点",
"repeater_packetForwarding": "数据包转发",
- "repeater_packetForwardingSubtitle": "启用重复器以转发数据包",
+ "repeater_packetForwardingSubtitle": "启用重复器,使其能够转发数据包",
"repeater_guestAccess": "访客访问",
- "repeater_guestAccessSubtitle": "允许访客仅读访问",
+ "repeater_guestAccessSubtitle": "允许访客仅限读取权限",
"repeater_privacyMode": "隐私模式",
- "repeater_privacyModeSubtitle": "隐藏在广告中的姓名/位置",
+ "repeater_privacyModeSubtitle": "在广告中隐藏姓名/位置",
"repeater_advertisementSettings": "广告设置",
- "repeater_localAdvertInterval": "本地广告间隔",
- "repeater_localAdvertIntervalMinutes": "{minutes} 分钟",
+ "repeater_localAdvertInterval": "本地广告投放时间段",
+ "repeater_localAdvertIntervalMinutes": "{minutes} minutes",
"@repeater_localAdvertIntervalMinutes": {
"placeholders": {
"minutes": {
@@ -1014,8 +1037,8 @@
}
}
},
- "repeater_floodAdvertInterval": "洪水广告间隔",
- "repeater_floodAdvertIntervalHours": "{hours} 小时",
+ "repeater_floodAdvertInterval": "洪水广告播放间隔",
+ "repeater_floodAdvertIntervalHours": "{hours} hours",
"@repeater_floodAdvertIntervalHours": {
"placeholders": {
"hours": {
@@ -1023,19 +1046,19 @@
}
}
},
- "repeater_encryptedAdvertInterval": "加密广告间隔",
+ "repeater_encryptedAdvertInterval": "加密的广告投放时间段",
"repeater_dangerZone": "危险区域",
"repeater_rebootRepeater": "重启重复器",
- "repeater_rebootRepeaterSubtitle": "重启重复器设备",
- "repeater_rebootRepeaterConfirm": "您确定要重启这个中继器吗?",
+ "repeater_rebootRepeaterSubtitle": "重新启动重复器设备",
+ "repeater_rebootRepeaterConfirm": "您确定要重新启动这个中继器吗?",
"repeater_regenerateIdentityKey": "重新生成身份密钥",
"repeater_regenerateIdentityKeySubtitle": "生成新的公钥/私钥对",
- "repeater_regenerateIdentityKeyConfirm": "这将生成一个重复器的新身份。继续吗?",
+ "repeater_regenerateIdentityKeyConfirm": "这将为复用器生成一个新的身份。继续吗?",
"repeater_eraseFileSystem": "删除文件系统",
"repeater_eraseFileSystemSubtitle": "格式化重复文件系统",
- "repeater_eraseFileSystemConfirm": "警告:这将擦除重复器上的所有数据。 这无法撤销!",
- "repeater_eraseSerialOnly": "通过串行控制台才能删除。",
- "repeater_commandSent": "命令已发送:{command}",
+ "repeater_eraseFileSystemConfirm": "警告:此操作将清除复用器上的所有数据。 无法恢复!",
+ "repeater_eraseSerialOnly": "“Erase”功能仅可通过串行控制台使用。",
+ "repeater_commandSent": "Command sent: {command}",
"@repeater_commandSent": {
"placeholders": {
"command": {
@@ -1043,7 +1066,7 @@
}
}
},
- "repeater_errorSendingCommand": "发送命令时出错:{error}",
+ "repeater_errorSendingCommand": "Error sending command: {error}",
"@repeater_errorSendingCommand": {
"placeholders": {
"error": {
@@ -1052,8 +1075,8 @@
}
},
"repeater_confirm": "确认",
- "repeater_settingsSaved": "设置已保存成功",
- "repeater_errorSavingSettings": "保存设置出错:{error}",
+ "repeater_settingsSaved": "设置已成功保存",
+ "repeater_errorSavingSettings": "Error saving settings: {error}",
"@repeater_errorSavingSettings": {
"placeholders": {
"error": {
@@ -1061,15 +1084,15 @@
}
}
},
- "repeater_refreshBasicSettings": "刷新基本设置",
- "repeater_refreshRadioSettings": "刷新无线电设置",
- "repeater_refreshTxPower": "刷新 TX 电量",
- "repeater_refreshLocationSettings": "刷新位置设置",
+ "repeater_refreshBasicSettings": "重置基本设置",
+ "repeater_refreshRadioSettings": "重置收音机设置",
+ "repeater_refreshTxPower": "重置 TX 电源",
+ "repeater_refreshLocationSettings": "重置位置设置",
"repeater_refreshPacketForwarding": "刷新包转发",
- "repeater_refreshGuestAccess": "刷新访客访问",
- "repeater_refreshPrivacyMode": "刷新隐私模式",
- "repeater_refreshAdvertisementSettings": "刷新广告设置",
- "repeater_refreshed": "{label} 已刷新",
+ "repeater_refreshGuestAccess": "重新获取访客访问权限",
+ "repeater_refreshPrivacyMode": "重置隐私模式",
+ "repeater_refreshAdvertisementSettings": "重置广告设置",
+ "repeater_refreshed": "{label} refreshed",
"@repeater_refreshed": {
"placeholders": {
"label": {
@@ -1077,7 +1100,7 @@
}
}
},
- "repeater_errorRefreshing": "刷新 {label} 时出错",
+ "repeater_errorRefreshing": "[保存:{label}]\n刷新 {label} 时出错",
"@repeater_errorRefreshing": {
"placeholders": {
"label": {
@@ -1085,18 +1108,18 @@
}
}
},
- "repeater_cliTitle": "重复器命令行工具",
- "repeater_debugNextCommand": "调试下一步命令",
+ "repeater_cliTitle": "重复器命令行界面",
+ "repeater_debugNextCommand": "调试下一条命令",
"repeater_commandHelp": "帮助",
- "repeater_clearHistory": "清除历史",
- "repeater_noCommandsSent": "尚未发送任何命令",
- "repeater_typeCommandOrUseQuick": "输入命令或使用快捷命令",
+ "repeater_clearHistory": "清晰的历史",
+ "repeater_noCommandsSent": "尚未发送任何指令",
+ "repeater_typeCommandOrUseQuick": "在下方输入命令,或使用快捷命令。",
"repeater_enterCommandHint": "输入命令...",
- "repeater_previousCommand": "上一个命令",
- "repeater_nextCommand": "下一步命令",
- "repeater_enterCommandFirst": "请输入一个命令",
- "repeater_cliCommandFrameTitle": "CLI 命令窗口",
- "repeater_cliCommandError": "错误:{error}",
+ "repeater_previousCommand": "之前的命令",
+ "repeater_nextCommand": "下一个指令",
+ "repeater_enterCommandFirst": "首先输入一个命令",
+ "repeater_cliCommandFrameTitle": "CLI 命令框架",
+ "repeater_cliCommandError": "Error: {error}",
"@repeater_cliCommandError": {
"placeholders": {
"error": {
@@ -1105,80 +1128,80 @@
}
},
"repeater_cliQuickGetName": "获取姓名",
- "repeater_cliQuickGetRadio": "获取收音机",
+ "repeater_cliQuickGetRadio": "收听广播",
"repeater_cliQuickGetTx": "获取 TX",
"repeater_cliQuickNeighbors": "邻居",
"repeater_cliQuickVersion": "版本",
- "repeater_cliQuickAdvertise": "发布",
+ "repeater_cliQuickAdvertise": "发布广告",
"repeater_cliQuickClock": "时钟",
- "repeater_cliHelpAdvert": "发送广告包",
- "repeater_cliHelpReboot": "重启设备。(请注意,可能会出现“超时”现象,这是正常现象)",
+ "repeater_cliHelpAdvert": "发送广告资料包",
+ "repeater_cliHelpReboot": "重置设备。 (请注意,您可能会收到“超时”错误,这是正常的现象)",
"repeater_cliHelpClock": "显示每个设备的当前时间。",
- "repeater_cliHelpPassword": "设置设备的新管理员密码。",
+ "repeater_cliHelpPassword": "为设备设置新的管理员密码。",
"repeater_cliHelpVersion": "显示设备版本和固件构建日期。",
- "repeater_cliHelpClearStats": "重置各种统计数值为零。",
- "repeater_cliHelpSetAf": "设置空闲时间因子。",
- "repeater_cliHelpSetTx": "设置 LoRa 传输功率 (重置生效)",
- "repeater_cliHelpSetRepeat": "启用或禁用此节点的重复器角色。",
- "repeater_cliHelpSetAllowReadOnly": "(房间服务器) 如果“开”了,则空密码登录将被允许,但不能向房间发布内容。(仅限读取)",
- "repeater_cliHelpSetFloodMax": "设置最大换路包数量(如果 >= 最大,则不转发包)。",
- "repeater_cliHelpSetIntThresh": "设置干扰阈值(以 dB 为单位)。默认值为 14。将设置为 0 以禁用通道干扰检测。",
- "repeater_cliHelpSetAgcResetInterval": "设置间隔以重置自动增益控制器。将设置为 0 以禁用。",
- "repeater_cliHelpSetMultiAcks": "启用或禁用“双 ACKs”功能。",
- "repeater_cliHelpSetAdvertInterval": "设置定时器间隔时间为分钟,以发送本地(零跳)广告包。将设置为0以禁用。",
- "repeater_cliHelpSetFloodAdvertInterval": "设置定时器间隔时间为小时,以发送洪水广告包。将设置为 0 以禁用。",
- "repeater_cliHelpSetGuestPassword": "设置/更新客人密码。(对于重复器,客人在登录时可以发送“获取统计”请求)",
+ "repeater_cliHelpClearStats": "重置各种统计指标,将其设置为零。",
+ "repeater_cliHelpSetAf": "设置时间因素。",
+ "repeater_cliHelpSetTx": "设置 LoRa 传输功率,单位为 dBm (相对于参考值)。 (重启以应用更改)",
+ "repeater_cliHelpSetRepeat": "启用或禁用此节点的重复器功能。",
+ "repeater_cliHelpSetAllowReadOnly": "(房间服务器)如果设置为“开启”,则允许使用空密码登录,但无法向房间发送消息(只能进行读取)。",
+ "repeater_cliHelpSetFloodMax": "设置最大传入数据包的跳数(如果大于或等于最大值,则不进行转发)。",
+ "repeater_cliHelpSetIntThresh": "设置干扰阈值(以dB为单位)。默认值为14。将设置为0以禁用频道干扰检测。",
+ "repeater_cliHelpSetAgcResetInterval": "设置间隔时间,用于重置自动增益控制器。设置为 0 以禁用。",
+ "repeater_cliHelpSetMultiAcks": "启用或禁用“双重确认”功能。",
+ "repeater_cliHelpSetAdvertInterval": "设置定时器间隔,单位为分钟,用于发送本地(无中继)的广告数据包。 将设置为 0 以禁用。",
+ "repeater_cliHelpSetFloodAdvertInterval": "设置定时器间隔时间为小时,以便发送广告信息包。将设置为 0 以禁用。",
+ "repeater_cliHelpSetGuestPassword": "设置/更新访客密码。 (对于访客,登录请求可以发送“获取统计”请求)",
"repeater_cliHelpSetName": "设置广告名称。",
- "repeater_cliHelpSetLat": "设置广告地图纬度(十进制度)",
- "repeater_cliHelpSetLon": "设置广告地图经度 (十进位)",
- "repeater_cliHelpSetRadio": "设置全新的无线电参数,并保存到偏好设置。需要执行“重启”命令才能应用。",
- "repeater_cliHelpSetRxDelay": "设置(实验性)的基础(必须大于 1 才能生效)是用于对接收到的数据包应用轻微延迟,基于信号强度/得分。将设置为 0 以禁用。",
- "repeater_cliHelpSetTxDelay": "设置一个与时间-在空气中(time-on-air)的系数,用于洪水模式的数据包,并结合随机插槽系统,以延迟其转发。(以降低碰撞的可能性)",
- "repeater_cliHelpSetDirectTxDelay": "与txdelay相同,但用于为直接模式包的转发应用随机延迟。",
- "repeater_cliHelpSetBridgeEnabled": "启用/禁用桥梁",
- "repeater_cliHelpSetBridgeDelay": "设置在重新发送数据包之前延迟时间。",
- "repeater_cliHelpSetBridgeSource": "选择桥梁是否会重传接收到的数据包或发送的数据包。",
- "repeater_cliHelpSetBridgeBaud": "设置rs232桥接的串口链路波特率。",
- "repeater_cliHelpSetBridgeSecret": "设置 espnow 桥的秘密。",
- "repeater_cliHelpSetAdcMultiplier": "设置自定义因子以调整报告的电池电压(仅限部分板卡支持)。",
- "repeater_cliHelpTempRadio": "设置临时无线电参数,持续指定的分钟数,之后恢复为原始无线电参数。(不保存到偏好设置)。",
- "repeater_cliHelpSetPerm": "修改ACL。如果“权限”为零,则删除匹配的条目(通过pubkey前缀)。如果pubkey-hex的完整长度且当前不在ACL中,则添加新条目。通过匹配pubkey前缀更新条目。权限位因固件角色而异,但低2位为:0(Guest)、1(只读)、2(读写)、3(Admin)",
- "repeater_cliHelpGetBridgeType": "获取桥接类型:无,RS232,ESPNow",
+ "repeater_cliHelpSetLat": "设置广告地图的纬度。(以十进制表示)",
+ "repeater_cliHelpSetLon": "设置广告地图的经度。 (十进制度)",
+ "repeater_cliHelpSetRadio": "完全重新设置无线电参数,并保存到偏好设置。需要执行“重启”命令才能生效。",
+ "repeater_cliHelpSetRxDelay": "设置(实验性):设置一个基础值(必须大于1才能生效),用于对接收到的数据包进行轻微延迟处理,该延迟值基于信号强度/评分。将该值设置为0以禁用。",
+ "repeater_cliHelpSetTxDelay": "通过将一个因子与“浮动模式”数据包的时间在空中停留时间相乘,并结合随机的“时隙”系统,来延迟其转发,从而降低数据包冲突的概率。",
+ "repeater_cliHelpSetDirectTxDelay": "与txdelay相同,但用于对直接模式数据包的转发进行随机延迟。",
+ "repeater_cliHelpSetBridgeEnabled": "启用/禁用桥接。",
+ "repeater_cliHelpSetBridgeDelay": "在重新发送数据包之前,设置延迟时间。",
+ "repeater_cliHelpSetBridgeSource": "选择桥接器是否会转发收到的数据包,还是转发发送的数据包。",
+ "repeater_cliHelpSetBridgeBaud": "为 RS232 桥接设置串行链路的波特率。",
+ "repeater_cliHelpSetBridgeSecret": "设置 ESPNOW 桥的秘密。",
+ "repeater_cliHelpSetAdcMultiplier": "设置自定义因子,用于调整报告的电池电压(仅在特定板上支持)。",
+ "repeater_cliHelpTempRadio": "设置临时收音机参数,持续指定分钟数,之后恢复到原始收音机参数。(不保存到偏好设置)。",
+ "repeater_cliHelpSetPerm": "修改 ACL。如果 \"permissions\" 的值为 0,则删除与 pubkey 相关的条目。如果 pubkey-hex 完整且当前不在 ACL 中,则添加新的条目。通过匹配 pubkey 相关的前缀来更新条目。不同固件角色的权限位有所不同,但低 2 位分别对应:0 (访客)、1 (只读)、2 (读写)、3 (管理员)。",
+ "repeater_cliHelpGetBridgeType": "支持桥接模式、RS232、ESPNOW。",
"repeater_cliHelpLogStart": "开始将数据包记录到文件系统。",
- "repeater_cliHelpLogStop": "停止将数据包记录到文件系统。",
- "repeater_cliHelpLogErase": "删除文件系统中的包日志。",
- "repeater_cliHelpNeighbors": "显示通过零跳广告收听的其他重复节点列表。 每行是 id-prefix-hex:时间戳:snr-times-4",
- "repeater_cliHelpNeighborRemove": "移除邻居列表中第一个匹配的条目(通过十六进制 pubkey 前缀)。",
- "repeater_cliHelpRegion": "(仅显示区域) 列出所有已定义的区域和当前的防洪权限。",
- "repeater_cliHelpRegionLoad": "注意:这是一个特殊的多命令调用。 随后的每个命令都是一个区域名称(用空格缩进以指示父级层次结构,至少需要一个空格)。 以发送一个空行/命令结束。",
- "repeater_cliHelpRegionGet": "搜索具有给定名称前缀的区域(或“”用于全局范围)。回复为“-> region-name (parent-name) ‘F’”",
- "repeater_cliHelpRegionPut": "添加或更新区域定义,使用指定名称。",
- "repeater_cliHelpRegionRemove": "删除指定名称的区域定义。(必须没有子区域)",
- "repeater_cliHelpRegionAllowf": "设置指定区域的“洪水”权限。(“”代表全局/遗留范围)",
- "repeater_cliHelpRegionDenyf": "移除指定区域的‘F’lood权限。 (注意:目前阶段不建议在此范围内使用,尤其是全局/旧版范围!!)",
- "repeater_cliHelpRegionHome": "回复当前“主页”区域。 (注意尚未应用,保留用于未来)",
- "repeater_cliHelpRegionHomeSet": "设置‘主页’区域。",
- "repeater_cliHelpRegionSave": "保存区域列表/地图到存储。",
- "repeater_cliHelpGps": "显示GPS状态。当GPS关闭时,回复仅为“关闭”,如果已开启,则回复为“开启”、“状态”、“定位”和卫星数量。",
- "repeater_cliHelpGpsOnOff": "切换 GPS 开启状态。",
- "repeater_cliHelpGpsSync": "同步节点时间与 GPS 钟。",
- "repeater_cliHelpGpsSetLoc": "设置节点位置至 GPS 坐标并保存偏好设置。",
- "repeater_cliHelpGpsAdvert": "提供节点广告配置位置:\n- none:不包含位置在广告中\n- share:分享 GPS 位置(来自 SensorManager)\n- prefs:在偏好设置中投放位置",
- "repeater_cliHelpGpsAdvertSet": "设置广告位置配置。",
+ "repeater_cliHelpLogStop": "停止将数据包记录写入文件系统。",
+ "repeater_cliHelpLogErase": "从文件系统中删除所有已记录的包信息。",
+ "repeater_cliHelpNeighbors": "显示了通过零跳广告收到的其他复用节点列表。 每行包含:id-前缀-十六进制:时间戳:信噪比(4次)",
+ "repeater_cliHelpNeighborRemove": "从邻居列表中删除第一个匹配项(通过十六进制的 pubkey 前缀)。",
+ "repeater_cliHelpRegion": "(仅限序列)列出所有已定义的区域以及当前的防洪许可。",
+ "repeater_cliHelpRegionLoad": "请注意:这是一个特殊的、包含多个命令的调用方式。 之后的每个命令都是一个区域名称(使用空格进行缩进,以表示父级关系,至少需要一个空格)。 结束方式是通过发送一个空行/命令。",
+ "repeater_cliHelpRegionGet": "搜索具有指定名称前缀的区域(或使用“*”表示全局范围)。 返回结果为“-> region-name (parent-name) 'F'”",
+ "repeater_cliHelpRegionPut": "添加或更新一个区域定义,并指定其名称。",
+ "repeater_cliHelpRegionRemove": "删除具有指定名称的区域定义。 (必须与指定名称完全匹配,且不能有子区域)",
+ "repeater_cliHelpRegionAllowf": "为指定区域设置“洪水”权限。(“*”表示全局/旧版本范围)",
+ "repeater_cliHelpRegionDenyf": "移除指定区域的“洪水”权限。(请注意:目前不建议在全局/旧版本中使用此功能!!)",
+ "repeater_cliHelpRegionHome": "回复当前“主区域”。(此功能尚未应用,仅供未来使用)",
+ "repeater_cliHelpRegionHomeSet": "设置“主”区域。",
+ "repeater_cliHelpRegionSave": "将区域列表/地图保存到存储中。",
+ "repeater_cliHelpGps": "显示 GPS 状态。当 GPS 处于关闭状态时,它只会显示“关闭”;当 GPS 处于开启状态时,它会显示“开启”、“状态”、“定位”、“卫星数量”等信息。",
+ "repeater_cliHelpGpsOnOff": "切换 GPS 设备的电源状态。",
+ "repeater_cliHelpGpsSync": "将节点时间与 GPS 钟同步。",
+ "repeater_cliHelpGpsSetLoc": "将节点的坐标设置为 GPS 坐标,并保存设置。",
+ "repeater_cliHelpGpsAdvert": "设置节点的位置广告配置:\n- none:不将位置信息包含在广告中\n- share:共享 GPS 位置(从 SensorManager 获取)\n- prefs:在偏好设置中展示的位置",
+ "repeater_cliHelpGpsAdvertSet": "设置广告的位置配置。",
"repeater_commandsListTitle": "命令列表",
- "repeater_commandsListNote": "注意:对于各种“设置...”命令,也存在“获取...”命令。",
+ "repeater_commandsListNote": "请注意:对于各种“set ...”命令,也存在“get ...”命令。",
"repeater_general": "通用",
"repeater_settingsCategory": "设置",
"repeater_bridge": "桥",
"repeater_logging": "记录",
- "repeater_neighborsRepeaterOnly": "邻居(仅限重复器)",
- "repeater_regionManagementRepeaterOnly": "区域管理(仅限重复器)",
- "repeater_regionNote": "区域命令已推出,用于管理区域定义和权限。",
- "repeater_gpsManagement": "GPS管理",
- "repeater_gpsNote": "GPS 命令已引入用于管理与位置相关的主题。",
- "telemetry_receivedData": "接收遥测数据",
+ "repeater_neighborsRepeaterOnly": "邻居(仅限重复功能)",
+ "repeater_regionManagementRepeaterOnly": "区域管理(仅限重复站点)",
+ "repeater_regionNote": "区域命令已引入,用于管理区域定义和权限。",
+ "repeater_gpsManagement": "GPS 管理",
+ "repeater_gpsNote": "已引入 GPS 命令,用于管理与位置相关的任务。",
+ "telemetry_receivedData": "接收到的遥测数据",
"telemetry_requestTimeout": "遥测请求超时。",
- "telemetry_errorLoading": "错误加载遥测数据:{error}",
+ "telemetry_errorLoading": "Error loading telemetry: {error}",
"@telemetry_errorLoading": {
"placeholders": {
"error": {
@@ -1197,7 +1220,7 @@
},
"telemetry_batteryLabel": "电池",
"telemetry_voltageLabel": "电压",
- "telemetry_mcuTemperatureLabel": "MCU 温度",
+ "telemetry_mcuTemperatureLabel": "MCU 的温度",
"telemetry_temperatureLabel": "温度",
"telemetry_currentLabel": "当前",
"telemetry_batteryValue": "{percent}% / {volts}V",
@@ -1238,18 +1261,46 @@
}
}
},
+ "neighbors_receivedData": "已收到邻居信息",
+ "neighbors_requestTimedOut": "邻居要求停止干扰。",
+ "neighbors_errorLoading": "Error loading neighbors: {error}",
+ "@neighbors_errorLoading": {
+ "placeholders": {
+ "error": {
+ "type": "String"
+ }
+ }
+ },
+ "neighbors_repeatersNeighbours": "重复使用的邻居",
+ "neighbors_noData": "没有可用的邻居信息。",
+ "neighbors_unknownContact": "Unknown {pubkey}",
+ "@neighbors_unknownContact": {
+ "placeholders": {
+ "pubkey": {
+ "type": "String"
+ }
+ }
+ },
+ "neighbors_heardAgo": "Heard: {time} ago",
+ "@neighbors_heardAgo": {
+ "placeholders": {
+ "time": {
+ "type": "String"
+ }
+ }
+ },
"channelPath_title": "数据包路径",
"channelPath_viewMap": "查看地图",
"channelPath_otherObservedPaths": "其他观察到的路径",
- "channelPath_repeaterHops": "重复跳跃",
- "channelPath_noHopDetails": "此包的详细信息未提供。",
+ "channelPath_repeaterHops": "复用跳跃",
+ "channelPath_noHopDetails": "对于此包,未提供详细信息。",
"channelPath_messageDetails": "消息详情",
"channelPath_senderLabel": "发件人",
"channelPath_timeLabel": "时间",
"channelPath_repeatsLabel": "重复",
"channelPath_pathLabel": "路径 {index}",
- "channelPath_observedLabel": "已观察",
- "channelPath_observedPathTitle": "观察路径 {index} • {hops}",
+ "channelPath_observedLabel": "观察到的",
+ "channelPath_observedPathTitle": "Observed path {index} • {hops}",
"@channelPath_observedPathTitle": {
"placeholders": {
"index": {
@@ -1260,7 +1311,7 @@
}
}
},
- "channelPath_noLocationData": "没有位置数据",
+ "channelPath_noLocationData": "没有位置信息",
"channelPath_timeWithDate": "{day}/{month} {time}",
"@channelPath_timeWithDate": {
"placeholders": {
@@ -1286,7 +1337,7 @@
"channelPath_unknownPath": "未知",
"channelPath_floodPath": "洪水",
"channelPath_directPath": "直接",
- "channelPath_observedZeroOf": "0 of {total} 跳跃",
+ "channelPath_observedZeroOf": "0 of {total} hops",
"@channelPath_observedZeroOf": {
"placeholders": {
"total": {
@@ -1294,7 +1345,7 @@
}
}
},
- "channelPath_observedSomeOf": "已观察到 {observed} 步中的 {total} 步",
+ "channelPath_observedSomeOf": "{observed} of {total} hops",
"@channelPath_observedSomeOf": {
"placeholders": {
"observed": {
@@ -1305,9 +1356,9 @@
}
}
},
- "channelPath_mapTitle": "路径地图",
- "channelPath_noRepeaterLocations": "此路径没有可用的重复器位置。",
- "channelPath_primaryPath": "路径 {index} (主)",
+ "channelPath_mapTitle": "路线图",
+ "channelPath_noRepeaterLocations": "这条路径上没有可用的中继器位置。",
+ "channelPath_primaryPath": "路径 {index} (主要路径)",
"@channelPath_primaryPath": {
"placeholders": {
"index": {
@@ -1323,7 +1374,7 @@
}
},
"channelPath_pathLabelTitle": "路径",
- "channelPath_observedPathHeader": "已观察路径",
+ "channelPath_observedPathHeader": "观察路径",
"channelPath_selectedPathLabel": "{label} • {prefixes}",
"@channelPath_selectedPathLabel": {
"placeholders": {
@@ -1335,68 +1386,14 @@
}
}
},
- "channelPath_noHopDetailsAvailable": "此包的跳跃详情不可用。",
- "channelPath_unknownRepeater": "未知重复器",
- "listFilter_tooltip": "筛选和排序",
- "listFilter_sortBy": "按类型排序",
- "listFilter_latestMessages": "最新消息",
- "listFilter_heardRecently": "最近听说",
- "listFilter_az": "A-Z",
- "listFilter_filters": "筛选",
- "listFilter_all": "全部",
- "listFilter_users": "用户",
- "listFilter_repeaters": "重复器",
- "listFilter_roomServers": "房间服务器",
- "listFilter_unreadOnly": "未读消息",
- "listFilter_newGroup": "新组",
- "@neighbors_errorLoading": {
- "placeholders": {
- "error": {
- "type": "String"
- }
- }
- },
- "repeater_neighboursSubtitle": "查看零跳邻居。",
- "repeater_neighbours": "邻居",
- "neighbors_receivedData": "收到邻居数据",
- "neighbors_requestTimedOut": "邻居请求超时处理。",
- "neighbors_errorLoading": "加载邻居时出错:{error}",
- "neighbors_repeatersNeighbours": "重复器邻居",
- "neighbors_noData": "没有可用的邻居数据。",
- "channels_joinPrivateChannel": "加入私密频道",
- "channels_createPrivateChannelDesc": "使用密钥保护。",
- "channels_joinPrivateChannelDesc": "手动输入密钥。",
- "channels_createPrivateChannel": "创建私聊频道",
- "channels_joinPublicChannel": "加入公共频道",
- "channels_joinPublicChannelDesc": "任何人都可以加入这个频道。",
- "channels_joinHashtagChannel": "加入标签频道",
- "channels_joinHashtagChannelDesc": "任何人都可以加入话题频道。",
- "channels_scanQrCode": "扫描二维码",
- "channels_scanQrCodeComingSoon": "即将到来",
- "channels_enterHashtag": "输入标签",
- "channels_hashtagHint": "例如 #团队",
- "@neighbors_unknownContact": {
- "placeholders": {
- "pubkey": {
- "type": "String"
- }
- }
- },
- "@neighbors_heardAgo": {
- "placeholders": {
- "time": {
- "type": "String"
- }
- }
- },
- "neighbors_heardAgo": "听到的时间:{time}前",
- "neighbors_unknownContact": "未知{pubkey}",
- "settings_locationGPSEnable": "启用GPS",
- "settings_locationGPSEnableSubtitle": "启用GPS自动更新位置。",
- "settings_locationIntervalSec": "GPS 间隔(秒)",
- "settings_locationIntervalInvalid": "时间间隔必须至少为60秒,且小于86400秒。",
- "contacts_manageRoom": "管理房间服务器",
- "room_management": "房间服务器管理",
+ "channelPath_noHopDetailsAvailable": "对于此包裹,尚无详细信息。",
+ "channelPath_unknownRepeater": "未知的重复设备",
+ "community_title": "社区",
+ "community_create": "建立社区",
+ "community_createDesc": "创建一个新的社群,并通过二维码进行分享。",
+ "community_join": "加入",
+ "community_joinTitle": "加入社区",
+ "community_joinConfirmation": "Do you want to join the community \"{name}\"?",
"@community_joinConfirmation": {
"placeholders": {
"name": {
@@ -1404,6 +1401,14 @@
}
}
},
+ "community_scanQr": "扫描社区二维码",
+ "community_scanInstructions": "将相机对准社区的二维码。",
+ "community_showQr": "显示二维码",
+ "community_publicChannel": "社区公共",
+ "community_hashtagChannel": "社区标签",
+ "community_name": "社区名称",
+ "community_enterName": "请输入社区名称",
+ "community_created": "Community \"{name}\" created",
"@community_created": {
"placeholders": {
"name": {
@@ -1411,6 +1416,7 @@
}
}
},
+ "community_joined": "Joined community \"{name}\"",
"@community_joined": {
"placeholders": {
"name": {
@@ -1418,6 +1424,8 @@
}
}
},
+ "community_qrTitle": "分享社区",
+ "community_qrInstructions": "Scan this QR code to join \"{name}\"",
"@community_qrInstructions": {
"placeholders": {
"name": {
@@ -1425,6 +1433,10 @@
}
}
},
+ "community_hashtagPrivacyHint": "仅社区成员才能加入社区话题标签的频道。",
+ "community_invalidQrCode": "无效的社区二维码",
+ "community_alreadyMember": "已经是会员",
+ "community_alreadyMemberMessage": "You are already a member of \"{name}\".",
"@community_alreadyMemberMessage": {
"placeholders": {
"name": {
@@ -1432,6 +1444,13 @@
}
}
},
+ "community_addPublicChannel": "添加公共频道",
+ "community_addPublicChannelHint": "自动添加该社区的公共频道",
+ "community_noCommunities": "目前还没有任何社区加入。",
+ "community_scanOrCreate": "扫描二维码或创建社群,即可开始。",
+ "community_manageCommunities": "管理社区",
+ "community_delete": "退出社区",
+ "community_deleteConfirm": "是否要删除\"{name}\"?",
"@community_deleteConfirm": {
"placeholders": {
"name": {
@@ -1439,50 +1458,7 @@
}
}
},
- "@community_deleted": {
- "placeholders": {
- "name": {
- "type": "String"
- }
- }
- },
- "@community_forCommunity": {
- "placeholders": {
- "name": {
- "type": "String"
- }
- }
- },
- "community_create": "创建社区",
- "community_title": "社区",
- "community_createDesc": "创建新的社区并可通过二维码分享。",
- "common_ok": "好的",
- "community_join": "加入",
- "community_joinTitle": "加入社区",
- "community_joinConfirmation": "您想加入社区 \"{name}\" 吗?",
- "community_scanQr": "扫描社区二维码",
- "community_scanInstructions": "将相机对准社区二维码",
- "community_showQr": "显示二维码",
- "community_publicChannel": "社区公开",
- "community_hashtagChannel": "社区标签",
- "community_name": "社区名称",
- "community_enterName": "请输入社区名称",
- "community_created": "社区“{name}”已创建",
- "community_joined": "加入社区 \"{name}\"",
- "community_qrTitle": "分享社区",
- "community_qrInstructions": "扫描此二维码加入{name}",
- "community_hashtagPrivacyHint": "社区标签频道仅社区成员可加入",
- "community_invalidQrCode": "无效的社区二维码",
- "community_alreadyMember": "已经是会员了",
- "community_alreadyMemberMessage": "您已经是 \"{name}\" 的会员。",
- "community_addPublicChannel": "添加社区公共频道",
- "community_addPublicChannelHint": "自动添加该社区的公共频道",
- "community_noCommunities": "尚未加入任何社区",
- "community_scanOrCreate": "扫描二维码或创建社区开始",
- "community_manageCommunities": "管理社群",
- "community_delete": "退出社区",
- "community_deleteConfirm": "退出 \"{name}\"?",
- "community_deleteChannelsWarning": "这也将删除 {count} 个频道及其消息。",
+ "community_deleteChannelsWarning": "这将同时删除 {count} 个频道及其所有消息。",
"@community_deleteChannelsWarning": {
"placeholders": {
"count": {
@@ -1490,15 +1466,16 @@
}
}
},
- "community_deleted": "已退出社区 \"{name}\"",
- "community_addHashtagChannel": "添加社区标签",
- "community_addHashtagChannelDesc": "添加一个话题频道给此社区",
- "community_selectCommunity": "选择社区",
- "community_regularHashtag": "常规话题标签",
- "community_regularHashtagDesc": "公共话题(任何人都可以加入)",
- "community_communityHashtag": "社区标签",
- "community_communityHashtagDesc": "仅限社区成员使用",
- "community_forCommunity": "对于 {name}",
+ "community_deleted": "Left community \"{name}\"",
+ "@community_deleted": {
+ "placeholders": {
+ "name": {
+ "type": "String"
+ }
+ }
+ },
+ "community_regenerateSecret": "恢复秘密",
+ "community_regenerateSecretConfirm": "[保存:{name}]\n是否需要重新生成\"{name}\"的密钥?所有成员都需要扫描新的二维码才能继续进行通信。",
"@community_regenerateSecretConfirm": {
"placeholders": {
"name": {
@@ -1506,6 +1483,8 @@
}
}
},
+ "community_regenerate": "再生",
+ "community_secretRegenerated": "[保护对象:{name}]\n秘密已恢复至\"{name}\"",
"@community_secretRegenerated": {
"placeholders": {
"name": {
@@ -1513,6 +1492,8 @@
}
}
},
+ "community_updateSecret": "更新秘密",
+ "community_secretUpdated": "“{name}”的秘密已更新",
"@community_secretUpdated": {
"placeholders": {
"name": {
@@ -1520,6 +1501,7 @@
}
}
},
+ "community_scanToUpdateSecret": "Scan the new QR code to update the secret for \"{name}\"",
"@community_scanToUpdateSecret": {
"placeholders": {
"name": {
@@ -1527,13 +1509,45 @@
}
}
},
- "community_regenerateSecret": "重新生成密钥",
- "community_secretRegenerated": "密码已重置为“{name}”",
- "community_regenerate": "重新生成",
- "community_regenerateSecretConfirm": "重新生成“{name}”的秘密密钥?所有成员将需要扫描新的二维码才能继续沟通。",
- "community_scanToUpdateSecret": "扫描新的二维码更新\"{name}\"的密码",
- "community_updateSecret": "更新密钥",
- "community_secretUpdated": "密码已更新为“{name}”",
+ "community_addHashtagChannel": "添加社区标签",
+ "community_addHashtagChannelDesc": "为这个社区创建一个带有话题标签的频道",
+ "community_selectCommunity": "选择社区",
+ "community_regularHashtag": "常用标签",
+ "community_regularHashtagDesc": "公共话题标签(任何人都可以参与)",
+ "community_communityHashtag": "社区标签",
+ "community_communityHashtagDesc": "仅限社区成员",
+ "community_forCommunity": "For {name}",
+ "@community_forCommunity": {
+ "placeholders": {
+ "name": {
+ "type": "String"
+ }
+ }
+ },
+ "listFilter_tooltip": "筛选和排序",
+ "listFilter_sortBy": "按排序",
+ "listFilter_latestMessages": "最新消息",
+ "listFilter_heardRecently": "最近听到的",
+ "listFilter_az": "A 到 Z",
+ "listFilter_filters": "过滤器",
+ "listFilter_all": "全部",
+ "listFilter_users": "用户",
+ "listFilter_repeaters": "重复器",
+ "listFilter_roomServers": "房间服务器",
+ "listFilter_unreadOnly": "仅显示未读消息",
+ "listFilter_newGroup": "新的团体",
+ "pathTrace_you": "您",
+ "pathTrace_failed": "路径追踪失败。",
+ "pathTrace_notAvailable": "无法获取路径信息。",
+ "pathTrace_refreshTooltip": "重新绘制路径。",
+ "contacts_pathTrace": "路径追踪",
+ "contacts_ping": "乒",
+ "contacts_repeaterPathTrace": "追踪路径至中继器",
+ "contacts_repeaterPing": "中继器",
+ "contacts_roomPathTrace": "追踪到房间服务器",
+ "contacts_roomPing": "会议室服务器",
+ "contacts_chatTraceRoute": "路径跟踪路线",
+ "contacts_pathTraceTo": "追踪路径至 {name}",
"@contacts_pathTraceTo": {
"placeholders": {
"name": {
@@ -1541,16 +1555,18 @@
}
}
},
- "pathTrace_you": "你",
- "pathTrace_failed": "路径追踪失败。",
- "pathTrace_notAvailable": "路径追踪不可用",
- "pathTrace_refreshTooltip": "刷新路径追踪",
- "contacts_pathTrace": "路径追踪",
- "contacts_ping": "ping",
- "contacts_repeaterPathTrace": "路径追踪到中继器",
- "contacts_repeaterPing": "Ping 中继器",
- "contacts_roomPathTrace": "路径追踪至房间服务器",
- "contacts_roomPing": "Ping 房间服务器",
- "contacts_chatTraceRoute": "路径追踪",
- "contacts_pathTraceTo": "追踪路由到 {name}"
+ "contacts_clipboardEmpty": "剪贴板为空。",
+ "contacts_invalidAdvertFormat": "无效的联系信息",
+ "contacts_contactImported": "已建立联系。",
+ "contacts_contactImportFailed": "未能导入联系人。",
+ "contacts_zeroHopAdvert": "零跳广告",
+ "contacts_floodAdvert": "防洪广告",
+ "contacts_copyAdvertToClipboard": "复制广告到剪贴板",
+ "contacts_addContactFromClipboard": "从剪贴板添加联系人",
+ "contacts_ShareContact": "复制联系方式到剪贴板",
+ "contacts_ShareContactZeroHop": "通过广告分享联系方式",
+ "contacts_zeroHopContactAdvertSent": "通过广告获取联系方式。",
+ "contacts_zeroHopContactAdvertFailed": "发送联系方式失败。",
+ "contacts_contactAdvertCopied": "广告内容已复制到剪贴板。",
+ "contacts_contactAdvertCopyFailed": "将广告复制到剪贴板操作失败。"
}
diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart
index c302fb3..efd7340 100644
--- a/lib/screens/channels_screen.dart
+++ b/lib/screens/channels_screen.dart
@@ -121,24 +121,44 @@ class _ChannelsScreenState extends State
centerTitle: true,
automaticallyImplyLeading: false,
actions: [
- if (_communities.isNotEmpty)
- IconButton(
- icon: const Icon(Icons.groups),
- tooltip: context.l10n.community_manageCommunities,
- onPressed: () => _showManageCommunitiesDialog(context),
- ),
- IconButton(
- icon: const Icon(Icons.bluetooth_disabled),
- tooltip: context.l10n.common_disconnect,
- onPressed: () => _disconnect(context),
- ),
- IconButton(
- icon: const Icon(Icons.tune),
- tooltip: context.l10n.common_settings,
- onPressed: () => Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => const SettingsScreen()),
- ),
+ PopupMenuButton(
+ itemBuilder: (context) => [
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.logout, color: Colors.red),
+ const SizedBox(width: 8),
+ Text(context.l10n.common_disconnect),
+ ],
+ ),
+ onTap: () => _disconnect(context),
+ ),
+ if (_communities.isNotEmpty)
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.groups),
+ const SizedBox(width: 8),
+ Text(context.l10n.community_manageCommunities),
+ ],
+ ),
+ onTap: () => _showManageCommunitiesDialog(context),
+ ),
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.settings),
+ const SizedBox(width: 8),
+ Text(context.l10n.settings_title),
+ ],
+ ),
+ onTap: () => Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SettingsScreen()),
+ ),
+ ),
+ ],
+ icon: const Icon(Icons.more_vert),
),
],
),
diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart
index d12abb0..48f94f9 100644
--- a/lib/screens/contacts_screen.dart
+++ b/lib/screens/contacts_screen.dart
@@ -1,8 +1,8 @@
import 'dart:async';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:meshcore_open/widgets/path_trace_dialog.dart';
+import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart';
@@ -34,6 +34,12 @@ enum RoomLoginDestination {
management,
}
+enum ContactOperationType {
+ import,
+ export,
+ zeroHopShare,
+}
+
class ContactsScreen extends StatefulWidget {
final bool hideBackButton;
@@ -53,17 +59,23 @@ class _ContactsScreenState extends State
final ContactGroupStore _groupStore = ContactGroupStore();
List _groups = [];
Timer? _searchDebounce;
-
+
+ final Set _pendingOperations = {};
+
+ StreamSubscription? _frameSubscription;
+
@override
void initState() {
super.initState();
_loadGroups();
+ _setupFrameListener();
}
@override
void dispose() {
_searchDebounce?.cancel();
_searchController.dispose();
+ _frameSubscription?.cancel();
super.dispose();
}
@@ -79,6 +91,130 @@ class _ContactsScreenState extends State
await _groupStore.saveGroups(_groups);
}
+ void _setupFrameListener() {
+ final connector = Provider.of(context, listen: false);
+ // Listen for incoming text messages from the repeater
+ _frameSubscription = connector.receivedFrames.listen((frame) {
+ if (frame.isEmpty) return;
+ final frameBuffer = BufferReader(frame);
+ final code = frameBuffer.readUInt8();
+
+ if (code == respCodeExportContact) {
+ final advertPacket = frameBuffer.readRemainingBytes();
+ // Validate packet has expected minimum size (98+ bytes per protocol)
+ if (advertPacket.length < 98) {
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
+ );
+ }
+ _pendingOperations.remove(ContactOperationType.export);
+ return;
+ }
+ final hexString = pubKeyToHex(advertPacket);
+ Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
+ }
+
+ if(code == respCodeOk) {
+ // Show a snackbar indicating success
+ if(!mounted) return;
+
+ if(_pendingOperations.contains(ContactOperationType.import)){
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_contactImported)),
+ );
+ }
+
+ if(_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_zeroHopContactAdvertSent)),
+ );
+ }
+
+ if(_pendingOperations.contains(ContactOperationType.export)) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)),
+ );
+ }
+
+ _pendingOperations.clear();
+ }
+
+ if(code == respCodeErr) {
+ // Show a snackbar indicating failure
+ if(!mounted) return;
+
+ if(_pendingOperations.contains(ContactOperationType.import)){
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_contactImportFailed)),
+ );
+ }
+
+ if(_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_zeroHopContactAdvertFailed)),
+ );
+ }
+ if(_pendingOperations.contains(ContactOperationType.export)) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_contactAdvertCopyFailed)),
+ );
+ }
+
+ _pendingOperations.clear();
+ }
+
+ });
+ }
+
+ Future _contactExport(Uint8List pubKey) async {
+ final connector = Provider.of(context, listen: false);
+ final exportContactFrame = buildExportContactFrame(pubKey);
+ _pendingOperations.add(ContactOperationType.export);
+ await connector.sendFrame(exportContactFrame);
+ }
+
+ Future _contactZeroHop(Uint8List pubKey) async {
+ final connector = Provider.of(context, listen: false);
+ final exportContactZeroHopFrame = buildZeroHopContact(pubKey);
+ _pendingOperations.add(ContactOperationType.zeroHopShare);
+ await connector.sendFrame(exportContactZeroHopFrame);
+ }
+
+ Future _contactImport() async {
+ final connector = Provider.of(context, listen: false);
+ final clipboardData = await Clipboard.getData('text/plain');
+ if (clipboardData == null || clipboardData.text == null) {
+ if(mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)),
+ );
+ }
+ return;
+ }
+ final text = clipboardData.text!.trim();
+ if (!text.startsWith('meshcore://')) {
+ if(mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
+ );
+ }
+ return;
+ }
+ final hexString = text.substring('meshcore://'.length);
+ try {
+ final importContactFrame = buildImportContactFrame(hexString);
+ _pendingOperations.add(ContactOperationType.import);
+ await connector.sendFrame(importContactFrame);
+ } catch (e) {
+ if(mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
+ );
+ }
+ }
+ }
+
@override
Widget build(BuildContext context) {
final connector = context.watch();
@@ -98,18 +234,87 @@ class _ContactsScreenState extends State
centerTitle: true,
automaticallyImplyLeading: false,
actions: [
- IconButton(
- icon: const Icon(Icons.bluetooth_disabled),
- tooltip: context.l10n.common_disconnect,
- onPressed: () => _disconnect(context, connector),
- ),
- IconButton(
- icon: const Icon(Icons.tune),
- tooltip: context.l10n.common_settings,
- onPressed: () => Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => const SettingsScreen()),
+ PopupMenuButton(itemBuilder: (context) => [
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.connect_without_contact),
+ const SizedBox(width: 8),
+ Text(context.l10n.contacts_zeroHopAdvert),
+ ],
+ ),
+ onTap: () => {
+ connector.sendSelfAdvert(flood: false),
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(SnackBar(content: Text(context.l10n.settings_advertisementSent))),
+ },
),
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.cell_tower),
+ const SizedBox(width: 8),
+ Text(context.l10n.contacts_floodAdvert),
+ ],
+ ),
+ onTap: () => {
+ connector.sendSelfAdvert(flood: true),
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(SnackBar(content: Text(context.l10n.settings_advertisementSent))),
+ },
+ ),
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.copy),
+ const SizedBox(width: 8),
+ Text(context.l10n.contacts_copyAdvertToClipboard),
+ ],
+ ),
+ onTap: () => _contactExport(Uint8List.fromList([])),
+ ),
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.paste),
+ const SizedBox(width: 8),
+ Text(context.l10n.contacts_addContactFromClipboard),
+ ],
+ ),
+ onTap: () => _contactImport(),
+ ),
+ ],
+ icon: const Icon(Icons.connect_without_contact),
+ ),
+ PopupMenuButton(
+ itemBuilder: (context) => [
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.logout, color: Colors.red),
+ const SizedBox(width: 8),
+ Text(context.l10n.common_disconnect),
+ ],
+ ),
+ onTap: () => _disconnect(context, connector),
+ ),
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.settings),
+ const SizedBox(width: 8),
+ Text(context.l10n.settings_title),
+ ],
+ ),
+ onTap: () => Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SettingsScreen()),
+ ),
+ ),
+ ],
+ icon: const Icon(Icons.more_vert),
),
],
),
@@ -834,6 +1039,23 @@ class _ContactsScreenState extends State
_openChat(context, contact);
},
),
+ ],
+ ListTile(
+ leading: const Icon(Icons.copy),
+ title: Text(context.l10n.contacts_ShareContact),
+ onTap: () {
+ Navigator.pop(sheetContext);
+ _contactExport(contact.publicKey);
+ },
+ ),
+ ListTile(
+ leading: const Icon(Icons.connect_without_contact),
+ title: Text(context.l10n.contacts_ShareContactZeroHop),
+ onTap: () {
+ Navigator.pop(sheetContext);
+ _contactZeroHop(contact.publicKey);
+ },
+ ),
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
title: Text(
@@ -845,7 +1067,6 @@ class _ContactsScreenState extends State
_confirmDelete(context, connector, contact);
},
),
- ],
],
),
),
@@ -906,9 +1127,10 @@ class _ContactTile extends StatelessWidget {
child: _buildContactAvatar(contact),
),
title: Text(contact.name),
- subtitle: Text(
- '${contact.typeLabel} • ${contact.pathLabel} ${contact.shortPubKeyHex}',
- ),
+ subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+ Text(contact.pathLabel),
+ Text(contact.shortPubKeyHex, style: TextStyle(fontSize: 12))
+ ],),
// Clamp text scaling in trailing section to prevent overflow while
// maintaining accessibility. Primary content (title/subtitle) scales normally.
trailing: MediaQuery(
@@ -929,8 +1151,13 @@ class _ContactTile extends StatelessWidget {
_formatLastSeen(context, lastSeen),
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
- if (contact.hasLocation)
- Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (contact.hasLocation)
+ Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
+ ],
+ ),
],
),
),
diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart
index 74e5cf9..edef811 100644
--- a/lib/screens/map_screen.dart
+++ b/lib/screens/map_screen.dart
@@ -245,20 +245,33 @@ class _MapScreenState extends State {
centerTitle: true,
automaticallyImplyLeading: false,
actions: [
- IconButton(
- icon: const Icon(Icons.bluetooth_disabled),
- tooltip: context.l10n.common_disconnect,
- onPressed: () => _disconnect(context, connector),
- ),
- IconButton(
- icon: const Icon(Icons.tune),
- tooltip: context.l10n.common_settings,
- onPressed: () => Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => const SettingsScreen(),
+ PopupMenuButton(
+ itemBuilder: (context) => [
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.logout, color: Colors.red),
+ const SizedBox(width: 8),
+ Text(context.l10n.common_disconnect),
+ ],
+ ),
+ onTap: () => _disconnect(context, connector),
),
- ),
+ PopupMenuItem(
+ child: Row(
+ children: [
+ const Icon(Icons.settings),
+ const SizedBox(width: 8),
+ Text(context.l10n.settings_title),
+ ],
+ ),
+ onTap: () => Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SettingsScreen()),
+ ),
+ ),
+ ],
+ icon: const Icon(Icons.more_vert),
),
],
),
diff --git a/tools/translate.py b/tools/translate.py
index 84d172a..a51d3d1 100644
--- a/tools/translate.py
+++ b/tools/translate.py
@@ -1,42 +1,21 @@
#!/usr/bin/env python3
"""
-translate_arb_with_ollama.py
+translate_arb_with_translategemma.py
-Translates ARB/JSON localization values using a local Ollama model, while:
-- preserving keys
-- skipping "@@locale" and all "@key" metadata blocks
-- preserving placeholders like {deviceName}, {count, plural, ...}
-- writing a new file with updated @@locale
-- printing progress as it runs
+Translates ARB/JSON localization files using TranslateGemma via Ollama.
+Preserves placeholders like {deviceName} and ICU plural/select formats.
Usage:
# Translate all strings:
- python translate_arb_with_ollama.py \
- --in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \
- --out /home/zjs81/Desktop/meshcore-open/lib/l10n/app_es.arb \
- --to-locale es \
- --model ministral-3:latest \
- --temperature 0 \
- --concurrency 4
+ python translate.py --in lib/l10n/app_en.arb --out lib/l10n/app_es.arb --to-locale es
- # Translate only missing/untranslated strings:
- python translate_arb_with_ollama.py \
- --in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \
- --out /home/zjs81/Desktop/meshcore-open/lib/l10n/app_es.arb \
- --to-locale es \
- --missing-only \
- --model ministral-3:latest
+ # Translate only missing strings:
+ python translate.py --in lib/l10n/app_en.arb --out lib/l10n/app_es.arb --to-locale es --missing-only
# Translate all locales (missing strings only):
- python translate_arb_with_ollama.py \
- --in /home/zjs81/Desktop/meshcore-open/lib/l10n/app_en.arb \
- --l10n-dir /home/zjs81/Desktop/meshcore-open/lib/l10n \
- --missing-only \
- --model ministral-3:latest
+ python translate.py --in lib/l10n/app_en.arb --l10n-dir lib/l10n --missing-only
"""
-from __future__ import annotations
-
import argparse
import json
import os
@@ -49,9 +28,8 @@ from typing import Any, Dict, List, Tuple, Optional
from urllib import request
-# Simple placeholder like {name}, {count}, {deviceName}
+# Placeholder patterns
SIMPLE_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")
-# ICU plural/select variable name extraction: {count, plural, ...} or {gender, select, ...}
ICU_VAR_RE = re.compile(r"\{(\w+)\s*,\s*(?:plural|select|selectordinal)\s*,", re.IGNORECASE)
@@ -61,263 +39,46 @@ class OllamaConfig:
model: str
timeout_s: float
temperature: float
- num_ctx: int
- num_predict: int
- top_p: float
-def http_post_json(url: str, payload: Dict[str, Any], timeout_s: float) -> Dict[str, Any]:
- data = json.dumps(payload).encode("utf-8")
- req = request.Request(
- url,
- data=data,
- headers={"Content-Type": "application/json"},
- method="POST",
- )
- with request.urlopen(req, timeout=timeout_s) as resp:
- body = resp.read().decode("utf-8")
- return json.loads(body)
-
-
-def strip_markdown(s: str) -> str:
- """Remove common markdown formatting from output."""
- # Remove bold/italic markers
- s = re.sub(r'\*\*(.+?)\*\*', r'\1', s)
- s = re.sub(r'\*(.+?)\*', r'\1', s)
- s = re.sub(r'__(.+?)__', r'\1', s)
- s = re.sub(r'_(.+?)_', r'\1', s)
- # Remove stray asterisks
- s = re.sub(r'\*+', '', s)
- return s.strip()
-
-
-def ollama_generate(cfg: OllamaConfig, prompt: str) -> str:
- url = cfg.host.rstrip("/") + "/api/generate"
- payload = {
- "model": cfg.model,
- "prompt": prompt,
- "stream": False,
- "options": {
- "temperature": cfg.temperature,
- "num_ctx": cfg.num_ctx,
- "num_predict": cfg.num_predict,
- "top_p": cfg.top_p,
- },
- }
- resp = http_post_json(url, payload, cfg.timeout_s)
- out = resp.get("response", "")
- # Clean up common LLM artifacts
- out = strip_markdown(out)
- return out.strip()
-
-
-def extract_placeholder_names(s: str) -> List[str]:
- """Extract placeholder variable names (not the full braced expression).
-
- For '{name}' returns ['name']
- For '{count} {count, plural, =1{hop} other{hops}}' returns ['count']
- """
- names = set()
- # Get ICU variable names first
- for m in ICU_VAR_RE.finditer(s):
- names.add(m.group(1))
- # Get simple placeholders, but skip if they're inside ICU blocks (text forms like {hop})
- # We do this by checking if the name is also an ICU variable - if not, it's a simple placeholder
- # unless it looks like a word (ICU text forms are usually short words)
- for m in SIMPLE_PLACEHOLDER_RE.finditer(s):
- name = m.group(1)
- # Check if this appears as a simple {name} placeholder (not inside ICU)
- # by looking at what comes after it
- full_match = m.group(0)
- pos = m.start()
- # Look for pattern like {name, plural/select - if found, skip (handled by ICU_VAR_RE)
- rest = s[pos:]
- if re.match(r"\{\w+\s*,\s*(?:plural|select|selectordinal)", rest, re.IGNORECASE):
- continue
- # Check if this is likely a text form inside ICU (preceded by =X{ or other{)
- before = s[:pos]
- if re.search(r"(?:=\d+|zero|one|two|few|many|other)\s*$", before, re.IGNORECASE):
- continue # This is a text form like "=1{hop}", skip it
- names.add(name)
- return sorted(names)
-
-
-def build_prompt(text: str, target_lang: str, placeholder_names: List[str], has_icu: bool, ask_confidence: bool = False) -> str:
- preserve_list = "\n".join(f"- {{{t}}}" for t in placeholder_names) if placeholder_names else "- (none)"
-
- icu_note = ""
- if has_icu:
- icu_note = (
- "ICU FORMAT RULES:\n"
- f"- This text uses ICU plural/select format: {{var, plural, =1{{singular}} other{{plural}}}}\n"
- "- Keep structure keywords EXACTLY: plural, select, =0, =1, =2, zero, one, two, few, many, other\n"
- f"- TRANSLATE the words inside each form to {target_lang}\n"
- "- Example: =1{item} other{items} -> translate 'item'/'items' but keep =1{{ }} other{{ }} structure\n\n"
- )
-
- if ask_confidence:
- return (
- f"Translate this UI string to {target_lang}.\n\n"
- "RULES:\n"
- "- Placeholders like {name}, {count} must appear EXACTLY unchanged.\n"
- "- Use infinitive verb forms for buttons (Save, Delete, etc.).\n"
- f"- Use natural {target_lang} word order.\n"
- "- Keep brand names and technical terms unchanged.\n\n"
- f"{icu_note}"
- f"Placeholders: {', '.join(f'{{{t}}}' for t in placeholder_names) if placeholder_names else 'none'}\n\n"
- f"English: {text}\n\n"
- "Respond with EXACTLY two lines:\n"
- "1. The translation (no quotes)\n"
- "2. Confidence score 1-5 (5=certain, 1=unsure)\n\n"
- "Example response:\n"
- "Guardar archivo\n"
- "5"
- )
- else:
- return (
- f"Translate this UI string to {target_lang}. Return ONLY the translation.\n\n"
- "RULES:\n"
- "- Output the translated text ONLY. No markdown, no quotes, no explanations.\n"
- "- Placeholders like {name}, {count} must appear EXACTLY unchanged.\n"
- "- Use infinitive verb forms for buttons (Save, Delete, etc.).\n"
- f"- Use natural {target_lang} word order.\n"
- "- Keep brand names and technical terms unchanged.\n"
- "- Translation length should be similar to the original.\n\n"
- f"{icu_note}"
- f"Placeholders: {', '.join(f'{{{t}}}' for t in placeholder_names) if placeholder_names else 'none'}\n\n"
- f"English: {text}\n"
- f"{target_lang}:"
- )
-
-
-def parse_confidence_response(response: str) -> Tuple[str, int]:
- """Parse response with translation and confidence score.
-
- Returns (translation, confidence) where confidence is 1-5, or 0 if unparseable.
- """
- lines = response.strip().split('\n')
- if len(lines) >= 2:
- translation = '\n'.join(lines[:-1]).strip() # All but last line
- try:
- # Try to extract number from last line
- last_line = lines[-1].strip()
- # Handle formats like "5", "5/5", "Confidence: 5"
- match = re.search(r'\b([1-5])\b', last_line)
- if match:
- confidence = int(match.group(1))
- return translation, confidence
- except ValueError:
- pass
- # Fallback: treat whole response as translation with unknown confidence
- return strip_markdown(response), 0
-
-
-def looks_like_translation_failed(src: str, out: str) -> bool:
- if not out:
- return True
- if src.strip() == out.strip() and len(src.strip()) > 8:
- return True
- # Detect hallucination: output much longer than input (3x+ for short strings, 2x for longer)
- src_len = len(src.strip())
- out_len = len(out.strip())
- if src_len < 50 and out_len > src_len * 3:
- return True
- if src_len >= 50 and out_len > src_len * 2:
- return True
- return False
-
-
-def has_icu_block(s: str) -> bool:
- """Check if string contains ICU plural/select block."""
- return bool(ICU_VAR_RE.search(s))
-
-
-def validate_preserved_tokens(src: str, out: str) -> Tuple[bool, Optional[str]]:
- """Validate that placeholder names are preserved in translation."""
- src_names = extract_placeholder_names(src)
-
- # Check each placeholder name appears in output
- for name in src_names:
- # Look for {name} or {name, plural/select...}
- pattern = r"\{" + re.escape(name) + r"(?:\}|\s*,)"
- if not re.search(pattern, out):
- return False, f"Missing placeholder: {{{name}}}"
-
- # If source has ICU block, output should too
- if has_icu_block(src) and not has_icu_block(out):
- return False, "ICU plural/select block missing in output"
-
- return True, None
-
-
-def compute_confidence(src: str, out: str) -> Tuple[float, List[str]]:
- """
- Compute confidence score (0.0 to 1.0) for a translation.
- Returns (score, list of issues).
- """
- issues = []
- score = 1.0
-
- src_len = len(src.strip())
- out_len = len(out.strip())
-
- # Length ratio check
- if src_len > 0:
- ratio = out_len / src_len
- if ratio < 0.3: # Way too short
- score -= 0.4
- issues.append("too_short")
- elif ratio < 0.5:
- score -= 0.2
- issues.append("short")
- elif ratio > 2.5: # Way too long
- score -= 0.4
- issues.append("too_long")
- elif ratio > 1.8:
- score -= 0.2
- issues.append("long")
-
- # Contains question mark when source doesn't (model asking questions)
- if '?' in out and '?' not in src:
- score -= 0.3
- issues.append("added_question")
-
- # Contains common LLM artifacts
- artifacts = ['```', '**', 'translation:', 'here is', 'certainly', 'i can', 'i\'ll']
- out_lower = out.lower()
- for artifact in artifacts:
- if artifact in out_lower:
- score -= 0.3
- issues.append(f"artifact:{artifact}")
- break
-
- # Output looks like it's in English still (common words)
- english_indicators = ['the ', ' is ', ' are ', ' was ', ' were ', ' have ', ' has ', 'you ', ' your ']
- english_count = sum(1 for ind in english_indicators if ind in out_lower)
- if english_count >= 3 and src_len > 20:
- score -= 0.3
- issues.append("likely_english")
-
- # Contains newlines when source doesn't
- if '\n' in out and '\n' not in src:
- score -= 0.2
- issues.append("added_newlines")
-
- # ICU/placeholder validation
- ok, _ = validate_preserved_tokens(src, out)
- if not ok:
- score -= 0.3
- issues.append("placeholder_error")
-
- return max(0.0, score), issues
-
-
-# Keys to skip translation (brand names)
-SKIP_KEYS = {
- "appTitle",
+# Language mapping (locale_code -> (language_name, translategemma_code))
+LOCALE_MAP = {
+ "es": ("Spanish", "es"),
+ "fr": ("French", "fr"),
+ "de": ("German", "de"),
+ "it": ("Italian", "it"),
+ "pt": ("Portuguese", "pt"),
+ "pt-BR": ("Brazilian Portuguese", "pt"),
+ "ja": ("Japanese", "ja"),
+ "ko": ("Korean", "ko"),
+ "zh": ("Chinese", "zh-Hans"),
+ "zh-Hant": ("Chinese", "zh-Hant"),
+ "ru": ("Russian", "ru"),
+ "uk": ("Ukrainian", "uk"),
+ "ar": ("Arabic", "ar"),
+ "hi": ("Hindi", "hi"),
+ "tr": ("Turkish", "tr"),
+ "nl": ("Dutch", "nl"),
+ "sv": ("Swedish", "sv"),
+ "no": ("Norwegian", "no"),
+ "da": ("Danish", "da"),
+ "fi": ("Finnish", "fi"),
+ "pl": ("Polish", "pl"),
+ "cs": ("Czech", "cs"),
+ "sk": ("Slovak", "sk"),
+ "sl": ("Slovenian", "sl"),
+ "bg": ("Bulgarian", "bg"),
+ "el": ("Greek", "el"),
+ "he": ("Hebrew", "he"),
+ "th": ("Thai", "th"),
+ "vi": ("Vietnamese", "vi"),
+ "id": ("Indonesian", "id"),
}
-# Manual translations for problematic strings (key -> {locale: translation})
+# Keys to skip translation
+SKIP_KEYS = {"appTitle"}
+
+# Manual translations for complex strings
MANUAL_TRANSLATIONS: Dict[str, Dict[str, str]] = {
"repeater_daysHoursMinsSecs": {
"es": "{days} días {hours}h {minutes}m {seconds}s",
@@ -340,98 +101,126 @@ MANUAL_TRANSLATIONS: Dict[str, Dict[str, str]] = {
}
-def is_translatable_entry(key: str, value: Any) -> bool:
- if key == "@@locale":
- return False
- if key in SKIP_KEYS:
- return False
- if key.startswith("@"):
- return False
- if not isinstance(value, str):
- return False
- if value.strip() == "":
- return False
- return True
+def http_post_json(url: str, payload: Dict[str, Any], timeout_s: float) -> Dict[str, Any]:
+ data = json.dumps(payload).encode("utf-8")
+ req = request.Request(url, data=data, headers={"Content-Type": "application/json"}, method="POST")
+ with request.urlopen(req, timeout=timeout_s) as resp:
+ return json.loads(resp.read().decode("utf-8"))
+
+
+def ollama_generate(cfg: OllamaConfig, prompt: str) -> str:
+ url = cfg.host.rstrip("/") + "/api/generate"
+ payload = {
+ "model": cfg.model,
+ "prompt": prompt,
+ "stream": False,
+ "options": {"temperature": cfg.temperature},
+ }
+ resp = http_post_json(url, payload, cfg.timeout_s)
+ return resp.get("response", "").strip()
+
+
+def extract_placeholder_names(s: str) -> List[str]:
+ """Extract placeholder variable names from string."""
+ names = set()
+
+ # Get ICU variable names
+ for m in ICU_VAR_RE.finditer(s):
+ names.add(m.group(1))
+
+ # Get simple placeholders (excluding ICU text forms)
+ for m in SIMPLE_PLACEHOLDER_RE.finditer(s):
+ name = m.group(1)
+ pos = m.start()
+ rest = s[pos:]
+
+ # Skip if this is part of an ICU block
+ if re.match(r"\{\w+\s*,\s*(?:plural|select|selectordinal)", rest, re.IGNORECASE):
+ continue
+
+ # Skip if this is a text form inside ICU (preceded by =X{ or other{)
+ before = s[:pos]
+ if re.search(r"(?:=\d+|zero|one|two|few|many|other)\s*$", before, re.IGNORECASE):
+ continue
+
+ names.add(name)
+
+ return sorted(names)
+
+
+def has_icu_block(s: str) -> bool:
+ """Check if string contains ICU plural/select block."""
+ return bool(ICU_VAR_RE.search(s))
+
+
+def build_prompt(text: str, target_lang: str, target_code: str, placeholder_names: List[str], has_icu: bool) -> str:
+ """Build TranslateGemma-compatible prompt with placeholder preservation instructions."""
+ # Build instructions for placeholder preservation
+ instructions = []
+ if placeholder_names:
+ placeholders = ', '.join(f'{{{t}}}' for t in placeholder_names)
+ instructions.append(f"CRITICAL: Keep these placeholders EXACTLY as they appear: {placeholders}")
+ if has_icu:
+ instructions.append("CRITICAL: Preserve ICU message format structure (plural, select, =0, =1, other, etc.). Only translate the text inside the forms.")
+
+ # Add instructions to the system prompt, not to the text itself
+ instruction_text = "\n".join(instructions) if instructions else ""
+ separator = "\n" if instruction_text else ""
+
+ # TranslateGemma expects this exact format (note the two blank lines before text)
+ return f"""You are a professional English (en) to {target_lang} ({target_code}) translator. Your goal is to accurately convey the meaning and nuances of the original English text while adhering to {target_lang} grammar, vocabulary, and cultural sensitivities.
+Produce only the {target_lang} translation, without any additional explanations or commentary.{separator}{instruction_text}
+Please translate the following English text into {target_lang}:
+
+
+{text}"""
+
+
+def validate_preserved_tokens(src: str, out: str) -> Tuple[bool, Optional[str]]:
+ """Validate that placeholder names are preserved."""
+ src_names = extract_placeholder_names(src)
+
+ for name in src_names:
+ pattern = r"\{" + re.escape(name) + r"(?:\}|\s*,)"
+ if not re.search(pattern, out):
+ return False, f"Missing placeholder: {{{name}}}"
+
+ if has_icu_block(src) and not has_icu_block(out):
+ return False, "ICU plural/select block missing"
+
+ return True, None
def translate_one(
key: str,
text: str,
target_lang: str,
+ target_code: str,
cfg: OllamaConfig,
retries: int,
backoff_s: float,
fallback_cfg: Optional[OllamaConfig] = None,
- confidence_threshold: float = 0.7,
- model_confidence_threshold: int = 4,
- ask_model_confidence: bool = True,
) -> Tuple[str, str, Optional[str], bool]:
- """
- Translate a single string.
- Returns (key, translated_text, error_or_none, used_fallback_model).
- """
+ """Translate a single string. Returns (key, translated_text, error_or_none, used_fallback)."""
placeholder_names = extract_placeholder_names(text)
text_has_icu = has_icu_block(text)
-
- # Ask for confidence if we have a fallback model
- should_ask_confidence = ask_model_confidence and fallback_cfg and fallback_cfg.model != cfg.model
- prompt = build_prompt(text, target_lang, placeholder_names, text_has_icu, ask_confidence=should_ask_confidence)
- used_fallback = False
+ prompt = build_prompt(text, target_lang, target_code, placeholder_names, text_has_icu)
last_err: Optional[str] = None
for attempt in range(retries + 1):
try:
- raw_out = ollama_generate(cfg, prompt)
-
- # Parse confidence if we asked for it
- if should_ask_confidence:
- out, model_confidence = parse_confidence_response(raw_out)
- else:
- out = raw_out
- model_confidence = 5 # Assume high confidence if not asked
-
+ out = ollama_generate(cfg, prompt)
+
+ # Validate placeholders
ok, why = validate_preserved_tokens(text, out)
if not ok:
last_err = f"Validation failed: {why}"
- # Retry without confidence format for simpler response
- prompt = build_prompt(text, target_lang, placeholder_names, text_has_icu, ask_confidence=False)
- prompt = (
- prompt
- + "\n\nIMPORTANT: You MUST keep every {...} segment exactly unchanged. "
- "If you cannot, return the original text unchanged."
- )
+ if attempt < retries:
+ time.sleep(backoff_s * (attempt + 1))
+ continue
raise ValueError(last_err)
- if looks_like_translation_failed(text, out) and attempt < retries:
- last_err = "Output identical/suspicious; retrying"
- time.sleep(backoff_s * (attempt + 1))
- continue
-
- # Check if model reported low confidence - use fallback
- if model_confidence > 0 and model_confidence < model_confidence_threshold and fallback_cfg:
- fallback_prompt = build_prompt(text, target_lang, placeholder_names, text_has_icu, ask_confidence=False)
- fallback_out = ollama_generate(fallback_cfg, fallback_prompt)
- fallback_ok, _ = validate_preserved_tokens(text, fallback_out)
- if fallback_ok and not looks_like_translation_failed(text, fallback_out):
- return key, fallback_out, None, True
-
- # Also check computed confidence and use fallback model if needed
- confidence, issues = compute_confidence(text, out)
- if confidence < confidence_threshold and fallback_cfg and fallback_cfg.model != cfg.model:
- # Low confidence - try with bigger model
- fallback_prompt = build_prompt(text, target_lang, placeholder_names, text_has_icu)
- fallback_out = ollama_generate(fallback_cfg, fallback_prompt)
- fallback_ok, _ = validate_preserved_tokens(text, fallback_out)
- fallback_conf, _ = compute_confidence(text, fallback_out)
-
- if fallback_ok and fallback_conf > confidence:
- # Fallback is better
- return key, fallback_out, None, True
- elif fallback_ok and not ok:
- # Original failed validation but fallback passed
- return key, fallback_out, None, True
-
- return key, out, None, used_fallback
+ return key, out, None, False
except Exception as e:
last_err = str(e)
@@ -439,21 +228,55 @@ def translate_one(
time.sleep(backoff_s * (attempt + 1))
continue
- # Last resort: try fallback model
- if fallback_cfg and fallback_cfg.model != cfg.model:
+ # Try fallback model if available
+ if fallback_cfg:
try:
- fallback_prompt = build_prompt(text, target_lang, placeholder_names, text_has_icu)
+ fallback_prompt = build_prompt(text, target_lang, target_code, placeholder_names, text_has_icu)
fallback_out = ollama_generate(fallback_cfg, fallback_prompt)
fallback_ok, _ = validate_preserved_tokens(text, fallback_out)
- if fallback_ok and not looks_like_translation_failed(text, fallback_out):
+ if fallback_ok:
return key, fallback_out, None, True
except Exception:
pass
- return key, text, last_err, False # fallback to original on failure
+ # Fallback to original
+ return key, text, last_err, False
+
+
+def is_translatable_entry(key: str, value: Any) -> bool:
+ """Check if an entry should be translated."""
+ if key == "@@locale" or key.startswith("@") or key in SKIP_KEYS:
+ return False
+ return isinstance(value, str) and value.strip() != ""
+
+
+def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]:
+ """Find keys that are missing or empty in target."""
+ missing = []
+ for key in source_data:
+ if key == "@@locale" or key.startswith("@"):
+ continue
+ if key not in target_data or (isinstance(target_data.get(key), str) and target_data[key].strip() == ""):
+ missing.append(key)
+ return missing
+
+
+def get_all_locale_files(l10n_dir: str, template_file: str) -> List[Tuple[str, str]]:
+ """Find all locale .arb files excluding template. Returns [(locale_code, file_path)]."""
+ locales = []
+ template_basename = os.path.basename(template_file)
+
+ for filename in os.listdir(l10n_dir):
+ if filename.endswith('.arb') and filename != template_basename:
+ if filename.startswith('app_'):
+ locale = filename[4:-4] # app_es.arb -> es
+ locales.append((locale, os.path.join(l10n_dir, filename)))
+
+ return sorted(locales)
def fmt_duration(seconds: float) -> str:
+ """Format duration as human-readable string."""
if seconds < 60:
return f"{seconds:.1f}s"
m = int(seconds // 60)
@@ -465,226 +288,25 @@ def fmt_duration(seconds: float) -> str:
return f"{h}h {m2}m"
-def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]:
- """Find keys that are in source but not in target, or have empty values (excluding metadata keys)."""
- missing = []
- for key in source_data:
- if key == "@@locale":
- continue
- if key.startswith("@"):
- continue
- if key not in target_data:
- missing.append(key)
- elif isinstance(target_data.get(key), str) and target_data[key].strip() == "":
- # Also include keys with empty string values
- missing.append(key)
- return missing
-
-
-def get_all_locale_files(l10n_dir: str, template_file: str) -> List[Tuple[str, str]]:
- """Find all locale .arb files in the directory, excluding the template.
-
- Returns list of (locale_code, file_path) tuples.
- """
- locales = []
- template_basename = os.path.basename(template_file)
-
- for filename in os.listdir(l10n_dir):
- if not filename.endswith('.arb'):
- continue
- if filename == template_basename:
- continue
- # Extract locale from filename like app_es.arb -> es
- if filename.startswith('app_') and filename.endswith('.arb'):
- locale = filename[4:-4] # Remove 'app_' prefix and '.arb' suffix
- filepath = os.path.join(l10n_dir, filename)
- locales.append((locale, filepath))
-
- return sorted(locales)
-
-
-def main() -> int:
- ap = argparse.ArgumentParser()
- ap.add_argument("--in", dest="in_path", required=True, help="Input .arb/.json file path (source/template)")
- ap.add_argument("--out", dest="out_path", default=None, help="Output .arb/.json file path (required unless using --l10n-dir)")
- ap.add_argument("--to-locale", default=None, help="Target locale code, e.g. es, fr, de (required unless using --l10n-dir)")
- ap.add_argument("--l10n-dir", default=None, help="Directory containing locale .arb files. When set, translates all locales.")
- ap.add_argument("--missing-only", action="store_true", help="Only translate keys missing from target file")
- ap.add_argument("--target-lang", default=None, help="Target language name for the model, e.g. Spanish (defaults from locale)")
- ap.add_argument("--model", default="gemma3:4b", help="Ollama model name")
- ap.add_argument("--fallback-model", default=None, help="Larger model to use for low-confidence translations")
- ap.add_argument("--confidence-threshold", type=float, default=0.7, help="Computed confidence threshold to trigger fallback (0.0-1.0)")
- ap.add_argument("--model-confidence-threshold", type=int, default=4, help="Model self-reported confidence threshold (1-5, use fallback if below)")
- ap.add_argument("--retry-model", default="ministral-3:latest", help="Model to use for end-of-run retries")
- ap.add_argument("--host", default="http://localhost:11434", help="Ollama host")
- ap.add_argument("--timeout", type=float, default=120.0, help="HTTP timeout seconds")
- ap.add_argument("--temperature", type=float, default=0.2, help="Model temperature")
- ap.add_argument("--num-ctx", type=int, default=4096, help="Context size")
- ap.add_argument("--num-predict", type=int, default=256, help="Max tokens to generate")
- ap.add_argument("--top-p", type=float, default=0.9, help="Top-p")
- ap.add_argument("--concurrency", type=int, default=4, help="Parallel requests")
- ap.add_argument("--retries", type=int, default=2, help="Retries per string")
- ap.add_argument("--backoff", type=float, default=0.6, help="Backoff seconds base")
- ap.add_argument("--dry-run", action="store_true", help="Do not write file; just print summary")
- ap.add_argument("--progress-every", type=int, default=1, help="Print progress every N completed strings (default: 1)")
- args = ap.parse_args()
-
- locale_map = {
- "es": "Spanish",
- "fr": "French",
- "de": "German",
- "it": "Italian",
- "pt": "Portuguese",
- "pt-BR": "Brazilian Portuguese",
- "ja": "Japanese",
- "ko": "Korean",
- "zh": "Chinese (Simplified)",
- "zh-Hant": "Chinese (Traditional)",
- "ru": "Russian",
- "uk": "Ukrainian",
- "ar": "Arabic",
- "hi": "Hindi",
- "tr": "Turkish",
- "nl": "Dutch",
- "sv": "Swedish",
- "no": "Norwegian",
- "da": "Danish",
- "fi": "Finnish",
- "pl": "Polish",
- "cs": "Czech",
- "sk": "Slovak",
- "sl": "Slovenian",
- "bg": "Bulgarian",
- "el": "Greek",
- "he": "Hebrew",
- "th": "Thai",
- "vi": "Vietnamese",
- "id": "Indonesian",
- }
-
- # Read source/template file
- try:
- with open(args.in_path, "r", encoding="utf-8") as f:
- source_data = json.load(f)
- except Exception as e:
- print(f"Failed to read input: {e}", file=sys.stderr)
- return 2
-
- if not isinstance(source_data, dict):
- print("Input JSON must be an object at top-level.", file=sys.stderr)
- return 2
-
- # If --l10n-dir is provided, process all locale files
- if args.l10n_dir:
- locales = get_all_locale_files(args.l10n_dir, args.in_path)
- if not locales:
- print(f"No locale files found in {args.l10n_dir}", file=sys.stderr)
- return 1
-
- print(f"Found {len(locales)} locale file(s) to process")
-
- total_translated = 0
- for locale_code, locale_path in locales:
- target_lang = locale_map.get(locale_code, locale_code)
-
- # Read existing target file
- try:
- with open(locale_path, "r", encoding="utf-8") as f:
- target_data = json.load(f)
- except Exception as e:
- print(f" [{locale_code}] Failed to read {locale_path}: {e}")
- continue
-
- if args.missing_only:
- missing_keys = find_missing_keys(source_data, target_data)
- if not missing_keys:
- print(f" [{locale_code}] No missing keys")
- continue
- print(f" [{locale_code}] {len(missing_keys)} missing key(s): {', '.join(missing_keys[:5])}{'...' if len(missing_keys) > 5 else ''}")
- else:
- missing_keys = None
-
- # Run translation for this locale
- result = translate_locale(
- source_data=source_data,
- target_data=target_data,
- target_locale=locale_code,
- target_lang=target_lang,
- out_path=locale_path,
- args=args,
- locale_map=locale_map,
- missing_keys=missing_keys,
- )
- total_translated += result
-
- print(f"\nTotal: {total_translated} string(s) translated across {len(locales)} locale(s)")
- return 0
-
- # Single locale mode - validate required args
- if not args.out_path:
- print("--out is required when not using --l10n-dir", file=sys.stderr)
- return 1
- if not args.to_locale:
- print("--to-locale is required when not using --l10n-dir", file=sys.stderr)
- return 1
-
- target_lang = args.target_lang or locale_map.get(args.to_locale, args.to_locale)
-
- # Read existing target file if --missing-only and file exists
- target_data: Dict[str, Any] = {}
- missing_keys: Optional[List[str]] = None
- if args.missing_only:
- if os.path.exists(args.out_path):
- try:
- with open(args.out_path, "r", encoding="utf-8") as f:
- target_data = json.load(f)
- missing_keys = find_missing_keys(source_data, target_data)
- if not missing_keys:
- print(f"No missing keys in {args.out_path}")
- return 0
- print(f"Found {len(missing_keys)} missing key(s) to translate")
- except Exception as e:
- print(f"Failed to read target file: {e}", file=sys.stderr)
- return 2
- else:
- print(f"Target file {args.out_path} does not exist. Will translate all strings.")
-
- result = translate_locale(
- source_data=source_data,
- target_data=target_data,
- target_locale=args.to_locale,
- target_lang=target_lang,
- out_path=args.out_path,
- args=args,
- locale_map=locale_map,
- missing_keys=missing_keys,
- )
- return 0 if result >= 0 else 1
-
-
def translate_locale(
source_data: Dict[str, Any],
target_data: Dict[str, Any],
target_locale: str,
target_lang: str,
+ target_code: str,
out_path: str,
args,
- locale_map: Dict[str, str],
missing_keys: Optional[List[str]] = None,
) -> int:
"""Translate a single locale. Returns number of strings translated."""
-
+
cfg = OllamaConfig(
host=args.host,
model=args.model,
timeout_s=args.timeout,
temperature=args.temperature,
- num_ctx=args.num_ctx,
- num_predict=args.num_predict,
- top_p=args.top_p,
)
- # Fallback model for low-confidence translations
fallback_cfg = None
if args.fallback_model:
fallback_cfg = OllamaConfig(
@@ -692,34 +314,27 @@ def translate_locale(
model=args.fallback_model,
timeout_s=args.timeout,
temperature=args.temperature,
- num_ctx=args.num_ctx,
- num_predict=args.num_predict,
- top_p=args.top_p,
)
- # Start with target data (preserves existing translations) or source data
- if target_data:
- out_data: Dict[str, Any] = dict(target_data)
- else:
- out_data: Dict[str, Any] = dict(source_data)
+ # Start with target data or source data
+ out_data: Dict[str, Any] = dict(target_data) if target_data else dict(source_data)
out_data["@@locale"] = target_locale
# Build list of items to translate
if missing_keys is not None:
- # Only translate missing keys
items: List[Tuple[str, str]] = [
- (k, source_data[k]) for k in missing_keys
+ (k, source_data[k]) for k in missing_keys
if is_translatable_entry(k, source_data.get(k))
]
- # Also copy over any metadata keys for missing items
+ # Copy metadata for missing items
for key in missing_keys:
meta_key = f"@{key}"
if meta_key in source_data:
out_data[meta_key] = source_data[meta_key]
else:
items: List[Tuple[str, str]] = [(k, v) for k, v in source_data.items() if is_translatable_entry(k, v)]
-
- # Apply manual translations first
+
+ # Apply manual translations
manual_count = 0
items_to_translate: List[Tuple[str, str]] = []
for k, v in items:
@@ -728,154 +343,73 @@ def translate_locale(
manual_count += 1
else:
items_to_translate.append((k, v))
-
+
if manual_count > 0:
print(f"Applied {manual_count} manual translation(s)")
-
- total = len(items_to_translate)
- if total == 0 and manual_count == 0:
- print("No translatable string entries found (excluding @@locale and @metadata).")
- return 0
-
- if total == 0:
- print("All strings handled by manual translations.")
- else:
- fallback_info = f" (fallback: {args.fallback_model})" if args.fallback_model else ""
- print(f"Translating {total} strings -> {target_lang} using {cfg.model}{fallback_info} (concurrency={args.concurrency})")
-
- start = time.time()
+ total = len(items_to_translate)
+ if total == 0:
+ if manual_count > 0:
+ print("All strings handled by manual translations.")
+ return manual_count
+
+ fallback_info = f" (fallback: {args.fallback_model})" if args.fallback_model else ""
+ print(f"Translating {total} strings -> {target_lang} using {cfg.model}{fallback_info} (concurrency={args.concurrency})")
+
+ start = time.time()
failures: List[Tuple[str, str]] = []
- translated_ok = manual_count # Count manual translations as OK
+ translated_ok = manual_count
fallback_used = 0
completed = 0
- # Build a lookup for original text by key
- items_dict: Dict[str, str] = dict(items_to_translate)
+ with ThreadPoolExecutor(max_workers=max(1, args.concurrency)) as ex:
+ future_to_key = {
+ ex.submit(
+ translate_one,
+ key=k,
+ text=v,
+ target_lang=target_lang,
+ target_code=target_code,
+ cfg=cfg,
+ retries=args.retries,
+ backoff_s=args.backoff,
+ fallback_cfg=fallback_cfg,
+ ): k
+ for (k, v) in items_to_translate
+ }
- # Submit all tasks up front
- if total > 0:
- with ThreadPoolExecutor(max_workers=max(1, args.concurrency)) as ex:
- future_to_key = {
- ex.submit(
- translate_one,
- key=k,
- text=v,
- target_lang=target_lang,
- cfg=cfg,
- retries=args.retries,
- backoff_s=args.backoff,
- fallback_cfg=fallback_cfg,
- confidence_threshold=args.confidence_threshold,
- model_confidence_threshold=args.model_confidence_threshold,
- ask_model_confidence=bool(args.fallback_model),
- ): k
- for (k, v) in items_to_translate
- }
+ for fut in as_completed(future_to_key):
+ k, translated, err, used_fallback = fut.result()
+ out_data[k] = translated
- for fut in as_completed(future_to_key):
- k, translated, err, used_fallback = fut.result()
- out_data[k] = translated
-
- completed += 1
- if err:
- failures.append((k, err))
- status = "FAIL"
+ completed += 1
+ if err:
+ failures.append((k, err))
+ status = "FAIL"
+ else:
+ translated_ok += 1
+ if used_fallback:
+ fallback_used += 1
+ status = "OK*"
else:
- translated_ok += 1
- if used_fallback:
- fallback_used += 1
- status = "OK*" # asterisk indicates fallback model was used
- else:
- status = "OK"
-
- if args.progress_every > 0 and (completed % args.progress_every == 0 or completed == total):
- elapsed = time.time() - start
- rate = completed / elapsed if elapsed > 0 else 0.0
- remaining = (total - completed) / rate if rate > 0 else 0.0
- # Keep it single-line friendly but readable.
- print(
- f"[{completed:>4}/{total}] {status:<4} {k} | "
- f"elapsed {fmt_duration(elapsed)} | ETA {fmt_duration(remaining)}"
- )
-
- elapsed = time.time() - start
- fallback_msg = f", used_fallback_model={fallback_used}" if fallback_used > 0 else ""
- print(f"Done in {fmt_duration(elapsed)}. OK={translated_ok}{fallback_msg}, errors={len(failures)}")
-
- # Retry failed translations at the end with increasing temperature
- retry_round = 1
- max_end_retries = 3
- retry_model = args.retry_model
- while failures and retry_round <= max_end_retries:
- # Increase temperature for each retry round
- retry_temp = min(cfg.temperature + (0.2 * retry_round), 1.0)
- print(f"\n--- Retry round {retry_round}/{max_end_retries} for {len(failures)} failed key(s) (model={retry_model}, temp={retry_temp:.1f}) ---")
- retry_items = [(k, items_dict[k]) for k, _ in failures]
- failures = []
- retry_completed = 0
- retry_total = len(retry_items)
- retry_start = time.time()
-
- # Create config with higher temperature (and optionally different model) for retries
- retry_cfg = OllamaConfig(
- host=cfg.host,
- model=retry_model,
- timeout_s=cfg.timeout_s,
- temperature=retry_temp,
- num_ctx=cfg.num_ctx,
- num_predict=cfg.num_predict,
- top_p=cfg.top_p,
- )
-
- with ThreadPoolExecutor(max_workers=max(1, args.concurrency)) as ex:
- future_to_key = {
- ex.submit(
- translate_one,
- key=k,
- text=v,
- target_lang=target_lang,
- cfg=retry_cfg,
- retries=args.retries,
- backoff_s=args.backoff,
- ): k
- for (k, v) in retry_items
- }
-
- for fut in as_completed(future_to_key):
- k, translated, err, used_fb = fut.result()
- out_data[k] = translated
-
- retry_completed += 1
- if err:
- failures.append((k, err))
- status = "FAIL"
- else:
- translated_ok += 1
status = "OK"
- if args.progress_every > 0 and (retry_completed % args.progress_every == 0 or retry_completed == retry_total):
- elapsed = time.time() - retry_start
- rate = retry_completed / elapsed if elapsed > 0 else 0.0
- remaining = (retry_total - retry_completed) / rate if rate > 0 else 0.0
- print(
- f"[{retry_completed:>4}/{retry_total}] {status:<4} {k} | "
- f"elapsed {fmt_duration(elapsed)} | ETA {fmt_duration(remaining)}"
- )
+ if completed % args.progress_every == 0 or completed == total:
+ elapsed = time.time() - start
+ rate = completed / elapsed if elapsed > 0 else 0.0
+ remaining = (total - completed) / rate if rate > 0 else 0.0
+ print(f"[{completed:>4}/{total}] {status:<4} {k} | elapsed {fmt_duration(elapsed)} | ETA {fmt_duration(remaining)}")
- retry_elapsed = time.time() - retry_start
- print(f"Retry round {retry_round} done in {fmt_duration(retry_elapsed)}. Remaining failures: {len(failures)}")
- retry_round += 1
-
- total_elapsed = time.time() - start
- print(f"\nTotal time: {fmt_duration(total_elapsed)}. OK={translated_ok}, final fallback={len(failures)}")
+ elapsed = time.time() - start
+ fallback_msg = f", fallback_used={fallback_used}" if fallback_used > 0 else ""
+ print(f"Done in {fmt_duration(elapsed)}. OK={translated_ok}{fallback_msg}, errors={len(failures)}")
if failures:
- print("Fallback keys (kept original English due to errors):")
- for k, err in failures[:60]:
+ print(f"{len(failures)} translation(s) kept original English:")
+ for k, err in failures[:20]:
print(f" - {k}: {err}")
- if len(failures) > 60:
- print(f" ... and {len(failures) - 60} more")
+ if len(failures) > 20:
+ print(f" ... and {len(failures) - 20} more")
if args.dry_run:
print("Dry run: not writing output file.")
@@ -893,5 +427,116 @@ def translate_locale(
return translated_ok
+def main() -> int:
+ ap = argparse.ArgumentParser(description="Translate ARB files using TranslateGemma")
+ ap.add_argument("--in", dest="in_path", required=True, help="Input .arb file (source/template)")
+ ap.add_argument("--out", dest="out_path", help="Output .arb file (required unless using --l10n-dir)")
+ ap.add_argument("--to-locale", help="Target locale code (es, fr, de, etc.)")
+ ap.add_argument("--l10n-dir", help="Directory with locale files (translates all locales)")
+ ap.add_argument("--missing-only", action="store_true", help="Only translate missing keys")
+ ap.add_argument("--model", default="translategemma:latest", help="Ollama model (translategemma:latest or specific versions)")
+ ap.add_argument("--fallback-model", help="Fallback model for failed translations (e.g., translategemma:27b)")
+ ap.add_argument("--host", default="http://localhost:11434", help="Ollama host")
+ ap.add_argument("--timeout", type=float, default=120.0, help="HTTP timeout seconds")
+ ap.add_argument("--temperature", type=float, default=0.0, help="Model temperature (0.0 for deterministic)")
+ ap.add_argument("--concurrency", type=int, default=4, help="Parallel requests")
+ ap.add_argument("--retries", type=int, default=2, help="Retries per string")
+ ap.add_argument("--backoff", type=float, default=0.6, help="Backoff seconds base")
+ ap.add_argument("--dry-run", action="store_true", help="Don't write output")
+ ap.add_argument("--progress-every", type=int, default=1, help="Print progress every N strings")
+ args = ap.parse_args()
+
+ # Read source file
+ try:
+ with open(args.in_path, "r", encoding="utf-8") as f:
+ source_data = json.load(f)
+ except Exception as e:
+ print(f"Failed to read input: {e}", file=sys.stderr)
+ return 2
+
+ if not isinstance(source_data, dict):
+ print("Input JSON must be an object at top-level.", file=sys.stderr)
+ return 2
+
+ # Process all locales if --l10n-dir is provided
+ if args.l10n_dir:
+ locales = get_all_locale_files(args.l10n_dir, args.in_path)
+ if not locales:
+ print(f"No locale files found in {args.l10n_dir}", file=sys.stderr)
+ return 1
+
+ print(f"Found {len(locales)} locale file(s) to process")
+
+ total_translated = 0
+ for locale_code, locale_path in locales:
+ lang_name, lang_code = LOCALE_MAP.get(locale_code, (locale_code, locale_code))
+
+ try:
+ with open(locale_path, "r", encoding="utf-8") as f:
+ target_data = json.load(f)
+ except Exception as e:
+ print(f" [{locale_code}] Failed to read {locale_path}: {e}")
+ continue
+
+ if args.missing_only:
+ missing_keys = find_missing_keys(source_data, target_data)
+ if not missing_keys:
+ print(f" [{locale_code}] No missing keys")
+ continue
+ print(f" [{locale_code}] {len(missing_keys)} missing key(s)")
+ else:
+ missing_keys = None
+
+ result = translate_locale(
+ source_data=source_data,
+ target_data=target_data,
+ target_locale=locale_code,
+ target_lang=lang_name,
+ target_code=lang_code,
+ out_path=locale_path,
+ args=args,
+ missing_keys=missing_keys,
+ )
+ total_translated += result
+
+ print(f"\nTotal: {total_translated} string(s) translated across {len(locales)} locale(s)")
+ return 0
+
+ # Single locale mode
+ if not args.out_path or not args.to_locale:
+ print("--out and --to-locale are required when not using --l10n-dir", file=sys.stderr)
+ return 1
+
+ lang_name, lang_code = LOCALE_MAP.get(args.to_locale, (args.to_locale, args.to_locale))
+
+ # Read existing target file if --missing-only
+ target_data: Dict[str, Any] = {}
+ missing_keys: Optional[List[str]] = None
+ if args.missing_only and os.path.exists(args.out_path):
+ try:
+ with open(args.out_path, "r", encoding="utf-8") as f:
+ target_data = json.load(f)
+ missing_keys = find_missing_keys(source_data, target_data)
+ if not missing_keys:
+ print(f"No missing keys in {args.out_path}")
+ return 0
+ print(f"Found {len(missing_keys)} missing key(s) to translate")
+ except Exception as e:
+ print(f"Failed to read target file: {e}", file=sys.stderr)
+ return 2
+
+ result = translate_locale(
+ source_data=source_data,
+ target_data=target_data,
+ target_locale=args.to_locale,
+ target_lang=lang_name,
+ target_code=lang_code,
+ out_path=args.out_path,
+ args=args,
+ missing_keys=missing_keys,
+ )
+ return 0 if result >= 0 else 1
+
+
if __name__ == "__main__":
- raise SystemExit(main())
\ No newline at end of file
+ raise SystemExit(main())
diff --git a/untranslated.json b/untranslated.json
index b9dadf3..9e26dfe 100644
--- a/untranslated.json
+++ b/untranslated.json
@@ -1,69 +1 @@
-{
- "bg": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "de": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "es": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "fr": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "it": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "nl": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "pl": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "pt": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "ru": [
- "appSettings_languageUk"
- ],
-
- "sk": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "sl": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "sv": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ],
-
- "uk": [
- "appSettings_languageRu"
- ],
-
- "zh": [
- "appSettings_languageRu",
- "appSettings_languageUk"
- ]
-}
+{}
\ No newline at end of file