feat: enhance localization support with additional strings and improved structure

This commit is contained in:
cogwheel0
2025-08-24 20:27:11 +05:30
parent 25201cbcfc
commit cc46799e20
15 changed files with 1150 additions and 365 deletions

View File

@@ -4,6 +4,13 @@
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>de</string>
<string>fr</string>
<string>it</string>
</array>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Conduit</string> <string>Conduit</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>

View File

@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
import '../../shared/theme/theme_extensions.dart'; import '../../shared/theme/theme_extensions.dart';
/// User-friendly error messages and recovery actions /// User-friendly error messages and recovery actions
@@ -438,7 +439,7 @@ class ErrorCard extends StatelessWidget {
if (showDetails && technicalDetails != null) ...[ if (showDetails && technicalDetails != null) ...[
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
ExpansionTile( ExpansionTile(
title: const Text('Technical Details'), title: Text(AppLocalizations.of(context)!.technicalDetails),
children: [ children: [
Container( Container(
width: double.infinity, width: double.infinity,
@@ -517,7 +518,7 @@ class ErrorDialog extends StatelessWidget {
children: [ children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error), Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
const SizedBox(width: Spacing.sm + Spacing.xs), const SizedBox(width: Spacing.sm + Spacing.xs),
const Text('Error'), Text(AppLocalizations.of(context)!.errorMessage),
], ],
), ),
content: Column( content: Column(
@@ -528,7 +529,7 @@ class ErrorDialog extends StatelessWidget {
if (showDetails && technicalDetails != null) ...[ if (showDetails && technicalDetails != null) ...[
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
ExpansionTile( ExpansionTile(
title: const Text('Technical Details'), title: Text(AppLocalizations.of(context)!.technicalDetails),
children: [ children: [
SelectableText( SelectableText(
technicalDetails!, technicalDetails!,
@@ -545,7 +546,7 @@ class ErrorDialog extends StatelessWidget {
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'), child: Text(AppLocalizations.of(context)!.cancel),
), ),
if (actions.isNotEmpty) if (actions.isNotEmpty)
ElevatedButton( ElevatedButton(

View File

@@ -243,7 +243,9 @@ class AsyncErrorBoundary extends ConsumerWidget {
(context as Element).markNeedsBuild(); (context as Element).markNeedsBuild();
}, },
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
label: Text(AppLocalizations.of(context)!.retry), label: Text(
AppLocalizations.of(context)?.retry ?? 'Retry',
),
), ),
], ],
], ],

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
import '../../../core/widgets/error_boundary.dart'; import '../../../core/widgets/error_boundary.dart';
import '../../../shared/widgets/optimized_list.dart'; import '../../../shared/widgets/optimized_list.dart';
import '../../../shared/theme/theme_extensions.dart'; import '../../../shared/theme/theme_extensions.dart';
@@ -805,7 +806,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
style: TextStyle(color: context.conduitTheme.textPrimary), style: TextStyle(color: context.conduitTheme.textPrimary),
maxLines: null, maxLines: null,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Enter your message', hintText: AppLocalizations.of(context)!.messageHintText,
hintStyle: TextStyle(color: context.conduitTheme.inputPlaceholder), hintStyle: TextStyle(color: context.conduitTheme.inputPlaceholder),
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: BorderSide(color: context.conduitTheme.inputBorder), borderSide: BorderSide(color: context.conduitTheme.inputBorder),
@@ -831,7 +832,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: context.conduitTheme.buttonPrimary, foregroundColor: context.conduitTheme.buttonPrimary,
), ),
child: const Text('Save'), child: Text(AppLocalizations.of(context)!.save),
), ),
], ],
), ),
@@ -911,7 +912,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
const SizedBox(height: Spacing.xl), const SizedBox(height: Spacing.xl),
Text( Text(
'Start a conversation', AppLocalizations.of(context)!.onboardStartTitle,
style: theme.textTheme.headlineSmall?.copyWith( style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: context.conduitTheme.textPrimary, color: context.conduitTheme.textPrimary,
@@ -921,7 +922,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
const SizedBox(height: Spacing.sm), const SizedBox(height: Spacing.sm),
Text( Text(
'Type below to begin', AppLocalizations.of(context)!.typeBelowToBegin,
style: theme.textTheme.bodyLarge?.copyWith( style: theme.textTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textSecondary, color: context.conduitTheme.textSecondary,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@@ -1221,7 +1222,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
size: IconSize.appBar, size: IconSize.appBar,
), ),
onPressed: _handleNewChat, onPressed: _handleNewChat,
tooltip: 'New Chat', tooltip: AppLocalizations.of(context)!.newChat,
), ),
] else ...[ ] else ...[
IconButton( IconButton(
@@ -1530,7 +1531,7 @@ class _ModelSelectorSheetState extends ConsumerState<_ModelSelectorSheet> {
controller: _searchController, controller: _searchController,
style: TextStyle(color: context.conduitTheme.textPrimary), style: TextStyle(color: context.conduitTheme.textPrimary),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Search...', hintText: AppLocalizations.of(context)!.searchModels,
hintStyle: TextStyle( hintStyle: TextStyle(
color: context.conduitTheme.inputPlaceholder, color: context.conduitTheme.inputPlaceholder,
), ),
@@ -2235,7 +2236,9 @@ class _VoiceInputSheetState extends ConsumerState<_VoiceInputSheet> {
icon: Platform.isIOS icon: Platform.isIOS
? CupertinoIcons.xmark ? CupertinoIcons.xmark
: Icons.close, : Icons.close,
tooltip: 'Close', tooltip: AppLocalizations.of(
context,
)!.closeButtonSemantic,
isCompact: true, isCompact: true,
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
@@ -2475,7 +2478,9 @@ class _VoiceInputSheetState extends ConsumerState<_VoiceInputSheet> {
ConduitIconButton( ConduitIconButton(
icon: Icons.close, icon: Icons.close,
isCompact: true, isCompact: true,
tooltip: 'Clear', tooltip: AppLocalizations.of(
context,
)!.clear,
onPressed: onPressed:
_recognizedText.isNotEmpty && _recognizedText.isNotEmpty &&
!_isTranscribing !_isTranscribing

View File

@@ -393,7 +393,9 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
icon: Platform.isIOS icon: Platform.isIOS
? CupertinoIcons.search ? CupertinoIcons.search
: Icons.search, : Icons.search,
label: 'Web', label: AppLocalizations.of(
context,
)!.web,
isActive: webSearchEnabled, isActive: webSearchEnabled,
onTap: widget.enabled onTap: widget.enabled
? () { ? () {
@@ -413,7 +415,9 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
icon: Platform.isIOS icon: Platform.isIOS
? CupertinoIcons.photo ? CupertinoIcons.photo
: Icons.image, : Icons.image,
label: 'Image Gen', label: AppLocalizations.of(
context,
)!.imageGen,
isActive: imageGenEnabled, isActive: imageGenEnabled,
onTap: widget.enabled onTap: widget.enabled
? () { ? () {

View File

@@ -3,6 +3,7 @@ import 'dart:io' show File, Platform;
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/providers/app_providers.dart'; import '../../../core/providers/app_providers.dart';
@@ -353,12 +354,12 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
children: [ children: [
Text( Text(
_isTranscribing _isTranscribing
? 'Transcribing' ? AppLocalizations.of(context)!.transcribing
: _isListening : _isListening
? (_voiceService.hasLocalStt ? (_voiceService.hasLocalStt
? 'Listening' ? AppLocalizations.of(context)!.listening
: 'Recording') : AppLocalizations.of(context)!.recording)
: 'Voice', : AppLocalizations.of(context)!.voiceInput,
style: TextStyle( style: TextStyle(
fontSize: AppTypography.headlineMedium, fontSize: AppTypography.headlineMedium,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -426,7 +427,9 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
icon: Platform.isIOS icon: Platform.isIOS
? CupertinoIcons.xmark ? CupertinoIcons.xmark
: Icons.close, : Icons.close,
tooltip: 'Close', tooltip: AppLocalizations.of(
context,
)!.closeButtonSemantic,
isCompact: true, isCompact: true,
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
@@ -456,11 +459,16 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
activeColor: context.conduitTheme.buttonPrimary, activeColor: context.conduitTheme.buttonPrimary,
), ),
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Text( Flexible(
'Hold to talk', child: Text(
style: TextStyle(color: context.conduitTheme.textSecondary), AppLocalizations.of(context)!.holdToTalk,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: context.conduitTheme.textSecondary,
), ),
const Spacer(), ),
),
const SizedBox(width: Spacing.sm),
ps.PlatformService.getPlatformSwitch( ps.PlatformService.getPlatformSwitch(
value: _autoSendFinal, value: _autoSendFinal,
onChanged: (v) async { onChanged: (v) async {
@@ -472,9 +480,14 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
activeColor: context.conduitTheme.buttonPrimary, activeColor: context.conduitTheme.buttonPrimary,
), ),
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Text( Flexible(
'Auto-send', child: Text(
style: TextStyle(color: context.conduitTheme.textSecondary), AppLocalizations.of(context)!.autoSend,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: context.conduitTheme.textSecondary,
),
),
), ),
], ],
), ),
@@ -521,8 +534,10 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
child: Semantics( child: Semantics(
button: true, button: true,
label: _isListening label: _isListening
? 'Stop listening' ? AppLocalizations.of(context)!.stopListening
: 'Start listening', : AppLocalizations.of(
context,
)!.startListening,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
@@ -617,7 +632,9 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
Row( Row(
children: [ children: [
Text( Text(
'Transcript', AppLocalizations.of(
context,
)!.transcript,
style: TextStyle( style: TextStyle(
fontSize: AppTypography.labelSmall, fontSize: AppTypography.labelSmall,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -630,7 +647,9 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
ConduitIconButton( ConduitIconButton(
icon: Icons.close, icon: Icons.close,
isCompact: true, isCompact: true,
tooltip: 'Clear', tooltip: AppLocalizations.of(
context,
)!.clear,
onPressed: onPressed:
_recognizedText.isNotEmpty && _recognizedText.isNotEmpty &&
!_isTranscribing !_isTranscribing
@@ -656,18 +675,13 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
), ),
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Text( Text(
'Transcribing…', AppLocalizations.of(
context,
)!.transcribing,
style: TextStyle( style: TextStyle(
fontSize: isUltra fontSize: isUltra
? AppTypography.bodySmall ? 12
: (isCompact : (isCompact ? 12 : 13),
? AppTypography
.bodyMedium
: AppTypography
.bodyLarge),
color: context
.conduitTheme
.textSecondary,
), ),
), ),
], ],
@@ -680,9 +694,15 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
_recognizedText.isEmpty _recognizedText.isEmpty
? (_isListening ? (_isListening
? (_voiceService.hasLocalStt ? (_voiceService.hasLocalStt
? 'Speak now…' ? AppLocalizations.of(
: 'Recording…') context,
: 'Tap Start to begin') )!.speakNow
: AppLocalizations.of(
context,
)!.recording)
: AppLocalizations.of(
context,
)!.typeBelowToBegin)
: _recognizedText, : _recognizedText,
style: TextStyle( style: TextStyle(
fontSize: isUltra fontSize: isUltra
@@ -731,7 +751,9 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
children: [ children: [
Expanded( Expanded(
child: ConduitButton( child: ConduitButton(
text: _isListening ? 'Stop' : 'Start', text: _isListening
? AppLocalizations.of(context)!.stop
: AppLocalizations.of(context)!.start,
isSecondary: true, isSecondary: true,
isCompact: isCompact, isCompact: isCompact,
onPressed: _isListening onPressed: _isListening
@@ -742,7 +764,7 @@ class _VoiceInputSheetState extends ConsumerState<VoiceInputSheet> {
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Expanded( Expanded(
child: ConduitButton( child: ConduitButton(
text: 'Send', text: AppLocalizations.of(context)!.send,
isCompact: isCompact, isCompact: isCompact,
onPressed: _recognizedText.isNotEmpty ? _sendText : null, onPressed: _recognizedText.isNotEmpty ? _sendText : null,
), ),

View File

@@ -34,8 +34,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
// UI state providers for sections // UI state providers for sections
static final _showArchivedProvider = StateProvider<bool>((ref) => false); static final _showArchivedProvider = StateProvider<bool>((ref) => false);
static final _expandedFoldersProvider = static final _expandedFoldersProvider = StateProvider<Map<String, bool>>(
StateProvider<Map<String, bool>>((ref) => {}); (ref) => {},
);
@override @override
void dispose() { void dispose() {
@@ -96,7 +97,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
children: [ children: [
// Centered title (no leading icon) // Centered title (no leading icon)
Text( Text(
'Chats', AppLocalizations.of(context)!.chats,
style: AppTypography.headlineSmallStyle.copyWith( style: AppTypography.headlineSmallStyle.copyWith(
color: theme.textPrimary, color: theme.textPrimary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -214,17 +215,21 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
// Build sections // Build sections
final pinned = list.where((c) => c.pinned == true).toList(); final pinned = list.where((c) => c.pinned == true).toList();
final regular = list final regular = list
.where((c) => .where(
(c) =>
c.pinned != true && c.pinned != true &&
c.archived != true && c.archived != true &&
(c.folderId == null || c.folderId!.isEmpty)) (c.folderId == null || c.folderId!.isEmpty),
)
.toList(); .toList();
final foldered = list final foldered = list
.where((c) => .where(
(c) =>
c.pinned != true && c.pinned != true &&
c.archived != true && c.archived != true &&
c.folderId != null && c.folderId != null &&
c.folderId!.isNotEmpty) c.folderId!.isNotEmpty,
)
.toList(); .toList();
final archived = list.where((c) => c.archived == true).toList(); final archived = list.where((c) => c.archived == true).toList();
@@ -237,7 +242,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
), ),
children: [ children: [
if (pinned.isNotEmpty) ...[ if (pinned.isNotEmpty) ...[
_buildSectionHeader('Pinned', pinned.length), _buildSectionHeader(
AppLocalizations.of(context)!.pinned,
pinned.length,
),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...pinned.map((conv) => _buildTileFor(conv)), ...pinned.map((conv) => _buildTileFor(conv)),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
@@ -250,7 +258,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
_buildUnfileDropTarget(), _buildUnfileDropTarget(),
const SizedBox(height: Spacing.sm), const SizedBox(height: Spacing.sm),
], ],
...ref.watch(foldersProvider).when( ...ref
.watch(foldersProvider)
.when(
data: (folders) { data: (folders) {
final grouped = <String, List<dynamic>>{}; final grouped = <String, List<dynamic>>{};
for (final c in foldered) { for (final c in foldered) {
@@ -266,16 +276,24 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildFolderHeader(folder.id, folder.name, convs.length), _buildFolderHeader(
folder.id,
folder.name,
convs.length,
),
if (isExpanded && convs.isNotEmpty) ...[ if (isExpanded && convs.isNotEmpty) ...[
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...convs.map((c) => _buildTileFor(c, inFolder: true)), ...convs.map(
(c) => _buildTileFor(c, inFolder: true),
),
const SizedBox(height: Spacing.sm), const SizedBox(height: Spacing.sm),
], ],
], ],
); );
}).toList(); }).toList();
return sections.isEmpty ? [const SizedBox.shrink()] : sections; return sections.isEmpty
? [const SizedBox.shrink()]
: sections;
}, },
loading: () => [const SizedBox.shrink()], loading: () => [const SizedBox.shrink()],
error: (e, st) => [const SizedBox.shrink()], error: (e, st) => [const SizedBox.shrink()],
@@ -283,7 +301,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
if (regular.isNotEmpty) ...[ if (regular.isNotEmpty) ...[
_buildSectionHeader(AppLocalizations.of(context)!.recent, regular.length), _buildSectionHeader(
AppLocalizations.of(context)!.recent,
regular.length,
),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...regular.map(_buildTileFor), ...regular.map(_buildTileFor),
], ],
@@ -295,7 +316,8 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
], ],
); );
}, },
loading: () => const Center(child: CircularProgressIndicator(strokeWidth: 2.0)), loading: () =>
const Center(child: CircularProgressIndicator(strokeWidth: 2.0)),
error: (e, _) => Center( error: (e, _) => Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(Spacing.md), padding: const EdgeInsets.all(Spacing.md),
@@ -330,17 +352,21 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
final pinned = list.where((c) => c.pinned == true).toList(); final pinned = list.where((c) => c.pinned == true).toList();
final regular = list final regular = list
.where((c) => .where(
(c) =>
c.pinned != true && c.pinned != true &&
c.archived != true && c.archived != true &&
(c.folderId == null || c.folderId!.isEmpty)) (c.folderId == null || c.folderId!.isEmpty),
)
.toList(); .toList();
final foldered = list final foldered = list
.where((c) => .where(
(c) =>
c.pinned != true && c.pinned != true &&
c.archived != true && c.archived != true &&
c.folderId != null && c.folderId != null &&
c.folderId!.isNotEmpty) c.folderId!.isNotEmpty,
)
.toList(); .toList();
final archived = list.where((c) => c.archived == true).toList(); final archived = list.where((c) => c.archived == true).toList();
@@ -355,7 +381,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
_buildSectionHeader('Results', list.length), _buildSectionHeader('Results', list.length),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
if (pinned.isNotEmpty) ...[ if (pinned.isNotEmpty) ...[
_buildSectionHeader('Pinned', pinned.length), _buildSectionHeader(
AppLocalizations.of(context)!.pinned,
pinned.length,
),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...pinned.map((conv) => _buildTileFor(conv)), ...pinned.map((conv) => _buildTileFor(conv)),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
@@ -367,7 +396,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
_buildUnfileDropTarget(), _buildUnfileDropTarget(),
const SizedBox(height: Spacing.sm), const SizedBox(height: Spacing.sm),
], ],
...ref.watch(foldersProvider).when( ...ref
.watch(foldersProvider)
.when(
data: (folders) { data: (folders) {
final grouped = <String, List<dynamic>>{}; final grouped = <String, List<dynamic>>{};
for (final c in foldered) { for (final c in foldered) {
@@ -382,23 +413,34 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
_buildFolderHeader(folder.id, folder.name, convs.length), _buildFolderHeader(
folder.id,
folder.name,
convs.length,
),
if (isExpanded && convs.isNotEmpty) ...[ if (isExpanded && convs.isNotEmpty) ...[
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...convs.map((c) => _buildTileFor(c, inFolder: true)), ...convs.map(
(c) => _buildTileFor(c, inFolder: true),
),
const SizedBox(height: Spacing.sm), const SizedBox(height: Spacing.sm),
], ],
], ],
); );
}).toList(); }).toList();
return sections.isEmpty ? [const SizedBox.shrink()] : sections; return sections.isEmpty
? [const SizedBox.shrink()]
: sections;
}, },
loading: () => [const SizedBox.shrink()], loading: () => [const SizedBox.shrink()],
error: (e, st) => [const SizedBox.shrink()], error: (e, st) => [const SizedBox.shrink()],
), ),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.md),
if (regular.isNotEmpty) ...[ if (regular.isNotEmpty) ...[
_buildSectionHeader(AppLocalizations.of(context)!.recent, regular.length), _buildSectionHeader(
AppLocalizations.of(context)!.recent,
regular.length,
),
const SizedBox(height: Spacing.xs), const SizedBox(height: Spacing.xs),
...regular.map(_buildTileFor), ...regular.map(_buildTileFor),
], ],
@@ -409,7 +451,8 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
], ],
); );
}, },
loading: () => const Center(child: CircularProgressIndicator(strokeWidth: 2.0)), loading: () =>
const Center(child: CircularProgressIndicator(strokeWidth: 2.0)),
error: (e, _) => Center( error: (e, _) => Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(Spacing.md), padding: const EdgeInsets.all(Spacing.md),
@@ -442,7 +485,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.6), color: theme.surfaceBackground.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(AppBorderRadius.xs), borderRadius: BorderRadius.circular(AppBorderRadius.xs),
border: Border.all(color: theme.dividerColor, width: BorderWidth.thin), border: Border.all(
color: theme.dividerColor,
width: BorderWidth.thin,
),
), ),
child: Text( child: Text(
'$count', '$count',
@@ -461,7 +507,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
return Row( return Row(
children: [ children: [
Text( Text(
'Folders', AppLocalizations.of(context)!.folders,
style: AppTypography.bodySmallStyle.copyWith( style: AppTypography.bodySmallStyle.copyWith(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: theme.textSecondary, color: theme.textSecondary,
@@ -473,7 +519,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
tooltip: AppLocalizations.of(context)!.newFolder, tooltip: AppLocalizations.of(context)!.newFolder,
icon: Icon( icon: Icon(
Platform.isIOS ? CupertinoIcons.folder_badge_plus : Icons.create_new_folder_outlined, Platform.isIOS
? CupertinoIcons.folder_badge_plus
: Icons.create_new_folder_outlined,
color: theme.iconPrimary, color: theme.iconPrimary,
), ),
onPressed: _promptCreateFolder, onPressed: _promptCreateFolder,
@@ -489,7 +537,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
backgroundColor: theme.surfaceBackground, backgroundColor: theme.surfaceBackground,
title: Text(AppLocalizations.of(context)!.newFolder, style: TextStyle(color: theme.textPrimary)), title: Text(
AppLocalizations.of(context)!.newFolder,
style: TextStyle(color: theme.textPrimary),
),
content: TextField( content: TextField(
controller: controller, controller: controller,
autofocus: true, autofocus: true,
@@ -497,8 +548,12 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: AppLocalizations.of(context)!.folderName, hintText: AppLocalizations.of(context)!.folderName,
hintStyle: TextStyle(color: theme.inputPlaceholder), hintStyle: TextStyle(color: theme.inputPlaceholder),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: theme.inputBorder)), enabledBorder: UnderlineInputBorder(
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: theme.buttonPrimary)), borderSide: BorderSide(color: theme.inputBorder),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: theme.buttonPrimary),
),
), ),
onSubmitted: (v) => Navigator.pop(ctx, controller.text.trim()), onSubmitted: (v) => Navigator.pop(ctx, controller.text.trim()),
), ),
@@ -527,7 +582,11 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
UiUtils.showMessage(context, AppLocalizations.of(context)!.folderCreated); UiUtils.showMessage(context, AppLocalizations.of(context)!.folderCreated);
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
UiUtils.showMessage(context, AppLocalizations.of(context)!.failedToCreateFolder, isError: true); UiUtils.showMessage(
context,
AppLocalizations.of(context)!.failedToCreateFolder,
isError: true,
);
} }
} }
@@ -557,12 +616,18 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
if (mounted) { if (mounted) {
UiUtils.showMessage( UiUtils.showMessage(
context, context,
AppLocalizations.of(context)!.movedChatToFolder(details.data.title, name), AppLocalizations.of(
context,
)!.movedChatToFolder(details.data.title, name),
); );
} }
} catch (_) { } catch (_) {
if (mounted) { if (mounted) {
UiUtils.showMessage(context, AppLocalizations.of(context)!.failedToMoveChat, isError: true); UiUtils.showMessage(
context,
AppLocalizations.of(context)!.failedToMoveChat,
isError: true,
);
} }
} }
}, },
@@ -596,8 +661,12 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
children: [ children: [
Icon( Icon(
isExpanded isExpanded
? (Platform.isIOS ? CupertinoIcons.folder_open : Icons.folder_open) ? (Platform.isIOS
: (Platform.isIOS ? CupertinoIcons.folder : Icons.folder), ? CupertinoIcons.folder_open
: Icons.folder_open)
: (Platform.isIOS
? CupertinoIcons.folder
: Icons.folder),
color: theme.iconPrimary, color: theme.iconPrimary,
), ),
const SizedBox(width: Spacing.sm), const SizedBox(width: Spacing.sm),
@@ -619,10 +688,14 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Icon( Icon(
isExpanded isExpanded
? (Platform.isIOS ? CupertinoIcons.chevron_up : Icons.expand_less) ? (Platform.isIOS
: (Platform.isIOS ? CupertinoIcons.chevron_down : Icons.expand_more), ? CupertinoIcons.chevron_up
: Icons.expand_less)
: (Platform.isIOS
? CupertinoIcons.chevron_down
: Icons.expand_more),
color: theme.iconSecondary, color: theme.iconSecondary,
) ),
], ],
), ),
), ),
@@ -654,11 +727,18 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
ref.invalidate(conversationsProvider); ref.invalidate(conversationsProvider);
ref.invalidate(foldersProvider); ref.invalidate(foldersProvider);
if (mounted) { if (mounted) {
UiUtils.showMessage(context, 'Removed "${details.data.title}" from folder'); UiUtils.showMessage(
context,
'Removed "${details.data.title}" from folder',
);
} }
} catch (_) { } catch (_) {
if (mounted) { if (mounted) {
UiUtils.showMessage(context, AppLocalizations.of(context)!.failedToMoveChat, isError: true); UiUtils.showMessage(
context,
AppLocalizations.of(context)!.failedToMoveChat,
isError: true,
);
} }
} }
}, },
@@ -713,7 +793,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
title: title, title: title,
pinned: conv.pinned == true, pinned: conv.pinned == true,
selected: isActive, selected: isActive,
onTap: _isLoadingConversation ? null : () => _selectConversation(context, conv.id), onTap: _isLoadingConversation
? null
: () => _selectConversation(context, conv.id),
// Remove long-press context menu to avoid conflict with drag gesture // Remove long-press context menu to avoid conflict with drag gesture
onLongPress: null, onLongPress: null,
onMorePressed: () { onMorePressed: () {
@@ -723,7 +805,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
); );
return Padding( return Padding(
padding: EdgeInsets.only(bottom: Spacing.xs, left: inFolder ? Spacing.md : 0), padding: EdgeInsets.only(
bottom: Spacing.xs,
left: inFolder ? Spacing.md : 0,
),
child: LongPressDraggable<_DragConversationData>( child: LongPressDraggable<_DragConversationData>(
data: _DragConversationData(id: conv.id, title: title), data: _DragConversationData(id: conv.id, title: title),
dragAnchorStrategy: pointerDragAnchorStrategy, dragAnchorStrategy: pointerDragAnchorStrategy,
@@ -768,7 +853,8 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
), ),
onDragStarted: () { onDragStarted: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
final hasFolder = (conv.folderId != null && (conv.folderId as String).isNotEmpty); final hasFolder =
(conv.folderId != null && (conv.folderId as String).isNotEmpty);
setState(() { setState(() {
_isDragging = true; _isDragging = true;
_draggingHasFolder = hasFolder; _draggingHasFolder = hasFolder;
@@ -794,12 +880,14 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
color: theme.surfaceBackground.withValues(alpha: 0.05), color: theme.surfaceBackground.withValues(alpha: 0.05),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
side: BorderSide(color: theme.dividerColor, width: BorderWidth.regular), side: BorderSide(
color: theme.dividerColor,
width: BorderWidth.regular,
),
), ),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
onTap: () => onTap: () => ref.read(_showArchivedProvider.notifier).state = !show,
ref.read(_showArchivedProvider.notifier).state = !show,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: Spacing.md, horizontal: Spacing.md,
@@ -816,7 +904,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
const SizedBox(width: Spacing.sm), const SizedBox(width: Spacing.sm),
Expanded( Expanded(
child: Text( child: Text(
'Archived', AppLocalizations.of(context)!.archived,
style: AppTypography.bodyLargeStyle.copyWith( style: AppTypography.bodyLargeStyle.copyWith(
color: theme.textPrimary, color: theme.textPrimary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -867,9 +955,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
ref.read(activeConversationProvider.notifier).state = full; ref.read(activeConversationProvider.notifier).state = full;
} else { } else {
// Fallback: let ChatPage handle if API missing // Fallback: let ChatPage handle if API missing
ref.read(activeConversationProvider.notifier).state = ref.read(activeConversationProvider.notifier).state = (await ref.read(
(await ref.read(conversationsProvider.future)) conversationsProvider.future,
.firstWhere((c) => c.id == id); )).firstWhere((c) => c.id == id);
} }
// Clear global loading before closing drawer // Clear global loading before closing drawer
@@ -890,7 +978,12 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
return SafeArea( return SafeArea(
top: false, top: false,
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(Spacing.sm, 0, Spacing.sm, Spacing.sm), padding: const EdgeInsets.fromLTRB(
Spacing.sm,
0,
Spacing.sm,
Spacing.sm,
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -901,7 +994,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.04), color: theme.surfaceBackground.withValues(alpha: 0.04),
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
border: Border.all(color: theme.dividerColor, width: BorderWidth.regular), border: Border.all(
color: theme.dividerColor,
width: BorderWidth.regular,
),
), ),
child: Row( child: Row(
children: [ children: [
@@ -910,12 +1006,20 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
height: IconSize.avatar, height: IconSize.avatar,
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.buttonPrimary.withValues(alpha: 0.15), color: theme.buttonPrimary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(AppBorderRadius.avatar), borderRadius: BorderRadius.circular(
border: Border.all(color: theme.buttonPrimary.withValues(alpha: 0.35), width: BorderWidth.thin), AppBorderRadius.avatar,
),
border: Border.all(
color: theme.buttonPrimary.withValues(alpha: 0.35),
width: BorderWidth.thin,
),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
(user.name ?? user.username ?? 'U').toString().substring(0, 1).toUpperCase(), (user.name ?? user.username ?? 'U')
.toString()
.substring(0, 1)
.toUpperCase(),
style: AppTypography.bodyLargeStyle.copyWith( style: AppTypography.bodyLargeStyle.copyWith(
color: theme.buttonPrimary, color: theme.buttonPrimary,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@@ -949,11 +1053,13 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
onPressed: () { onPressed: () {
Navigator.of(context).maybePop(); Navigator.of(context).maybePop();
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const ProfilePage()), MaterialPageRoute(
builder: (_) => const ProfilePage(),
),
); );
}, },
child: Text(AppLocalizations.of(context)!.manage), child: Text(AppLocalizations.of(context)!.manage),
) ),
], ],
), ),
), ),
@@ -974,7 +1080,9 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
context: context, context: context,
backgroundColor: theme.surfaceBackground, backgroundColor: theme.surfaceBackground,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(AppBorderRadius.lg)), borderRadius: BorderRadius.vertical(
top: Radius.circular(AppBorderRadius.lg),
),
), ),
builder: (sheetContext) { builder: (sheetContext) {
return SafeArea( return SafeArea(
@@ -984,8 +1092,12 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
ListTile( ListTile(
leading: Icon( leading: Icon(
isPinned isPinned
? (Platform.isIOS ? CupertinoIcons.pin_slash : Icons.push_pin_outlined) ? (Platform.isIOS
: (Platform.isIOS ? CupertinoIcons.pin_fill : Icons.push_pin_rounded), ? CupertinoIcons.pin_slash
: Icons.push_pin_outlined)
: (Platform.isIOS
? CupertinoIcons.pin_fill
: Icons.push_pin_rounded),
color: theme.iconPrimary, color: theme.iconPrimary,
), ),
title: Text( title: Text(
@@ -1001,15 +1113,23 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
await chat.pinConversation(ref, conv.id, !isPinned); await chat.pinConversation(ref, conv.id, !isPinned);
} catch (_) { } catch (_) {
if (!mounted) return; if (!mounted) return;
UiUtils.showMessage(this.context, AppLocalizations.of(context)!.failedToUpdatePin, isError: true); UiUtils.showMessage(
this.context,
AppLocalizations.of(context)!.failedToUpdatePin,
isError: true,
);
} }
}, },
), ),
ListTile( ListTile(
leading: Icon( leading: Icon(
isArchived isArchived
? (Platform.isIOS ? CupertinoIcons.archivebox_fill : Icons.unarchive_rounded) ? (Platform.isIOS
: (Platform.isIOS ? CupertinoIcons.archivebox : Icons.archive_rounded), ? CupertinoIcons.archivebox_fill
: Icons.unarchive_rounded)
: (Platform.isIOS
? CupertinoIcons.archivebox
: Icons.archive_rounded),
color: theme.iconPrimary, color: theme.iconPrimary,
), ),
title: Text( title: Text(
@@ -1025,7 +1145,11 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
await chat.archiveConversation(ref, conv.id, !isArchived); await chat.archiveConversation(ref, conv.id, !isArchived);
} catch (_) { } catch (_) {
if (!mounted) return; if (!mounted) return;
UiUtils.showMessage(this.context, AppLocalizations.of(context)!.failedToUpdateArchive, isError: true); UiUtils.showMessage(
this.context,
AppLocalizations.of(context)!.failedToUpdateArchive,
isError: true,
);
} }
}, },
), ),
@@ -1034,7 +1158,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
Platform.isIOS ? CupertinoIcons.pencil : Icons.edit_rounded, Platform.isIOS ? CupertinoIcons.pencil : Icons.edit_rounded,
color: theme.iconPrimary, color: theme.iconPrimary,
), ),
title: Text(AppLocalizations.of(context)!.rename, style: TextStyle(color: theme.textPrimary)), title: Text(
AppLocalizations.of(context)!.rename,
style: TextStyle(color: theme.textPrimary),
),
onTap: () async { onTap: () async {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
@@ -1047,7 +1174,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
Platform.isIOS ? CupertinoIcons.delete : Icons.delete_rounded, Platform.isIOS ? CupertinoIcons.delete : Icons.delete_rounded,
color: theme.error, color: theme.error,
), ),
title: Text(AppLocalizations.of(context)!.delete, style: TextStyle(color: theme.error)), title: Text(
AppLocalizations.of(context)!.delete,
style: TextStyle(color: theme.error),
),
onTap: () async { onTap: () async {
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
@@ -1074,7 +1204,10 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
builder: (dialogContext) { builder: (dialogContext) {
return AlertDialog( return AlertDialog(
backgroundColor: theme.surfaceBackground, backgroundColor: theme.surfaceBackground,
title: Text(AppLocalizations.of(context)!.renameChat, style: TextStyle(color: theme.textPrimary)), title: Text(
AppLocalizations.of(context)!.renameChat,
style: TextStyle(color: theme.textPrimary),
),
content: TextField( content: TextField(
controller: controller, controller: controller,
autofocus: true, autofocus: true,
@@ -1121,12 +1254,17 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
ref.invalidate(conversationsProvider); ref.invalidate(conversationsProvider);
final active = ref.read(activeConversationProvider); final active = ref.read(activeConversationProvider);
if (active?.id == conversationId) { if (active?.id == conversationId) {
ref.read(activeConversationProvider.notifier).state = ref.read(activeConversationProvider.notifier).state = active!.copyWith(
active!.copyWith(title: newName); title: newName,
);
} }
} catch (_) { } catch (_) {
if (!mounted) return; if (!mounted) return;
UiUtils.showMessage(this.context, AppLocalizations.of(context)!.failedToRenameChat, isError: true); UiUtils.showMessage(
this.context,
AppLocalizations.of(context)!.failedToRenameChat,
isError: true,
);
} }
} }
@@ -1157,7 +1295,11 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
ref.invalidate(conversationsProvider); ref.invalidate(conversationsProvider);
} catch (_) { } catch (_) {
if (!mounted) return; if (!mounted) return;
UiUtils.showMessage(this.context, AppLocalizations.of(context)!.failedToDeleteChat, isError: true); UiUtils.showMessage(
this.context,
AppLocalizations.of(context)!.failedToDeleteChat,
isError: true,
);
} }
} }
} }
@@ -1195,7 +1337,9 @@ class _ConversationTile extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
side: BorderSide( side: BorderSide(
color: selected ? theme.buttonPrimary.withValues(alpha: 0.5) : theme.dividerColor, color: selected
? theme.buttonPrimary.withValues(alpha: 0.5)
: theme.dividerColor,
width: BorderWidth.regular, width: BorderWidth.regular,
), ),
), ),
@@ -1226,9 +1370,14 @@ class _ConversationTile extends StatelessWidget {
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 36, minHeight: 36), constraints: const BoxConstraints(
minWidth: 36,
minHeight: 36,
),
icon: Icon( icon: Icon(
Platform.isIOS ? CupertinoIcons.ellipsis : Icons.more_vert_rounded, Platform.isIOS
? CupertinoIcons.ellipsis
: Icons.more_vert_rounded,
color: theme.iconSecondary, color: theme.iconSecondary,
size: IconSize.md, size: IconSize.md,
), ),

View File

@@ -119,7 +119,9 @@ class ProfilePage extends ConsumerWidget {
centerTitle: true, centerTitle: true,
), ),
body: Center( body: Center(
child: ImprovedLoadingState(message: AppLocalizations.of(context)!.loadingProfile), child: ImprovedLoadingState(
message: AppLocalizations.of(context)!.loadingProfile,
),
), ),
), ),
error: (error, stack) => Scaffold( error: (error, stack) => Scaffold(
@@ -316,10 +318,9 @@ class ProfilePage extends ConsumerWidget {
data: (models) { data: (models) {
final currentModel = models.firstWhere( final currentModel = models.firstWhere(
(m) => m.id == settings.defaultModel, (m) => m.id == settings.defaultModel,
orElse: () => models.isNotEmpty ? models.first : const Model( orElse: () => models.isNotEmpty
id: 'none', ? models.first
name: 'No models available', : const Model(id: 'none', name: 'No models available'),
),
); );
return ListTile( return ListTile(
@@ -352,7 +353,9 @@ class ProfilePage extends ConsumerWidget {
), ),
), ),
subtitle: Text( subtitle: Text(
settings.defaultModel != null ? currentModel.name : AppLocalizations.of(context)!.autoSelect, settings.defaultModel != null
? currentModel.name
: AppLocalizations.of(context)!.autoSelect,
style: context.conduitTheme.bodySmall?.copyWith( style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary, color: context.conduitTheme.textSecondary,
), ),
@@ -484,7 +487,7 @@ class ProfilePage extends ConsumerWidget {
), ),
), ),
title: Text( title: Text(
AppLocalizations.of(context)!.menuItem, AppLocalizations.of(context)!.appLanguage,
style: context.conduitTheme.bodyLarge?.copyWith( style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary, color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -600,7 +603,7 @@ class ProfilePage extends ConsumerWidget {
), ),
), ),
title: Text( title: Text(
'Dark Mode', AppLocalizations.of(context)!.darkMode,
style: context.conduitTheme.bodyLarge?.copyWith( style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary, color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -720,7 +723,11 @@ class ProfilePage extends ConsumerWidget {
} }
} }
Future<void> _showModelSelector(BuildContext context, WidgetRef ref, List<Model> models) async { Future<void> _showModelSelector(
BuildContext context,
WidgetRef ref,
List<Model> models,
) async {
final result = await showModalBottomSheet<String?>( final result = await showModalBottomSheet<String?>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
@@ -736,7 +743,9 @@ class ProfilePage extends ConsumerWidget {
if (result != null) { if (result != null) {
// Handle special case: 'auto-select' should be stored as null // Handle special case: 'auto-select' should be stored as null
final modelIdToSave = result == 'auto-select' ? null : result; final modelIdToSave = result == 'auto-select' ? null : result;
await ref.read(appSettingsProvider.notifier).setDefaultModel(modelIdToSave); await ref
.read(appSettingsProvider.notifier)
.setDefaultModel(modelIdToSave);
} }
} }
@@ -765,10 +774,12 @@ class _DefaultModelBottomSheet extends ConsumerStatefulWidget {
}); });
@override @override
ConsumerState<_DefaultModelBottomSheet> createState() => _DefaultModelBottomSheetState(); ConsumerState<_DefaultModelBottomSheet> createState() =>
_DefaultModelBottomSheetState();
} }
class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomSheet> { class _DefaultModelBottomSheetState
extends ConsumerState<_DefaultModelBottomSheet> {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
String _searchQuery = ''; String _searchQuery = '';
List<Model> _filteredModels = []; List<Model> _filteredModels = [];
@@ -896,18 +907,24 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
filled: true, filled: true,
fillColor: context.conduitTheme.inputBackground, fillColor: context.conduitTheme.inputBackground,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(
AppBorderRadius.md,
),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(
AppBorderRadius.md,
),
borderSide: BorderSide( borderSide: BorderSide(
color: context.conduitTheme.inputBorder, color: context.conduitTheme.inputBorder,
width: 1, width: 1,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(
AppBorderRadius.md,
),
borderSide: BorderSide( borderSide: BorderSide(
color: context.conduitTheme.buttonPrimary, color: context.conduitTheme.buttonPrimary,
width: 1, width: 1,
@@ -937,10 +954,16 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
), ),
const SizedBox(width: Spacing.xs), const SizedBox(width: Spacing.xs),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.conduitTheme.surfaceBackground.withValues(alpha: 0.6), color: context.conduitTheme.surfaceBackground
borderRadius: BorderRadius.circular(AppBorderRadius.xs), .withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(
AppBorderRadius.xs,
),
border: Border.all( border: Border.all(
color: context.conduitTheme.dividerColor, color: context.conduitTheme.dividerColor,
width: BorderWidth.thin, width: BorderWidth.thin,
@@ -994,7 +1017,8 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
final model = _filteredModels[index]; final model = _filteredModels[index];
final isAutoSelect = model.id == 'auto-select'; final isAutoSelect = model.id == 'auto-select';
final isSelected = isAutoSelect final isSelected = isAutoSelect
? _selectedModelId == null || _selectedModelId == 'auto-select' ? _selectedModelId == null ||
_selectedModelId == 'auto-select'
: _selectedModelId == model.id; : _selectedModelId == model.id;
return _buildModelListTile( return _buildModelListTile(
@@ -1003,8 +1027,9 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
isAutoSelect: isAutoSelect, isAutoSelect: isAutoSelect,
onTap: () { onTap: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
final selectedId = final selectedId = isAutoSelect
isAutoSelect ? 'auto-select' : model.id; ? 'auto-select'
: model.id;
// Return selection immediately; caller handles persisting // Return selection immediately; caller handles persisting
Navigator.pop(context, selectedId); Navigator.pop(context, selectedId);
}, },
@@ -1070,13 +1095,19 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
width: 32, width: 32,
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary.withValues(alpha: 0.15), color: context.conduitTheme.buttonPrimary.withValues(
alpha: 0.15,
),
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
), ),
child: Icon( child: Icon(
isAutoSelect isAutoSelect
? (Platform.isIOS ? CupertinoIcons.wand_stars : Icons.auto_awesome) ? (Platform.isIOS
: (Platform.isIOS ? CupertinoIcons.cube : Icons.psychology), ? CupertinoIcons.wand_stars
: Icons.auto_awesome)
: (Platform.isIOS
? CupertinoIcons.cube
: Icons.psychology),
color: context.conduitTheme.buttonPrimary, color: context.conduitTheme.buttonPrimary,
size: 16, size: 16,
), ),
@@ -1087,7 +1118,9 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
isAutoSelect ? AppLocalizations.of(context)!.autoSelect : model.name, isAutoSelect
? AppLocalizations.of(context)!.autoSelect
: model.name,
style: TextStyle( style: TextStyle(
color: context.conduitTheme.textPrimary, color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -1143,13 +1176,17 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
borderRadius: BorderRadius.circular(AppBorderRadius.md), borderRadius: BorderRadius.circular(AppBorderRadius.md),
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? context.conduitTheme.buttonPrimary.withValues(alpha: 0.6) ? context.conduitTheme.buttonPrimary.withValues(
alpha: 0.6,
)
: context.conduitTheme.dividerColor, : context.conduitTheme.dividerColor,
), ),
), ),
child: Icon( child: Icon(
isSelected isSelected
? (Platform.isIOS ? CupertinoIcons.check_mark : Icons.check) ? (Platform.isIOS
? CupertinoIcons.check_mark
: Icons.check)
: (Platform.isIOS ? CupertinoIcons.add : Icons.add), : (Platform.isIOS ? CupertinoIcons.add : Icons.add),
color: isSelected color: isSelected
? context.conduitTheme.textInverse ? context.conduitTheme.textInverse

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -59,9 +60,10 @@ class _UnifiedToolsModalState extends ConsumerState<UnifiedToolsModal> {
Column( Column(
children: [ children: [
_buildFeatureTile( _buildFeatureTile(
title: 'Web Search', title: AppLocalizations.of(context)!.webSearch,
description: description: AppLocalizations.of(
'Let the assistant search the internet while answering.', context,
)!.webSearchDescription,
icon: Platform.isIOS icon: Platform.isIOS
? CupertinoIcons.search ? CupertinoIcons.search
: Icons.search, : Icons.search,
@@ -74,9 +76,10 @@ class _UnifiedToolsModalState extends ConsumerState<UnifiedToolsModal> {
), ),
if (imageGenAvailable) if (imageGenAvailable)
_buildFeatureTile( _buildFeatureTile(
title: 'Image Generation', title: AppLocalizations.of(context)!.imageGeneration,
description: description: AppLocalizations.of(
'Generate images from your prompt and attach them.', context,
)!.imageGenerationDescription,
icon: Platform.isIOS icon: Platform.isIOS
? CupertinoIcons.photo ? CupertinoIcons.photo
: Icons.image, : Icons.image,

View File

@@ -64,7 +64,8 @@ import 'app_localizations_it.dart';
/// be consistent with the languages listed in the AppLocalizations.supportedLocales /// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property. /// property.
abstract class AppLocalizations { abstract class AppLocalizations {
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName; final String localeName;
@@ -72,7 +73,8 @@ abstract class AppLocalizations {
return Localizations.of<AppLocalizations>(context, AppLocalizations); return Localizations.of<AppLocalizations>(context, AppLocalizations);
} }
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate(); static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations /// A list of this localizations delegate along with the default localizations
/// delegates. /// delegates.
@@ -84,7 +86,8 @@ abstract class AppLocalizations {
/// Additional delegates can be added by appending to this list in /// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list /// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required. /// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[ static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate, delegate,
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
@@ -96,7 +99,7 @@ abstract class AppLocalizations {
Locale('de'), Locale('de'),
Locale('en'), Locale('en'),
Locale('fr'), Locale('fr'),
Locale('it') Locale('it'),
]; ];
/// No description provided for @appTitle. /// No description provided for @appTitle.
@@ -1190,9 +1193,154 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Conduit information and links'** /// **'Conduit information and links'**
String get aboutAppSubtitle; String get aboutAppSubtitle;
/// No description provided for @appLanguage.
///
/// In en, this message translates to:
/// **'App language'**
String get appLanguage;
/// No description provided for @typeBelowToBegin.
///
/// In en, this message translates to:
/// **'Type below to begin'**
String get typeBelowToBegin;
/// No description provided for @listening.
///
/// In en, this message translates to:
/// **'Listening…'**
String get listening;
/// No description provided for @recording.
///
/// In en, this message translates to:
/// **'Recording…'**
String get recording;
/// No description provided for @transcribing.
///
/// In en, this message translates to:
/// **'Transcribing…'**
String get transcribing;
/// No description provided for @speakNow.
///
/// In en, this message translates to:
/// **'Speak now…'**
String get speakNow;
/// No description provided for @chats.
///
/// In en, this message translates to:
/// **'Chats'**
String get chats;
/// No description provided for @darkMode.
///
/// In en, this message translates to:
/// **'Dark Mode'**
String get darkMode;
/// No description provided for @transcript.
///
/// In en, this message translates to:
/// **'Transcript'**
String get transcript;
/// No description provided for @pinned.
///
/// In en, this message translates to:
/// **'Pinned'**
String get pinned;
/// No description provided for @folders.
///
/// In en, this message translates to:
/// **'Folders'**
String get folders;
/// No description provided for @archived.
///
/// In en, this message translates to:
/// **'Archived'**
String get archived;
/// No description provided for @holdToTalk.
///
/// In en, this message translates to:
/// **'Hold to talk'**
String get holdToTalk;
/// No description provided for @autoSend.
///
/// In en, this message translates to:
/// **'Auto-send'**
String get autoSend;
/// No description provided for @stopListening.
///
/// In en, this message translates to:
/// **'Stop listening'**
String get stopListening;
/// No description provided for @startListening.
///
/// In en, this message translates to:
/// **'Start listening'**
String get startListening;
/// No description provided for @start.
///
/// In en, this message translates to:
/// **'Start'**
String get start;
/// No description provided for @stop.
///
/// In en, this message translates to:
/// **'Stop'**
String get stop;
/// No description provided for @web.
///
/// In en, this message translates to:
/// **'Web'**
String get web;
/// No description provided for @imageGen.
///
/// In en, this message translates to:
/// **'Image Gen'**
String get imageGen;
/// No description provided for @webSearch.
///
/// In en, this message translates to:
/// **'Web Search'**
String get webSearch;
/// No description provided for @webSearchDescription.
///
/// In en, this message translates to:
/// **'Let the assistant search the internet while answering.'**
String get webSearchDescription;
/// No description provided for @imageGeneration.
///
/// In en, this message translates to:
/// **'Image Generation'**
String get imageGeneration;
/// No description provided for @imageGenerationDescription.
///
/// In en, this message translates to:
/// **'Generate images from your prompt and attach them.'**
String get imageGenerationDescription;
} }
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> { class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate(); const _AppLocalizationsDelegate();
@override @override
@@ -1201,27 +1349,30 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>
} }
@override @override
bool isSupported(Locale locale) => <String>['de', 'en', 'fr', 'it'].contains(locale.languageCode); bool isSupported(Locale locale) =>
<String>['de', 'en', 'fr', 'it'].contains(locale.languageCode);
@override @override
bool shouldReload(_AppLocalizationsDelegate old) => false; bool shouldReload(_AppLocalizationsDelegate old) => false;
} }
AppLocalizations lookupAppLocalizations(Locale locale) { AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified. // Lookup logic when only language code is specified.
switch (locale.languageCode) { switch (locale.languageCode) {
case 'de': return AppLocalizationsDe(); case 'de':
case 'en': return AppLocalizationsEn(); return AppLocalizationsDe();
case 'fr': return AppLocalizationsFr(); case 'en':
case 'it': return AppLocalizationsIt(); return AppLocalizationsEn();
case 'fr':
return AppLocalizationsFr();
case 'it':
return AppLocalizationsIt();
} }
throw FlutterError( throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue ' 'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.' 'that was used.',
); );
} }

View File

@@ -30,7 +30,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get unableToLoadProfile => 'Profil konnte nicht geladen werden'; String get unableToLoadProfile => 'Profil konnte nicht geladen werden';
@override @override
String get pleaseCheckConnection => 'Bitte überprüfe deine Verbindung und versuche es erneut'; String get pleaseCheckConnection =>
'Bitte überprüfe deine Verbindung und versuche es erneut';
@override @override
String get account => 'Konto'; String get account => 'Konto';
@@ -63,7 +64,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get searchModels => 'Modelle suchen...'; String get searchModels => 'Modelle suchen...';
@override @override
String get errorMessage => 'Etwas ist schief gelaufen. Bitte versuche es erneut.'; String get errorMessage =>
'Etwas ist schief gelaufen. Bitte versuche es erneut.';
@override @override
String get loginButton => 'Anmelden'; String get loginButton => 'Anmelden';
@@ -116,7 +118,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get noFilesYet => 'Noch keine Dateien'; String get noFilesYet => 'Noch keine Dateien';
@override @override
String get uploadDocsPrompt => 'Lade Dokumente hoch, um sie in deinen Unterhaltungen mit Conduit zu verwenden'; String get uploadDocsPrompt =>
'Lade Dokumente hoch, um sie in deinen Unterhaltungen mit Conduit zu verwenden';
@override @override
String get uploadFirstFile => 'Erste Datei hochladen'; String get uploadFirstFile => 'Erste Datei hochladen';
@@ -125,7 +128,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get knowledgeBaseEmpty => 'Wissensdatenbank ist leer'; String get knowledgeBaseEmpty => 'Wissensdatenbank ist leer';
@override @override
String get createCollectionsPrompt => 'Erstelle Sammlungen verwandter Dokumente zur einfachen Referenz'; String get createCollectionsPrompt =>
'Erstelle Sammlungen verwandter Dokumente zur einfachen Referenz';
@override @override
String get chooseSourcePhoto => 'Quelle auswählen'; String get chooseSourcePhoto => 'Quelle auswählen';
@@ -151,7 +155,8 @@ class AppLocalizationsDe extends AppLocalizations {
} }
@override @override
String get kbCreationComingSoon => 'Erstellung der Wissensdatenbank kommt bald!'; String get kbCreationComingSoon =>
'Erstellung der Wissensdatenbank kommt bald!';
@override @override
String get backToServerSetup => 'Zur Servereinrichtung zurück'; String get backToServerSetup => 'Zur Servereinrichtung zurück';
@@ -163,7 +168,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get signIn => 'Anmelden'; String get signIn => 'Anmelden';
@override @override
String get enterCredentials => 'Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen'; String get enterCredentials =>
'Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen';
@override @override
String get credentials => 'Zugangsdaten'; String get credentials => 'Zugangsdaten';
@@ -184,7 +190,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get connectToServer => 'Mit Server verbinden'; String get connectToServer => 'Mit Server verbinden';
@override @override
String get enterServerAddress => 'Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen'; String get enterServerAddress =>
'Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen';
@override @override
String get serverUrl => 'Server-URL'; String get serverUrl => 'Server-URL';
@@ -193,7 +200,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get serverUrlHint => 'https://dein-server.com'; String get serverUrlHint => 'https://dein-server.com';
@override @override
String get enterServerUrlSemantic => 'Gib deine Server-URL oder IP-Adresse ein'; String get enterServerUrlSemantic =>
'Gib deine Server-URL oder IP-Adresse ein';
@override @override
String get headerName => 'Header-Name'; String get headerName => 'Header-Name';
@@ -223,7 +231,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get demoModeActive => 'Demo-Modus aktiv'; String get demoModeActive => 'Demo-Modus aktiv';
@override @override
String get skipServerSetupTryDemo => 'Servereinrichtung überspringen und Demo testen'; String get skipServerSetupTryDemo =>
'Servereinrichtung überspringen und Demo testen';
@override @override
String get enterDemo => 'Demo starten'; String get enterDemo => 'Demo starten';
@@ -232,7 +241,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get demoBadge => 'Demo'; String get demoBadge => 'Demo';
@override @override
String get serverNotOpenWebUI => 'Dies scheint kein Open-WebUI-Server zu sein.'; String get serverNotOpenWebUI =>
'Dies scheint kein Open-WebUI-Server zu sein.';
@override @override
String get serverUrlEmpty => 'Server-URL darf nicht leer sein'; String get serverUrlEmpty => 'Server-URL darf nicht leer sein';
@@ -241,10 +251,12 @@ class AppLocalizationsDe extends AppLocalizations {
String get invalidUrlFormat => 'Ungültiges URL-Format. Bitte Eingabe prüfen.'; String get invalidUrlFormat => 'Ungültiges URL-Format. Bitte Eingabe prüfen.';
@override @override
String get onlyHttpHttps => 'Nur HTTP- und HTTPS-Protokolle werden unterstützt.'; String get onlyHttpHttps =>
'Nur HTTP- und HTTPS-Protokolle werden unterstützt.';
@override @override
String get serverAddressRequired => 'Serveradresse erforderlich (z. B. 192.168.1.10 oder example.com).'; String get serverAddressRequired =>
'Serveradresse erforderlich (z. B. 192.168.1.10 oder example.com).';
@override @override
String get portRange => 'Port muss zwischen 1 und 65535 liegen.'; String get portRange => 'Port muss zwischen 1 und 65535 liegen.';
@@ -253,13 +265,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get invalidIpFormat => 'Ungültiges IP-Format. Beispiel: 192.168.1.10.'; String get invalidIpFormat => 'Ungültiges IP-Format. Beispiel: 192.168.1.10.';
@override @override
String get couldNotConnectGeneric => 'Verbindung fehlgeschlagen. Adresse prüfen und erneut versuchen.'; String get couldNotConnectGeneric =>
'Verbindung fehlgeschlagen. Adresse prüfen und erneut versuchen.';
@override @override
String get weCouldntReachServer => 'Server nicht erreichbar. Verbindung und Serverstatus prüfen.'; String get weCouldntReachServer =>
'Server nicht erreichbar. Verbindung und Serverstatus prüfen.';
@override @override
String get connectionTimedOut => 'Zeitüberschreitung. Server eventuell ausgelastet oder blockiert.'; String get connectionTimedOut =>
'Zeitüberschreitung. Server eventuell ausgelastet oder blockiert.';
@override @override
String get useHttpOrHttpsOnly => 'Nur http:// oder https:// verwenden.'; String get useHttpOrHttpsOnly => 'Nur http:// oder https:// verwenden.';
@@ -268,19 +283,23 @@ class AppLocalizationsDe extends AppLocalizations {
String get loginFailed => 'Anmeldung fehlgeschlagen'; String get loginFailed => 'Anmeldung fehlgeschlagen';
@override @override
String get invalidCredentials => 'Ungültiger Benutzername oder Passwort. Bitte erneut versuchen.'; String get invalidCredentials =>
'Ungültiger Benutzername oder Passwort. Bitte erneut versuchen.';
@override @override
String get serverRedirectingHttps => 'Server leitet um. HTTPS-Konfiguration prüfen.'; String get serverRedirectingHttps =>
'Server leitet um. HTTPS-Konfiguration prüfen.';
@override @override
String get unableToConnectServer => 'Verbindung zum Server nicht möglich. Bitte Verbindung prüfen.'; String get unableToConnectServer =>
'Verbindung zum Server nicht möglich. Bitte Verbindung prüfen.';
@override @override
String get requestTimedOut => 'Zeitüberschreitung. Bitte erneut versuchen.'; String get requestTimedOut => 'Zeitüberschreitung. Bitte erneut versuchen.';
@override @override
String get genericSignInFailed => 'Anmeldung nicht möglich. Zugangsdaten und Server prüfen.'; String get genericSignInFailed =>
'Anmeldung nicht möglich. Zugangsdaten und Server prüfen.';
@override @override
String get skip => 'Überspringen'; String get skip => 'Überspringen';
@@ -295,7 +314,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get onboardStartTitle => 'Unterhaltung starten'; String get onboardStartTitle => 'Unterhaltung starten';
@override @override
String get onboardStartSubtitle => 'Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.'; String get onboardStartSubtitle =>
'Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.';
@override @override
String get onboardStartBullet1 => 'Modellname oben antippen, um zu wechseln'; String get onboardStartBullet1 => 'Modellname oben antippen, um zu wechseln';
@@ -307,7 +327,8 @@ class AppLocalizationsDe extends AppLocalizations {
String get onboardAttachTitle => 'Kontext anhängen'; String get onboardAttachTitle => 'Kontext anhängen';
@override @override
String get onboardAttachSubtitle => 'Antworten mit Dateien oder Bildern untermauern.'; String get onboardAttachSubtitle =>
'Antworten mit Dateien oder Bildern untermauern.';
@override @override
String get onboardAttachBullet1 => 'Dateien: PDFs, Dokumente, Datensätze'; String get onboardAttachBullet1 => 'Dateien: PDFs, Dokumente, Datensätze';
@@ -325,19 +346,23 @@ class AppLocalizationsDe extends AppLocalizations {
String get onboardSpeakBullet1 => 'Jederzeit stoppen; Text bleibt erhalten'; String get onboardSpeakBullet1 => 'Jederzeit stoppen; Text bleibt erhalten';
@override @override
String get onboardSpeakBullet2 => 'Ideal für kurze Notizen oder lange Prompts'; String get onboardSpeakBullet2 =>
'Ideal für kurze Notizen oder lange Prompts';
@override @override
String get onboardQuickTitle => 'Schnellaktionen'; String get onboardQuickTitle => 'Schnellaktionen';
@override @override
String get onboardQuickSubtitle => 'Links oben das Menü für Chats und Navigation öffnen.'; String get onboardQuickSubtitle =>
'Links oben das Menü für Chats und Navigation öffnen.';
@override @override
String get onboardQuickBullet1 => 'Menü tippen, um Chats und Navigation zu öffnen'; String get onboardQuickBullet1 =>
'Menü tippen, um Chats und Navigation zu öffnen';
@override @override
String get onboardQuickBullet2 => 'Schnell zu Neuer Chat, Dateien oder Profil springen'; String get onboardQuickBullet2 =>
'Schnell zu Neuer Chat, Dateien oder Profil springen';
@override @override
String get addAttachment => 'Anhang hinzufügen'; String get addAttachment => 'Anhang hinzufügen';
@@ -404,13 +429,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get emptyImageData => 'Leere Bilddaten'; String get emptyImageData => 'Leere Bilddaten';
@override @override
String get offlineBanner => 'Du bist offline. Einige Funktionen sind eingeschränkt.'; String get offlineBanner =>
'Du bist offline. Einige Funktionen sind eingeschränkt.';
@override @override
String get featureRequiresInternet => 'Diese Funktion erfordert eine Internetverbindung'; String get featureRequiresInternet =>
'Diese Funktion erfordert eine Internetverbindung';
@override @override
String get messagesWillSendWhenOnline => 'Nachrichten werden gesendet, sobald du wieder online bist'; String get messagesWillSendWhenOnline =>
'Nachrichten werden gesendet, sobald du wieder online bist';
@override @override
String get confirm => 'Bestätigen'; String get confirm => 'Bestätigen';
@@ -576,4 +604,78 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get aboutAppSubtitle => 'Conduit Informationen und Links'; String get aboutAppSubtitle => 'Conduit Informationen und Links';
@override
String get appLanguage => 'App-Sprache';
@override
String get typeBelowToBegin => 'Unten tippen, um zu beginnen';
@override
String get listening => 'Zuhören…';
@override
String get recording => 'Aufnahme…';
@override
String get transcribing => 'Transkription…';
@override
String get speakNow => 'Jetzt sprechen…';
@override
String get chats => 'Chats';
@override
String get darkMode => 'Dunkler Modus';
@override
String get transcript => 'Transkript';
@override
String get pinned => 'Angeheftet';
@override
String get folders => 'Ordner';
@override
String get archived => 'Archiviert';
@override
String get holdToTalk => 'Zum Sprechen halten';
@override
String get autoSend => 'Automatisch senden';
@override
String get stopListening => 'Zuhören stoppen';
@override
String get startListening => 'Zuhören starten';
@override
String get start => 'Start';
@override
String get stop => 'Stopp';
@override
String get web => 'Web';
@override
String get imageGen => 'Bild-Gen';
@override
String get webSearch => 'Websuche';
@override
String get webSearchDescription =>
'Lassen Sie den Assistenten beim Antworten im Internet suchen.';
@override
String get imageGeneration => 'Bilderzeugung';
@override
String get imageGenerationDescription =>
'Bilder aus Ihrer Eingabe generieren und anhängen.';
} }

View File

@@ -30,7 +30,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get unableToLoadProfile => 'Unable to load profile'; String get unableToLoadProfile => 'Unable to load profile';
@override @override
String get pleaseCheckConnection => 'Please check your connection and try again'; String get pleaseCheckConnection =>
'Please check your connection and try again';
@override @override
String get account => 'Account'; String get account => 'Account';
@@ -116,7 +117,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get noFilesYet => 'No files yet'; String get noFilesYet => 'No files yet';
@override @override
String get uploadDocsPrompt => 'Upload documents to reference in your conversations with Conduit'; String get uploadDocsPrompt =>
'Upload documents to reference in your conversations with Conduit';
@override @override
String get uploadFirstFile => 'Upload your first file'; String get uploadFirstFile => 'Upload your first file';
@@ -125,7 +127,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get knowledgeBaseEmpty => 'Knowledge base is empty'; String get knowledgeBaseEmpty => 'Knowledge base is empty';
@override @override
String get createCollectionsPrompt => 'Create collections of related documents for easy reference'; String get createCollectionsPrompt =>
'Create collections of related documents for easy reference';
@override @override
String get chooseSourcePhoto => 'Choose your source'; String get chooseSourcePhoto => 'Choose your source';
@@ -163,7 +166,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get signIn => 'Sign In'; String get signIn => 'Sign In';
@override @override
String get enterCredentials => 'Enter your credentials to access your AI conversations'; String get enterCredentials =>
'Enter your credentials to access your AI conversations';
@override @override
String get credentials => 'Credentials'; String get credentials => 'Credentials';
@@ -184,7 +188,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get connectToServer => 'Connect to Server'; String get connectToServer => 'Connect to Server';
@override @override
String get enterServerAddress => 'Enter your Open-WebUI server address to get started'; String get enterServerAddress =>
'Enter your Open-WebUI server address to get started';
@override @override
String get serverUrl => 'Server URL'; String get serverUrl => 'Server URL';
@@ -232,7 +237,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get demoBadge => 'Demo'; String get demoBadge => 'Demo';
@override @override
String get serverNotOpenWebUI => 'This does not appear to be an Open-WebUI server.'; String get serverNotOpenWebUI =>
'This does not appear to be an Open-WebUI server.';
@override @override
String get serverUrlEmpty => 'Server URL cannot be empty'; String get serverUrlEmpty => 'Server URL cannot be empty';
@@ -244,22 +250,27 @@ class AppLocalizationsEn extends AppLocalizations {
String get onlyHttpHttps => 'Only HTTP and HTTPS protocols are supported.'; String get onlyHttpHttps => 'Only HTTP and HTTPS protocols are supported.';
@override @override
String get serverAddressRequired => 'Server address is required (e.g., 192.168.1.10 or example.com).'; String get serverAddressRequired =>
'Server address is required (e.g., 192.168.1.10 or example.com).';
@override @override
String get portRange => 'Port must be between 1 and 65535.'; String get portRange => 'Port must be between 1 and 65535.';
@override @override
String get invalidIpFormat => 'Invalid IP address format. Use format like 192.168.1.10.'; String get invalidIpFormat =>
'Invalid IP address format. Use format like 192.168.1.10.';
@override @override
String get couldNotConnectGeneric => 'Couldn\'t connect. Double-check the address and try again.'; String get couldNotConnectGeneric =>
'Couldn\'t connect. Double-check the address and try again.';
@override @override
String get weCouldntReachServer => 'We couldn\'t reach the server. Check your connection and that the server is running.'; String get weCouldntReachServer =>
'We couldn\'t reach the server. Check your connection and that the server is running.';
@override @override
String get connectionTimedOut => 'Connection timed out. The server might be busy or blocked by a firewall.'; String get connectionTimedOut =>
'Connection timed out. The server might be busy or blocked by a firewall.';
@override @override
String get useHttpOrHttpsOnly => 'Use http:// or https:// only.'; String get useHttpOrHttpsOnly => 'Use http:// or https:// only.';
@@ -268,19 +279,23 @@ class AppLocalizationsEn extends AppLocalizations {
String get loginFailed => 'Login failed'; String get loginFailed => 'Login failed';
@override @override
String get invalidCredentials => 'Invalid username or password. Please try again.'; String get invalidCredentials =>
'Invalid username or password. Please try again.';
@override @override
String get serverRedirectingHttps => 'The server is redirecting requests. Check your server\'s HTTPS configuration.'; String get serverRedirectingHttps =>
'The server is redirecting requests. Check your server\'s HTTPS configuration.';
@override @override
String get unableToConnectServer => 'Unable to connect to server. Please check your connection.'; String get unableToConnectServer =>
'Unable to connect to server. Please check your connection.';
@override @override
String get requestTimedOut => 'The request timed out. Please try again.'; String get requestTimedOut => 'The request timed out. Please try again.';
@override @override
String get genericSignInFailed => 'We couldn\'t sign you in. Check your credentials and server settings.'; String get genericSignInFailed =>
'We couldn\'t sign you in. Check your credentials and server settings.';
@override @override
String get skip => 'Skip'; String get skip => 'Skip';
@@ -295,10 +310,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get onboardStartTitle => 'Start a conversation'; String get onboardStartTitle => 'Start a conversation';
@override @override
String get onboardStartSubtitle => 'Choose a model, then type below to begin. Tap New Chat anytime.'; String get onboardStartSubtitle =>
'Choose a model, then type below to begin. Tap New Chat anytime.';
@override @override
String get onboardStartBullet1 => 'Tap the model name in the top bar to switch models'; String get onboardStartBullet1 =>
'Tap the model name in the top bar to switch models';
@override @override
String get onboardStartBullet2 => 'Use New Chat to reset context'; String get onboardStartBullet2 => 'Use New Chat to reset context';
@@ -307,7 +324,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get onboardAttachTitle => 'Attach context'; String get onboardAttachTitle => 'Attach context';
@override @override
String get onboardAttachSubtitle => 'Ground responses by adding files or images.'; String get onboardAttachSubtitle =>
'Ground responses by adding files or images.';
@override @override
String get onboardAttachBullet1 => 'Files: PDFs, docs, datasets'; String get onboardAttachBullet1 => 'Files: PDFs, docs, datasets';
@@ -319,7 +337,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get onboardSpeakTitle => 'Speak naturally'; String get onboardSpeakTitle => 'Speak naturally';
@override @override
String get onboardSpeakSubtitle => 'Tap the mic to dictate with live waveform feedback.'; String get onboardSpeakSubtitle =>
'Tap the mic to dictate with live waveform feedback.';
@override @override
String get onboardSpeakBullet1 => 'Stop anytime; partial text is preserved'; String get onboardSpeakBullet1 => 'Stop anytime; partial text is preserved';
@@ -331,13 +350,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get onboardQuickTitle => 'Quick actions'; String get onboardQuickTitle => 'Quick actions';
@override @override
String get onboardQuickSubtitle => 'Use the topleft menu to open the chats list and navigation.'; String get onboardQuickSubtitle =>
'Use the topleft menu to open the chats list and navigation.';
@override @override
String get onboardQuickBullet1 => 'Tap the menu to open the chats list and navigation'; String get onboardQuickBullet1 =>
'Tap the menu to open the chats list and navigation';
@override @override
String get onboardQuickBullet2 => 'Jump instantly to New Chat, Files, or Profile'; String get onboardQuickBullet2 =>
'Jump instantly to New Chat, Files, or Profile';
@override @override
String get addAttachment => 'Add attachment'; String get addAttachment => 'Add attachment';
@@ -407,10 +429,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get offlineBanner => 'You\'re offline. Some features may be limited.'; String get offlineBanner => 'You\'re offline. Some features may be limited.';
@override @override
String get featureRequiresInternet => 'This feature requires an internet connection'; String get featureRequiresInternet =>
'This feature requires an internet connection';
@override @override
String get messagesWillSendWhenOnline => 'Messages will be sent when you\'re back online'; String get messagesWillSendWhenOnline =>
'Messages will be sent when you\'re back online';
@override @override
String get confirm => 'Confirm'; String get confirm => 'Confirm';
@@ -576,4 +600,78 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get aboutAppSubtitle => 'Conduit information and links'; String get aboutAppSubtitle => 'Conduit information and links';
@override
String get appLanguage => 'App language';
@override
String get typeBelowToBegin => 'Type below to begin';
@override
String get listening => 'Listening…';
@override
String get recording => 'Recording…';
@override
String get transcribing => 'Transcribing…';
@override
String get speakNow => 'Speak now…';
@override
String get chats => 'Chats';
@override
String get darkMode => 'Dark Mode';
@override
String get transcript => 'Transcript';
@override
String get pinned => 'Pinned';
@override
String get folders => 'Folders';
@override
String get archived => 'Archived';
@override
String get holdToTalk => 'Hold to talk';
@override
String get autoSend => 'Auto-send';
@override
String get stopListening => 'Stop listening';
@override
String get startListening => 'Start listening';
@override
String get start => 'Start';
@override
String get stop => 'Stop';
@override
String get web => 'Web';
@override
String get imageGen => 'Image Gen';
@override
String get webSearch => 'Web Search';
@override
String get webSearchDescription =>
'Let the assistant search the internet while answering.';
@override
String get imageGeneration => 'Image Generation';
@override
String get imageGenerationDescription =>
'Generate images from your prompt and attach them.';
} }

View File

@@ -30,7 +30,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get unableToLoadProfile => 'Impossible de charger le profil'; String get unableToLoadProfile => 'Impossible de charger le profil';
@override @override
String get pleaseCheckConnection => 'Veuillez vérifier votre connexion et réessayer'; String get pleaseCheckConnection =>
'Veuillez vérifier votre connexion et réessayer';
@override @override
String get account => 'Compte'; String get account => 'Compte';
@@ -116,7 +117,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get noFilesYet => 'Pas encore de fichiers'; String get noFilesYet => 'Pas encore de fichiers';
@override @override
String get uploadDocsPrompt => 'Importez des documents à utiliser dans vos conversations avec Conduit'; String get uploadDocsPrompt =>
'Importez des documents à utiliser dans vos conversations avec Conduit';
@override @override
String get uploadFirstFile => 'Importer votre premier fichier'; String get uploadFirstFile => 'Importer votre premier fichier';
@@ -125,7 +127,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get knowledgeBaseEmpty => 'La base de connaissances est vide'; String get knowledgeBaseEmpty => 'La base de connaissances est vide';
@override @override
String get createCollectionsPrompt => 'Créez des collections de documents liés pour une référence facile'; String get createCollectionsPrompt =>
'Créez des collections de documents liés pour une référence facile';
@override @override
String get chooseSourcePhoto => 'Choisir la source'; String get chooseSourcePhoto => 'Choisir la source';
@@ -151,7 +154,8 @@ class AppLocalizationsFr extends AppLocalizations {
} }
@override @override
String get kbCreationComingSoon => 'La création de la base de connaissances arrive bientôt !'; String get kbCreationComingSoon =>
'La création de la base de connaissances arrive bientôt !';
@override @override
String get backToServerSetup => 'Retour à la configuration du serveur'; String get backToServerSetup => 'Retour à la configuration du serveur';
@@ -163,7 +167,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get signIn => 'Se connecter'; String get signIn => 'Se connecter';
@override @override
String get enterCredentials => 'Entrez vos identifiants pour accéder à vos conversations IA'; String get enterCredentials =>
'Entrez vos identifiants pour accéder à vos conversations IA';
@override @override
String get credentials => 'Identifiants'; String get credentials => 'Identifiants';
@@ -184,7 +189,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get connectToServer => 'Se connecter au serveur'; String get connectToServer => 'Se connecter au serveur';
@override @override
String get enterServerAddress => 'Saisissez l\'adresse de votre serveur Open-WebUI pour commencer'; String get enterServerAddress =>
'Saisissez l\'adresse de votre serveur Open-WebUI pour commencer';
@override @override
String get serverUrl => 'URL du serveur'; String get serverUrl => 'URL du serveur';
@@ -193,7 +199,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get serverUrlHint => 'https://votre-serveur.com'; String get serverUrlHint => 'https://votre-serveur.com';
@override @override
String get enterServerUrlSemantic => 'Saisissez l\'URL ou l\'adresse IP de votre serveur'; String get enterServerUrlSemantic =>
'Saisissez l\'URL ou l\'adresse IP de votre serveur';
@override @override
String get headerName => 'Nom de l\'en-tête'; String get headerName => 'Nom de l\'en-tête';
@@ -223,7 +230,8 @@ class AppLocalizationsFr extends AppLocalizations {
String get demoModeActive => 'Mode démo activé'; String get demoModeActive => 'Mode démo activé';
@override @override
String get skipServerSetupTryDemo => 'Ignorer la configuration et essayer la démo'; String get skipServerSetupTryDemo =>
'Ignorer la configuration et essayer la démo';
@override @override
String get enterDemo => 'Entrer en démo'; String get enterDemo => 'Entrer en démo';
@@ -232,34 +240,42 @@ class AppLocalizationsFr extends AppLocalizations {
String get demoBadge => 'Démo'; String get demoBadge => 'Démo';
@override @override
String get serverNotOpenWebUI => 'Ceci ne semble pas être un serveur Open-WebUI.'; String get serverNotOpenWebUI =>
'Ceci ne semble pas être un serveur Open-WebUI.';
@override @override
String get serverUrlEmpty => 'L\'URL du serveur ne peut pas être vide'; String get serverUrlEmpty => 'L\'URL du serveur ne peut pas être vide';
@override @override
String get invalidUrlFormat => 'Format d\'URL invalide. Veuillez vérifier votre saisie.'; String get invalidUrlFormat =>
'Format d\'URL invalide. Veuillez vérifier votre saisie.';
@override @override
String get onlyHttpHttps => 'Seuls les protocoles HTTP et HTTPS sont pris en charge.'; String get onlyHttpHttps =>
'Seuls les protocoles HTTP et HTTPS sont pris en charge.';
@override @override
String get serverAddressRequired => 'Adresse du serveur requise (ex. 192.168.1.10 ou example.com).'; String get serverAddressRequired =>
'Adresse du serveur requise (ex. 192.168.1.10 ou example.com).';
@override @override
String get portRange => 'Le port doit être compris entre 1 et 65535.'; String get portRange => 'Le port doit être compris entre 1 et 65535.';
@override @override
String get invalidIpFormat => 'Format d\'IP invalide. Exemple : 192.168.1.10.'; String get invalidIpFormat =>
'Format d\'IP invalide. Exemple : 192.168.1.10.';
@override @override
String get couldNotConnectGeneric => 'Connexion impossible. Vérifiez l\'adresse et réessayez.'; String get couldNotConnectGeneric =>
'Connexion impossible. Vérifiez l\'adresse et réessayez.';
@override @override
String get weCouldntReachServer => 'Impossible d\'atteindre le serveur. Vérifiez la connexion et l\'état du serveur.'; String get weCouldntReachServer =>
'Impossible d\'atteindre le serveur. Vérifiez la connexion et l\'état du serveur.';
@override @override
String get connectionTimedOut => 'Délai d\'attente dépassé. Le serveur est peut-être occupé ou bloqué.'; String get connectionTimedOut =>
'Délai d\'attente dépassé. Le serveur est peut-être occupé ou bloqué.';
@override @override
String get useHttpOrHttpsOnly => 'Utilisez uniquement http:// ou https://.'; String get useHttpOrHttpsOnly => 'Utilisez uniquement http:// ou https://.';
@@ -268,19 +284,23 @@ class AppLocalizationsFr extends AppLocalizations {
String get loginFailed => 'Échec de la connexion'; String get loginFailed => 'Échec de la connexion';
@override @override
String get invalidCredentials => 'Nom d\'utilisateur ou mot de passe invalide. Réessayez.'; String get invalidCredentials =>
'Nom d\'utilisateur ou mot de passe invalide. Réessayez.';
@override @override
String get serverRedirectingHttps => 'Le serveur redirige les requêtes. Vérifiez la configuration HTTPS.'; String get serverRedirectingHttps =>
'Le serveur redirige les requêtes. Vérifiez la configuration HTTPS.';
@override @override
String get unableToConnectServer => 'Impossible de se connecter au serveur. Vérifiez votre connexion.'; String get unableToConnectServer =>
'Impossible de se connecter au serveur. Vérifiez votre connexion.';
@override @override
String get requestTimedOut => 'Délai d\'attente dépassé. Réessayez.'; String get requestTimedOut => 'Délai d\'attente dépassé. Réessayez.';
@override @override
String get genericSignInFailed => 'Connexion impossible. Vérifiez vos identifiants et le serveur.'; String get genericSignInFailed =>
'Connexion impossible. Vérifiez vos identifiants et le serveur.';
@override @override
String get skip => 'Ignorer'; String get skip => 'Ignorer';
@@ -295,22 +315,27 @@ class AppLocalizationsFr extends AppLocalizations {
String get onboardStartTitle => 'Commencer une conversation'; String get onboardStartTitle => 'Commencer une conversation';
@override @override
String get onboardStartSubtitle => 'Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.'; String get onboardStartSubtitle =>
'Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.';
@override @override
String get onboardStartBullet1 => 'Touchez le nom du modèle en haut pour changer'; String get onboardStartBullet1 =>
'Touchez le nom du modèle en haut pour changer';
@override @override
String get onboardStartBullet2 => 'Utilisez Nouveau chat pour réinitialiser le contexte'; String get onboardStartBullet2 =>
'Utilisez Nouveau chat pour réinitialiser le contexte';
@override @override
String get onboardAttachTitle => 'Ajouter du contexte'; String get onboardAttachTitle => 'Ajouter du contexte';
@override @override
String get onboardAttachSubtitle => 'Améliorez les réponses en ajoutant des fichiers ou des images.'; String get onboardAttachSubtitle =>
'Améliorez les réponses en ajoutant des fichiers ou des images.';
@override @override
String get onboardAttachBullet1 => 'Fichiers : PDF, documents, jeux de données'; String get onboardAttachBullet1 =>
'Fichiers : PDF, documents, jeux de données';
@override @override
String get onboardAttachBullet2 => 'Images : photos ou captures d\'écran'; String get onboardAttachBullet2 => 'Images : photos ou captures d\'écran';
@@ -319,25 +344,31 @@ class AppLocalizationsFr extends AppLocalizations {
String get onboardSpeakTitle => 'Parlez naturellement'; String get onboardSpeakTitle => 'Parlez naturellement';
@override @override
String get onboardSpeakSubtitle => 'Touchez le micro pour dicter avec retour visuel.'; String get onboardSpeakSubtitle =>
'Touchez le micro pour dicter avec retour visuel.';
@override @override
String get onboardSpeakBullet1 => 'Arrêtez à tout moment ; le texte partiel est conservé'; String get onboardSpeakBullet1 =>
'Arrêtez à tout moment ; le texte partiel est conservé';
@override @override
String get onboardSpeakBullet2 => 'Idéal pour des notes rapides ou de longs prompts'; String get onboardSpeakBullet2 =>
'Idéal pour des notes rapides ou de longs prompts';
@override @override
String get onboardQuickTitle => 'Actions rapides'; String get onboardQuickTitle => 'Actions rapides';
@override @override
String get onboardQuickSubtitle => 'Utilisez le menu en haut à gauche pour ouvrir la liste des chats et la navigation.'; String get onboardQuickSubtitle =>
'Utilisez le menu en haut à gauche pour ouvrir la liste des chats et la navigation.';
@override @override
String get onboardQuickBullet1 => 'Touchez le menu pour ouvrir les chats et la navigation'; String get onboardQuickBullet1 =>
'Touchez le menu pour ouvrir les chats et la navigation';
@override @override
String get onboardQuickBullet2 => 'Accédez rapidement à Nouveau chat, Fichiers ou Profil'; String get onboardQuickBullet2 =>
'Accédez rapidement à Nouveau chat, Fichiers ou Profil';
@override @override
String get addAttachment => 'Ajouter une pièce jointe'; String get addAttachment => 'Ajouter une pièce jointe';
@@ -404,13 +435,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get emptyImageData => 'Données d\'image vides'; String get emptyImageData => 'Données d\'image vides';
@override @override
String get offlineBanner => 'Vous êtes hors ligne. Certaines fonctions peuvent être limitées.'; String get offlineBanner =>
'Vous êtes hors ligne. Certaines fonctions peuvent être limitées.';
@override @override
String get featureRequiresInternet => 'Cette fonctionnalité nécessite une connexion Internet'; String get featureRequiresInternet =>
'Cette fonctionnalité nécessite une connexion Internet';
@override @override
String get messagesWillSendWhenOnline => 'Les messages seront envoyés lorsque vous serez de nouveau en ligne'; String get messagesWillSendWhenOnline =>
'Les messages seront envoyés lorsque vous serez de nouveau en ligne';
@override @override
String get confirm => 'Confirmer'; String get confirm => 'Confirmer';
@@ -576,4 +610,78 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get aboutAppSubtitle => 'Informations et liens Conduit'; String get aboutAppSubtitle => 'Informations et liens Conduit';
@override
String get appLanguage => 'Langue de l\'app';
@override
String get typeBelowToBegin => 'Tapez ci-dessous pour commencer';
@override
String get listening => 'Écoute…';
@override
String get recording => 'Enregistrement…';
@override
String get transcribing => 'Transcription…';
@override
String get speakNow => 'Parlez maintenant…';
@override
String get chats => 'Discussions';
@override
String get darkMode => 'Mode sombre';
@override
String get transcript => 'Transcription';
@override
String get pinned => 'Épinglés';
@override
String get folders => 'Dossiers';
@override
String get archived => 'Archivés';
@override
String get holdToTalk => 'Maintenir pour parler';
@override
String get autoSend => 'Envoi auto';
@override
String get stopListening => 'Arrêter l\'écoute';
@override
String get startListening => 'Commencer l\'écoute';
@override
String get start => 'Démarrer';
@override
String get stop => 'Arrêter';
@override
String get web => 'Web';
@override
String get imageGen => 'Gén. image';
@override
String get webSearch => 'Recherche Web';
@override
String get webSearchDescription =>
'Laissez l\'assistant rechercher sur Internet pendant la réponse.';
@override
String get imageGeneration => 'Génération d\'images';
@override
String get imageGenerationDescription =>
'Générez des images à partir de votre prompt et joignez-les.';
} }

View File

@@ -116,7 +116,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get noFilesYet => 'Ancora nessun file'; String get noFilesYet => 'Ancora nessun file';
@override @override
String get uploadDocsPrompt => 'Carica documenti da usare nelle conversazioni con Conduit'; String get uploadDocsPrompt =>
'Carica documenti da usare nelle conversazioni con Conduit';
@override @override
String get uploadFirstFile => 'Carica il tuo primo file'; String get uploadFirstFile => 'Carica il tuo primo file';
@@ -125,7 +126,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get knowledgeBaseEmpty => 'La base di conoscenza è vuota'; String get knowledgeBaseEmpty => 'La base di conoscenza è vuota';
@override @override
String get createCollectionsPrompt => 'Crea raccolte di documenti correlati per un rapido riferimento'; String get createCollectionsPrompt =>
'Crea raccolte di documenti correlati per un rapido riferimento';
@override @override
String get chooseSourcePhoto => 'Scegli origine'; String get chooseSourcePhoto => 'Scegli origine';
@@ -151,7 +153,8 @@ class AppLocalizationsIt extends AppLocalizations {
} }
@override @override
String get kbCreationComingSoon => 'La creazione della base di conoscenza arriverà presto!'; String get kbCreationComingSoon =>
'La creazione della base di conoscenza arriverà presto!';
@override @override
String get backToServerSetup => 'Torna alla configurazione del server'; String get backToServerSetup => 'Torna alla configurazione del server';
@@ -163,7 +166,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get signIn => 'Accedi'; String get signIn => 'Accedi';
@override @override
String get enterCredentials => 'Inserisci le credenziali per accedere alle conversazioni IA'; String get enterCredentials =>
'Inserisci le credenziali per accedere alle conversazioni IA';
@override @override
String get credentials => 'Credenziali'; String get credentials => 'Credenziali';
@@ -184,7 +188,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get connectToServer => 'Connetti al server'; String get connectToServer => 'Connetti al server';
@override @override
String get enterServerAddress => 'Inserisci l\'indirizzo del server Open-WebUI per iniziare'; String get enterServerAddress =>
'Inserisci l\'indirizzo del server Open-WebUI per iniziare';
@override @override
String get serverUrl => 'URL del server'; String get serverUrl => 'URL del server';
@@ -193,7 +198,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get serverUrlHint => 'https://tuo-server.com'; String get serverUrlHint => 'https://tuo-server.com';
@override @override
String get enterServerUrlSemantic => 'Inserisci l\'URL o l\'indirizzo IP del server'; String get enterServerUrlSemantic =>
'Inserisci l\'URL o l\'indirizzo IP del server';
@override @override
String get headerName => 'Nome header'; String get headerName => 'Nome header';
@@ -223,7 +229,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get demoModeActive => 'Modalità demo attiva'; String get demoModeActive => 'Modalità demo attiva';
@override @override
String get skipServerSetupTryDemo => 'Salta configurazione server e prova la demo'; String get skipServerSetupTryDemo =>
'Salta configurazione server e prova la demo';
@override @override
String get enterDemo => 'Entra in demo'; String get enterDemo => 'Entra in demo';
@@ -244,7 +251,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get onlyHttpHttps => 'Sono supportati solo i protocolli HTTP e HTTPS.'; String get onlyHttpHttps => 'Sono supportati solo i protocolli HTTP e HTTPS.';
@override @override
String get serverAddressRequired => 'Indirizzo server richiesto (es. 192.168.1.10 o example.com).'; String get serverAddressRequired =>
'Indirizzo server richiesto (es. 192.168.1.10 o example.com).';
@override @override
String get portRange => 'La porta deve essere tra 1 e 65535.'; String get portRange => 'La porta deve essere tra 1 e 65535.';
@@ -253,13 +261,16 @@ class AppLocalizationsIt extends AppLocalizations {
String get invalidIpFormat => 'Formato IP non valido. Esempio: 192.168.1.10.'; String get invalidIpFormat => 'Formato IP non valido. Esempio: 192.168.1.10.';
@override @override
String get couldNotConnectGeneric => 'Impossibile connettersi. Verifica l\'indirizzo e riprova.'; String get couldNotConnectGeneric =>
'Impossibile connettersi. Verifica l\'indirizzo e riprova.';
@override @override
String get weCouldntReachServer => 'Impossibile raggiungere il server. Verifica connessione e stato del server.'; String get weCouldntReachServer =>
'Impossibile raggiungere il server. Verifica connessione e stato del server.';
@override @override
String get connectionTimedOut => 'Tempo scaduto. Il server potrebbe essere occupato o bloccato.'; String get connectionTimedOut =>
'Tempo scaduto. Il server potrebbe essere occupato o bloccato.';
@override @override
String get useHttpOrHttpsOnly => 'Usa solo http:// o https://.'; String get useHttpOrHttpsOnly => 'Usa solo http:// o https://.';
@@ -268,19 +279,23 @@ class AppLocalizationsIt extends AppLocalizations {
String get loginFailed => 'Accesso non riuscito'; String get loginFailed => 'Accesso non riuscito';
@override @override
String get invalidCredentials => 'Nome utente o password non validi. Riprova.'; String get invalidCredentials =>
'Nome utente o password non validi. Riprova.';
@override @override
String get serverRedirectingHttps => 'Il server sta reindirizzando. Controlla la configurazione HTTPS.'; String get serverRedirectingHttps =>
'Il server sta reindirizzando. Controlla la configurazione HTTPS.';
@override @override
String get unableToConnectServer => 'Impossibile connettersi al server. Controlla la connessione.'; String get unableToConnectServer =>
'Impossibile connettersi al server. Controlla la connessione.';
@override @override
String get requestTimedOut => 'Richiesta scaduta. Riprova.'; String get requestTimedOut => 'Richiesta scaduta. Riprova.';
@override @override
String get genericSignInFailed => 'Impossibile accedere. Controlla credenziali e server.'; String get genericSignInFailed =>
'Impossibile accedere. Controlla credenziali e server.';
@override @override
String get skip => 'Salta'; String get skip => 'Salta';
@@ -295,10 +310,12 @@ class AppLocalizationsIt extends AppLocalizations {
String get onboardStartTitle => 'Inizia una conversazione'; String get onboardStartTitle => 'Inizia una conversazione';
@override @override
String get onboardStartSubtitle => 'Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.'; String get onboardStartSubtitle =>
'Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.';
@override @override
String get onboardStartBullet1 => 'Tocca il nome del modello in alto per cambiare'; String get onboardStartBullet1 =>
'Tocca il nome del modello in alto per cambiare';
@override @override
String get onboardStartBullet2 => 'Usa Nuova chat per azzerare il contesto'; String get onboardStartBullet2 => 'Usa Nuova chat per azzerare il contesto';
@@ -307,7 +324,8 @@ class AppLocalizationsIt extends AppLocalizations {
String get onboardAttachTitle => 'Aggiungi contesto'; String get onboardAttachTitle => 'Aggiungi contesto';
@override @override
String get onboardAttachSubtitle => 'Migliora le risposte aggiungendo file o immagini.'; String get onboardAttachSubtitle =>
'Migliora le risposte aggiungendo file o immagini.';
@override @override
String get onboardAttachBullet1 => 'File: PDF, documenti, dataset'; String get onboardAttachBullet1 => 'File: PDF, documenti, dataset';
@@ -319,10 +337,12 @@ class AppLocalizationsIt extends AppLocalizations {
String get onboardSpeakTitle => 'Parla in modo naturale'; String get onboardSpeakTitle => 'Parla in modo naturale';
@override @override
String get onboardSpeakSubtitle => 'Tocca il microfono per dettare con feedback visivo.'; String get onboardSpeakSubtitle =>
'Tocca il microfono per dettare con feedback visivo.';
@override @override
String get onboardSpeakBullet1 => 'Interrompi in qualsiasi momento; il testo parziale viene mantenuto'; String get onboardSpeakBullet1 =>
'Interrompi in qualsiasi momento; il testo parziale viene mantenuto';
@override @override
String get onboardSpeakBullet2 => 'Ottimo per note rapide o prompt lunghi'; String get onboardSpeakBullet2 => 'Ottimo per note rapide o prompt lunghi';
@@ -331,10 +351,12 @@ class AppLocalizationsIt extends AppLocalizations {
String get onboardQuickTitle => 'Azioni rapide'; String get onboardQuickTitle => 'Azioni rapide';
@override @override
String get onboardQuickSubtitle => 'Usa il menu in alto a sinistra per aprire l\'elenco chat e la navigazione.'; String get onboardQuickSubtitle =>
'Usa il menu in alto a sinistra per aprire l\'elenco chat e la navigazione.';
@override @override
String get onboardQuickBullet1 => 'Tocca il menu per aprire chat e navigazione'; String get onboardQuickBullet1 =>
'Tocca il menu per aprire chat e navigazione';
@override @override
String get onboardQuickBullet2 => 'Vai subito a Nuova chat, File o Profilo'; String get onboardQuickBullet2 => 'Vai subito a Nuova chat, File o Profilo';
@@ -404,13 +426,16 @@ class AppLocalizationsIt extends AppLocalizations {
String get emptyImageData => 'Dati immagine vuoti'; String get emptyImageData => 'Dati immagine vuoti';
@override @override
String get offlineBanner => 'Sei offline. Alcune funzioni potrebbero essere limitate.'; String get offlineBanner =>
'Sei offline. Alcune funzioni potrebbero essere limitate.';
@override @override
String get featureRequiresInternet => 'Questa funzione richiede una connessione Internet'; String get featureRequiresInternet =>
'Questa funzione richiede una connessione Internet';
@override @override
String get messagesWillSendWhenOnline => 'I messaggi verranno inviati quando tornerai online'; String get messagesWillSendWhenOnline =>
'I messaggi verranno inviati quando tornerai online';
@override @override
String get confirm => 'Conferma'; String get confirm => 'Conferma';
@@ -569,11 +594,86 @@ class AppLocalizationsIt extends AppLocalizations {
String get deleteChatTitle => 'Elimina chat'; String get deleteChatTitle => 'Elimina chat';
@override @override
String get deleteChatMessage => 'Questa chat verrà eliminata definitivamente.'; String get deleteChatMessage =>
'Questa chat verrà eliminata definitivamente.';
@override @override
String get aboutApp => 'Informazioni sull\'app'; String get aboutApp => 'Informazioni sull\'app';
@override @override
String get aboutAppSubtitle => 'Informazioni e link di Conduit'; String get aboutAppSubtitle => 'Informazioni e link di Conduit';
@override
String get appLanguage => 'Lingua dell\'app';
@override
String get typeBelowToBegin => 'Scrivi qui sotto per iniziare';
@override
String get listening => 'In ascolto…';
@override
String get recording => 'Registrazione…';
@override
String get transcribing => 'Trascrizione…';
@override
String get speakNow => 'Parla ora…';
@override
String get chats => 'Chat';
@override
String get darkMode => 'Tema scuro';
@override
String get transcript => 'Trascrizione';
@override
String get pinned => 'In evidenza';
@override
String get folders => 'Cartelle';
@override
String get archived => 'Archiviati';
@override
String get holdToTalk => 'Tieni premuto per parlare';
@override
String get autoSend => 'Invio automatico';
@override
String get stopListening => 'Interrompi ascolto';
@override
String get startListening => 'Avvia ascolto';
@override
String get start => 'Avvia';
@override
String get stop => 'Stop';
@override
String get web => 'Web';
@override
String get imageGen => 'Gen. immagini';
@override
String get webSearch => 'Ricerca Web';
@override
String get webSearchDescription =>
'Lascia che l\'assistente cerchi sul web mentre risponde.';
@override
String get imageGeneration => 'Generazione immagini';
@override
String get imageGenerationDescription =>
'Genera immagini dal prompt e allegale.';
} }

View File

@@ -15,7 +15,6 @@ import 'core/auth/auth_state_manager.dart';
import 'core/utils/debug_logger.dart'; import 'core/utils/debug_logger.dart';
import 'features/onboarding/views/onboarding_sheet.dart'; import 'features/onboarding/views/onboarding_sheet.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:conduit/l10n/app_localizations.dart'; import 'package:conduit/l10n/app_localizations.dart';
import 'features/chat/views/chat_page.dart'; import 'features/chat/views/chat_page.dart';
import 'features/navigation/views/splash_launcher_page.dart'; import 'features/navigation/views/splash_launcher_page.dart';
@@ -101,25 +100,19 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
navigatorKey: NavigationService.navigatorKey, navigatorKey: NavigationService.navigatorKey,
locale: locale, locale: locale,
localizationsDelegates: const [ localizationsDelegates: AppLocalizations.localizationsDelegates,
AppLocalizations.delegate, supportedLocales: AppLocalizations.supportedLocales,
GlobalMaterialLocalizations.delegate, localeListResolutionCallback: (deviceLocales, supported) {
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('de'),
Locale('fr'),
Locale('it'),
],
localeResolutionCallback: (deviceLocale, supported) {
if (locale != null) return locale; // User override wins if (locale != null) return locale; // User override wins
if (deviceLocale == null) return const Locale('en'); if (deviceLocales == null || deviceLocales.isEmpty) {
for (final loc in supported) { return supported.first;
if (loc.languageCode == deviceLocale.languageCode) return loc;
} }
return const Locale('en'); for (final device in deviceLocales) {
for (final loc in supported) {
if (loc.languageCode == device.languageCode) return loc;
}
}
return supported.first;
}, },
builder: (context, child) { builder: (context, child) {
// Keep a subtle fade for navigation transitions only // Keep a subtle fade for navigation transitions only
@@ -184,7 +177,8 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
if (authNavState == AuthNavigationState.error) { if (authNavState == AuthNavigationState.error) {
return _buildErrorState( return _buildErrorState(
ref.watch(authErrorProvider3) ?? AppLocalizations.of(context)!.errorMessage, ref.watch(authErrorProvider3) ??
AppLocalizations.of(context)!.errorMessage,
); );
} }
@@ -199,7 +193,9 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
loading: () => _buildInitialLoadingSkeleton(context), loading: () => _buildInitialLoadingSkeleton(context),
error: (error, stackTrace) { error: (error, stackTrace) {
DebugLogger.error('Server provider error', error); DebugLogger.error('Server provider error', error);
return _buildErrorState(AppLocalizations.of(context)!.unableToConnectServer); return _buildErrorState(
AppLocalizations.of(context)!.unableToConnectServer,
);
}, },
); );
}, },