feat(i18n/socket): add WebSocket error messages and show connect errors
This commit is contained in:
@@ -3262,7 +3262,7 @@ class ApiService {
|
|||||||
_traceApi('Initiating background tools flow (task-based)');
|
_traceApi('Initiating background tools flow (task-based)');
|
||||||
_traceApi('Posting to /api/chat/completions');
|
_traceApi('Posting to /api/chat/completions');
|
||||||
|
|
||||||
// Fire in background; poll chat for updates and stream deltas to UI
|
// Fire in background; all updates will come via WebSocket events
|
||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
final resp = await _dio.post('/api/chat/completions', data: data);
|
final resp = await _dio.post('/api/chat/completions', data: data);
|
||||||
@@ -3272,26 +3272,11 @@ class ApiService {
|
|||||||
: null;
|
: null;
|
||||||
_traceApi('Background task created: $taskId');
|
_traceApi('Background task created: $taskId');
|
||||||
|
|
||||||
// If no session/socket provided, fall back to polling for updates.
|
// Close the controller immediately - all streaming will happen via WebSocket
|
||||||
final pollChatId = (conversationId != null && conversationId.isNotEmpty)
|
// No polling fallback to avoid duplication issues
|
||||||
? conversationId
|
|
||||||
: null;
|
|
||||||
final requiresPolling =
|
|
||||||
sessionIdOverride == null || sessionIdOverride.isEmpty;
|
|
||||||
|
|
||||||
if (requiresPolling && pollChatId != null) {
|
|
||||||
final chatId = pollChatId;
|
|
||||||
await _pollChatForMessageUpdates(
|
|
||||||
chatId: chatId,
|
|
||||||
messageId: messageId,
|
|
||||||
streamController: streamController,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Close the controller so listeners don't hang waiting for chunks
|
|
||||||
if (!streamController.isClosed) {
|
if (!streamController.isClosed) {
|
||||||
streamController.close();
|
streamController.close();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_traceApi('Background tools flow failed: $e');
|
_traceApi('Background tools flow failed: $e');
|
||||||
if (!streamController.isClosed) streamController.close();
|
if (!streamController.isClosed) streamController.close();
|
||||||
@@ -3329,224 +3314,6 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll the server chat until the assistant message is populated with tool results,
|
|
||||||
// then stream deltas to the UI and close.
|
|
||||||
Future<void> _pollChatForMessageUpdates({
|
|
||||||
required String chatId,
|
|
||||||
required String messageId,
|
|
||||||
required StreamController<String> streamController,
|
|
||||||
}) async {
|
|
||||||
String last = '';
|
|
||||||
int stableCount = 0;
|
|
||||||
final started = DateTime.now();
|
|
||||||
|
|
||||||
bool containsDone(String s) =>
|
|
||||||
s.contains('<details type="tool_calls"') && s.contains('done="true"');
|
|
||||||
|
|
||||||
// Allow much longer time for large completions, matching OpenWebUI's generous timeouts
|
|
||||||
while (DateTime.now().difference(started).inSeconds < 600) {
|
|
||||||
// Increased from 180 to 600 seconds (10 minutes)
|
|
||||||
try {
|
|
||||||
// Small delay between polls
|
|
||||||
await Future.delayed(const Duration(milliseconds: 900));
|
|
||||||
|
|
||||||
final resp = await _dio.get('/api/v1/chats/$chatId');
|
|
||||||
final data = resp.data as Map<String, dynamic>;
|
|
||||||
|
|
||||||
// Locate assistant content from multiple shapes
|
|
||||||
String content = '';
|
|
||||||
|
|
||||||
Map<String, dynamic>? chatObj = (data['chat'] is Map<String, dynamic>)
|
|
||||||
? data['chat'] as Map<String, dynamic>
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// 1) Preferred: chat.messages (list) – try exact id first
|
|
||||||
if (chatObj != null && chatObj['messages'] is List) {
|
|
||||||
final List messagesList = chatObj['messages'] as List;
|
|
||||||
final target = messagesList.firstWhere(
|
|
||||||
(m) => (m is Map && (m['id']?.toString() == messageId)),
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (target != null) {
|
|
||||||
final rawContent = (target as Map)['content'];
|
|
||||||
if (rawContent is List) {
|
|
||||||
final textItem = rawContent.firstWhere(
|
|
||||||
(i) => i is Map && i['type'] == 'text',
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (textItem != null) {
|
|
||||||
content = textItem['text']?.toString() ?? '';
|
|
||||||
}
|
|
||||||
} else if (rawContent is String) {
|
|
||||||
content = rawContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Fallback: chat.history.messages (map) – try exact id
|
|
||||||
if (content.isEmpty && chatObj != null) {
|
|
||||||
final history = chatObj['history'];
|
|
||||||
if (history is Map && history['messages'] is Map) {
|
|
||||||
final Map<String, dynamic> messagesMap =
|
|
||||||
(history['messages'] as Map).cast<String, dynamic>();
|
|
||||||
final msg = messagesMap[messageId];
|
|
||||||
if (msg is Map) {
|
|
||||||
final rawContent = msg['content'];
|
|
||||||
if (rawContent is String) {
|
|
||||||
content = rawContent;
|
|
||||||
} else if (rawContent is List) {
|
|
||||||
final textItem = rawContent.firstWhere(
|
|
||||||
(i) => i is Map && i['type'] == 'text',
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (textItem != null) {
|
|
||||||
content = textItem['text']?.toString() ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Last resort: top-level messages (list) – try exact id
|
|
||||||
if (content.isEmpty && data['messages'] is List) {
|
|
||||||
final List topMessages = data['messages'] as List;
|
|
||||||
final target = topMessages.firstWhere(
|
|
||||||
(m) => (m is Map && (m['id']?.toString() == messageId)),
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (target != null) {
|
|
||||||
final rawContent = (target as Map)['content'];
|
|
||||||
if (rawContent is String) {
|
|
||||||
content = rawContent;
|
|
||||||
} else if (rawContent is List) {
|
|
||||||
final textItem = rawContent.firstWhere(
|
|
||||||
(i) => i is Map && i['type'] == 'text',
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (textItem != null) {
|
|
||||||
content = textItem['text']?.toString() ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: We intentionally removed the fallback to "any latest assistant message"
|
|
||||||
// because it causes duplication issues in multi-turn conversations.
|
|
||||||
// If we can't find the specific message by ID, we skip this poll iteration
|
|
||||||
// and wait for the next one rather than showing content from a different message.
|
|
||||||
|
|
||||||
if (content.isEmpty) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream only the delta when content grows monotonically
|
|
||||||
if (content.startsWith(last)) {
|
|
||||||
final delta = content.substring(last.length);
|
|
||||||
if (delta.isNotEmpty && !streamController.isClosed) {
|
|
||||||
streamController.add(delta);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback: replace entire content by emitting a separator + full content
|
|
||||||
if (!streamController.isClosed) {
|
|
||||||
streamController.add('\n');
|
|
||||||
streamController.add(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Stop when we detect done=true on tool_calls or when content stabilizes
|
|
||||||
if (containsDone(content)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If content hasn't changed for several polls, assume completion,
|
|
||||||
// but be more conservative to avoid cutting off long responses.
|
|
||||||
// OpenWebUI relies more on explicit done signals than stability checks.
|
|
||||||
final prev = last;
|
|
||||||
if (content == prev && content.isNotEmpty) {
|
|
||||||
stableCount++;
|
|
||||||
} else if (content != prev) {
|
|
||||||
stableCount = 0;
|
|
||||||
}
|
|
||||||
// Increased threshold from 3 to 8 polls to be more conservative
|
|
||||||
// This gives ~7-8 seconds of stability before assuming completion
|
|
||||||
if (content.isNotEmpty && stableCount >= 8) {
|
|
||||||
DebugLogger.log(
|
|
||||||
'Content stable for $stableCount polls, assuming completion',
|
|
||||||
scope: 'api/polling',
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
last = content;
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore transient errors and continue polling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final backfill: one last attempt to fetch the latest content
|
|
||||||
// in case the server wrote the final message after our last poll.
|
|
||||||
try {
|
|
||||||
if (!streamController.isClosed) {
|
|
||||||
final resp = await _dio.get('/api/v1/chats/$chatId');
|
|
||||||
final data = resp.data as Map<String, dynamic>;
|
|
||||||
String content = '';
|
|
||||||
Map<String, dynamic>? chatObj = (data['chat'] is Map<String, dynamic>)
|
|
||||||
? data['chat'] as Map<String, dynamic>
|
|
||||||
: null;
|
|
||||||
if (chatObj != null && chatObj['messages'] is List) {
|
|
||||||
final List messagesList = chatObj['messages'] as List;
|
|
||||||
final target = messagesList.firstWhere(
|
|
||||||
(m) => (m is Map && (m['id']?.toString() == messageId)),
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (target != null) {
|
|
||||||
final rawContent = (target as Map)['content'];
|
|
||||||
if (rawContent is String) {
|
|
||||||
content = rawContent;
|
|
||||||
} else if (rawContent is List) {
|
|
||||||
final textItem = rawContent.firstWhere(
|
|
||||||
(i) => i is Map && i['type'] == 'text',
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (textItem != null) {
|
|
||||||
content = (textItem as Map)['text']?.toString() ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (content.isEmpty && chatObj != null) {
|
|
||||||
final history = chatObj['history'];
|
|
||||||
if (history is Map && history['messages'] is Map) {
|
|
||||||
final Map<String, dynamic> messagesMap =
|
|
||||||
(history['messages'] as Map).cast<String, dynamic>();
|
|
||||||
final msg = messagesMap[messageId];
|
|
||||||
if (msg is Map) {
|
|
||||||
final rawContent = msg['content'];
|
|
||||||
if (rawContent is String) {
|
|
||||||
content = rawContent;
|
|
||||||
} else if (rawContent is List) {
|
|
||||||
final textItem = rawContent.firstWhere(
|
|
||||||
(i) => i is Map && i['type'] == 'text',
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (textItem != null) {
|
|
||||||
content = (textItem as Map)['text']?.toString() ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (content.isNotEmpty && content != last) {
|
|
||||||
streamController.add('\n');
|
|
||||||
streamController.add(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
if (!streamController.isClosed) {
|
|
||||||
streamController.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel an active streaming message by its messageId (client-side abort)
|
// Cancel an active streaming message by its messageId (client-side abort)
|
||||||
void cancelStreamingMessage(String messageId) {
|
void cancelStreamingMessage(String messageId) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:socket_io_client/socket_io_client.dart' as io;
|
import 'package:socket_io_client/socket_io_client.dart' as io;
|
||||||
|
|
||||||
import '../models/server_config.dart';
|
import '../models/server_config.dart';
|
||||||
import '../utils/debug_logger.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
|
import 'navigation_service.dart';
|
||||||
import 'socket_tls_override.dart';
|
import 'socket_tls_override.dart';
|
||||||
|
import '../../shared/utils/ui_utils.dart';
|
||||||
|
|
||||||
typedef SocketChatEventHandler =
|
typedef SocketChatEventHandler =
|
||||||
void Function(
|
void Function(
|
||||||
@@ -71,10 +73,10 @@ class SocketService with WidgetsBindingObserver {
|
|||||||
final path = '/ws/socket.io';
|
final path = '/ws/socket.io';
|
||||||
|
|
||||||
final builder = io.OptionBuilder()
|
final builder = io.OptionBuilder()
|
||||||
// Transport selection
|
// Transport selection - WebSocket only, no polling fallback
|
||||||
.setTransports(websocketOnly ? ['websocket'] : ['polling', 'websocket'])
|
.setTransports(['websocket'])
|
||||||
.setRememberUpgrade(!websocketOnly)
|
.setRememberUpgrade(false)
|
||||||
.setUpgrade(!websocketOnly)
|
.setUpgrade(false)
|
||||||
// Tune reconnect/backoff and timeouts
|
// Tune reconnect/backoff and timeouts
|
||||||
.setReconnectionAttempts(0) // 0/Infinity semantics: unlimited attempts
|
.setReconnectionAttempts(0) // 0/Infinity semantics: unlimited attempts
|
||||||
.setReconnectionDelay(1000)
|
.setReconnectionDelay(1000)
|
||||||
@@ -252,7 +254,6 @@ class SocketService with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleConnect(dynamic _) {
|
void _handleConnect(dynamic _) {
|
||||||
DebugLogger.log('Socket connected: ${_socket?.id}', scope: 'socket');
|
|
||||||
if (_authToken != null && _authToken!.isNotEmpty) {
|
if (_authToken != null && _authToken!.isNotEmpty) {
|
||||||
_socket?.emit('user-join', {
|
_socket?.emit('user-join', {
|
||||||
'auth': {'token': _authToken},
|
'auth': {'token': _authToken},
|
||||||
@@ -261,14 +262,10 @@ class SocketService with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleReconnectAttempt(dynamic attempt) {
|
void _handleReconnectAttempt(dynamic attempt) {
|
||||||
DebugLogger.log('Socket reconnect_attempt: $attempt', scope: 'socket');
|
// Silent reconnection attempt
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleReconnect(dynamic attempt) {
|
void _handleReconnect(dynamic attempt) {
|
||||||
DebugLogger.log(
|
|
||||||
'Socket reconnected after $attempt attempts',
|
|
||||||
scope: 'socket',
|
|
||||||
);
|
|
||||||
if (_authToken != null && _authToken!.isNotEmpty) {
|
if (_authToken != null && _authToken!.isNotEmpty) {
|
||||||
_socket?.emit('user-join', {
|
_socket?.emit('user-join', {
|
||||||
'auth': {'token': _authToken},
|
'auth': {'token': _authToken},
|
||||||
@@ -277,15 +274,39 @@ class SocketService with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleConnectError(dynamic err) {
|
void _handleConnectError(dynamic err) {
|
||||||
DebugLogger.log('Socket connect_error: $err', scope: 'socket');
|
// Show user-facing error notification
|
||||||
|
final context = NavigationService.context;
|
||||||
|
if (context != null) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
if (l10n != null) {
|
||||||
|
UiUtils.showMessage(
|
||||||
|
context,
|
||||||
|
l10n.websocketConnectionError,
|
||||||
|
isError: true,
|
||||||
|
duration: const Duration(seconds: 5),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleReconnectFailed(dynamic _) {
|
void _handleReconnectFailed(dynamic _) {
|
||||||
DebugLogger.log('Socket reconnect_failed', scope: 'socket');
|
// Show user-facing error notification
|
||||||
|
final context = NavigationService.context;
|
||||||
|
if (context != null) {
|
||||||
|
final l10n = AppLocalizations.of(context);
|
||||||
|
if (l10n != null) {
|
||||||
|
UiUtils.showMessage(
|
||||||
|
context,
|
||||||
|
l10n.websocketReconnectFailed,
|
||||||
|
isError: true,
|
||||||
|
duration: const Duration(seconds: 5),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDisconnect(dynamic reason) {
|
void _handleDisconnect(dynamic reason) {
|
||||||
DebugLogger.log('Socket disconnected: $reason', scope: 'socket');
|
// Silent disconnect
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleChatEvent(dynamic data, [dynamic ack]) {
|
void _handleChatEvent(dynamic data, [dynamic ack]) {
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ class ActiveSocketStream {
|
|||||||
|
|
||||||
/// Unified streaming helper for chat send/regenerate flows.
|
/// Unified streaming helper for chat send/regenerate flows.
|
||||||
///
|
///
|
||||||
/// This attaches chunked polling streams (fallback) plus WebSocket event handlers,
|
/// This attaches WebSocket event handlers and manages background search/image-gen
|
||||||
/// and manages background search/image-gen UI updates. It operates via callbacks to
|
/// UI updates. It operates via callbacks to avoid tight coupling with provider files
|
||||||
/// avoid tight coupling with provider files for easier reuse and testing.
|
/// for easier reuse and testing.
|
||||||
ActiveSocketStream attachUnifiedChunkedStreaming({
|
ActiveSocketStream attachUnifiedChunkedStreaming({
|
||||||
required Stream<String> stream,
|
required Stream<String> stream,
|
||||||
required bool webSearchEnabled,
|
required bool webSearchEnabled,
|
||||||
@@ -1140,9 +1140,8 @@ ActiveSocketStream attachUnifiedChunkedStreaming({
|
|||||||
// Unregister from persistent service
|
// Unregister from persistent service
|
||||||
persistentService.unregisterStream(streamId);
|
persistentService.unregisterStream(streamId);
|
||||||
|
|
||||||
// Only finish streaming if no socket subscriptions are active
|
// Stream completion without socket subscriptions indicates a simple flow
|
||||||
// This indicates a polling-driven flow where the stream ending means completion
|
// For WebSocket flows, completion should be handled by socket events (done: true)
|
||||||
// For socket flows, completion should be handled by socket events (done: true)
|
|
||||||
if (socketSubscriptions.isEmpty) {
|
if (socketSubscriptions.isEmpty) {
|
||||||
finishStreaming();
|
finishStreaming();
|
||||||
Future.microtask(refreshConversationSnapshot);
|
Future.microtask(refreshConversationSnapshot);
|
||||||
|
|||||||
@@ -337,5 +337,7 @@
|
|||||||
"transportModeAuto": "Auto (Polling + WebSocket)",
|
"transportModeAuto": "Auto (Polling + WebSocket)",
|
||||||
"transportModeWs": "Nur WebSocket",
|
"transportModeWs": "Nur WebSocket",
|
||||||
"transportModeAutoInfo": "Robuster in restriktiven Netzwerken. Wechselt nach Möglichkeit zu WebSocket.",
|
"transportModeAutoInfo": "Robuster in restriktiven Netzwerken. Wechselt nach Möglichkeit zu WebSocket.",
|
||||||
"transportModeWsInfo": "Geringerer Overhead, kann jedoch hinter strikten Proxys/Firewalls fehlschlagen."
|
"transportModeWsInfo": "Geringerer Overhead, kann jedoch hinter strikten Proxys/Firewalls fehlschlagen.",
|
||||||
|
"websocketConnectionError": "Echtzeit-Verbindung konnte nicht hergestellt werden. Bitte überprüfen Sie Ihr Netzwerk und die Serverkonfiguration.",
|
||||||
|
"websocketReconnectFailed": "Echtzeit-Verbindung fehlgeschlagen. Streaming funktioniert möglicherweise nicht ordnungsgemäß."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -700,7 +700,10 @@
|
|||||||
"@transportModeWs": {"description": "Dropdown option label for WebSocket-only transport."},
|
"@transportModeWs": {"description": "Dropdown option label for WebSocket-only transport."},
|
||||||
"transportModeAutoInfo": "More robust on restrictive networks. Upgrades to WebSocket when possible.",
|
"transportModeAutoInfo": "More robust on restrictive networks. Upgrades to WebSocket when possible.",
|
||||||
"@transportModeAutoInfo": {"description": "Footnote text for the Auto transport mode."},
|
"@transportModeAutoInfo": {"description": "Footnote text for the Auto transport mode."},
|
||||||
"transportModeWsInfo": "Lower overhead, but may fail behind strict proxies/firewalls."
|
"transportModeWsInfo": "Lower overhead, but may fail behind strict proxies/firewalls.",
|
||||||
,
|
"@transportModeWsInfo": {"description": "Footnote text for the WebSocket-only transport mode."},
|
||||||
"@transportModeWsInfo": {"description": "Footnote text for the WebSocket-only transport mode."}
|
"websocketConnectionError": "Unable to establish real-time connection. Please check your network and server configuration.",
|
||||||
|
"@websocketConnectionError": {"description": "Error message shown when WebSocket connection fails initially."},
|
||||||
|
"websocketReconnectFailed": "Real-time connection failed. Streaming may not work properly.",
|
||||||
|
"@websocketReconnectFailed": {"description": "Error message shown when WebSocket reconnection attempts fail."}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,5 +330,7 @@
|
|||||||
"transportModeAuto": "Automático (Polling + WebSocket)",
|
"transportModeAuto": "Automático (Polling + WebSocket)",
|
||||||
"transportModeWs": "Solo WebSocket",
|
"transportModeWs": "Solo WebSocket",
|
||||||
"transportModeAutoInfo": "Más robusto en redes restrictivas. Se actualiza a WebSocket cuando es posible.",
|
"transportModeAutoInfo": "Más robusto en redes restrictivas. Se actualiza a WebSocket cuando es posible.",
|
||||||
"transportModeWsInfo": "Menor sobrecarga, pero puede fallar detrás de proxies/firewalls estrictos."
|
"transportModeWsInfo": "Menor sobrecarga, pero puede fallar detrás de proxies/firewalls estrictos.",
|
||||||
|
"websocketConnectionError": "No se puede establecer la conexión en tiempo real. Por favor, verifica tu red y la configuración del servidor.",
|
||||||
|
"websocketReconnectFailed": "Fallo en la conexión en tiempo real. El streaming podría no funcionar correctamente."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,5 +337,7 @@
|
|||||||
"transportModeAuto": "Auto (Polling + WebSocket)",
|
"transportModeAuto": "Auto (Polling + WebSocket)",
|
||||||
"transportModeWs": "WebSocket uniquement",
|
"transportModeWs": "WebSocket uniquement",
|
||||||
"transportModeAutoInfo": "Plus robuste sur les réseaux restrictifs. Passe à WebSocket lorsque possible.",
|
"transportModeAutoInfo": "Plus robuste sur les réseaux restrictifs. Passe à WebSocket lorsque possible.",
|
||||||
"transportModeWsInfo": "Moins de surcharge, mais peut échouer derrière des proxys/firewalls stricts."
|
"transportModeWsInfo": "Moins de surcharge, mais peut échouer derrière des proxys/firewalls stricts.",
|
||||||
|
"websocketConnectionError": "Impossible d'établir une connexion en temps réel. Veuillez vérifier votre réseau et la configuration du serveur.",
|
||||||
|
"websocketReconnectFailed": "Échec de la connexion en temps réel. Le streaming pourrait ne pas fonctionner correctement."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,5 +337,7 @@
|
|||||||
"transportModeAuto": "Auto (Polling + WebSocket)",
|
"transportModeAuto": "Auto (Polling + WebSocket)",
|
||||||
"transportModeWs": "Solo WebSocket",
|
"transportModeWs": "Solo WebSocket",
|
||||||
"transportModeAutoInfo": "Più robusto nelle reti restrittive. Passa a WebSocket quando possibile.",
|
"transportModeAutoInfo": "Più robusto nelle reti restrittive. Passa a WebSocket quando possibile.",
|
||||||
"transportModeWsInfo": "Minore overhead, ma può fallire dietro proxy/firewall restrittivi."
|
"transportModeWsInfo": "Minore overhead, ma può fallire dietro proxy/firewall restrittivi.",
|
||||||
|
"websocketConnectionError": "Impossibile stabilire una connessione in tempo reale. Si prega di controllare la rete e la configurazione del server.",
|
||||||
|
"websocketReconnectFailed": "Connessione in tempo reale fallita. Lo streaming potrebbe non funzionare correttamente."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1849,6 +1849,18 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Lower overhead, but may fail behind strict proxies/firewalls.'**
|
/// **'Lower overhead, but may fail behind strict proxies/firewalls.'**
|
||||||
String get transportModeWsInfo;
|
String get transportModeWsInfo;
|
||||||
|
|
||||||
|
/// Error message shown when WebSocket connection fails initially.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Unable to establish real-time connection. Please check your network and server configuration.'**
|
||||||
|
String get websocketConnectionError;
|
||||||
|
|
||||||
|
/// Error message shown when WebSocket reconnection attempts fail.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Real-time connection failed. Streaming may not work properly.'**
|
||||||
|
String get websocketReconnectFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -965,4 +965,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get transportModeWsInfo =>
|
String get transportModeWsInfo =>
|
||||||
'Geringerer Overhead, kann jedoch hinter strikten Proxys/Firewalls fehlschlagen.';
|
'Geringerer Overhead, kann jedoch hinter strikten Proxys/Firewalls fehlschlagen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketConnectionError =>
|
||||||
|
'Echtzeit-Verbindung konnte nicht hergestellt werden. Bitte überprüfen Sie Ihr Netzwerk und die Serverkonfiguration.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketReconnectFailed =>
|
||||||
|
'Echtzeit-Verbindung fehlgeschlagen. Streaming funktioniert möglicherweise nicht ordnungsgemäß.';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -957,4 +957,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get transportModeWsInfo =>
|
String get transportModeWsInfo =>
|
||||||
'Lower overhead, but may fail behind strict proxies/firewalls.';
|
'Lower overhead, but may fail behind strict proxies/firewalls.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketConnectionError =>
|
||||||
|
'Unable to establish real-time connection. Please check your network and server configuration.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketReconnectFailed =>
|
||||||
|
'Real-time connection failed. Streaming may not work properly.';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -971,4 +971,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get transportModeWsInfo =>
|
String get transportModeWsInfo =>
|
||||||
'Moins de surcharge, mais peut échouer derrière des proxys/firewalls stricts.';
|
'Moins de surcharge, mais peut échouer derrière des proxys/firewalls stricts.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketConnectionError =>
|
||||||
|
'Impossible d\'établir une connexion en temps réel. Veuillez vérifier votre réseau et la configuration du serveur.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketReconnectFailed =>
|
||||||
|
'Échec de la connexion en temps réel. Le streaming pourrait ne pas fonctionner correctement.';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -960,4 +960,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get transportModeWsInfo =>
|
String get transportModeWsInfo =>
|
||||||
'Minore overhead, ma può fallire dietro proxy/firewall restrittivi.';
|
'Minore overhead, ma può fallire dietro proxy/firewall restrittivi.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketConnectionError =>
|
||||||
|
'Impossibile stabilire una connessione in tempo reale. Si prega di controllare la rete e la configurazione del server.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get websocketReconnectFailed =>
|
||||||
|
'Connessione in tempo reale fallita. Lo streaming potrebbe non funzionare correttamente.';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,5 +330,7 @@
|
|||||||
"transportModeAuto": "Automatisch (Polling + WebSocket)",
|
"transportModeAuto": "Automatisch (Polling + WebSocket)",
|
||||||
"transportModeWs": "Alleen WebSocket",
|
"transportModeWs": "Alleen WebSocket",
|
||||||
"transportModeAutoInfo": "Robuuster op beperkende netwerken. Upgrade naar WebSocket indien mogelijk.",
|
"transportModeAutoInfo": "Robuuster op beperkende netwerken. Upgrade naar WebSocket indien mogelijk.",
|
||||||
"transportModeWsInfo": "Lagere overhead, maar kan mislukken achter strikte proxies/firewalls."
|
"transportModeWsInfo": "Lagere overhead, maar kan mislukken achter strikte proxies/firewalls.",
|
||||||
|
"websocketConnectionError": "Kan geen realtime verbinding maken. Controleer uw netwerk en serverconfiguratie.",
|
||||||
|
"websocketReconnectFailed": "Realtime verbinding mislukt. Streaming werkt mogelijk niet goed."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,5 +330,7 @@
|
|||||||
"transportModeAuto": "Авто (опрос + WebSocket)",
|
"transportModeAuto": "Авто (опрос + WebSocket)",
|
||||||
"transportModeWs": "Только WebSocket",
|
"transportModeWs": "Только WebSocket",
|
||||||
"transportModeAutoInfo": "Более надежен в ограничительных сетях. Переходит на WebSocket, когда это возможно.",
|
"transportModeAutoInfo": "Более надежен в ограничительных сетях. Переходит на WebSocket, когда это возможно.",
|
||||||
"transportModeWsInfo": "Меньше накладных расходов, но может не работать за строгими прокси/брандмауэрами."
|
"transportModeWsInfo": "Меньше накладных расходов, но может не работать за строгими прокси/брандмауэрами.",
|
||||||
|
"websocketConnectionError": "Не удалось установить соединение в реальном времени. Пожалуйста, проверьте сеть и конфигурацию сервера.",
|
||||||
|
"websocketReconnectFailed": "Сбой соединения в реальном времени. Потоковая передача может работать неправильно."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,5 +330,7 @@
|
|||||||
"transportModeAuto": "自动(轮询 + WebSocket)",
|
"transportModeAuto": "自动(轮询 + WebSocket)",
|
||||||
"transportModeWs": "仅 WebSocket",
|
"transportModeWs": "仅 WebSocket",
|
||||||
"transportModeAutoInfo": "在限制性网络上更稳健。在可能的情况下升级到 WebSocket。",
|
"transportModeAutoInfo": "在限制性网络上更稳健。在可能的情况下升级到 WebSocket。",
|
||||||
"transportModeWsInfo": "开销较低,但可能在严格的代理/防火墙后失败。"
|
"transportModeWsInfo": "开销较低,但可能在严格的代理/防火墙后失败。",
|
||||||
|
"websocketConnectionError": "无法建立实时连接。请检查您的网络和服务器配置。",
|
||||||
|
"websocketReconnectFailed": "实时连接失败。流式传输可能无法正常工作。"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user