feat(tts): add auto mode for text-to-speech engine selection
This commit is contained in:
BIN
flutter_01.png
Normal file
BIN
flutter_01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 257 KiB |
@@ -12,7 +12,7 @@ part 'settings_service.g.dart';
|
|||||||
enum SttPreference { auto, deviceOnly, serverOnly }
|
enum SttPreference { auto, deviceOnly, serverOnly }
|
||||||
|
|
||||||
/// TTS engine selection
|
/// TTS engine selection
|
||||||
enum TtsEngine { device, server }
|
enum TtsEngine { auto, device, server }
|
||||||
|
|
||||||
/// Service for managing app-wide settings including accessibility preferences
|
/// Service for managing app-wide settings including accessibility preferences
|
||||||
class SettingsService {
|
class SettingsService {
|
||||||
@@ -223,11 +223,15 @@ class SettingsService {
|
|||||||
|
|
||||||
static TtsEngine _parseTtsEngine(String? raw) {
|
static TtsEngine _parseTtsEngine(String? raw) {
|
||||||
switch ((raw ?? '').toLowerCase()) {
|
switch ((raw ?? '').toLowerCase()) {
|
||||||
|
case 'auto':
|
||||||
|
case '':
|
||||||
|
return TtsEngine.auto;
|
||||||
case 'server':
|
case 'server':
|
||||||
return TtsEngine.server;
|
return TtsEngine.server;
|
||||||
case 'device':
|
case 'device':
|
||||||
default:
|
|
||||||
return TtsEngine.device;
|
return TtsEngine.device;
|
||||||
|
default:
|
||||||
|
return TtsEngine.auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +413,7 @@ class AppSettings {
|
|||||||
this.ttsSpeechRate = 0.5,
|
this.ttsSpeechRate = 0.5,
|
||||||
this.ttsPitch = 1.0,
|
this.ttsPitch = 1.0,
|
||||||
this.ttsVolume = 1.0,
|
this.ttsVolume = 1.0,
|
||||||
this.ttsEngine = TtsEngine.device,
|
this.ttsEngine = TtsEngine.auto,
|
||||||
this.ttsServerVoiceId,
|
this.ttsServerVoiceId,
|
||||||
this.ttsServerVoiceName,
|
this.ttsServerVoiceName,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -107,11 +107,9 @@ class TextToSpeechController extends Notifier<TextToSpeechState> {
|
|||||||
// Listen to settings changes and update TTS when initialized
|
// Listen to settings changes and update TTS when initialized
|
||||||
ref.listen<AppSettings>(appSettingsProvider, (previous, next) {
|
ref.listen<AppSettings>(appSettingsProvider, (previous, next) {
|
||||||
if (_service.isInitialized && _service.isAvailable) {
|
if (_service.isInitialized && _service.isAvailable) {
|
||||||
final selectedVoice = next.ttsEngine == TtsEngine.server
|
|
||||||
? next.ttsServerVoiceId
|
|
||||||
: next.ttsVoice;
|
|
||||||
_service.updateSettings(
|
_service.updateSettings(
|
||||||
voice: selectedVoice,
|
voice: next.ttsVoice,
|
||||||
|
serverVoice: next.ttsServerVoiceId,
|
||||||
speechRate: next.ttsSpeechRate,
|
speechRate: next.ttsSpeechRate,
|
||||||
pitch: next.ttsPitch,
|
pitch: next.ttsPitch,
|
||||||
volume: next.ttsVolume,
|
volume: next.ttsVolume,
|
||||||
@@ -137,9 +135,8 @@ class TextToSpeechController extends Notifier<TextToSpeechState> {
|
|||||||
final settings = ref.read(appSettingsProvider);
|
final settings = ref.read(appSettingsProvider);
|
||||||
final future = _service
|
final future = _service
|
||||||
.initialize(
|
.initialize(
|
||||||
voice: settings.ttsEngine == TtsEngine.server
|
deviceVoice: settings.ttsVoice,
|
||||||
? settings.ttsServerVoiceId
|
serverVoice: settings.ttsServerVoiceId,
|
||||||
: settings.ttsVoice,
|
|
||||||
speechRate: settings.ttsSpeechRate,
|
speechRate: settings.ttsSpeechRate,
|
||||||
pitch: settings.ttsPitch,
|
pitch: settings.ttsPitch,
|
||||||
volume: settings.ttsVolume,
|
volume: settings.ttsVolume,
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ class TextToSpeechService {
|
|||||||
final FlutterTts _tts = FlutterTts();
|
final FlutterTts _tts = FlutterTts();
|
||||||
final AudioPlayer _player = AudioPlayer();
|
final AudioPlayer _player = AudioPlayer();
|
||||||
final ApiService? _api;
|
final ApiService? _api;
|
||||||
TtsEngine _engine = TtsEngine.device;
|
TtsEngine _engine = TtsEngine.auto;
|
||||||
String? _preferredVoice;
|
String? _preferredVoice;
|
||||||
|
String? _serverPreferredVoice;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
bool _available = false;
|
bool _available = false;
|
||||||
bool _voiceConfigured = false;
|
bool _voiceConfigured = false;
|
||||||
@@ -41,6 +42,8 @@ class TextToSpeechService {
|
|||||||
|
|
||||||
bool get isInitialized => _initialized;
|
bool get isInitialized => _initialized;
|
||||||
bool get isAvailable => _available;
|
bool get isAvailable => _available;
|
||||||
|
bool get deviceEngineAvailable => _deviceEngineAvailable;
|
||||||
|
bool get serverEngineAvailable => _api != null;
|
||||||
|
|
||||||
TextToSpeechService({ApiService? api}) : _api = api {
|
TextToSpeechService({ApiService? api}) : _api = api {
|
||||||
// Wire minimal player events to callbacks
|
// Wire minimal player events to callbacks
|
||||||
@@ -59,6 +62,69 @@ class TextToSpeechService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _configureDeviceEngine({
|
||||||
|
required String? voice,
|
||||||
|
required double speechRate,
|
||||||
|
required double pitch,
|
||||||
|
required double volume,
|
||||||
|
}) async {
|
||||||
|
_deviceEngineAvailable = false;
|
||||||
|
try {
|
||||||
|
await _tts.awaitSpeakCompletion(false);
|
||||||
|
await _tts.setVolume(volume);
|
||||||
|
await _tts.setSpeechRate(speechRate);
|
||||||
|
await _tts.setPitch(pitch);
|
||||||
|
|
||||||
|
if (!kIsWeb && Platform.isIOS) {
|
||||||
|
await _tts.setSharedInstance(true);
|
||||||
|
await _tts.setIosAudioCategory(IosTextToSpeechAudioCategory.playback, [
|
||||||
|
IosTextToSpeechAudioCategoryOptions.mixWithOthers,
|
||||||
|
IosTextToSpeechAudioCategoryOptions.defaultToSpeaker,
|
||||||
|
IosTextToSpeechAudioCategoryOptions.allowBluetooth,
|
||||||
|
IosTextToSpeechAudioCategoryOptions.allowBluetoothA2DP,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_engine != TtsEngine.server) {
|
||||||
|
await _setVoiceByName(_preferredVoice);
|
||||||
|
} else {
|
||||||
|
_voiceConfigured = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceEngineAvailable = true;
|
||||||
|
} catch (e) {
|
||||||
|
_voiceConfigured = false;
|
||||||
|
_deviceEngineAvailable = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _computeAvailability() {
|
||||||
|
final serverAvailable = _api != null;
|
||||||
|
switch (_engine) {
|
||||||
|
case TtsEngine.device:
|
||||||
|
return _deviceEngineAvailable;
|
||||||
|
case TtsEngine.server:
|
||||||
|
return serverAvailable;
|
||||||
|
case TtsEngine.auto:
|
||||||
|
return _deviceEngineAvailable || serverAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldUseServer() {
|
||||||
|
if (_engine == TtsEngine.server) {
|
||||||
|
return _api != null;
|
||||||
|
}
|
||||||
|
if (_engine == TtsEngine.device) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Auto: prefer device when available, otherwise fall back to server
|
||||||
|
if (_deviceEngineAvailable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _api != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Register callbacks for TTS lifecycle events
|
/// Register callbacks for TTS lifecycle events
|
||||||
void bindHandlers({
|
void bindHandlers({
|
||||||
VoidCallback? onStart,
|
VoidCallback? onStart,
|
||||||
@@ -96,56 +162,58 @@ class TextToSpeechService {
|
|||||||
|
|
||||||
/// Initialize the native TTS engine lazily
|
/// Initialize the native TTS engine lazily
|
||||||
Future<bool> initialize({
|
Future<bool> initialize({
|
||||||
String? voice,
|
String? deviceVoice,
|
||||||
|
String? serverVoice,
|
||||||
double speechRate = 0.5,
|
double speechRate = 0.5,
|
||||||
double pitch = 1.0,
|
double pitch = 1.0,
|
||||||
double volume = 1.0,
|
double volume = 1.0,
|
||||||
TtsEngine engine = TtsEngine.device,
|
TtsEngine engine = TtsEngine.auto,
|
||||||
}) async {
|
}) async {
|
||||||
if (_initialized) {
|
if (_initialized) {
|
||||||
|
_engine = engine;
|
||||||
|
if (deviceVoice != null) {
|
||||||
|
_preferredVoice = deviceVoice;
|
||||||
|
_voiceConfigured = false;
|
||||||
|
}
|
||||||
|
if (serverVoice != null) {
|
||||||
|
_serverPreferredVoice = serverVoice;
|
||||||
|
}
|
||||||
|
_available = _computeAvailability();
|
||||||
return _available;
|
return _available;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
_engine = engine;
|
||||||
_engine = engine;
|
_preferredVoice = deviceVoice;
|
||||||
_preferredVoice = voice;
|
_serverPreferredVoice = serverVoice;
|
||||||
await _tts.awaitSpeakCompletion(false);
|
_voiceConfigured = false;
|
||||||
|
|
||||||
// Set volume
|
if (_engine != TtsEngine.server || _api == null) {
|
||||||
await _tts.setVolume(volume);
|
try {
|
||||||
|
await _configureDeviceEngine(
|
||||||
// Set speech rate
|
voice: deviceVoice,
|
||||||
await _tts.setSpeechRate(speechRate);
|
speechRate: speechRate,
|
||||||
|
pitch: pitch,
|
||||||
// Set pitch
|
volume: volume,
|
||||||
await _tts.setPitch(pitch);
|
);
|
||||||
|
} catch (e) {
|
||||||
if (!kIsWeb && Platform.isIOS) {
|
if (_engine == TtsEngine.device) {
|
||||||
await _tts.setSharedInstance(true);
|
_available = false;
|
||||||
await _tts.setIosAudioCategory(IosTextToSpeechAudioCategory.playback, [
|
_onError?.call(e.toString());
|
||||||
IosTextToSpeechAudioCategoryOptions.mixWithOthers,
|
_initialized = true;
|
||||||
IosTextToSpeechAudioCategoryOptions.defaultToSpeaker,
|
return _available;
|
||||||
IosTextToSpeechAudioCategoryOptions.allowBluetooth,
|
}
|
||||||
IosTextToSpeechAudioCategoryOptions.allowBluetoothA2DP,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Set the voice (specific or default) when using device engine
|
|
||||||
if (_engine == TtsEngine.device) {
|
|
||||||
await _setVoiceByName(voice);
|
|
||||||
}
|
|
||||||
_deviceEngineAvailable = true;
|
|
||||||
} catch (e) {
|
|
||||||
_deviceEngineAvailable = false;
|
_deviceEngineAvailable = false;
|
||||||
if (_engine != TtsEngine.server) {
|
try {
|
||||||
_available = false;
|
await _tts.awaitSpeakCompletion(false);
|
||||||
_onError?.call(e.toString());
|
await _tts.setVolume(volume);
|
||||||
_initialized = true;
|
await _tts.setSpeechRate(speechRate);
|
||||||
return _available;
|
await _tts.setPitch(pitch);
|
||||||
}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
_available = _engine == TtsEngine.server || _deviceEngineAvailable;
|
_available = _computeAvailability();
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
return _available;
|
return _available;
|
||||||
}
|
}
|
||||||
@@ -156,10 +224,23 @@ class TextToSpeechService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!_initialized) {
|
if (!_initialized) {
|
||||||
await initialize(voice: _preferredVoice, engine: _engine);
|
await initialize(
|
||||||
|
deviceVoice: _preferredVoice,
|
||||||
|
serverVoice: _serverPreferredVoice,
|
||||||
|
engine: _engine,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_engine == TtsEngine.server && _api != null) {
|
final bool useServer = _shouldUseServer();
|
||||||
|
|
||||||
|
if (useServer) {
|
||||||
|
if (_api == null) {
|
||||||
|
if (_deviceEngineAvailable) {
|
||||||
|
await _speakOnDevice(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw StateError('Server text-to-speech is unavailable');
|
||||||
|
}
|
||||||
// Server-backed TTS with sentence chunking & queued playback
|
// Server-backed TTS with sentence chunking & queued playback
|
||||||
try {
|
try {
|
||||||
await _startServerChunkedPlayback(text);
|
await _startServerChunkedPlayback(text);
|
||||||
@@ -196,7 +277,7 @@ class TextToSpeechService {
|
|||||||
Future<void> pause() async {
|
Future<void> pause() async {
|
||||||
if (!_initialized) return;
|
if (!_initialized) return;
|
||||||
try {
|
try {
|
||||||
if (_engine == TtsEngine.server) {
|
if (_shouldUseServer()) {
|
||||||
await _player.pause();
|
await _player.pause();
|
||||||
_handlePause();
|
_handlePause();
|
||||||
} else if (_deviceEngineAvailable) {
|
} else if (_deviceEngineAvailable) {
|
||||||
@@ -210,7 +291,7 @@ class TextToSpeechService {
|
|||||||
Future<void> resume() async {
|
Future<void> resume() async {
|
||||||
if (!_initialized) return;
|
if (!_initialized) return;
|
||||||
try {
|
try {
|
||||||
if (_engine == TtsEngine.server) {
|
if (_shouldUseServer()) {
|
||||||
if (_waitingNext && (_currentIndex + 1) < _buffered.length) {
|
if (_waitingNext && (_currentIndex + 1) < _buffered.length) {
|
||||||
_waitingNext = false;
|
_waitingNext = false;
|
||||||
await _playNextIfBuffered(_session);
|
await _playNextIfBuffered(_session);
|
||||||
@@ -235,7 +316,7 @@ class TextToSpeechService {
|
|||||||
_expectedChunks = 0;
|
_expectedChunks = 0;
|
||||||
_currentIndex = -1;
|
_currentIndex = -1;
|
||||||
_waitingNext = false;
|
_waitingNext = false;
|
||||||
if (_engine == TtsEngine.server) {
|
if (_shouldUseServer()) {
|
||||||
await _player.stop();
|
await _player.stop();
|
||||||
_handleCancel();
|
_handleCancel();
|
||||||
} else {
|
} else {
|
||||||
@@ -254,17 +335,23 @@ class TextToSpeechService {
|
|||||||
/// Update TTS settings on-the-fly
|
/// Update TTS settings on-the-fly
|
||||||
Future<void> updateSettings({
|
Future<void> updateSettings({
|
||||||
Object? voice = const _VoiceNotProvided(),
|
Object? voice = const _VoiceNotProvided(),
|
||||||
|
Object? serverVoice = const _VoiceNotProvided(),
|
||||||
double? speechRate,
|
double? speechRate,
|
||||||
double? pitch,
|
double? pitch,
|
||||||
double? volume,
|
double? volume,
|
||||||
TtsEngine? engine,
|
TtsEngine? engine,
|
||||||
}) async {
|
}) async {
|
||||||
final voiceProvided = voice is! _VoiceNotProvided;
|
final voiceProvided = voice is! _VoiceNotProvided;
|
||||||
|
final serverVoiceProvided = serverVoice is! _VoiceNotProvided;
|
||||||
final voiceValue = voiceProvided ? voice as String? : null;
|
final voiceValue = voiceProvided ? voice as String? : null;
|
||||||
|
final serverVoiceValue = serverVoiceProvided
|
||||||
|
? serverVoice as String?
|
||||||
|
: null;
|
||||||
if (!_initialized || !_available) {
|
if (!_initialized || !_available) {
|
||||||
// Allow engine and voice to update before init
|
// Allow engine and voice to update before init
|
||||||
if (engine != null) _engine = engine;
|
if (engine != null) _engine = engine;
|
||||||
if (voiceProvided) _preferredVoice = voiceValue;
|
if (voiceProvided) _preferredVoice = voiceValue;
|
||||||
|
if (serverVoiceProvided) _serverPreferredVoice = serverVoiceValue;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,6 +362,9 @@ class TextToSpeechService {
|
|||||||
if (voiceProvided) {
|
if (voiceProvided) {
|
||||||
_preferredVoice = voiceValue;
|
_preferredVoice = voiceValue;
|
||||||
}
|
}
|
||||||
|
if (serverVoiceProvided) {
|
||||||
|
_serverPreferredVoice = serverVoiceValue;
|
||||||
|
}
|
||||||
if (volume != null) {
|
if (volume != null) {
|
||||||
await _tts.setVolume(volume);
|
await _tts.setVolume(volume);
|
||||||
}
|
}
|
||||||
@@ -284,13 +374,15 @@ class TextToSpeechService {
|
|||||||
if (pitch != null) {
|
if (pitch != null) {
|
||||||
await _tts.setPitch(pitch);
|
await _tts.setPitch(pitch);
|
||||||
}
|
}
|
||||||
// Set specific voice by name on device engine
|
// Set specific voice by name on device-capable engines
|
||||||
if (_engine == TtsEngine.device && voiceProvided) {
|
if (_engine != TtsEngine.server && voiceProvided) {
|
||||||
await _setVoiceByName(_preferredVoice);
|
await _setVoiceByName(_preferredVoice);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_onError?.call(e.toString());
|
_onError?.call(e.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_available = _computeAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set voice by name, or use system default if null
|
/// Set voice by name, or use system default if null
|
||||||
@@ -343,7 +435,11 @@ class TextToSpeechService {
|
|||||||
/// Get available voices from the TTS engine
|
/// Get available voices from the TTS engine
|
||||||
Future<List<Map<String, dynamic>>> getAvailableVoices() async {
|
Future<List<Map<String, dynamic>>> getAvailableVoices() async {
|
||||||
if (!_initialized) {
|
if (!_initialized) {
|
||||||
await initialize(voice: _preferredVoice, engine: _engine);
|
await initialize(
|
||||||
|
deviceVoice: _preferredVoice,
|
||||||
|
serverVoice: _serverPreferredVoice,
|
||||||
|
engine: _engine,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_engine == TtsEngine.server && _api != null) {
|
if (_engine == TtsEngine.server && _api != null) {
|
||||||
@@ -425,6 +521,10 @@ class TextToSpeechService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _resolveServerVoice() async {
|
Future<String?> _resolveServerVoice() async {
|
||||||
|
final serverSelected = _serverPreferredVoice?.trim();
|
||||||
|
if (serverSelected != null && serverSelected.isNotEmpty) {
|
||||||
|
return serverSelected;
|
||||||
|
}
|
||||||
final selected = _preferredVoice?.trim();
|
final selected = _preferredVoice?.trim();
|
||||||
if (selected != null && selected.isNotEmpty) {
|
if (selected != null && selected.isNotEmpty) {
|
||||||
return selected;
|
return selected;
|
||||||
|
|||||||
@@ -132,9 +132,8 @@ class VoiceCallService {
|
|||||||
// Initialize TTS with current app settings (engine/voice/rate/pitch/volume)
|
// Initialize TTS with current app settings (engine/voice/rate/pitch/volume)
|
||||||
final settings = _ref.read(appSettingsProvider);
|
final settings = _ref.read(appSettingsProvider);
|
||||||
await _tts.initialize(
|
await _tts.initialize(
|
||||||
voice: settings.ttsEngine == TtsEngine.server
|
deviceVoice: settings.ttsVoice,
|
||||||
? settings.ttsServerVoiceId
|
serverVoice: settings.ttsServerVoiceId,
|
||||||
: settings.ttsVoice,
|
|
||||||
speechRate: settings.ttsSpeechRate,
|
speechRate: settings.ttsSpeechRate,
|
||||||
pitch: settings.ttsPitch,
|
pitch: settings.ttsPitch,
|
||||||
volume: settings.ttsVolume,
|
volume: settings.ttsVolume,
|
||||||
@@ -587,11 +586,9 @@ VoiceCallService voiceCallService(Ref ref) {
|
|||||||
// Keep TTS settings in sync with app settings during a call
|
// Keep TTS settings in sync with app settings during a call
|
||||||
ref.listen<AppSettings>(appSettingsProvider, (previous, next) {
|
ref.listen<AppSettings>(appSettingsProvider, (previous, next) {
|
||||||
// Update voice/engine and runtime parameters
|
// Update voice/engine and runtime parameters
|
||||||
final selectedVoice = next.ttsEngine == TtsEngine.server
|
|
||||||
? next.ttsServerVoiceId
|
|
||||||
: next.ttsVoice;
|
|
||||||
service._tts.updateSettings(
|
service._tts.updateSettings(
|
||||||
voice: selectedVoice,
|
voice: next.ttsVoice,
|
||||||
|
serverVoice: next.ttsServerVoiceId,
|
||||||
speechRate: next.ttsSpeechRate,
|
speechRate: next.ttsSpeechRate,
|
||||||
pitch: next.ttsPitch,
|
pitch: next.ttsPitch,
|
||||||
volume: next.ttsVolume,
|
volume: next.ttsVolume,
|
||||||
|
|||||||
@@ -698,6 +698,35 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
) {
|
) {
|
||||||
final theme = context.conduitTheme;
|
final theme = context.conduitTheme;
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
final ttsService = ref.watch(textToSpeechServiceProvider);
|
||||||
|
final bool deviceAvailable =
|
||||||
|
ttsService.deviceEngineAvailable || !ttsService.isInitialized;
|
||||||
|
final bool serverAvailable = ttsService.serverEngineAvailable;
|
||||||
|
final bool autoSelectable = deviceAvailable || serverAvailable;
|
||||||
|
final bool deviceSelectable = deviceAvailable;
|
||||||
|
final bool serverSelectable = serverAvailable;
|
||||||
|
final ttsDescription = _ttsPreferenceDescription(l10n, settings);
|
||||||
|
final warnings = <String>[];
|
||||||
|
switch (settings.ttsEngine) {
|
||||||
|
case TtsEngine.auto:
|
||||||
|
if (!deviceAvailable) {
|
||||||
|
warnings.add(l10n.ttsDeviceUnavailableWarning);
|
||||||
|
}
|
||||||
|
if (!serverAvailable) {
|
||||||
|
warnings.add(l10n.ttsServerUnavailableWarning);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TtsEngine.device:
|
||||||
|
if (!deviceAvailable) {
|
||||||
|
warnings.add(l10n.ttsDeviceUnavailableWarning);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TtsEngine.server:
|
||||||
|
if (!serverAvailable) {
|
||||||
|
warnings.add(l10n.ttsServerUnavailableWarning);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -733,82 +762,160 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
) ??
|
) ??
|
||||||
TextStyle(color: theme.sidebarForeground, fontSize: 14),
|
TextStyle(color: theme.sidebarForeground, fontSize: 14),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
],
|
||||||
Wrap(
|
),
|
||||||
spacing: Spacing.sm,
|
const SizedBox(height: Spacing.sm),
|
||||||
children: [
|
Wrap(
|
||||||
ChoiceChip(
|
spacing: Spacing.sm,
|
||||||
label: Text(l10n.ttsEngineDevice),
|
runSpacing: Spacing.sm,
|
||||||
selected: settings.ttsEngine == TtsEngine.device,
|
children: [
|
||||||
showCheckmark: false,
|
ChoiceChip(
|
||||||
selectedColor: theme.buttonPrimary,
|
label: Text(l10n.ttsEngineAuto),
|
||||||
backgroundColor: theme.cardBackground,
|
selected: settings.ttsEngine == TtsEngine.auto,
|
||||||
side: BorderSide(
|
showCheckmark: false,
|
||||||
color: settings.ttsEngine == TtsEngine.device
|
selectedColor: theme.buttonPrimary,
|
||||||
? theme.buttonPrimary.withValues(alpha: 0.6)
|
backgroundColor: theme.cardBackground,
|
||||||
: theme.textPrimary.withValues(alpha: 0.2),
|
side: BorderSide(
|
||||||
),
|
color: settings.ttsEngine == TtsEngine.auto
|
||||||
labelStyle: TextStyle(
|
? theme.buttonPrimary.withValues(alpha: 0.6)
|
||||||
color: settings.ttsEngine == TtsEngine.device
|
: theme.textPrimary.withValues(
|
||||||
? theme.buttonPrimaryText
|
alpha: autoSelectable ? 0.2 : 0.12,
|
||||||
: theme.textPrimary,
|
),
|
||||||
fontWeight: FontWeight.w600,
|
),
|
||||||
),
|
labelStyle: TextStyle(
|
||||||
onSelected: (v) {
|
color: settings.ttsEngine == TtsEngine.auto
|
||||||
if (v) {
|
? theme.buttonPrimaryText
|
||||||
final notifier = ref.read(
|
: theme.textPrimary.withValues(
|
||||||
appSettingsProvider.notifier,
|
alpha: autoSelectable ? 1.0 : 0.45,
|
||||||
);
|
),
|
||||||
notifier.setTtsEngine(TtsEngine.device);
|
fontWeight: FontWeight.w600,
|
||||||
// Keep previous voice (device voices)
|
),
|
||||||
|
onSelected: autoSelectable
|
||||||
|
? (value) {
|
||||||
|
if (value) {
|
||||||
|
ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setTtsEngine(TtsEngine.auto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
: null,
|
||||||
),
|
),
|
||||||
ChoiceChip(
|
ChoiceChip(
|
||||||
label: Text(l10n.ttsEngineServer),
|
label: Text(l10n.ttsEngineDevice),
|
||||||
selected: settings.ttsEngine == TtsEngine.server,
|
selected: settings.ttsEngine == TtsEngine.device,
|
||||||
showCheckmark: false,
|
showCheckmark: false,
|
||||||
selectedColor: theme.buttonPrimary,
|
selectedColor: theme.buttonPrimary,
|
||||||
backgroundColor: theme.cardBackground,
|
backgroundColor: theme.cardBackground,
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: settings.ttsEngine == TtsEngine.server
|
color: settings.ttsEngine == TtsEngine.device
|
||||||
? theme.buttonPrimary.withValues(alpha: 0.6)
|
? theme.buttonPrimary.withValues(alpha: 0.6)
|
||||||
: theme.textPrimary.withValues(alpha: 0.2),
|
: theme.textPrimary.withValues(
|
||||||
),
|
alpha: deviceSelectable ? 0.2 : 0.12,
|
||||||
labelStyle: TextStyle(
|
),
|
||||||
color: settings.ttsEngine == TtsEngine.server
|
),
|
||||||
? theme.buttonPrimaryText
|
labelStyle: TextStyle(
|
||||||
: theme.textPrimary,
|
color: settings.ttsEngine == TtsEngine.device
|
||||||
fontWeight: FontWeight.w600,
|
? theme.buttonPrimaryText
|
||||||
),
|
: theme.textPrimary.withValues(
|
||||||
onSelected: (v) {
|
alpha: deviceSelectable ? 1.0 : 0.45,
|
||||||
if (v) {
|
),
|
||||||
final notifier = ref.read(
|
fontWeight: FontWeight.w600,
|
||||||
appSettingsProvider.notifier,
|
),
|
||||||
);
|
onSelected: deviceSelectable
|
||||||
// Clear device-specific voice so server can default
|
? (value) {
|
||||||
notifier.setTtsVoice(null);
|
if (value) {
|
||||||
notifier.setTtsEngine(TtsEngine.server);
|
ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setTtsEngine(TtsEngine.device);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
: null,
|
||||||
),
|
),
|
||||||
],
|
ChoiceChip(
|
||||||
|
label: Text(l10n.ttsEngineServer),
|
||||||
|
selected: settings.ttsEngine == TtsEngine.server,
|
||||||
|
showCheckmark: false,
|
||||||
|
selectedColor: theme.buttonPrimary,
|
||||||
|
backgroundColor: theme.cardBackground,
|
||||||
|
side: BorderSide(
|
||||||
|
color: settings.ttsEngine == TtsEngine.server
|
||||||
|
? theme.buttonPrimary.withValues(alpha: 0.6)
|
||||||
|
: theme.textPrimary.withValues(
|
||||||
|
alpha: serverSelectable ? 0.2 : 0.12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: settings.ttsEngine == TtsEngine.server
|
||||||
|
? theme.buttonPrimaryText
|
||||||
|
: theme.textPrimary.withValues(
|
||||||
|
alpha: serverSelectable ? 1.0 : 0.45,
|
||||||
|
),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
onSelected: serverSelectable
|
||||||
|
? (value) {
|
||||||
|
if (value) {
|
||||||
|
final notifier = ref.read(
|
||||||
|
appSettingsProvider.notifier,
|
||||||
|
);
|
||||||
|
notifier.setTtsVoice(null);
|
||||||
|
notifier.setTtsEngine(TtsEngine.server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: Spacing.sm),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Text(
|
||||||
|
ttsDescription,
|
||||||
|
key: ValueKey<String>(
|
||||||
|
'tts-desc-${settings.ttsEngine.name}',
|
||||||
|
),
|
||||||
|
style:
|
||||||
|
theme.bodyMedium?.copyWith(
|
||||||
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.9,
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
TextStyle(
|
||||||
|
color: theme.sidebarForeground.withValues(
|
||||||
|
alpha: 0.9,
|
||||||
|
),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (warnings.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: Spacing.sm),
|
||||||
|
...warnings.map(
|
||||||
|
(warning) => Padding(
|
||||||
|
padding: const EdgeInsets.only(top: Spacing.xs),
|
||||||
|
child: Text(
|
||||||
|
warning,
|
||||||
|
style:
|
||||||
|
theme.bodySmall?.copyWith(
|
||||||
|
color: theme.error,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
) ??
|
||||||
|
TextStyle(
|
||||||
|
color: theme.error,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.sm),
|
const SizedBox(height: Spacing.sm),
|
||||||
_ExpandableCard(
|
_ExpandableCard(
|
||||||
title: l10n.ttsVoice,
|
title: l10n.ttsVoice,
|
||||||
subtitle: _getDisplayVoiceName(
|
subtitle: _ttsVoiceSubtitle(l10n, settings),
|
||||||
settings.ttsEngine == TtsEngine.server
|
|
||||||
? ((settings.ttsServerVoiceName ?? settings.ttsServerVoiceId) ??
|
|
||||||
'')
|
|
||||||
: (settings.ttsVoice ?? ''),
|
|
||||||
l10n.ttsSystemDefault,
|
|
||||||
),
|
|
||||||
icon: UiUtils.platformIcon(
|
icon: UiUtils.platformIcon(
|
||||||
ios: CupertinoIcons.speaker_3,
|
ios: CupertinoIcons.speaker_3,
|
||||||
android: Icons.record_voice_over,
|
android: Icons.record_voice_over,
|
||||||
@@ -827,14 +934,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
color: theme.buttonPrimary,
|
color: theme.buttonPrimary,
|
||||||
),
|
),
|
||||||
title: l10n.ttsVoice,
|
title: l10n.ttsVoice,
|
||||||
subtitle: _getDisplayVoiceName(
|
subtitle: _ttsVoiceSubtitle(l10n, settings),
|
||||||
settings.ttsEngine == TtsEngine.server
|
|
||||||
? ((settings.ttsServerVoiceName ??
|
|
||||||
settings.ttsServerVoiceId) ??
|
|
||||||
'')
|
|
||||||
: (settings.ttsVoice ?? ''),
|
|
||||||
l10n.ttsSystemDefault,
|
|
||||||
),
|
|
||||||
onTap: () => _showVoicePickerSheet(context, ref, settings),
|
onTap: () => _showVoicePickerSheet(context, ref, settings),
|
||||||
),
|
),
|
||||||
const SizedBox(height: Spacing.md),
|
const SizedBox(height: Spacing.md),
|
||||||
@@ -928,6 +1028,39 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _ttsPreferenceDescription(
|
||||||
|
AppLocalizations l10n,
|
||||||
|
AppSettings settings,
|
||||||
|
) {
|
||||||
|
switch (settings.ttsEngine) {
|
||||||
|
case TtsEngine.auto:
|
||||||
|
return l10n.ttsEngineAutoDescription;
|
||||||
|
case TtsEngine.device:
|
||||||
|
return l10n.ttsEngineDeviceDescription;
|
||||||
|
case TtsEngine.server:
|
||||||
|
return l10n.ttsEngineServerDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _ttsVoiceSubtitle(AppLocalizations l10n, AppSettings settings) {
|
||||||
|
final deviceName = _getDisplayVoiceName(
|
||||||
|
settings.ttsVoice,
|
||||||
|
l10n.ttsSystemDefault,
|
||||||
|
);
|
||||||
|
final serverVoice =
|
||||||
|
(settings.ttsServerVoiceName ?? settings.ttsServerVoiceId) ?? '';
|
||||||
|
final serverName = _getDisplayVoiceName(serverVoice, l10n.ttsSystemDefault);
|
||||||
|
|
||||||
|
switch (settings.ttsEngine) {
|
||||||
|
case TtsEngine.auto:
|
||||||
|
return '${l10n.ttsEngineDevice}: $deviceName • ${l10n.ttsEngineServer}: $serverName';
|
||||||
|
case TtsEngine.device:
|
||||||
|
return deviceName;
|
||||||
|
case TtsEngine.server:
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSliderTile(
|
Widget _buildSliderTile(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
WidgetRef ref, {
|
WidgetRef ref, {
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "Server",
|
"sttEngineServer": "Server",
|
||||||
"sttEngineAutoDescription": "Verwendet die Erkennung auf dem Gerät, wenn verfügbar, und greift sonst auf deinen Server zurück.",
|
"sttEngineAutoDescription": "Verwendet die Erkennung auf dem Gerät, wenn verfügbar, und greift sonst auf deinen Server zurück.",
|
||||||
"sttEngineDeviceDescription": "Behält Audio auf diesem Gerät. Spracheingabe funktioniert nicht, wenn das Gerät keine Spracherkennung unterstützt.",
|
"sttEngineDeviceDescription": "Behält Audio auf diesem Gerät. Spracheingabe funktioniert nicht, wenn das Gerät keine Spracherkennung unterstützt.",
|
||||||
"sttEngineServerDescription": "Sendet Aufnahmen immer an deinen Conduit-Server zur Transkription.",
|
"sttEngineServerDescription": "Sendet Aufnahmen immer an deinen OpenWebUI-Server zur Transkription.",
|
||||||
"sttDeviceUnavailableWarning": "Auf diesem Gerät steht keine Spracherkennung zur Verfügung.",
|
"sttDeviceUnavailableWarning": "Auf diesem Gerät steht keine Spracherkennung zur Verfügung.",
|
||||||
"sttServerUnavailableWarning": "Verbinde dich mit einem Server mit aktivierter Transkription, um diese Option zu nutzen.",
|
"sttServerUnavailableWarning": "Verbinde dich mit einem Server mit aktivierter Transkription, um diese Option zu nutzen.",
|
||||||
"ttsSettings": "Text zu Sprache",
|
"ttsSettings": "Text zu Sprache",
|
||||||
|
"ttsEngineAuto": "Automatisch",
|
||||||
|
"ttsEngineAutoDescription": "Verwendet die Sprachausgabe auf dem Gerät, wenn verfügbar, und greift sonst auf deinen Server zurück.",
|
||||||
|
"ttsEngineDeviceDescription": "Behält die Ausgabe auf diesem Gerät. Sprachausgabe funktioniert nicht, wenn das Gerät keine TTS-Unterstützung bietet.",
|
||||||
|
"ttsEngineServerDescription": "Sendet die Ausgabe immer an deinen OpenWebUI-Server.",
|
||||||
|
"ttsDeviceUnavailableWarning": "Sprachausgabe auf dem Gerät steht auf diesem Gerät nicht zur Verfügung.",
|
||||||
|
"ttsServerUnavailableWarning": "Verbinde dich mit einem Server mit aktivierter Sprachausgabe, um diese Option zu nutzen.",
|
||||||
"ttsVoice": "Stimme",
|
"ttsVoice": "Stimme",
|
||||||
"ttsSpeechRate": "Sprechgeschwindigkeit",
|
"ttsSpeechRate": "Sprechgeschwindigkeit",
|
||||||
"ttsPitch": "Tonhöhe",
|
"ttsPitch": "Tonhöhe",
|
||||||
|
|||||||
@@ -1247,7 +1247,7 @@
|
|||||||
"@sttEngineDeviceDescription": {
|
"@sttEngineDeviceDescription": {
|
||||||
"description": "Description shown when on-device speech-to-text preference is active."
|
"description": "Description shown when on-device speech-to-text preference is active."
|
||||||
},
|
},
|
||||||
"sttEngineServerDescription": "Always send recordings to your Conduit server for transcription.",
|
"sttEngineServerDescription": "Always send recordings to your OpenWebUI server for transcription.",
|
||||||
"@sttEngineServerDescription": {
|
"@sttEngineServerDescription": {
|
||||||
"description": "Description shown when server speech-to-text preference is active."
|
"description": "Description shown when server speech-to-text preference is active."
|
||||||
},
|
},
|
||||||
@@ -1263,6 +1263,10 @@
|
|||||||
"@ttsEngineLabel": {
|
"@ttsEngineLabel": {
|
||||||
"description": "Label for selecting the text-to-speech engine."
|
"description": "Label for selecting the text-to-speech engine."
|
||||||
},
|
},
|
||||||
|
"ttsEngineAuto": "Auto",
|
||||||
|
"@ttsEngineAuto": {
|
||||||
|
"description": "Chip label for automatically selecting the text-to-speech engine."
|
||||||
|
},
|
||||||
"ttsEngineDevice": "On device",
|
"ttsEngineDevice": "On device",
|
||||||
"@ttsEngineDevice": {
|
"@ttsEngineDevice": {
|
||||||
"description": "Chip label for using on-device text-to-speech."
|
"description": "Chip label for using on-device text-to-speech."
|
||||||
@@ -1271,6 +1275,26 @@
|
|||||||
"@ttsEngineServer": {
|
"@ttsEngineServer": {
|
||||||
"description": "Chip label for using server-side text-to-speech."
|
"description": "Chip label for using server-side text-to-speech."
|
||||||
},
|
},
|
||||||
|
"ttsEngineAutoDescription": "Use on-device speech when available and fall back to your server.",
|
||||||
|
"@ttsEngineAutoDescription": {
|
||||||
|
"description": "Description shown when automatic text-to-speech preference is active."
|
||||||
|
},
|
||||||
|
"ttsEngineDeviceDescription": "Keep synthesis on this device. Voice playback stops working if on-device TTS isn’t supported.",
|
||||||
|
"@ttsEngineDeviceDescription": {
|
||||||
|
"description": "Description shown when on-device text-to-speech preference is active."
|
||||||
|
},
|
||||||
|
"ttsEngineServerDescription": "Always request audio from your OpenWebUI server.",
|
||||||
|
"@ttsEngineServerDescription": {
|
||||||
|
"description": "Description shown when server text-to-speech preference is active."
|
||||||
|
},
|
||||||
|
"ttsDeviceUnavailableWarning": "On-device text-to-speech isn’t available on this device.",
|
||||||
|
"@ttsDeviceUnavailableWarning": {
|
||||||
|
"description": "Warning shown when on-device text-to-speech is unavailable."
|
||||||
|
},
|
||||||
|
"ttsServerUnavailableWarning": "Connect to a server with text-to-speech enabled to use this option.",
|
||||||
|
"@ttsServerUnavailableWarning": {
|
||||||
|
"description": "Warning shown when server text-to-speech is unavailable."
|
||||||
|
},
|
||||||
"ttsSettings": "Text to Speech",
|
"ttsSettings": "Text to Speech",
|
||||||
"@ttsSettings": {
|
"@ttsSettings": {
|
||||||
"description": "Section header for TTS-related customization options."
|
"description": "Section header for TTS-related customization options."
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "Servidor",
|
"sttEngineServer": "Servidor",
|
||||||
"sttEngineAutoDescription": "Usa el reconocimiento en el dispositivo cuando esté disponible y, si no, recurre a tu servidor.",
|
"sttEngineAutoDescription": "Usa el reconocimiento en el dispositivo cuando esté disponible y, si no, recurre a tu servidor.",
|
||||||
"sttEngineDeviceDescription": "Mantiene el audio en este dispositivo. La entrada de voz no funciona si el dispositivo no admite reconocimiento de voz.",
|
"sttEngineDeviceDescription": "Mantiene el audio en este dispositivo. La entrada de voz no funciona si el dispositivo no admite reconocimiento de voz.",
|
||||||
"sttEngineServerDescription": "Envía siempre las grabaciones a tu servidor Conduit para la transcripción.",
|
"sttEngineServerDescription": "Envía siempre las grabaciones a tu servidor OpenWebUI para la transcripción.",
|
||||||
"sttDeviceUnavailableWarning": "El reconocimiento de voz en el dispositivo no está disponible en este dispositivo.",
|
"sttDeviceUnavailableWarning": "El reconocimiento de voz en el dispositivo no está disponible en este dispositivo.",
|
||||||
"sttServerUnavailableWarning": "Conéctate a un servidor con transcripción habilitada para usar esta opción.",
|
"sttServerUnavailableWarning": "Conéctate a un servidor con transcripción habilitada para usar esta opción.",
|
||||||
"ttsSettings": "Texto a voz",
|
"ttsSettings": "Texto a voz",
|
||||||
|
"ttsEngineAuto": "Automático",
|
||||||
|
"ttsEngineAutoDescription": "Usa la síntesis en el dispositivo cuando esté disponible y, si no, recurre a tu servidor.",
|
||||||
|
"ttsEngineDeviceDescription": "Mantiene la síntesis en este dispositivo. La reproducción de voz no funciona si el dispositivo no admite TTS.",
|
||||||
|
"ttsEngineServerDescription": "Solicita siempre el audio a tu servidor OpenWebUI.",
|
||||||
|
"ttsDeviceUnavailableWarning": "La síntesis de voz en el dispositivo no está disponible en este dispositivo.",
|
||||||
|
"ttsServerUnavailableWarning": "Conéctate a un servidor con texto a voz habilitado para usar esta opción.",
|
||||||
"ttsVoice": "Voz",
|
"ttsVoice": "Voz",
|
||||||
"ttsSpeechRate": "Velocidad de voz",
|
"ttsSpeechRate": "Velocidad de voz",
|
||||||
"ttsPitch": "Tono",
|
"ttsPitch": "Tono",
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "Serveur",
|
"sttEngineServer": "Serveur",
|
||||||
"sttEngineAutoDescription": "Utilise la reconnaissance sur l’appareil quand c’est possible, sinon bascule vers votre serveur.",
|
"sttEngineAutoDescription": "Utilise la reconnaissance sur l’appareil quand c’est possible, sinon bascule vers votre serveur.",
|
||||||
"sttEngineDeviceDescription": "Conserve l’audio sur cet appareil. L’entrée vocale cesse de fonctionner si la reconnaissance vocale n’est pas prise en charge.",
|
"sttEngineDeviceDescription": "Conserve l’audio sur cet appareil. L’entrée vocale cesse de fonctionner si la reconnaissance vocale n’est pas prise en charge.",
|
||||||
"sttEngineServerDescription": "Envoie toujours les enregistrements à votre serveur Conduit pour transcription.",
|
"sttEngineServerDescription": "Envoie toujours les enregistrements à votre serveur OpenWebUI pour transcription.",
|
||||||
"sttDeviceUnavailableWarning": "La reconnaissance vocale sur l’appareil n’est pas disponible sur cet appareil.",
|
"sttDeviceUnavailableWarning": "La reconnaissance vocale sur l’appareil n’est pas disponible sur cet appareil.",
|
||||||
"sttServerUnavailableWarning": "Connectez-vous à un serveur avec la transcription activée pour utiliser cette option.",
|
"sttServerUnavailableWarning": "Connectez-vous à un serveur avec la transcription activée pour utiliser cette option.",
|
||||||
"ttsSettings": "Synthèse vocale",
|
"ttsSettings": "Synthèse vocale",
|
||||||
|
"ttsEngineAuto": "Auto",
|
||||||
|
"ttsEngineAutoDescription": "Utilise la synthèse locale quand c’est possible, sinon bascule vers votre serveur.",
|
||||||
|
"ttsEngineDeviceDescription": "Garde la synthèse sur cet appareil. La lecture vocale ne fonctionne plus si l’appareil n’offre pas la synthèse vocale.",
|
||||||
|
"ttsEngineServerDescription": "Demande toujours l'audio à votre serveur OpenWebUI.",
|
||||||
|
"ttsDeviceUnavailableWarning": "La synthèse vocale sur l’appareil n’est pas disponible sur cet appareil.",
|
||||||
|
"ttsServerUnavailableWarning": "Connectez-vous à un serveur avec la synthèse vocale activée pour utiliser cette option.",
|
||||||
"ttsVoice": "Voix",
|
"ttsVoice": "Voix",
|
||||||
"ttsSpeechRate": "Vitesse de parole",
|
"ttsSpeechRate": "Vitesse de parole",
|
||||||
"ttsPitch": "Hauteur",
|
"ttsPitch": "Hauteur",
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "Server",
|
"sttEngineServer": "Server",
|
||||||
"sttEngineAutoDescription": "Usa il riconoscimento sul dispositivo quando disponibile e altrimenti passa al tuo server.",
|
"sttEngineAutoDescription": "Usa il riconoscimento sul dispositivo quando disponibile e altrimenti passa al tuo server.",
|
||||||
"sttEngineDeviceDescription": "Mantiene l’audio su questo dispositivo. L’input vocale non funziona se il dispositivo non supporta il riconoscimento vocale.",
|
"sttEngineDeviceDescription": "Mantiene l’audio su questo dispositivo. L’input vocale non funziona se il dispositivo non supporta il riconoscimento vocale.",
|
||||||
"sttEngineServerDescription": "Invia sempre le registrazioni al tuo server Conduit per la trascrizione.",
|
"sttEngineServerDescription": "Invia sempre le registrazioni al tuo server OpenWebUI per la trascrizione.",
|
||||||
"sttDeviceUnavailableWarning": "Il riconoscimento vocale sul dispositivo non è disponibile su questo dispositivo.",
|
"sttDeviceUnavailableWarning": "Il riconoscimento vocale sul dispositivo non è disponibile su questo dispositivo.",
|
||||||
"sttServerUnavailableWarning": "Collegati a un server con la trascrizione abilitata per usare questa opzione.",
|
"sttServerUnavailableWarning": "Collegati a un server con la trascrizione abilitata per usare questa opzione.",
|
||||||
"ttsSettings": "Sintesi vocale",
|
"ttsSettings": "Sintesi vocale",
|
||||||
|
"ttsEngineAuto": "Automatico",
|
||||||
|
"ttsEngineAutoDescription": "Usa la sintesi sul dispositivo quando disponibile e altrimenti passa al tuo server.",
|
||||||
|
"ttsEngineDeviceDescription": "Mantiene la sintesi su questo dispositivo. La riproduzione vocale non funziona se il dispositivo non supporta il TTS.",
|
||||||
|
"ttsEngineServerDescription": "Richiede sempre l'audio dal tuo server OpenWebUI.",
|
||||||
|
"ttsDeviceUnavailableWarning": "La sintesi vocale sul dispositivo non è disponibile su questo dispositivo.",
|
||||||
|
"ttsServerUnavailableWarning": "Collegati a un server con la sintesi vocale abilitata per usare questa opzione.",
|
||||||
"ttsVoice": "Voce",
|
"ttsVoice": "Voce",
|
||||||
"ttsSpeechRate": "Velocità di sintesi vocale",
|
"ttsSpeechRate": "Velocità di sintesi vocale",
|
||||||
"ttsPitch": "Tonalità",
|
"ttsPitch": "Tonalità",
|
||||||
|
|||||||
@@ -1829,7 +1829,7 @@ abstract class AppLocalizations {
|
|||||||
/// Description shown when server speech-to-text preference is active.
|
/// Description shown when server speech-to-text preference is active.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Always send recordings to your Conduit server for transcription.'**
|
/// **'Always send recordings to your OpenWebUI server for transcription.'**
|
||||||
String get sttEngineServerDescription;
|
String get sttEngineServerDescription;
|
||||||
|
|
||||||
/// Warning shown when the user selects on-device speech recognition but it is unavailable.
|
/// Warning shown when the user selects on-device speech recognition but it is unavailable.
|
||||||
@@ -1850,6 +1850,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Engine'**
|
/// **'Engine'**
|
||||||
String get ttsEngineLabel;
|
String get ttsEngineLabel;
|
||||||
|
|
||||||
|
/// Chip label for automatically selecting the text-to-speech engine.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Auto'**
|
||||||
|
String get ttsEngineAuto;
|
||||||
|
|
||||||
/// Chip label for using on-device text-to-speech.
|
/// Chip label for using on-device text-to-speech.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1862,6 +1868,36 @@ abstract class AppLocalizations {
|
|||||||
/// **'Server'**
|
/// **'Server'**
|
||||||
String get ttsEngineServer;
|
String get ttsEngineServer;
|
||||||
|
|
||||||
|
/// Description shown when automatic text-to-speech preference is active.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Use on-device speech when available and fall back to your server.'**
|
||||||
|
String get ttsEngineAutoDescription;
|
||||||
|
|
||||||
|
/// Description shown when on-device text-to-speech preference is active.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Keep synthesis on this device. Voice playback stops working if on-device TTS isn’t supported.'**
|
||||||
|
String get ttsEngineDeviceDescription;
|
||||||
|
|
||||||
|
/// Description shown when server text-to-speech preference is active.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Always request audio from your OpenWebUI server.'**
|
||||||
|
String get ttsEngineServerDescription;
|
||||||
|
|
||||||
|
/// Warning shown when on-device text-to-speech is unavailable.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'On-device text-to-speech isn’t available on this device.'**
|
||||||
|
String get ttsDeviceUnavailableWarning;
|
||||||
|
|
||||||
|
/// Warning shown when server text-to-speech is unavailable.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Connect to a server with text-to-speech enabled to use this option.'**
|
||||||
|
String get ttsServerUnavailableWarning;
|
||||||
|
|
||||||
/// Section header for TTS-related customization options.
|
/// Section header for TTS-related customization options.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -956,7 +956,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttEngineServerDescription =>
|
String get sttEngineServerDescription =>
|
||||||
'Sendet Aufnahmen immer an deinen Conduit-Server zur Transkription.';
|
'Sendet Aufnahmen immer an deinen OpenWebUI-Server zur Transkription.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttDeviceUnavailableWarning =>
|
String get sttDeviceUnavailableWarning =>
|
||||||
@@ -969,12 +969,35 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get ttsEngineLabel => 'Engine';
|
String get ttsEngineLabel => 'Engine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAuto => 'Automatisch';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineDevice => 'Auf dem Gerät';
|
String get ttsEngineDevice => 'Auf dem Gerät';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineServer => 'Server';
|
String get ttsEngineServer => 'Server';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAutoDescription =>
|
||||||
|
'Verwendet die Sprachausgabe auf dem Gerät, wenn verfügbar, und greift sonst auf deinen Server zurück.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineDeviceDescription =>
|
||||||
|
'Behält die Ausgabe auf diesem Gerät. Sprachausgabe funktioniert nicht, wenn das Gerät keine TTS-Unterstützung bietet.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineServerDescription =>
|
||||||
|
'Sendet die Ausgabe immer an deinen OpenWebUI-Server.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsDeviceUnavailableWarning =>
|
||||||
|
'Sprachausgabe auf dem Gerät steht auf diesem Gerät nicht zur Verfügung.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsServerUnavailableWarning =>
|
||||||
|
'Verbinde dich mit einem Server mit aktivierter Sprachausgabe, um diese Option zu nutzen.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsSettings => 'Text zu Sprache';
|
String get ttsSettings => 'Text zu Sprache';
|
||||||
|
|
||||||
|
|||||||
@@ -950,7 +950,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttEngineServerDescription =>
|
String get sttEngineServerDescription =>
|
||||||
'Always send recordings to your Conduit server for transcription.';
|
'Always send recordings to your OpenWebUI server for transcription.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttDeviceUnavailableWarning =>
|
String get sttDeviceUnavailableWarning =>
|
||||||
@@ -963,12 +963,35 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get ttsEngineLabel => 'Engine';
|
String get ttsEngineLabel => 'Engine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAuto => 'Auto';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineDevice => 'On device';
|
String get ttsEngineDevice => 'On device';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineServer => 'Server';
|
String get ttsEngineServer => 'Server';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAutoDescription =>
|
||||||
|
'Use on-device speech when available and fall back to your server.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineDeviceDescription =>
|
||||||
|
'Keep synthesis on this device. Voice playback stops working if on-device TTS isn’t supported.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineServerDescription =>
|
||||||
|
'Always request audio from your OpenWebUI server.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsDeviceUnavailableWarning =>
|
||||||
|
'On-device text-to-speech isn’t available on this device.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsServerUnavailableWarning =>
|
||||||
|
'Connect to a server with text-to-speech enabled to use this option.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsSettings => 'Text to Speech';
|
String get ttsSettings => 'Text to Speech';
|
||||||
|
|
||||||
|
|||||||
@@ -965,7 +965,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttEngineServerDescription =>
|
String get sttEngineServerDescription =>
|
||||||
'Envoie toujours les enregistrements à votre serveur Conduit pour transcription.';
|
'Envoie toujours les enregistrements à votre serveur OpenWebUI pour transcription.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttDeviceUnavailableWarning =>
|
String get sttDeviceUnavailableWarning =>
|
||||||
@@ -978,12 +978,35 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get ttsEngineLabel => 'Moteur';
|
String get ttsEngineLabel => 'Moteur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAuto => 'Auto';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineDevice => 'Sur l\'appareil';
|
String get ttsEngineDevice => 'Sur l\'appareil';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineServer => 'Serveur';
|
String get ttsEngineServer => 'Serveur';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAutoDescription =>
|
||||||
|
'Utilise la synthèse locale quand c’est possible, sinon bascule vers votre serveur.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineDeviceDescription =>
|
||||||
|
'Garde la synthèse sur cet appareil. La lecture vocale ne fonctionne plus si l’appareil n’offre pas la synthèse vocale.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineServerDescription =>
|
||||||
|
'Demande toujours l\'audio à votre serveur OpenWebUI.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsDeviceUnavailableWarning =>
|
||||||
|
'La synthèse vocale sur l’appareil n’est pas disponible sur cet appareil.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsServerUnavailableWarning =>
|
||||||
|
'Connectez-vous à un serveur avec la synthèse vocale activée pour utiliser cette option.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsSettings => 'Synthèse vocale';
|
String get ttsSettings => 'Synthèse vocale';
|
||||||
|
|
||||||
|
|||||||
@@ -954,7 +954,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttEngineServerDescription =>
|
String get sttEngineServerDescription =>
|
||||||
'Invia sempre le registrazioni al tuo server Conduit per la trascrizione.';
|
'Invia sempre le registrazioni al tuo server OpenWebUI per la trascrizione.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sttDeviceUnavailableWarning =>
|
String get sttDeviceUnavailableWarning =>
|
||||||
@@ -967,12 +967,35 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get ttsEngineLabel => 'Motore';
|
String get ttsEngineLabel => 'Motore';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAuto => 'Automatico';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineDevice => 'Sul dispositivo';
|
String get ttsEngineDevice => 'Sul dispositivo';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsEngineServer => 'Server';
|
String get ttsEngineServer => 'Server';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineAutoDescription =>
|
||||||
|
'Usa la sintesi sul dispositivo quando disponibile e altrimenti passa al tuo server.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineDeviceDescription =>
|
||||||
|
'Mantiene la sintesi su questo dispositivo. La riproduzione vocale non funziona se il dispositivo non supporta il TTS.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsEngineServerDescription =>
|
||||||
|
'Richiede sempre l\'audio dal tuo server OpenWebUI.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsDeviceUnavailableWarning =>
|
||||||
|
'La sintesi vocale sul dispositivo non è disponibile su questo dispositivo.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ttsServerUnavailableWarning =>
|
||||||
|
'Collegati a un server con la sintesi vocale abilitata per usare questa opzione.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ttsSettings => 'Sintesi vocale';
|
String get ttsSettings => 'Sintesi vocale';
|
||||||
|
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "Server",
|
"sttEngineServer": "Server",
|
||||||
"sttEngineAutoDescription": "Gebruikt spraakherkenning op het apparaat wanneer beschikbaar en valt anders terug op je server.",
|
"sttEngineAutoDescription": "Gebruikt spraakherkenning op het apparaat wanneer beschikbaar en valt anders terug op je server.",
|
||||||
"sttEngineDeviceDescription": "Houdt audio op dit apparaat. Spraakinput werkt niet als het apparaat geen spraakherkenning ondersteunt.",
|
"sttEngineDeviceDescription": "Houdt audio op dit apparaat. Spraakinput werkt niet als het apparaat geen spraakherkenning ondersteunt.",
|
||||||
"sttEngineServerDescription": "Stuurt opnames altijd naar je Conduit-server voor transcriptie.",
|
"sttEngineServerDescription": "Stuurt opnames altijd naar je OpenWebUI-server voor transcriptie.",
|
||||||
"sttDeviceUnavailableWarning": "Spraakherkenning op het apparaat is niet beschikbaar op dit apparaat.",
|
"sttDeviceUnavailableWarning": "Spraakherkenning op het apparaat is niet beschikbaar op dit apparaat.",
|
||||||
"sttServerUnavailableWarning": "Verbind met een server met transcriptie ingeschakeld om deze optie te gebruiken.",
|
"sttServerUnavailableWarning": "Verbind met een server met transcriptie ingeschakeld om deze optie te gebruiken.",
|
||||||
"ttsSettings": "Tekst naar spraak",
|
"ttsSettings": "Tekst naar spraak",
|
||||||
|
"ttsEngineAuto": "Automatisch",
|
||||||
|
"ttsEngineAutoDescription": "Gebruikt spraaksynthese op het apparaat wanneer beschikbaar en valt anders terug op je server.",
|
||||||
|
"ttsEngineDeviceDescription": "Houdt de synthese op dit apparaat. Spraakweergave werkt niet als het apparaat geen TTS ondersteunt.",
|
||||||
|
"ttsEngineServerDescription": "Vraagt altijd audio op bij je OpenWebUI-server.",
|
||||||
|
"ttsDeviceUnavailableWarning": "Spraaksynthese op het apparaat is niet beschikbaar op dit apparaat.",
|
||||||
|
"ttsServerUnavailableWarning": "Verbind met een server met tekst-naar-spraak ingeschakeld om deze optie te gebruiken.",
|
||||||
"ttsVoice": "Stem",
|
"ttsVoice": "Stem",
|
||||||
"ttsSpeechRate": "Spraaksnelheid",
|
"ttsSpeechRate": "Spraaksnelheid",
|
||||||
"ttsPitch": "Toonhoogte",
|
"ttsPitch": "Toonhoogte",
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "Сервер",
|
"sttEngineServer": "Сервер",
|
||||||
"sttEngineAutoDescription": "Использует распознавание на устройстве, когда это возможно, иначе переключается на ваш сервер.",
|
"sttEngineAutoDescription": "Использует распознавание на устройстве, когда это возможно, иначе переключается на ваш сервер.",
|
||||||
"sttEngineDeviceDescription": "Оставляет звук на этом устройстве. Голосовой ввод не работает, если устройство не поддерживает распознавание речи.",
|
"sttEngineDeviceDescription": "Оставляет звук на этом устройстве. Голосовой ввод не работает, если устройство не поддерживает распознавание речи.",
|
||||||
"sttEngineServerDescription": "Всегда отправляет записи на сервер Conduit для транскрибации.",
|
"sttEngineServerDescription": "Всегда отправляет записи на сервер OpenWebUI для транскрибации.",
|
||||||
"sttDeviceUnavailableWarning": "Распознавание речи на устройстве недоступно на этом устройстве.",
|
"sttDeviceUnavailableWarning": "Распознавание речи на устройстве недоступно на этом устройстве.",
|
||||||
"sttServerUnavailableWarning": "Подключитесь к серверу с включённой транскрибацией, чтобы использовать эту опцию.",
|
"sttServerUnavailableWarning": "Подключитесь к серверу с включённой транскрибацией, чтобы использовать эту опцию.",
|
||||||
"ttsSettings": "Преобразование текста в речь",
|
"ttsSettings": "Преобразование текста в речь",
|
||||||
|
"ttsEngineAuto": "Авто",
|
||||||
|
"ttsEngineAutoDescription": "Использует синтез речи на устройстве, когда это возможно, иначе переключается на ваш сервер.",
|
||||||
|
"ttsEngineDeviceDescription": "Оставляет синтез на этом устройстве. Воспроизведение голоса не работает, если устройство не поддерживает синтез речи.",
|
||||||
|
"ttsEngineServerDescription": "Всегда запрашивает аудио у вашего сервера OpenWebUI.",
|
||||||
|
"ttsDeviceUnavailableWarning": "Синтез речи на устройстве недоступен на этом устройстве.",
|
||||||
|
"ttsServerUnavailableWarning": "Подключитесь к серверу с включённым синтезом речи, чтобы использовать эту опцию.",
|
||||||
"ttsVoice": "Голос",
|
"ttsVoice": "Голос",
|
||||||
"ttsSpeechRate": "Скорость речи",
|
"ttsSpeechRate": "Скорость речи",
|
||||||
"ttsPitch": "Высота тона",
|
"ttsPitch": "Высота тона",
|
||||||
|
|||||||
@@ -314,10 +314,16 @@
|
|||||||
"sttEngineServer": "服务器",
|
"sttEngineServer": "服务器",
|
||||||
"sttEngineAutoDescription": "在可用时使用本机识别,否则切换到你的服务器。",
|
"sttEngineAutoDescription": "在可用时使用本机识别,否则切换到你的服务器。",
|
||||||
"sttEngineDeviceDescription": "音频会保留在此设备上。如果设备不支持语音识别,语音输入将不可用。",
|
"sttEngineDeviceDescription": "音频会保留在此设备上。如果设备不支持语音识别,语音输入将不可用。",
|
||||||
"sttEngineServerDescription": "始终将录音发送到你的 Conduit 服务器进行转写。",
|
"sttEngineServerDescription": "始终将录音发送到你的 OpenWebUI 服务器进行转写。",
|
||||||
"sttDeviceUnavailableWarning": "此设备不支持本机语音识别。",
|
"sttDeviceUnavailableWarning": "此设备不支持本机语音识别。",
|
||||||
"sttServerUnavailableWarning": "连接到启用转写功能的服务器后才能使用此选项。",
|
"sttServerUnavailableWarning": "连接到启用转写功能的服务器后才能使用此选项。",
|
||||||
"ttsSettings": "文本转语音",
|
"ttsSettings": "文本转语音",
|
||||||
|
"ttsEngineAuto": "自动",
|
||||||
|
"ttsEngineAutoDescription": "在可用时使用本机合成,否则切换到你的服务器。",
|
||||||
|
"ttsEngineDeviceDescription": "在此设备上完成合成。如果设备不支持文本转语音,语音播放将不可用。",
|
||||||
|
"ttsEngineServerDescription": "始终向你的 OpenWebUI 服务器请求音频。",
|
||||||
|
"ttsDeviceUnavailableWarning": "此设备不支持本机文本转语音。",
|
||||||
|
"ttsServerUnavailableWarning": "连接到启用文本转语音的服务器后才能使用此选项。",
|
||||||
"ttsVoice": "语音",
|
"ttsVoice": "语音",
|
||||||
"ttsSpeechRate": "语速",
|
"ttsSpeechRate": "语速",
|
||||||
"ttsPitch": "音调",
|
"ttsPitch": "音调",
|
||||||
|
|||||||
Reference in New Issue
Block a user