feat: localisation with en, de, fr and it
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,7 +11,6 @@
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
AGENTS.md
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
|
||||
23
AGENTS.md
Normal file
23
AGENTS.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Conduit - Flutter Mobile App for Open-WebUI
|
||||
|
||||
## Build & Test Commands
|
||||
```bash
|
||||
flutter pub get # Install dependencies
|
||||
flutter pub run build_runner build --delete-conflicting-outputs # Generate code
|
||||
flutter analyze # Run static analysis
|
||||
flutter run -d ios/android # Run debug build
|
||||
flutter build apk --release # Build Android release
|
||||
flutter build ipa --release # Build iOS release
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
- **State Management**: Use Riverpod providers in `providers/` folders
|
||||
- **Architecture**: Follow clean architecture - `core/`, `features/`, `shared/`
|
||||
- **Imports**: Group by package/relative, use absolute paths for project files
|
||||
- **Models**: Use Freezed for data classes with `.freezed.dart` and `.g.dart` generated files
|
||||
- **Error Handling**: Use ApiErrorHandler and error interceptors, avoid print statements
|
||||
- **Naming**: snake_case files, PascalCase classes, camelCase methods/variables
|
||||
- **Async**: Prefer async/await over raw Futures, handle errors with try-catch
|
||||
- **Widgets**: Separate presentation (widgets/) from business logic (services/)
|
||||
- **UI Design**: Use AppTheme colors/styles and ConduitThemeExtension for consistent design
|
||||
- **Dependencies**: Check pubspec.yaml before adding packages - prefer existing solutions
|
||||
66
README.md
66
README.md
@@ -108,6 +108,70 @@ The app will request permissions for:
|
||||
- Camera access
|
||||
- Photo library access
|
||||
|
||||
## Localization (i18n)
|
||||
|
||||
- Supported locales: `en`, `de`, `fr`, `it`.
|
||||
- Uses Flutter's `gen_l10n` with ARB files and the `intl` package for date/number formatting.
|
||||
|
||||
### Install & Generate
|
||||
|
||||
- Install packages:
|
||||
- `flutter_localizations` (Flutter SDK)
|
||||
- `intl: ^0.20.2`
|
||||
- Files are under `lib/l10n/*.arb`. The template is `app_en.arb`.
|
||||
- Generate localizations:
|
||||
- `flutter gen-l10n`
|
||||
- or run a full build: `flutter pub get && flutter gen-l10n`
|
||||
|
||||
### Usage Examples
|
||||
|
||||
- Basic text:
|
||||
- `Text(AppLocalizations.of(context)!.appTitle)`
|
||||
- With placeholder:
|
||||
- `Text(AppLocalizations.of(context)!.dynamicContentWithPlaceholder('Alex'))`
|
||||
- Pluralization:
|
||||
- `Text(AppLocalizations.of(context)!.itemsCount(3))`
|
||||
- Date/time formatting:
|
||||
- `final dateText = DateFormat.yMMMMEEEEd(Localizations.localeOf(context).toString()).format(DateTime.now());`
|
||||
- `Text(dateText)`
|
||||
- Number formatting:
|
||||
- `final price = NumberFormat.currency(locale: Localizations.localeOf(context).toString(), symbol: '€').format(1234.56);`
|
||||
- `Text(price)`
|
||||
|
||||
### Add a New Language
|
||||
|
||||
- Create a new ARB file in `lib/l10n/`, e.g. `app_es.arb`.
|
||||
- Copy keys from `app_en.arb` and provide translated values.
|
||||
- Ensure placeholders and plural rules match the template.
|
||||
- Add the locale to `supportedLocales` in `MaterialApp` (see `lib/main.dart`).
|
||||
- Regenerate: `flutter gen-l10n`.
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Key naming: use lowerCamelCase (e.g., `loginButton`, `errorMessage`).
|
||||
- Include `@` metadata with `description` for context and `placeholders` with examples.
|
||||
- Prefer ICU plural/select syntax in ARB for quantities and genders.
|
||||
- Avoid concatenating strings at runtime; use placeholders in ARB.
|
||||
|
||||
### In‑App Locale Switching
|
||||
|
||||
- Open the Profile page → Settings tile → choose `System`, `English`, `Deutsch`, `Français`, or `Italiano`.
|
||||
- Selection persists across app launches.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- Build fails with ARB placeholder errors:
|
||||
- Ensure every placeholder has an example string and correct type.
|
||||
- Missing translation at runtime:
|
||||
- Flutter falls back to English; search for hard‑coded strings and replace with `AppLocalizations`.
|
||||
- iOS strings not changing:
|
||||
- Restart the app after changing system language or use the in‑app language selector.
|
||||
|
||||
### References
|
||||
|
||||
- Flutter localization: https://docs.flutter.dev/ui/accessibility-and-localization/internationalization
|
||||
- Intl package: https://pub.dev/packages/intl
|
||||
|
||||
## Architecture
|
||||
|
||||
The app follows a clean architecture pattern with:
|
||||
@@ -157,4 +221,4 @@ This project is licensed under the GPL3 License - see the LICENSE file for detai
|
||||
|
||||
## Support
|
||||
|
||||
For issues and feature requests, please use the [GitHub Issues](https://github.com/cogwheel0/conduit/issues) page.
|
||||
For issues and feature requests, please use the [GitHub Issues](https://github.com/cogwheel0/conduit/issues) page.
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'api_error_handler.dart';
|
||||
import 'api_error_interceptor.dart';
|
||||
import '../../shared/theme/app_theme.dart';
|
||||
import '../../shared/theme/theme_extensions.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
/// Enhanced error service with comprehensive error handling capabilities
|
||||
/// Provides unified error management across the application
|
||||
@@ -136,8 +137,8 @@ class EnhancedErrorService {
|
||||
action: isRetryableError && onRetry != null
|
||||
? SnackBarAction(
|
||||
label: retryDelay != null && retryDelay.inSeconds > 5
|
||||
? 'Retry (${retryDelay.inSeconds}s)'
|
||||
: 'Retry',
|
||||
? "${AppLocalizations.of(context)!.retry} (${retryDelay.inSeconds}s)"
|
||||
: AppLocalizations.of(context)!.retry,
|
||||
textColor: AppTheme.neutral50,
|
||||
onPressed: onRetry,
|
||||
)
|
||||
@@ -208,14 +209,14 @@ class EnhancedErrorService {
|
||||
Navigator.of(context).pop();
|
||||
onRetry();
|
||||
},
|
||||
child: const Text('Retry'),
|
||||
child: Text(AppLocalizations.of(context)!.retry),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
onDismiss?.call();
|
||||
},
|
||||
child: const Text('OK'),
|
||||
child: Text(AppLocalizations.of(context)!.ok),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -281,7 +282,7 @@ class EnhancedErrorService {
|
||||
ElevatedButton.icon(
|
||||
onPressed: onRetry,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Try Again'),
|
||||
label: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -79,6 +79,36 @@ class ThemeModeNotifier extends StateNotifier<ThemeMode> {
|
||||
}
|
||||
}
|
||||
|
||||
// Locale provider
|
||||
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale?>(
|
||||
(ref) {
|
||||
final storage = ref.watch(optimizedStorageServiceProvider);
|
||||
return LocaleNotifier(storage);
|
||||
},
|
||||
);
|
||||
|
||||
class LocaleNotifier extends StateNotifier<Locale?> {
|
||||
final OptimizedStorageService _storage;
|
||||
|
||||
LocaleNotifier(this._storage) : super(null) {
|
||||
_loadLocale();
|
||||
}
|
||||
|
||||
void _loadLocale() {
|
||||
final code = _storage.getLocaleCode();
|
||||
if (code != null && code.isNotEmpty) {
|
||||
state = Locale(code);
|
||||
} else {
|
||||
state = null; // system
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setLocale(Locale? locale) async {
|
||||
state = locale;
|
||||
await _storage.setLocaleCode(locale?.languageCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Server connection providers - optimized with caching
|
||||
final serverConfigsProvider = FutureProvider<List<ServerConfig>>((ref) async {
|
||||
final storage = ref.watch(optimizedStorageServiceProvider);
|
||||
|
||||
@@ -23,6 +23,7 @@ class OptimizedStorageService {
|
||||
static const String _activeServerIdKey = 'active_server_id';
|
||||
static const String _rememberCredentialsKey = 'remember_credentials';
|
||||
static const String _themeModeKey = 'theme_mode';
|
||||
static const String _localeCodeKey = 'locale_code_v1';
|
||||
static const String _localConversationsKey = 'local_conversations';
|
||||
static const String _onboardingSeenKey = 'onboarding_seen_v1';
|
||||
static const String _reviewerModeKey = 'reviewer_mode_v1';
|
||||
@@ -226,6 +227,20 @@ class OptimizedStorageService {
|
||||
await _prefs.setString(_themeModeKey, mode);
|
||||
}
|
||||
|
||||
/// Locale Management
|
||||
String? getLocaleCode() {
|
||||
// Returns a locale code like 'en', 'de', 'fr', 'it'. Null means system.
|
||||
return _prefs.getString(_localeCodeKey);
|
||||
}
|
||||
|
||||
Future<void> setLocaleCode(String? code) async {
|
||||
if (code == null || code.isEmpty) {
|
||||
await _prefs.remove(_localeCodeKey);
|
||||
} else {
|
||||
await _prefs.setString(_localeCodeKey, code);
|
||||
}
|
||||
}
|
||||
|
||||
/// Onboarding
|
||||
Future<bool> getOnboardingSeen() async {
|
||||
return _prefs.getBool(_onboardingSeenKey) ?? false;
|
||||
|
||||
@@ -182,12 +182,12 @@ class UserFriendlyErrorHandler {
|
||||
List<ErrorRecoveryAction> _getServerRecoveryActions() {
|
||||
return [
|
||||
ErrorRecoveryAction(
|
||||
label: 'Try Again',
|
||||
label: 'Retry',
|
||||
action: ErrorActionType.retry,
|
||||
description: 'Retry your request',
|
||||
),
|
||||
ErrorRecoveryAction(
|
||||
label: 'Wait & Retry',
|
||||
label: 'Retry',
|
||||
action: ErrorActionType.retryLater,
|
||||
description: 'Wait a moment then try again',
|
||||
),
|
||||
@@ -223,7 +223,7 @@ class UserFriendlyErrorHandler {
|
||||
description: 'Sign in to your account',
|
||||
),
|
||||
ErrorRecoveryAction(
|
||||
label: 'Try Again',
|
||||
label: 'Retry',
|
||||
action: ErrorActionType.retry,
|
||||
description: 'Retry the request',
|
||||
),
|
||||
@@ -280,7 +280,7 @@ class UserFriendlyErrorHandler {
|
||||
description: 'Select another file',
|
||||
),
|
||||
ErrorRecoveryAction(
|
||||
label: 'Try Again',
|
||||
label: 'Retry',
|
||||
action: ErrorActionType.retry,
|
||||
description: 'Retry the operation',
|
||||
),
|
||||
@@ -314,7 +314,7 @@ class UserFriendlyErrorHandler {
|
||||
description: 'Open app settings to grant permissions',
|
||||
),
|
||||
ErrorRecoveryAction(
|
||||
label: 'Try Again',
|
||||
label: 'Retry',
|
||||
action: ErrorActionType.retry,
|
||||
description: 'Retry after granting permission',
|
||||
),
|
||||
@@ -324,7 +324,7 @@ class UserFriendlyErrorHandler {
|
||||
List<ErrorRecoveryAction> _getGenericRecoveryActions() {
|
||||
return [
|
||||
ErrorRecoveryAction(
|
||||
label: 'Try Again',
|
||||
label: 'Retry',
|
||||
action: ErrorActionType.retry,
|
||||
description: 'Retry the operation',
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../shared/theme/theme_extensions.dart';
|
||||
import '../error/enhanced_error_service.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
/// Error boundary widget that catches and handles errors in child widgets
|
||||
class ErrorBoundary extends ConsumerStatefulWidget {
|
||||
@@ -122,7 +123,7 @@ class _ErrorBoundaryState extends ConsumerState<ErrorBoundary> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Something went wrong',
|
||||
AppLocalizations.of(context)!.errorMessage,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
),
|
||||
@@ -140,7 +141,7 @@ class _ErrorBoundaryState extends ConsumerState<ErrorBoundary> {
|
||||
FilledButton.icon(
|
||||
onPressed: _retry,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Try Again'),
|
||||
label: Text(AppLocalizations.of(context)!.retry),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -239,7 +240,7 @@ class AsyncErrorBoundary extends ConsumerWidget {
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Retry'),
|
||||
label: Text(AppLocalizations.of(context)!.retry),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -15,6 +15,7 @@ import '../../../shared/theme/theme_extensions.dart';
|
||||
import '../../../shared/widgets/conduit_components.dart';
|
||||
import '../../../core/auth/auth_state_manager.dart';
|
||||
import '../../../core/utils/debug_logger.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
class AuthenticationPage extends ConsumerStatefulWidget {
|
||||
final ServerConfig serverConfig;
|
||||
@@ -61,6 +62,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
}
|
||||
|
||||
Future<void> _signIn() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() {
|
||||
@@ -87,7 +89,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
|
||||
if (!success) {
|
||||
final authState = ref.read(authStateManagerProvider);
|
||||
throw Exception(authState.error ?? 'Login failed');
|
||||
throw Exception(authState.error ?? l10n.loginFailed);
|
||||
}
|
||||
|
||||
// Success - navigation will be handled by auth state change
|
||||
@@ -106,15 +108,15 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
|
||||
String _formatLoginError(String error) {
|
||||
if (error.contains('401') || error.contains('Unauthorized')) {
|
||||
return 'Invalid username or password. Please try again.';
|
||||
return AppLocalizations.of(context)!.invalidCredentials;
|
||||
} else if (error.contains('redirect')) {
|
||||
return 'The server is redirecting requests. Check your server\'s HTTPS configuration.';
|
||||
return AppLocalizations.of(context)!.serverRedirectingHttps;
|
||||
} else if (error.contains('SocketException')) {
|
||||
return 'Unable to connect to server. Please check your connection.';
|
||||
return AppLocalizations.of(context)!.unableToConnectServer;
|
||||
} else if (error.contains('timeout')) {
|
||||
return 'The request timed out. Please try again.';
|
||||
return AppLocalizations.of(context)!.requestTimedOut;
|
||||
}
|
||||
return 'We couldn\'t sign you in. Check your credentials and server settings.';
|
||||
return AppLocalizations.of(context)!.genericSignInFailed;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -201,7 +203,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
ConduitIconButton(
|
||||
icon: Platform.isIOS ? CupertinoIcons.back : Icons.arrow_back,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
tooltip: 'Back to server setup',
|
||||
tooltip: AppLocalizations.of(context)!.backToServerSetup,
|
||||
),
|
||||
const Spacer(),
|
||||
// Progress indicator (step 2 of 2)
|
||||
@@ -263,7 +265,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Connected to Server',
|
||||
AppLocalizations.of(context)!.connectedToServer,
|
||||
style: context.conduitTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context.conduitTheme.success,
|
||||
@@ -302,7 +304,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
),
|
||||
const SizedBox(height: Spacing.lg),
|
||||
Text(
|
||||
'Sign In',
|
||||
AppLocalizations.of(context)!.signIn,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.conduitTheme.headingLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -314,7 +316,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
Text(
|
||||
'Enter your credentials to access your AI conversations',
|
||||
AppLocalizations.of(context)!.enterCredentials,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
@@ -370,7 +372,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
icon: Platform.isIOS
|
||||
? CupertinoIcons.person_circle
|
||||
: Icons.account_circle_outlined,
|
||||
label: 'Credentials',
|
||||
label: AppLocalizations.of(context)!.credentials,
|
||||
isSelected: !_useApiKey,
|
||||
onTap: () => setState(() => _useApiKey = false),
|
||||
),
|
||||
@@ -380,7 +382,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
icon: Platform.isIOS
|
||||
? CupertinoIcons.lock_shield
|
||||
: Icons.vpn_key_outlined,
|
||||
label: 'API Key',
|
||||
label: AppLocalizations.of(context)!.apiKey,
|
||||
isSelected: _useApiKey,
|
||||
onTap: () => setState(() => _useApiKey = true),
|
||||
),
|
||||
@@ -469,7 +471,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
key: const ValueKey('api_key_form'),
|
||||
children: [
|
||||
AccessibleFormField(
|
||||
label: 'API Key',
|
||||
label: AppLocalizations.of(context)!.apiKey,
|
||||
hint: 'sk-...',
|
||||
controller: _apiKeyController,
|
||||
validator: InputValidationService.combine([
|
||||
@@ -477,7 +479,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
(value) => InputValidationService.validateMinLength(
|
||||
value,
|
||||
10,
|
||||
fieldName: 'API Key',
|
||||
fieldName: AppLocalizations.of(context)!.apiKey,
|
||||
),
|
||||
]),
|
||||
obscureText: _obscurePassword,
|
||||
@@ -513,7 +515,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
key: const ValueKey('credentials_form'),
|
||||
children: [
|
||||
AccessibleFormField(
|
||||
label: 'Username or Email',
|
||||
label: AppLocalizations.of(context)!.usernameOrEmail,
|
||||
hint: 'Enter your username or email',
|
||||
controller: _usernameController,
|
||||
validator: InputValidationService.combine([
|
||||
@@ -531,7 +533,7 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
),
|
||||
const SizedBox(height: Spacing.lg),
|
||||
AccessibleFormField(
|
||||
label: 'Password',
|
||||
label: AppLocalizations.of(context)!.password,
|
||||
hint: 'Enter your password',
|
||||
controller: _passwordController,
|
||||
validator: InputValidationService.combine([
|
||||
@@ -576,8 +578,8 @@ class _AuthenticationPageState extends ConsumerState<AuthenticationPage> {
|
||||
text: _isSigningIn
|
||||
? 'Signing in...'
|
||||
: _useApiKey
|
||||
? 'Sign in with API Key'
|
||||
: 'Sign In',
|
||||
? AppLocalizations.of(context)!.signInWithApiKey
|
||||
: AppLocalizations.of(context)!.signIn,
|
||||
icon: _isSigningIn
|
||||
? null
|
||||
: (Platform.isIOS
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/widgets/error_boundary.dart';
|
||||
import '../../../shared/theme/theme_extensions.dart';
|
||||
import '../../../shared/widgets/conduit_components.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import 'server_connection_page.dart';
|
||||
|
||||
/// Entry point for the connection and sign-in flow
|
||||
@@ -26,12 +27,12 @@ class ConnectAndSignInPage extends ConsumerWidget {
|
||||
return ErrorBoundary(
|
||||
child: Scaffold(
|
||||
backgroundColor: context.conduitTheme.surfaceBackground,
|
||||
body: const Center(
|
||||
body: Center(
|
||||
child: ConduitLoadingIndicator(
|
||||
message: 'Loading...',
|
||||
message: AppLocalizations.of(context)!.loadingContent,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
import '../../../core/models/server_config.dart';
|
||||
import '../../../core/providers/app_providers.dart';
|
||||
@@ -116,7 +117,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
|
||||
String _validateAndFormatUrl(String input) {
|
||||
if (input.isEmpty) {
|
||||
throw Exception('Server URL cannot be empty');
|
||||
throw Exception(AppLocalizations.of(context)!.serverUrlEmpty);
|
||||
}
|
||||
|
||||
// Clean up the input
|
||||
@@ -135,29 +136,29 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
// Parse and validate the URI
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri == null) {
|
||||
throw Exception('Invalid URL format. Please check your input.');
|
||||
throw Exception(AppLocalizations.of(context)!.invalidUrlFormat);
|
||||
}
|
||||
|
||||
// Validate scheme
|
||||
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
||||
throw Exception('Only HTTP and HTTPS protocols are supported.');
|
||||
throw Exception(AppLocalizations.of(context)!.onlyHttpHttps);
|
||||
}
|
||||
|
||||
// Validate host
|
||||
if (uri.host.isEmpty) {
|
||||
throw Exception('Server address is required (e.g., 192.168.1.10 or example.com).');
|
||||
throw Exception(AppLocalizations.of(context)!.serverAddressRequired);
|
||||
}
|
||||
|
||||
// Validate port if specified
|
||||
if (uri.hasPort) {
|
||||
if (uri.port < 1 || uri.port > 65535) {
|
||||
throw Exception('Port must be between 1 and 65535.');
|
||||
throw Exception(AppLocalizations.of(context)!.portRange);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate IP address format if it looks like an IP
|
||||
if (_isIPAddress(uri.host) && !_isValidIPAddress(uri.host)) {
|
||||
throw Exception('Invalid IP address format. Use format like 192.168.1.10.');
|
||||
throw Exception(AppLocalizations.of(context)!.invalidIpFormat);
|
||||
}
|
||||
|
||||
return url;
|
||||
@@ -192,15 +193,15 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
|
||||
// Handle specific error types
|
||||
if (error.contains('SocketException')) {
|
||||
return 'We couldn\'t reach the server. Check your connection and that the server is running.';
|
||||
return AppLocalizations.of(context)!.weCouldntReachServer;
|
||||
} else if (error.contains('timeout')) {
|
||||
return 'Connection timed out. The server might be busy or blocked by a firewall.';
|
||||
return AppLocalizations.of(context)!.connectionTimedOut;
|
||||
} else if (error.contains('Server URL cannot be empty')) {
|
||||
return 'Please enter a server address.';
|
||||
return AppLocalizations.of(context)!.serverUrlEmpty;
|
||||
} else if (error.contains('Invalid URL format')) {
|
||||
return 'Invalid server address format. Examples:\n• 192.168.1.10:3000\n• example.com\n• https://myserver.com';
|
||||
return AppLocalizations.of(context)!.invalidUrlFormat;
|
||||
} else if (error.contains('Only HTTP and HTTPS')) {
|
||||
return 'Use http:// or https:// only.';
|
||||
return AppLocalizations.of(context)!.useHttpOrHttpsOnly;
|
||||
} else if (error.contains('Server address is required')) {
|
||||
return cleanError;
|
||||
} else if (error.contains('Port must be between')) {
|
||||
@@ -208,10 +209,10 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
} else if (error.contains('Invalid IP address format')) {
|
||||
return cleanError;
|
||||
} else if (error.contains('This does not appear to be an Open-WebUI server')) {
|
||||
return 'This server doesn\'t appear to be running Open-WebUI. Please check the address.';
|
||||
return AppLocalizations.of(context)!.serverNotOpenWebUI;
|
||||
}
|
||||
|
||||
return 'Couldn\'t connect. Double-check the address and try again.';
|
||||
return AppLocalizations.of(context)!.couldNotConnectGeneric;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -379,7 +380,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'Connect to Server',
|
||||
AppLocalizations.of(context)!.connectToServer,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.conduitTheme.headingLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -391,7 +392,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
Text(
|
||||
'Enter your Open-WebUI server address to get started',
|
||||
AppLocalizations.of(context)!.enterServerAddress,
|
||||
textAlign: TextAlign.center,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
@@ -611,7 +612,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: AccessibleFormField(
|
||||
label: 'Header Name',
|
||||
label: AppLocalizations.of(context)!.headerName,
|
||||
hint: 'X-Custom-Header',
|
||||
controller: _headerKeyController,
|
||||
validator: (value) => _validateHeaderKey(value ?? ''),
|
||||
@@ -624,8 +625,8 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: AccessibleFormField(
|
||||
label: 'Header Value',
|
||||
hint: 'api-key-123 or Bearer token',
|
||||
label: AppLocalizations.of(context)!.headerValue,
|
||||
hint: AppLocalizations.of(context)!.headerValueHint,
|
||||
controller: _headerValueController,
|
||||
validator: (value) => _validateHeaderValue(value ?? ''),
|
||||
semanticLabel: 'Enter header value',
|
||||
@@ -638,8 +639,8 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
icon: Platform.isIOS ? CupertinoIcons.plus : Icons.add,
|
||||
onPressed: _customHeaders.length >= 10 ? null : _addCustomHeader,
|
||||
tooltip: _customHeaders.length >= 10
|
||||
? 'Maximum headers reached'
|
||||
: 'Add header',
|
||||
? AppLocalizations.of(context)!.maximumHeadersReached
|
||||
: AppLocalizations.of(context)!.addHeader,
|
||||
backgroundColor: _customHeaders.length >= 10
|
||||
? context.conduitTheme.surfaceContainer
|
||||
: context.conduitTheme.buttonPrimary,
|
||||
@@ -694,7 +695,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
ConduitIconButton(
|
||||
icon: Platform.isIOS ? CupertinoIcons.xmark : Icons.close,
|
||||
onPressed: () => _removeCustomHeader(entry.key),
|
||||
tooltip: 'Remove header',
|
||||
tooltip: AppLocalizations.of(context)!.removeHeader,
|
||||
backgroundColor: context.conduitTheme.error.withValues(alpha: 0.1),
|
||||
iconColor: context.conduitTheme.error,
|
||||
isCompact: true,
|
||||
@@ -710,7 +711,9 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: Spacing.lg),
|
||||
child: ConduitButton(
|
||||
text: _isConnecting ? 'Connecting...' : 'Connect to Server',
|
||||
text: _isConnecting
|
||||
? AppLocalizations.of(context)!.connecting
|
||||
: AppLocalizations.of(context)!.connectToServerButton,
|
||||
icon: _isConnecting
|
||||
? null
|
||||
: (Platform.isIOS ? CupertinoIcons.arrow_right : Icons.arrow_forward),
|
||||
@@ -866,4 +869,4 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
|
||||
});
|
||||
HapticFeedback.lightImpact();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../../../shared/theme/theme_extensions.dart';
|
||||
import '../../../shared/widgets/markdown/streaming_markdown_widget.dart';
|
||||
import '../../../core/utils/reasoning_parser.dart';
|
||||
import 'enhanced_image_attachment.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
class AssistantMessageWidget extends ConsumerStatefulWidget {
|
||||
final dynamic message;
|
||||
@@ -559,7 +560,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
icon: Platform.isIOS
|
||||
? CupertinoIcons.arrow_clockwise
|
||||
: Icons.refresh,
|
||||
label: 'Retry',
|
||||
label: AppLocalizations.of(context)!.retry,
|
||||
onTap: widget.onRegenerate,
|
||||
),
|
||||
] else ...[
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:dio/dio.dart' as dio;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import '../../../shared/theme/theme_extensions.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import '../../../core/providers/app_providers.dart';
|
||||
import '../../auth/providers/unified_auth_providers.dart';
|
||||
|
||||
@@ -74,6 +75,7 @@ class _EnhancedImageAttachmentState
|
||||
}
|
||||
|
||||
Future<void> _loadImage() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
// Check global cache first
|
||||
if (_globalImageCache.containsKey(widget.attachmentId)) {
|
||||
if (mounted) {
|
||||
@@ -142,7 +144,7 @@ class _EnhancedImageAttachmentState
|
||||
return;
|
||||
} else {
|
||||
// If API service is not available, show error
|
||||
final error = 'Unable to load image: API service not available';
|
||||
final error = l10n.unableToLoadImage;
|
||||
_globalErrorStates[widget.attachmentId] = error;
|
||||
_globalLoadingStates[widget.attachmentId] = false;
|
||||
if (mounted) {
|
||||
@@ -157,7 +159,7 @@ class _EnhancedImageAttachmentState
|
||||
|
||||
final api = ref.read(apiServiceProvider);
|
||||
if (api == null) {
|
||||
final error = 'API service not available';
|
||||
final error = l10n.apiUnavailable;
|
||||
_globalErrorStates[widget.attachmentId] = error;
|
||||
_globalLoadingStates[widget.attachmentId] = false;
|
||||
if (mounted) {
|
||||
@@ -176,7 +178,7 @@ class _EnhancedImageAttachmentState
|
||||
final ext = fileName.toLowerCase().split('.').last;
|
||||
|
||||
if (!['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].contains(ext)) {
|
||||
final error = 'Not an image file: $fileName';
|
||||
final error = l10n.notAnImageFile(fileName);
|
||||
_globalErrorStates[widget.attachmentId] = error;
|
||||
_globalLoadingStates[widget.attachmentId] = false;
|
||||
if (mounted) {
|
||||
@@ -214,7 +216,7 @@ class _EnhancedImageAttachmentState
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
final error = 'Failed to load image: ${e.toString()}';
|
||||
final error = l10n.failedToLoadImage(e.toString());
|
||||
_globalErrorStates[widget.attachmentId] = error;
|
||||
_globalLoadingStates[widget.attachmentId] = false;
|
||||
if (mounted) {
|
||||
@@ -443,7 +445,7 @@ class _EnhancedImageAttachmentState
|
||||
if (commaIndex != -1) {
|
||||
actualBase64 = _cachedImageData!.substring(commaIndex + 1);
|
||||
} else {
|
||||
throw Exception('Invalid data URL format');
|
||||
throw Exception(AppLocalizations.of(context)!.invalidDataUrl);
|
||||
}
|
||||
} else {
|
||||
actualBase64 = _cachedImageData!;
|
||||
@@ -456,14 +458,14 @@ class _EnhancedImageAttachmentState
|
||||
fit: BoxFit.cover,
|
||||
gaplessPlayback: true, // Prevents flashing during rebuilds
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
_errorMessage = 'Failed to decode image';
|
||||
_errorMessage = AppLocalizations.of(context)!.failedToDecodeImage;
|
||||
return _buildErrorState();
|
||||
},
|
||||
);
|
||||
|
||||
return _wrapImage(imageWidget);
|
||||
} catch (e) {
|
||||
_errorMessage = 'Invalid image format';
|
||||
_errorMessage = AppLocalizations.of(context)!.invalidImageFormat;
|
||||
return _buildErrorState();
|
||||
}
|
||||
}
|
||||
@@ -650,6 +652,7 @@ class FullScreenImageViewer extends ConsumerWidget {
|
||||
}
|
||||
|
||||
Future<void> _shareImage(BuildContext context, WidgetRef ref) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
try {
|
||||
Uint8List bytes;
|
||||
String? fileExtension;
|
||||
@@ -679,7 +682,7 @@ class FullScreenImageViewer extends ConsumerWidget {
|
||||
);
|
||||
final data = response.data;
|
||||
if (data == null || data.isEmpty) {
|
||||
throw Exception('Empty image data');
|
||||
throw Exception(l10n.emptyImageData);
|
||||
}
|
||||
bytes = Uint8List.fromList(data);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../tools/widgets/unified_tools_modal.dart';
|
||||
import '../../tools/providers/tools_providers.dart';
|
||||
|
||||
import '../../../shared/utils/platform_utils.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
class ModernChatInput extends ConsumerStatefulWidget {
|
||||
final Function(String) onSendMessage;
|
||||
@@ -264,7 +265,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
onTap: widget.enabled
|
||||
? _showAttachmentOptions
|
||||
: null,
|
||||
tooltip: 'Add attachment',
|
||||
tooltip: AppLocalizations.of(context)!.addAttachment,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
],
|
||||
@@ -272,8 +273,8 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
textField: true,
|
||||
label: 'Message input',
|
||||
hint: 'Type your message',
|
||||
label: AppLocalizations.of(context)!.messageInputLabel,
|
||||
hint: AppLocalizations.of(context)!.messageInputHint,
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
@@ -291,7 +292,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
color: context.conduitTheme.inputText,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Message...',
|
||||
hintText: AppLocalizations.of(context)!.messageHintText,
|
||||
hintStyle: TextStyle(
|
||||
color:
|
||||
context.conduitTheme.inputPlaceholder,
|
||||
@@ -363,7 +364,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
onTap: widget.enabled
|
||||
? _showAttachmentOptions
|
||||
: null,
|
||||
tooltip: 'Add attachment',
|
||||
tooltip: AppLocalizations.of(context)!.addAttachment,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
// Tools button
|
||||
@@ -374,7 +375,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
_showUnifiedToolsModal();
|
||||
}
|
||||
: null,
|
||||
tooltip: 'Tools',
|
||||
tooltip: AppLocalizations.of(context)!.tools,
|
||||
isActive:
|
||||
ref
|
||||
.watch(selectedToolIdsProvider)
|
||||
@@ -391,7 +392,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
onTap: widget.enabled
|
||||
? widget.onVoiceInput
|
||||
: null,
|
||||
tooltip: 'Voice input',
|
||||
tooltip: AppLocalizations.of(context)!.voiceInput,
|
||||
isActive: _isRecording,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
@@ -431,7 +432,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
// Generating -> STOP variant
|
||||
if (isGenerating) {
|
||||
return Tooltip(
|
||||
message: 'Stop generating',
|
||||
message: AppLocalizations.of(context)!.stopGenerating,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -482,7 +483,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
|
||||
// Default SEND variant
|
||||
return Tooltip(
|
||||
message: enabled ? 'Send message' : 'Send',
|
||||
message: enabled ? AppLocalizations.of(context)!.sendMessage : AppLocalizations.of(context)!.send,
|
||||
child: Opacity(
|
||||
opacity: enabled ? Alpha.primary : Alpha.disabled,
|
||||
child: IgnorePointer(
|
||||
@@ -626,7 +627,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
Expanded(
|
||||
child: _buildAttachmentOption(
|
||||
icon: Platform.isIOS ? CupertinoIcons.doc : Icons.attach_file,
|
||||
label: 'File',
|
||||
label: AppLocalizations.of(context)!.file,
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
Navigator.pop(context); // Close modal
|
||||
@@ -637,7 +638,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
Expanded(
|
||||
child: _buildAttachmentOption(
|
||||
icon: Platform.isIOS ? CupertinoIcons.photo : Icons.image,
|
||||
label: 'Photo',
|
||||
label: AppLocalizations.of(context)!.photo,
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
Navigator.pop(context); // Close modal
|
||||
@@ -650,7 +651,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
icon: Platform.isIOS
|
||||
? CupertinoIcons.camera
|
||||
: Icons.camera_alt,
|
||||
label: 'Camera',
|
||||
label: AppLocalizations.of(context)!.camera,
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
Navigator.pop(context); // Close modal
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../../../shared/widgets/improved_loading_states.dart';
|
||||
|
||||
import '../../../shared/utils/ui_utils.dart';
|
||||
import '../../../shared/widgets/sheet_handle.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
/// Files page for managing documents and uploads
|
||||
class WorkspacePage extends ConsumerStatefulWidget {
|
||||
@@ -108,10 +109,10 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
size: IconSize.button,
|
||||
),
|
||||
onPressed: () => NavigationService.goBack(),
|
||||
tooltip: 'Back',
|
||||
tooltip: AppLocalizations.of(context)!.back,
|
||||
),
|
||||
title: Text(
|
||||
'Workspace',
|
||||
AppLocalizations.of(context)!.workspace,
|
||||
style: AppTypography.headlineSmallStyle.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -162,7 +163,7 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
Expanded(
|
||||
child: _buildTabButton(
|
||||
index: 0,
|
||||
label: 'Recent Files',
|
||||
label: AppLocalizations.of(context)!.recentFiles,
|
||||
isSelected: _selectedTab == 0,
|
||||
),
|
||||
),
|
||||
@@ -170,7 +171,7 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
Expanded(
|
||||
child: _buildTabButton(
|
||||
index: 1,
|
||||
label: 'Knowledge Base',
|
||||
label: AppLocalizations.of(context)!.knowledgeBase,
|
||||
isSelected: _selectedTab == 1,
|
||||
),
|
||||
),
|
||||
@@ -229,11 +230,10 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
ios: CupertinoIcons.doc,
|
||||
android: Icons.description_outlined,
|
||||
),
|
||||
title: 'No files yet',
|
||||
subtitle:
|
||||
'Upload documents to reference in your conversations with Conduit',
|
||||
title: AppLocalizations.of(context)!.noFilesYet,
|
||||
subtitle: AppLocalizations.of(context)!.uploadDocsPrompt,
|
||||
onAction: _showUploadOptions,
|
||||
actionLabel: 'Upload your first file',
|
||||
actionLabel: AppLocalizations.of(context)!.uploadFirstFile,
|
||||
showAnimation: true,
|
||||
),
|
||||
).animate().fadeIn(
|
||||
@@ -251,8 +251,8 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
ios: CupertinoIcons.book,
|
||||
android: Icons.library_books,
|
||||
),
|
||||
title: 'Knowledge base is empty',
|
||||
subtitle: 'Create collections of related documents for easy reference',
|
||||
title: AppLocalizations.of(context)!.knowledgeBaseEmpty,
|
||||
subtitle: AppLocalizations.of(context)!.createCollectionsPrompt,
|
||||
onAction: _showKnowledgeBaseOptions,
|
||||
actionLabel: 'Create knowledge base',
|
||||
showAnimation: true,
|
||||
@@ -293,7 +293,7 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(Spacing.modalPadding),
|
||||
child: Text(
|
||||
'Upload File',
|
||||
AppLocalizations.of(context)!.uploadFileTitle,
|
||||
style: context.conduitTheme.headingSmall?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -307,8 +307,8 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
ios: CupertinoIcons.camera,
|
||||
android: Icons.camera_alt,
|
||||
),
|
||||
title: 'Take Photo',
|
||||
subtitle: 'Capture a document or image',
|
||||
title: AppLocalizations.of(context)!.takePhoto,
|
||||
subtitle: AppLocalizations.of(context)!.captureDocumentOrImage,
|
||||
onTap: () => _handleUploadOption('camera'),
|
||||
),
|
||||
_buildUploadOption(
|
||||
@@ -316,8 +316,8 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
ios: CupertinoIcons.photo,
|
||||
android: Icons.photo_library,
|
||||
),
|
||||
title: 'Photo Library',
|
||||
subtitle: 'Choose from your photos',
|
||||
title: AppLocalizations.of(context)!.chooseFromGallery,
|
||||
subtitle: AppLocalizations.of(context)!.chooseFromGallery,
|
||||
onTap: () => _handleUploadOption('gallery'),
|
||||
),
|
||||
_buildUploadOption(
|
||||
@@ -325,8 +325,8 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
ios: CupertinoIcons.doc,
|
||||
android: Icons.description,
|
||||
),
|
||||
title: 'Document',
|
||||
subtitle: 'PDF, Word, or text file',
|
||||
title: AppLocalizations.of(context)!.document,
|
||||
subtitle: AppLocalizations.of(context)!.documentHint,
|
||||
onTap: () => _handleUploadOption('document'),
|
||||
),
|
||||
|
||||
@@ -430,10 +430,16 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage>
|
||||
|
||||
void _handleUploadOption(String type) {
|
||||
NavigationService.goBack();
|
||||
UiUtils.showMessage(context, 'File upload for $type is coming soon!');
|
||||
UiUtils.showMessage(
|
||||
context,
|
||||
AppLocalizations.of(context)!.fileUploadComingSoon(type),
|
||||
);
|
||||
}
|
||||
|
||||
void _showKnowledgeBaseOptions() {
|
||||
UiUtils.showMessage(context, 'Knowledge base creation is coming soon!');
|
||||
UiUtils.showMessage(
|
||||
context,
|
||||
AppLocalizations.of(context)!.kbCreationComingSoon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import '../../../shared/theme/theme_extensions.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import '../../../shared/widgets/sheet_handle.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
class OnboardingSheet extends StatefulWidget {
|
||||
const OnboardingSheet({super.key});
|
||||
@@ -14,44 +15,39 @@ class OnboardingSheet extends StatefulWidget {
|
||||
class _OnboardingSheetState extends State<OnboardingSheet> {
|
||||
final PageController _controller = PageController();
|
||||
int _index = 0;
|
||||
late List<_OnboardingPage> _pages;
|
||||
|
||||
final List<_OnboardingPage> _pages = const [
|
||||
_OnboardingPage(
|
||||
title: 'Start a conversation',
|
||||
subtitle:
|
||||
'Choose a model, then type below to begin. Tap New Chat anytime.',
|
||||
icon: CupertinoIcons.chat_bubble_2,
|
||||
bullets: [
|
||||
'Tap the model name in the top bar to switch models',
|
||||
'Use New Chat to reset context',
|
||||
],
|
||||
),
|
||||
_OnboardingPage(
|
||||
title: 'Attach context',
|
||||
subtitle: 'Ground responses by adding files or images.',
|
||||
icon: CupertinoIcons.doc_on_doc,
|
||||
bullets: ['Files: PDFs, docs, datasets', 'Images: photos or screenshots'],
|
||||
),
|
||||
_OnboardingPage(
|
||||
title: 'Speak naturally',
|
||||
subtitle: 'Tap the mic to dictate with live waveform feedback.',
|
||||
icon: CupertinoIcons.mic_fill,
|
||||
bullets: [
|
||||
'Stop anytime; partial text is preserved',
|
||||
'Great for quick notes or long prompts',
|
||||
],
|
||||
),
|
||||
_OnboardingPage(
|
||||
title: 'Quick actions',
|
||||
subtitle:
|
||||
'Use the top‑left menu to open the chats list and navigation.',
|
||||
icon: CupertinoIcons.line_horizontal_3,
|
||||
bullets: [
|
||||
'Tap the menu to open the chats list and navigation',
|
||||
'Jump instantly to New Chat, Files, or Profile',
|
||||
],
|
||||
),
|
||||
];
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
_pages = [
|
||||
_OnboardingPage(
|
||||
title: l10n.onboardStartTitle,
|
||||
subtitle: l10n.onboardStartSubtitle,
|
||||
icon: CupertinoIcons.chat_bubble_2,
|
||||
bullets: [l10n.onboardStartBullet1, l10n.onboardStartBullet2],
|
||||
),
|
||||
_OnboardingPage(
|
||||
title: l10n.onboardAttachTitle,
|
||||
subtitle: l10n.onboardAttachSubtitle,
|
||||
icon: CupertinoIcons.doc_on_doc,
|
||||
bullets: [l10n.onboardAttachBullet1, l10n.onboardAttachBullet2],
|
||||
),
|
||||
_OnboardingPage(
|
||||
title: l10n.onboardSpeakTitle,
|
||||
subtitle: l10n.onboardSpeakSubtitle,
|
||||
icon: CupertinoIcons.mic_fill,
|
||||
bullets: [l10n.onboardSpeakBullet1, l10n.onboardSpeakBullet2],
|
||||
),
|
||||
_OnboardingPage(
|
||||
title: l10n.onboardQuickTitle,
|
||||
subtitle: l10n.onboardQuickSubtitle,
|
||||
icon: CupertinoIcons.line_horizontal_3,
|
||||
bullets: [l10n.onboardQuickBullet1, l10n.onboardQuickBullet2],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
void _next() {
|
||||
if (_index < _pages.length - 1) {
|
||||
@@ -133,7 +129,7 @@ class _OnboardingSheetState extends State<OnboardingSheet> {
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
'Skip',
|
||||
AppLocalizations.of(context)!.skip,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
),
|
||||
@@ -155,7 +151,11 @@ class _OnboardingSheetState extends State<OnboardingSheet> {
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(_index == _pages.length - 1 ? 'Done' : 'Next'),
|
||||
child: Text(
|
||||
_index == _pages.length - 1
|
||||
? AppLocalizations.of(context)!.done
|
||||
: AppLocalizations.of(context)!.next,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import '../../../core/widgets/error_boundary.dart';
|
||||
import '../../../shared/widgets/improved_loading_states.dart';
|
||||
|
||||
@@ -46,12 +47,12 @@ class ProfilePage extends ConsumerWidget {
|
||||
color: context.conduitTheme.textPrimary,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
tooltip: 'Back',
|
||||
tooltip: AppLocalizations.of(context)!.back,
|
||||
),
|
||||
toolbarHeight: kToolbarHeight,
|
||||
titleSpacing: 0.0,
|
||||
title: Text(
|
||||
'You',
|
||||
AppLocalizations.of(context)!.you,
|
||||
style: AppTypography.headlineSmallStyle.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -106,10 +107,10 @@ class ProfilePage extends ConsumerWidget {
|
||||
color: context.conduitTheme.textPrimary,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
tooltip: 'Back',
|
||||
tooltip: AppLocalizations.of(context)!.back,
|
||||
),
|
||||
title: Text(
|
||||
'You',
|
||||
AppLocalizations.of(context)!.you,
|
||||
style: AppTypography.headlineSmallStyle.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -117,8 +118,8 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: const Center(
|
||||
child: ImprovedLoadingState(message: 'Loading profile...'),
|
||||
body: Center(
|
||||
child: ImprovedLoadingState(message: AppLocalizations.of(context)!.loadingProfile),
|
||||
),
|
||||
),
|
||||
error: (error, stack) => Scaffold(
|
||||
@@ -136,10 +137,10 @@ class ProfilePage extends ConsumerWidget {
|
||||
color: context.conduitTheme.textPrimary,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
tooltip: 'Back',
|
||||
tooltip: AppLocalizations.of(context)!.back,
|
||||
),
|
||||
title: Text(
|
||||
'You',
|
||||
AppLocalizations.of(context)!.you,
|
||||
style: AppTypography.headlineSmallStyle.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -149,8 +150,8 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
body: Center(
|
||||
child: ImprovedEmptyState(
|
||||
title: 'Unable to load profile',
|
||||
subtitle: 'Please check your connection and try again',
|
||||
title: AppLocalizations.of(context)!.unableToLoadProfile,
|
||||
subtitle: AppLocalizations.of(context)!.pleaseCheckConnection,
|
||||
icon: UiUtils.platformIcon(
|
||||
ios: CupertinoIcons.exclamationmark_triangle,
|
||||
android: Icons.error_outline,
|
||||
@@ -213,7 +214,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Account',
|
||||
AppLocalizations.of(context)!.account,
|
||||
style: context.conduitTheme.headingSmall?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
),
|
||||
@@ -227,6 +228,8 @@ class ProfilePage extends ConsumerWidget {
|
||||
Divider(color: context.conduitTheme.dividerColor, height: 1),
|
||||
_buildThemeToggleTile(context, ref),
|
||||
Divider(color: context.conduitTheme.dividerColor, height: 1),
|
||||
_buildLanguageTile(context, ref),
|
||||
Divider(color: context.conduitTheme.dividerColor, height: 1),
|
||||
_buildAboutTile(context),
|
||||
Divider(color: context.conduitTheme.dividerColor, height: 1),
|
||||
_buildAccountOption(
|
||||
@@ -234,8 +237,8 @@ class ProfilePage extends ConsumerWidget {
|
||||
ios: CupertinoIcons.square_arrow_left,
|
||||
android: Icons.logout,
|
||||
),
|
||||
title: 'Sign Out',
|
||||
subtitle: 'End your session',
|
||||
title: AppLocalizations.of(context)!.signOut,
|
||||
subtitle: AppLocalizations.of(context)!.endYourSession,
|
||||
onTap: () => _signOut(context, ref),
|
||||
isDestructive: true,
|
||||
),
|
||||
@@ -342,14 +345,14 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'Default Model',
|
||||
AppLocalizations.of(context)!.defaultModel,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
settings.defaultModel != null ? currentModel.name : 'Auto-select',
|
||||
settings.defaultModel != null ? currentModel.name : AppLocalizations.of(context)!.autoSelect,
|
||||
style: context.conduitTheme.bodySmall?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
),
|
||||
@@ -388,14 +391,14 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'Default Model',
|
||||
AppLocalizations.of(context)!.defaultModel,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'Loading models...',
|
||||
AppLocalizations.of(context)!.loadingModels,
|
||||
style: context.conduitTheme.bodySmall?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
),
|
||||
@@ -424,14 +427,14 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
'Default Model',
|
||||
AppLocalizations.of(context)!.defaultModel,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'Failed to load models',
|
||||
AppLocalizations.of(context)!.failedToLoadModels,
|
||||
style: context.conduitTheme.bodySmall?.copyWith(
|
||||
color: context.conduitTheme.error,
|
||||
),
|
||||
@@ -440,6 +443,132 @@ class ProfilePage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLanguageTile(BuildContext context, WidgetRef ref) {
|
||||
final locale = ref.watch(localeProvider);
|
||||
final currentCode = locale?.languageCode ?? 'system';
|
||||
final label = () {
|
||||
switch (currentCode) {
|
||||
case 'en':
|
||||
return 'English';
|
||||
case 'de':
|
||||
return 'Deutsch';
|
||||
case 'fr':
|
||||
return 'Français';
|
||||
case 'it':
|
||||
return 'Italiano';
|
||||
default:
|
||||
return 'System';
|
||||
}
|
||||
}();
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.listItemPadding,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(Spacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.buttonPrimary.withValues(
|
||||
alpha: Alpha.highlight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||
),
|
||||
child: Icon(
|
||||
UiUtils.platformIcon(
|
||||
ios: CupertinoIcons.globe,
|
||||
android: Icons.language,
|
||||
),
|
||||
color: context.conduitTheme.buttonPrimary,
|
||||
size: IconSize.medium,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.menuItem,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
label,
|
||||
style: context.conduitTheme.bodySmall?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
trailing: Icon(
|
||||
UiUtils.platformIcon(
|
||||
ios: CupertinoIcons.chevron_right,
|
||||
android: Icons.chevron_right,
|
||||
),
|
||||
color: context.conduitTheme.iconSecondary,
|
||||
size: IconSize.small,
|
||||
),
|
||||
onTap: () async {
|
||||
final selected = await _showLanguageSelector(context, currentCode);
|
||||
if (selected != null) {
|
||||
if (selected == 'system') {
|
||||
await ref.read(localeProvider.notifier).setLocale(null);
|
||||
} else {
|
||||
await ref.read(localeProvider.notifier).setLocale(Locale(selected));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> _showLanguageSelector(BuildContext context, String current) {
|
||||
return showModalBottomSheet<String>(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.surfaceBackground,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppBorderRadius.modal),
|
||||
),
|
||||
boxShadow: ConduitShadows.modal,
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: Spacing.sm),
|
||||
ListTile(
|
||||
title: const Text('System'),
|
||||
trailing: current == 'system' ? const Icon(Icons.check) : null,
|
||||
onTap: () => Navigator.pop(context, 'system'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('English'),
|
||||
trailing: current == 'en' ? const Icon(Icons.check) : null,
|
||||
onTap: () => Navigator.pop(context, 'en'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Deutsch'),
|
||||
trailing: current == 'de' ? const Icon(Icons.check) : null,
|
||||
onTap: () => Navigator.pop(context, 'de'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Français'),
|
||||
trailing: current == 'fr' ? const Icon(Icons.check) : null,
|
||||
onTap: () => Navigator.pop(context, 'fr'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Italiano'),
|
||||
trailing: current == 'it' ? const Icon(Icons.check) : null,
|
||||
onTap: () => Navigator.pop(context, 'it'),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildThemeToggleTile(BuildContext context, WidgetRef ref) {
|
||||
final themeMode = ref.watch(themeModeProvider);
|
||||
final platformBrightness = MediaQuery.platformBrightnessOf(context);
|
||||
@@ -579,7 +708,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: const Text('Close'),
|
||||
child: Text(AppLocalizations.of(ctx)!.closeButtonSemantic),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -614,9 +743,9 @@ class ProfilePage extends ConsumerWidget {
|
||||
void _signOut(BuildContext context, WidgetRef ref) async {
|
||||
final confirm = await UiUtils.showConfirmationDialog(
|
||||
context,
|
||||
title: 'Sign out?',
|
||||
message: 'You\'ll need to sign in again to continue',
|
||||
confirmText: 'Sign out',
|
||||
title: AppLocalizations.of(context)!.signOut,
|
||||
message: AppLocalizations.of(context)!.endYourSession,
|
||||
confirmText: AppLocalizations.of(context)!.signOut,
|
||||
isDestructive: true,
|
||||
);
|
||||
|
||||
@@ -756,7 +885,7 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
|
||||
controller: _searchController,
|
||||
style: TextStyle(color: context.conduitTheme.textPrimary),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search models...',
|
||||
hintText: AppLocalizations.of(context)!.searchModels,
|
||||
hintStyle: TextStyle(
|
||||
color: context.conduitTheme.inputPlaceholder,
|
||||
),
|
||||
@@ -799,7 +928,7 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Available Models',
|
||||
AppLocalizations.of(context)!.availableModels,
|
||||
style: AppTypography.bodySmallStyle.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context.conduitTheme.textSecondary,
|
||||
@@ -848,7 +977,7 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
|
||||
),
|
||||
const SizedBox(height: Spacing.md),
|
||||
Text(
|
||||
'No results',
|
||||
AppLocalizations.of(context)!.noResults,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
fontSize: AppTypography.bodyLarge,
|
||||
@@ -958,7 +1087,7 @@ class _DefaultModelBottomSheetState extends ConsumerState<_DefaultModelBottomShe
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isAutoSelect ? 'Auto-select' : model.name,
|
||||
isAutoSelect ? AppLocalizations.of(context)!.autoSelect : model.name,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
145
lib/l10n/app_de.arb
Normal file
145
lib/l10n/app_de.arb
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"@@locale": "de",
|
||||
"appTitle": "Conduit",
|
||||
"initializationFailed": "Initialisierung fehlgeschlagen",
|
||||
"retry": "Erneut versuchen",
|
||||
"back": "Zurück",
|
||||
"you": "Du",
|
||||
"loadingProfile": "Profil wird geladen...",
|
||||
"unableToLoadProfile": "Profil konnte nicht geladen werden",
|
||||
"pleaseCheckConnection": "Bitte überprüfe deine Verbindung und versuche es erneut",
|
||||
"account": "Konto",
|
||||
"signOut": "Abmelden",
|
||||
"endYourSession": "Sitzung beenden",
|
||||
"defaultModel": "Standardmodell",
|
||||
"autoSelect": "Automatische Auswahl",
|
||||
"loadingModels": "Modelle werden geladen...",
|
||||
"failedToLoadModels": "Modelle konnten nicht geladen werden",
|
||||
"availableModels": "Verfügbare Modelle",
|
||||
"noResults": "Keine Ergebnisse",
|
||||
"searchModels": "Modelle suchen...",
|
||||
"errorMessage": "Etwas ist schief gelaufen. Bitte versuche es erneut.",
|
||||
"loginButton": "Anmelden",
|
||||
"menuItem": "Einstellungen",
|
||||
"dynamicContentWithPlaceholder": "Willkommen, {name}!",
|
||||
"itemsCount": "{count, plural, =0{Keine Elemente} one{1 Element} other{{count} Elemente}}",
|
||||
"closeButtonSemantic": "Schließen",
|
||||
"loadingContent": "Inhalt wird geladen",
|
||||
"noItems": "Keine Elemente",
|
||||
"noItemsToDisplay": "Keine Elemente zum Anzeigen",
|
||||
"loadMore": "Mehr laden",
|
||||
"workspace": "Arbeitsbereich",
|
||||
"recentFiles": "Zuletzt verwendete Dateien",
|
||||
"knowledgeBase": "Wissensdatenbank",
|
||||
"noFilesYet": "Noch keine Dateien",
|
||||
"uploadDocsPrompt": "Lade Dokumente hoch, um sie in deinen Unterhaltungen mit Conduit zu verwenden",
|
||||
"uploadFirstFile": "Erste Datei hochladen",
|
||||
"knowledgeBaseEmpty": "Wissensdatenbank ist leer",
|
||||
"createCollectionsPrompt": "Erstelle Sammlungen verwandter Dokumente zur einfachen Referenz",
|
||||
"chooseSourcePhoto": "Quelle auswählen",
|
||||
"takePhoto": "Foto aufnehmen",
|
||||
"chooseFromGallery": "Aus Fotos auswählen",
|
||||
"document": "Dokument",
|
||||
"documentHint": "PDF-, Word- oder Textdatei",
|
||||
"uploadFileTitle": "Datei hochladen",
|
||||
"fileUploadComingSoon": "Dateiupload für {type} kommt bald!",
|
||||
"kbCreationComingSoon": "Erstellung der Wissensdatenbank kommt bald!",
|
||||
"backToServerSetup": "Zur Servereinrichtung zurück",
|
||||
"connectedToServer": "Mit Server verbunden",
|
||||
"signIn": "Anmelden",
|
||||
"enterCredentials": "Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen",
|
||||
"credentials": "Zugangsdaten",
|
||||
"apiKey": "API-Schlüssel",
|
||||
"usernameOrEmail": "Benutzername oder E‑Mail",
|
||||
"password": "Passwort",
|
||||
"signInWithApiKey": "Mit API-Schlüssel anmelden",
|
||||
"connectToServer": "Mit Server verbinden",
|
||||
"enterServerAddress": "Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen",
|
||||
"serverUrl": "Server-URL",
|
||||
"serverUrlHint": "https://dein-server.com",
|
||||
"enterServerUrlSemantic": "Gib deine Server-URL oder IP-Adresse ein",
|
||||
"headerName": "Header-Name",
|
||||
"headerValue": "Header-Wert",
|
||||
"headerValueHint": "api-key-123 oder Bearer-Token",
|
||||
"addHeader": "Header hinzufügen",
|
||||
"maximumHeadersReached": "Maximale Anzahl erreicht",
|
||||
"removeHeader": "Header entfernen",
|
||||
"connecting": "Verbindung wird hergestellt...",
|
||||
"connectToServerButton": "Mit Server verbinden",
|
||||
"demoModeActive": "Demo-Modus aktiv",
|
||||
"skipServerSetupTryDemo": "Servereinrichtung überspringen und Demo testen",
|
||||
"enterDemo": "Demo starten",
|
||||
"demoBadge": "Demo",
|
||||
"serverNotOpenWebUI": "Dies scheint kein Open-WebUI-Server zu sein.",
|
||||
"serverUrlEmpty": "Server-URL darf nicht leer sein",
|
||||
"invalidUrlFormat": "Ungültiges URL-Format. Bitte Eingabe prüfen.",
|
||||
"onlyHttpHttps": "Nur HTTP- und HTTPS-Protokolle werden unterstützt.",
|
||||
"serverAddressRequired": "Serveradresse erforderlich (z. B. 192.168.1.10 oder example.com).",
|
||||
"portRange": "Port muss zwischen 1 und 65535 liegen.",
|
||||
"invalidIpFormat": "Ungültiges IP-Format. Beispiel: 192.168.1.10.",
|
||||
"couldNotConnectGeneric": "Verbindung fehlgeschlagen. Adresse prüfen und erneut versuchen.",
|
||||
"weCouldntReachServer": "Server nicht erreichbar. Verbindung und Serverstatus prüfen.",
|
||||
"connectionTimedOut": "Zeitüberschreitung. Server eventuell ausgelastet oder blockiert.",
|
||||
"useHttpOrHttpsOnly": "Nur http:// oder https:// verwenden.",
|
||||
"loginFailed": "Anmeldung fehlgeschlagen",
|
||||
"invalidCredentials": "Ungültiger Benutzername oder Passwort. Bitte erneut versuchen.",
|
||||
"serverRedirectingHttps": "Server leitet um. HTTPS-Konfiguration prüfen.",
|
||||
"unableToConnectServer": "Verbindung zum Server nicht möglich. Bitte Verbindung prüfen.",
|
||||
"requestTimedOut": "Zeitüberschreitung. Bitte erneut versuchen.",
|
||||
"genericSignInFailed": "Anmeldung nicht möglich. Zugangsdaten und Server prüfen.",
|
||||
"skip": "Überspringen",
|
||||
"next": "Weiter",
|
||||
"done": "Fertig",
|
||||
"onboardStartTitle": "Unterhaltung starten",
|
||||
"onboardStartSubtitle": "Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.",
|
||||
"onboardStartBullet1": "Modellname oben antippen, um zu wechseln",
|
||||
"onboardStartBullet2": "Mit Neuer Chat den Kontext zurücksetzen",
|
||||
"onboardAttachTitle": "Kontext anhängen",
|
||||
"onboardAttachSubtitle": "Antworten mit Dateien oder Bildern untermauern.",
|
||||
"onboardAttachBullet1": "Dateien: PDFs, Dokumente, Datensätze",
|
||||
"onboardAttachBullet2": "Bilder: Fotos oder Screenshots",
|
||||
"onboardSpeakTitle": "Natürlich sprechen",
|
||||
"onboardSpeakSubtitle": "Auf das Mikro tippen, um zu diktieren.",
|
||||
"onboardSpeakBullet1": "Jederzeit stoppen; Text bleibt erhalten",
|
||||
"onboardSpeakBullet2": "Ideal für kurze Notizen oder lange Prompts",
|
||||
"onboardQuickTitle": "Schnellaktionen",
|
||||
"onboardQuickSubtitle": "Links oben das Menü für Chats und Navigation öffnen.",
|
||||
"onboardQuickBullet1": "Menü tippen, um Chats und Navigation zu öffnen",
|
||||
"onboardQuickBullet2": "Schnell zu Neuer Chat, Dateien oder Profil springen"
|
||||
,
|
||||
"addAttachment": "Anhang hinzufügen",
|
||||
"tools": "Werkzeuge",
|
||||
"voiceInput": "Spracheingabe",
|
||||
"messageInputLabel": "Nachrichteneingabe",
|
||||
"messageInputHint": "Nachricht eingeben",
|
||||
"messageHintText": "Nachricht...",
|
||||
"stopGenerating": "Generierung stoppen",
|
||||
"send": "Senden",
|
||||
"sendMessage": "Nachricht senden",
|
||||
"file": "Datei",
|
||||
"photo": "Foto",
|
||||
"camera": "Kamera",
|
||||
"apiUnavailable": "API-Dienst nicht verfügbar",
|
||||
"unableToLoadImage": "Bild kann nicht geladen werden",
|
||||
"notAnImageFile": "Keine Bilddatei: {fileName}",
|
||||
"failedToLoadImage": "Bild konnte nicht geladen werden: {error}",
|
||||
"invalidDataUrl": "Ungültiges Data-URL-Format",
|
||||
"failedToDecodeImage": "Bild konnte nicht decodiert werden",
|
||||
"invalidImageFormat": "Ungültiges Bildformat",
|
||||
"emptyImageData": "Leere Bilddaten"
|
||||
,
|
||||
"offlineBanner": "Du bist offline. Einige Funktionen sind eingeschränkt.",
|
||||
"featureRequiresInternet": "Diese Funktion erfordert eine Internetverbindung",
|
||||
"messagesWillSendWhenOnline": "Nachrichten werden gesendet, sobald du wieder online bist",
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen"
|
||||
,
|
||||
"ok": "OK",
|
||||
"inputField": "Eingabefeld",
|
||||
"captureDocumentOrImage": "Dokument oder Bild aufnehmen",
|
||||
"checkConnection": "Verbindung prüfen",
|
||||
"openSettings": "Einstellungen öffnen",
|
||||
"chooseDifferentFile": "Andere Datei wählen",
|
||||
"goBack": "Zurück",
|
||||
"technicalDetails": "Technische Details"
|
||||
}
|
||||
169
lib/l10n/app_en.arb
Normal file
169
lib/l10n/app_en.arb
Normal file
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"appTitle": "Conduit",
|
||||
"initializationFailed": "Initialization Failed",
|
||||
"retry": "Retry",
|
||||
"back": "Back",
|
||||
"you": "You",
|
||||
"loadingProfile": "Loading profile...",
|
||||
"unableToLoadProfile": "Unable to load profile",
|
||||
"pleaseCheckConnection": "Please check your connection and try again",
|
||||
"account": "Account",
|
||||
"signOut": "Sign Out",
|
||||
"endYourSession": "End your session",
|
||||
"defaultModel": "Default Model",
|
||||
"autoSelect": "Auto-select",
|
||||
"loadingModels": "Loading models...",
|
||||
"failedToLoadModels": "Failed to load models",
|
||||
"availableModels": "Available Models",
|
||||
"noResults": "No results",
|
||||
"searchModels": "Search models...",
|
||||
"errorMessage": "Something went wrong. Please try again.",
|
||||
"loginButton": "Login",
|
||||
"menuItem": "Settings",
|
||||
"dynamicContentWithPlaceholder": "Welcome, {name}!",
|
||||
"@dynamicContentWithPlaceholder": {
|
||||
"description": "Greeting message with a dynamic user name.",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String",
|
||||
"example": "Alex"
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}",
|
||||
"@itemsCount": {
|
||||
"description": "Pluralized count of items.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"closeButtonSemantic": "Close",
|
||||
"loadingContent": "Loading content",
|
||||
"noItems": "No items",
|
||||
"noItemsToDisplay": "No items to display",
|
||||
"loadMore": "Load More",
|
||||
"workspace": "Workspace",
|
||||
"recentFiles": "Recent Files",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"noFilesYet": "No files yet",
|
||||
"uploadDocsPrompt": "Upload documents to reference in your conversations with Conduit",
|
||||
"uploadFirstFile": "Upload your first file",
|
||||
"knowledgeBaseEmpty": "Knowledge base is empty",
|
||||
"createCollectionsPrompt": "Create collections of related documents for easy reference",
|
||||
"chooseSourcePhoto": "Choose your source",
|
||||
"takePhoto": "Take a photo",
|
||||
"chooseFromGallery": "Choose from your photos",
|
||||
"document": "Document",
|
||||
"documentHint": "PDF, Word, or text file",
|
||||
"uploadFileTitle": "Upload File",
|
||||
"fileUploadComingSoon": "File upload for {type} is coming soon!",
|
||||
"@fileUploadComingSoon": {
|
||||
"placeholders": {"type": {"type": "String", "example": "gallery"}},
|
||||
"description": "Temporary message for upcoming upload feature by type"
|
||||
},
|
||||
"kbCreationComingSoon": "Knowledge base creation is coming soon!",
|
||||
"backToServerSetup": "Back to server setup",
|
||||
"connectedToServer": "Connected to Server",
|
||||
"signIn": "Sign In",
|
||||
"enterCredentials": "Enter your credentials to access your AI conversations",
|
||||
"credentials": "Credentials",
|
||||
"apiKey": "API Key",
|
||||
"usernameOrEmail": "Username or Email",
|
||||
"password": "Password",
|
||||
"signInWithApiKey": "Sign in with API Key",
|
||||
"connectToServer": "Connect to Server",
|
||||
"enterServerAddress": "Enter your Open-WebUI server address to get started",
|
||||
"serverUrl": "Server URL",
|
||||
"serverUrlHint": "https://your-server.com",
|
||||
"enterServerUrlSemantic": "Enter your server URL or IP address",
|
||||
"headerName": "Header Name",
|
||||
"headerValue": "Header Value",
|
||||
"headerValueHint": "api-key-123 or Bearer token",
|
||||
"addHeader": "Add header",
|
||||
"maximumHeadersReached": "Maximum headers reached",
|
||||
"removeHeader": "Remove header",
|
||||
"connecting": "Connecting...",
|
||||
"connectToServerButton": "Connect to Server",
|
||||
"demoModeActive": "Demo Mode Active",
|
||||
"skipServerSetupTryDemo": "Skip server setup and try the demo",
|
||||
"enterDemo": "Enter Demo",
|
||||
"demoBadge": "Demo",
|
||||
"serverNotOpenWebUI": "This does not appear to be an Open-WebUI server.",
|
||||
"serverUrlEmpty": "Server URL cannot be empty",
|
||||
"invalidUrlFormat": "Invalid URL format. Please check your input.",
|
||||
"onlyHttpHttps": "Only HTTP and HTTPS protocols are supported.",
|
||||
"serverAddressRequired": "Server address is required (e.g., 192.168.1.10 or example.com).",
|
||||
"portRange": "Port must be between 1 and 65535.",
|
||||
"invalidIpFormat": "Invalid IP address format. Use format like 192.168.1.10.",
|
||||
"couldNotConnectGeneric": "Couldn't connect. Double-check the address and try again.",
|
||||
"weCouldntReachServer": "We couldn't reach the server. Check your connection and that the server is running.",
|
||||
"connectionTimedOut": "Connection timed out. The server might be busy or blocked by a firewall.",
|
||||
"useHttpOrHttpsOnly": "Use http:// or https:// only.",
|
||||
"loginFailed": "Login failed",
|
||||
"invalidCredentials": "Invalid username or password. Please try again.",
|
||||
"serverRedirectingHttps": "The server is redirecting requests. Check your server's HTTPS configuration.",
|
||||
"unableToConnectServer": "Unable to connect to server. Please check your connection.",
|
||||
"requestTimedOut": "The request timed out. Please try again.",
|
||||
"genericSignInFailed": "We couldn't sign you in. Check your credentials and server settings.",
|
||||
"skip": "Skip",
|
||||
"next": "Next",
|
||||
"done": "Done",
|
||||
"onboardStartTitle": "Start a conversation",
|
||||
"onboardStartSubtitle": "Choose a model, then type below to begin. Tap New Chat anytime.",
|
||||
"onboardStartBullet1": "Tap the model name in the top bar to switch models",
|
||||
"onboardStartBullet2": "Use New Chat to reset context",
|
||||
"onboardAttachTitle": "Attach context",
|
||||
"onboardAttachSubtitle": "Ground responses by adding files or images.",
|
||||
"onboardAttachBullet1": "Files: PDFs, docs, datasets",
|
||||
"onboardAttachBullet2": "Images: photos or screenshots",
|
||||
"onboardSpeakTitle": "Speak naturally",
|
||||
"onboardSpeakSubtitle": "Tap the mic to dictate with live waveform feedback.",
|
||||
"onboardSpeakBullet1": "Stop anytime; partial text is preserved",
|
||||
"onboardSpeakBullet2": "Great for quick notes or long prompts",
|
||||
"onboardQuickTitle": "Quick actions",
|
||||
"onboardQuickSubtitle": "Use the top‑left menu to open the chats list and navigation.",
|
||||
"onboardQuickBullet1": "Tap the menu to open the chats list and navigation",
|
||||
"onboardQuickBullet2": "Jump instantly to New Chat, Files, or Profile"
|
||||
,
|
||||
"addAttachment": "Add attachment",
|
||||
"tools": "Tools",
|
||||
"voiceInput": "Voice input",
|
||||
"messageInputLabel": "Message input",
|
||||
"messageInputHint": "Type your message",
|
||||
"messageHintText": "Message...",
|
||||
"stopGenerating": "Stop generating",
|
||||
"send": "Send",
|
||||
"sendMessage": "Send message",
|
||||
"file": "File",
|
||||
"photo": "Photo",
|
||||
"camera": "Camera",
|
||||
"apiUnavailable": "API service not available",
|
||||
"unableToLoadImage": "Unable to load image",
|
||||
"notAnImageFile": "Not an image file: {fileName}",
|
||||
"@notAnImageFile": {"placeholders": {"fileName": {"type": "String", "example": "image.txt"}}},
|
||||
"failedToLoadImage": "Failed to load image: {error}",
|
||||
"@failedToLoadImage": {"placeholders": {"error": {"type": "String", "example": "Network error"}}},
|
||||
"invalidDataUrl": "Invalid data URL format",
|
||||
"failedToDecodeImage": "Failed to decode image",
|
||||
"invalidImageFormat": "Invalid image format",
|
||||
"emptyImageData": "Empty image data"
|
||||
,
|
||||
"offlineBanner": "You're offline. Some features may be limited.",
|
||||
"featureRequiresInternet": "This feature requires an internet connection",
|
||||
"messagesWillSendWhenOnline": "Messages will be sent when you're back online",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel"
|
||||
,
|
||||
"ok": "OK",
|
||||
"inputField": "Input field",
|
||||
"captureDocumentOrImage": "Capture a document or image",
|
||||
"checkConnection": "Check Connection",
|
||||
"openSettings": "Open Settings",
|
||||
"chooseDifferentFile": "Choose Different File",
|
||||
"goBack": "Go Back",
|
||||
"technicalDetails": "Technical Details"
|
||||
}
|
||||
145
lib/l10n/app_fr.arb
Normal file
145
lib/l10n/app_fr.arb
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"@@locale": "fr",
|
||||
"appTitle": "Conduit",
|
||||
"initializationFailed": "Échec de l'initialisation",
|
||||
"retry": "Réessayer",
|
||||
"back": "Retour",
|
||||
"you": "Vous",
|
||||
"loadingProfile": "Chargement du profil...",
|
||||
"unableToLoadProfile": "Impossible de charger le profil",
|
||||
"pleaseCheckConnection": "Veuillez vérifier votre connexion et réessayer",
|
||||
"account": "Compte",
|
||||
"signOut": "Se déconnecter",
|
||||
"endYourSession": "Terminer votre session",
|
||||
"defaultModel": "Modèle par défaut",
|
||||
"autoSelect": "Sélection automatique",
|
||||
"loadingModels": "Chargement des modèles...",
|
||||
"failedToLoadModels": "Échec du chargement des modèles",
|
||||
"availableModels": "Modèles disponibles",
|
||||
"noResults": "Aucun résultat",
|
||||
"searchModels": "Rechercher des modèles...",
|
||||
"errorMessage": "Une erreur s'est produite. Veuillez réessayer.",
|
||||
"loginButton": "Connexion",
|
||||
"menuItem": "Paramètres",
|
||||
"dynamicContentWithPlaceholder": "Bienvenue, {name} !",
|
||||
"itemsCount": "{count, plural, =0{Aucun élément} one{1 élément} other{{count} éléments}}",
|
||||
"closeButtonSemantic": "Fermer",
|
||||
"loadingContent": "Chargement du contenu",
|
||||
"noItems": "Aucun élément",
|
||||
"noItemsToDisplay": "Aucun élément à afficher",
|
||||
"loadMore": "Charger plus",
|
||||
"workspace": "Espace de travail",
|
||||
"recentFiles": "Fichiers récents",
|
||||
"knowledgeBase": "Base de connaissances",
|
||||
"noFilesYet": "Pas encore de fichiers",
|
||||
"uploadDocsPrompt": "Importez des documents à utiliser dans vos conversations avec Conduit",
|
||||
"uploadFirstFile": "Importer votre premier fichier",
|
||||
"knowledgeBaseEmpty": "La base de connaissances est vide",
|
||||
"createCollectionsPrompt": "Créez des collections de documents liés pour une référence facile",
|
||||
"chooseSourcePhoto": "Choisir la source",
|
||||
"takePhoto": "Prendre une photo",
|
||||
"chooseFromGallery": "Choisir depuis vos photos",
|
||||
"document": "Document",
|
||||
"documentHint": "Fichier PDF, Word ou texte",
|
||||
"uploadFileTitle": "Importer un fichier",
|
||||
"fileUploadComingSoon": "Le téléversement de fichiers pour {type} arrive bientôt !",
|
||||
"kbCreationComingSoon": "La création de la base de connaissances arrive bientôt !",
|
||||
"backToServerSetup": "Retour à la configuration du serveur",
|
||||
"connectedToServer": "Connecté au serveur",
|
||||
"signIn": "Se connecter",
|
||||
"enterCredentials": "Entrez vos identifiants pour accéder à vos conversations IA",
|
||||
"credentials": "Identifiants",
|
||||
"apiKey": "Clé API",
|
||||
"usernameOrEmail": "Nom d'utilisateur ou e‑mail",
|
||||
"password": "Mot de passe",
|
||||
"signInWithApiKey": "Se connecter avec une clé API",
|
||||
"connectToServer": "Se connecter au serveur",
|
||||
"enterServerAddress": "Saisissez l'adresse de votre serveur Open-WebUI pour commencer",
|
||||
"serverUrl": "URL du serveur",
|
||||
"serverUrlHint": "https://votre-serveur.com",
|
||||
"enterServerUrlSemantic": "Saisissez l'URL ou l'adresse IP de votre serveur",
|
||||
"headerName": "Nom de l'en-tête",
|
||||
"headerValue": "Valeur de l'en-tête",
|
||||
"headerValueHint": "api-key-123 ou jeton Bearer",
|
||||
"addHeader": "Ajouter l'en-tête",
|
||||
"maximumHeadersReached": "Nombre maximal atteint",
|
||||
"removeHeader": "Supprimer l'en-tête",
|
||||
"connecting": "Connexion en cours...",
|
||||
"connectToServerButton": "Se connecter au serveur",
|
||||
"demoModeActive": "Mode démo activé",
|
||||
"skipServerSetupTryDemo": "Ignorer la configuration et essayer la démo",
|
||||
"enterDemo": "Entrer en démo",
|
||||
"demoBadge": "Démo",
|
||||
"serverNotOpenWebUI": "Ceci ne semble pas être un serveur Open-WebUI.",
|
||||
"serverUrlEmpty": "L'URL du serveur ne peut pas être vide",
|
||||
"invalidUrlFormat": "Format d'URL invalide. Veuillez vérifier votre saisie.",
|
||||
"onlyHttpHttps": "Seuls les protocoles HTTP et HTTPS sont pris en charge.",
|
||||
"serverAddressRequired": "Adresse du serveur requise (ex. 192.168.1.10 ou example.com).",
|
||||
"portRange": "Le port doit être compris entre 1 et 65535.",
|
||||
"invalidIpFormat": "Format d'IP invalide. Exemple : 192.168.1.10.",
|
||||
"couldNotConnectGeneric": "Connexion impossible. Vérifiez l'adresse et réessayez.",
|
||||
"weCouldntReachServer": "Impossible d'atteindre le serveur. Vérifiez la connexion et l'état du serveur.",
|
||||
"connectionTimedOut": "Délai d'attente dépassé. Le serveur est peut-être occupé ou bloqué.",
|
||||
"useHttpOrHttpsOnly": "Utilisez uniquement http:// ou https://.",
|
||||
"loginFailed": "Échec de la connexion",
|
||||
"invalidCredentials": "Nom d'utilisateur ou mot de passe invalide. Réessayez.",
|
||||
"serverRedirectingHttps": "Le serveur redirige les requêtes. Vérifiez la configuration HTTPS.",
|
||||
"unableToConnectServer": "Impossible de se connecter au serveur. Vérifiez votre connexion.",
|
||||
"requestTimedOut": "Délai d'attente dépassé. Réessayez.",
|
||||
"genericSignInFailed": "Connexion impossible. Vérifiez vos identifiants et le serveur.",
|
||||
"skip": "Ignorer",
|
||||
"next": "Suivant",
|
||||
"done": "Terminé",
|
||||
"onboardStartTitle": "Commencer une conversation",
|
||||
"onboardStartSubtitle": "Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.",
|
||||
"onboardStartBullet1": "Touchez le nom du modèle en haut pour changer",
|
||||
"onboardStartBullet2": "Utilisez Nouveau chat pour réinitialiser le contexte",
|
||||
"onboardAttachTitle": "Ajouter du contexte",
|
||||
"onboardAttachSubtitle": "Améliorez les réponses en ajoutant des fichiers ou des images.",
|
||||
"onboardAttachBullet1": "Fichiers : PDF, documents, jeux de données",
|
||||
"onboardAttachBullet2": "Images : photos ou captures d'écran",
|
||||
"onboardSpeakTitle": "Parlez naturellement",
|
||||
"onboardSpeakSubtitle": "Touchez le micro pour dicter avec retour visuel.",
|
||||
"onboardSpeakBullet1": "Arrêtez à tout moment ; le texte partiel est conservé",
|
||||
"onboardSpeakBullet2": "Idéal pour des notes rapides ou de longs prompts",
|
||||
"onboardQuickTitle": "Actions rapides",
|
||||
"onboardQuickSubtitle": "Utilisez le menu en haut à gauche pour ouvrir la liste des chats et la navigation.",
|
||||
"onboardQuickBullet1": "Touchez le menu pour ouvrir les chats et la navigation",
|
||||
"onboardQuickBullet2": "Accédez rapidement à Nouveau chat, Fichiers ou Profil"
|
||||
,
|
||||
"addAttachment": "Ajouter une pièce jointe",
|
||||
"tools": "Outils",
|
||||
"voiceInput": "Entrée vocale",
|
||||
"messageInputLabel": "Saisie du message",
|
||||
"messageInputHint": "Saisissez votre message",
|
||||
"messageHintText": "Message...",
|
||||
"stopGenerating": "Arrêter la génération",
|
||||
"send": "Envoyer",
|
||||
"sendMessage": "Envoyer le message",
|
||||
"file": "Fichier",
|
||||
"photo": "Photo",
|
||||
"camera": "Appareil photo",
|
||||
"apiUnavailable": "Service API indisponible",
|
||||
"unableToLoadImage": "Impossible de charger l'image",
|
||||
"notAnImageFile": "Ce n'est pas un fichier image : {fileName}",
|
||||
"failedToLoadImage": "Échec du chargement de l'image : {error}",
|
||||
"invalidDataUrl": "Format d'URL de données invalide",
|
||||
"failedToDecodeImage": "Échec du décodage de l'image",
|
||||
"invalidImageFormat": "Format d'image invalide",
|
||||
"emptyImageData": "Données d'image vides"
|
||||
,
|
||||
"offlineBanner": "Vous êtes hors ligne. Certaines fonctions peuvent être limitées.",
|
||||
"featureRequiresInternet": "Cette fonctionnalité nécessite une connexion Internet",
|
||||
"messagesWillSendWhenOnline": "Les messages seront envoyés lorsque vous serez de nouveau en ligne",
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler"
|
||||
,
|
||||
"ok": "OK",
|
||||
"inputField": "Champ de saisie",
|
||||
"captureDocumentOrImage": "Capturer un document ou une image",
|
||||
"checkConnection": "Vérifier la connexion",
|
||||
"openSettings": "Ouvrir les réglages",
|
||||
"chooseDifferentFile": "Choisir un autre fichier",
|
||||
"goBack": "Retour",
|
||||
"technicalDetails": "Détails techniques"
|
||||
}
|
||||
145
lib/l10n/app_it.arb
Normal file
145
lib/l10n/app_it.arb
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"@@locale": "it",
|
||||
"appTitle": "Conduit",
|
||||
"initializationFailed": "Inizializzazione non riuscita",
|
||||
"retry": "Riprova",
|
||||
"back": "Indietro",
|
||||
"you": "Tu",
|
||||
"loadingProfile": "Caricamento profilo...",
|
||||
"unableToLoadProfile": "Impossibile caricare il profilo",
|
||||
"pleaseCheckConnection": "Controlla la connessione e riprova",
|
||||
"account": "Account",
|
||||
"signOut": "Esci",
|
||||
"endYourSession": "Termina la sessione",
|
||||
"defaultModel": "Modello predefinito",
|
||||
"autoSelect": "Selezione automatica",
|
||||
"loadingModels": "Caricamento modelli...",
|
||||
"failedToLoadModels": "Impossibile caricare i modelli",
|
||||
"availableModels": "Modelli disponibili",
|
||||
"noResults": "Nessun risultato",
|
||||
"searchModels": "Cerca modelli...",
|
||||
"errorMessage": "Qualcosa è andato storto. Riprova.",
|
||||
"loginButton": "Accedi",
|
||||
"menuItem": "Impostazioni",
|
||||
"dynamicContentWithPlaceholder": "Benvenuto, {name}!",
|
||||
"itemsCount": "{count, plural, =0{Nessun elemento} one{1 elemento} other{{count} elementi}}",
|
||||
"closeButtonSemantic": "Chiudi",
|
||||
"loadingContent": "Caricamento contenuto",
|
||||
"noItems": "Nessun elemento",
|
||||
"noItemsToDisplay": "Nessun elemento da visualizzare",
|
||||
"loadMore": "Carica altro",
|
||||
"workspace": "Spazio di lavoro",
|
||||
"recentFiles": "File recenti",
|
||||
"knowledgeBase": "Base di conoscenza",
|
||||
"noFilesYet": "Ancora nessun file",
|
||||
"uploadDocsPrompt": "Carica documenti da usare nelle conversazioni con Conduit",
|
||||
"uploadFirstFile": "Carica il tuo primo file",
|
||||
"knowledgeBaseEmpty": "La base di conoscenza è vuota",
|
||||
"createCollectionsPrompt": "Crea raccolte di documenti correlati per un rapido riferimento",
|
||||
"chooseSourcePhoto": "Scegli origine",
|
||||
"takePhoto": "Scatta una foto",
|
||||
"chooseFromGallery": "Scegli dalle foto",
|
||||
"document": "Documento",
|
||||
"documentHint": "File PDF, Word o di testo",
|
||||
"uploadFileTitle": "Carica file",
|
||||
"fileUploadComingSoon": "Il caricamento file per {type} arriverà presto!",
|
||||
"kbCreationComingSoon": "La creazione della base di conoscenza arriverà presto!",
|
||||
"backToServerSetup": "Torna alla configurazione del server",
|
||||
"connectedToServer": "Connesso al server",
|
||||
"signIn": "Accedi",
|
||||
"enterCredentials": "Inserisci le credenziali per accedere alle conversazioni IA",
|
||||
"credentials": "Credenziali",
|
||||
"apiKey": "Chiave API",
|
||||
"usernameOrEmail": "Username o e‑mail",
|
||||
"password": "Password",
|
||||
"signInWithApiKey": "Accedi con chiave API",
|
||||
"connectToServer": "Connetti al server",
|
||||
"enterServerAddress": "Inserisci l'indirizzo del server Open-WebUI per iniziare",
|
||||
"serverUrl": "URL del server",
|
||||
"serverUrlHint": "https://tuo-server.com",
|
||||
"enterServerUrlSemantic": "Inserisci l'URL o l'indirizzo IP del server",
|
||||
"headerName": "Nome header",
|
||||
"headerValue": "Valore header",
|
||||
"headerValueHint": "api-key-123 o token Bearer",
|
||||
"addHeader": "Aggiungi header",
|
||||
"maximumHeadersReached": "Numero massimo raggiunto",
|
||||
"removeHeader": "Rimuovi header",
|
||||
"connecting": "Connessione in corso...",
|
||||
"connectToServerButton": "Connetti al server",
|
||||
"demoModeActive": "Modalità demo attiva",
|
||||
"skipServerSetupTryDemo": "Salta configurazione server e prova la demo",
|
||||
"enterDemo": "Entra in demo",
|
||||
"demoBadge": "Demo",
|
||||
"serverNotOpenWebUI": "Questo non sembra un server Open-WebUI.",
|
||||
"serverUrlEmpty": "L'URL del server non può essere vuoto",
|
||||
"invalidUrlFormat": "Formato URL non valido. Controlla l'input.",
|
||||
"onlyHttpHttps": "Sono supportati solo i protocolli HTTP e HTTPS.",
|
||||
"serverAddressRequired": "Indirizzo server richiesto (es. 192.168.1.10 o example.com).",
|
||||
"portRange": "La porta deve essere tra 1 e 65535.",
|
||||
"invalidIpFormat": "Formato IP non valido. Esempio: 192.168.1.10.",
|
||||
"couldNotConnectGeneric": "Impossibile connettersi. Verifica l'indirizzo e riprova.",
|
||||
"weCouldntReachServer": "Impossibile raggiungere il server. Verifica connessione e stato del server.",
|
||||
"connectionTimedOut": "Tempo scaduto. Il server potrebbe essere occupato o bloccato.",
|
||||
"useHttpOrHttpsOnly": "Usa solo http:// o https://.",
|
||||
"loginFailed": "Accesso non riuscito",
|
||||
"invalidCredentials": "Nome utente o password non validi. Riprova.",
|
||||
"serverRedirectingHttps": "Il server sta reindirizzando. Controlla la configurazione HTTPS.",
|
||||
"unableToConnectServer": "Impossibile connettersi al server. Controlla la connessione.",
|
||||
"requestTimedOut": "Richiesta scaduta. Riprova.",
|
||||
"genericSignInFailed": "Impossibile accedere. Controlla credenziali e server.",
|
||||
"skip": "Salta",
|
||||
"next": "Avanti",
|
||||
"done": "Fatto",
|
||||
"onboardStartTitle": "Inizia una conversazione",
|
||||
"onboardStartSubtitle": "Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.",
|
||||
"onboardStartBullet1": "Tocca il nome del modello in alto per cambiare",
|
||||
"onboardStartBullet2": "Usa Nuova chat per azzerare il contesto",
|
||||
"onboardAttachTitle": "Aggiungi contesto",
|
||||
"onboardAttachSubtitle": "Migliora le risposte aggiungendo file o immagini.",
|
||||
"onboardAttachBullet1": "File: PDF, documenti, dataset",
|
||||
"onboardAttachBullet2": "Immagini: foto o screenshot",
|
||||
"onboardSpeakTitle": "Parla in modo naturale",
|
||||
"onboardSpeakSubtitle": "Tocca il microfono per dettare con feedback visivo.",
|
||||
"onboardSpeakBullet1": "Interrompi in qualsiasi momento; il testo parziale viene mantenuto",
|
||||
"onboardSpeakBullet2": "Ottimo per note rapide o prompt lunghi",
|
||||
"onboardQuickTitle": "Azioni rapide",
|
||||
"onboardQuickSubtitle": "Usa il menu in alto a sinistra per aprire l'elenco chat e la navigazione.",
|
||||
"onboardQuickBullet1": "Tocca il menu per aprire chat e navigazione",
|
||||
"onboardQuickBullet2": "Vai subito a Nuova chat, File o Profilo"
|
||||
,
|
||||
"addAttachment": "Aggiungi allegato",
|
||||
"tools": "Strumenti",
|
||||
"voiceInput": "Input vocale",
|
||||
"messageInputLabel": "Input messaggio",
|
||||
"messageInputHint": "Scrivi il tuo messaggio",
|
||||
"messageHintText": "Messaggio...",
|
||||
"stopGenerating": "Interrompi generazione",
|
||||
"send": "Invia",
|
||||
"sendMessage": "Invia messaggio",
|
||||
"file": "File",
|
||||
"photo": "Foto",
|
||||
"camera": "Fotocamera",
|
||||
"apiUnavailable": "Servizio API non disponibile",
|
||||
"unableToLoadImage": "Impossibile caricare l'immagine",
|
||||
"notAnImageFile": "Non è un file immagine: {fileName}",
|
||||
"failedToLoadImage": "Impossibile caricare l'immagine: {error}",
|
||||
"invalidDataUrl": "Formato data URL non valido",
|
||||
"failedToDecodeImage": "Impossibile decodificare l'immagine",
|
||||
"invalidImageFormat": "Formato immagine non valido",
|
||||
"emptyImageData": "Dati immagine vuoti"
|
||||
,
|
||||
"offlineBanner": "Sei offline. Alcune funzioni potrebbero essere limitate.",
|
||||
"featureRequiresInternet": "Questa funzione richiede una connessione Internet",
|
||||
"messagesWillSendWhenOnline": "I messaggi verranno inviati quando tornerai online",
|
||||
"confirm": "Conferma",
|
||||
"cancel": "Annulla"
|
||||
,
|
||||
"ok": "OK",
|
||||
"inputField": "Campo di input",
|
||||
"captureDocumentOrImage": "Acquisisci un documento o un'immagine",
|
||||
"checkConnection": "Controlla connessione",
|
||||
"openSettings": "Apri impostazioni",
|
||||
"chooseDifferentFile": "Scegli un altro file",
|
||||
"goBack": "Indietro",
|
||||
"technicalDetails": "Dettagli tecnici"
|
||||
}
|
||||
969
lib/l10n/app_localizations.dart
Normal file
969
lib/l10n/app_localizations.dart
Normal file
@@ -0,0 +1,969 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_de.dart';
|
||||
import 'app_localizations_en.dart';
|
||||
import 'app_localizations_fr.dart';
|
||||
import 'app_localizations_it.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||
/// returned by `AppLocalizations.of(context)`.
|
||||
///
|
||||
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||
/// `localizationDelegates` list, and the locales they support in the app's
|
||||
/// `supportedLocales` list. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'l10n/app_localizations.dart';
|
||||
///
|
||||
/// return MaterialApp(
|
||||
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||
/// home: MyApplicationHome(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ## Update pubspec.yaml
|
||||
///
|
||||
/// Please make sure to update your pubspec.yaml to include the following
|
||||
/// packages:
|
||||
///
|
||||
/// ```yaml
|
||||
/// dependencies:
|
||||
/// # Internationalization support.
|
||||
/// flutter_localizations:
|
||||
/// sdk: flutter
|
||||
/// intl: any # Use the pinned version from flutter_localizations
|
||||
///
|
||||
/// # Rest of dependencies
|
||||
/// ```
|
||||
///
|
||||
/// ## iOS Applications
|
||||
///
|
||||
/// iOS applications define key application metadata, including supported
|
||||
/// locales, in an Info.plist file that is built into the application bundle.
|
||||
/// To configure the locales supported by your app, you’ll need to edit this
|
||||
/// file.
|
||||
///
|
||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||
/// project’s Runner folder.
|
||||
///
|
||||
/// Next, select the Information Property List item, select Add Item from the
|
||||
/// Editor menu, then select Localizations from the pop-up menu.
|
||||
///
|
||||
/// Select and expand the newly-created Localizations item then, for each
|
||||
/// locale your application supports, add a new item and select the locale
|
||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||
/// property.
|
||||
abstract class AppLocalizations {
|
||||
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||
|
||||
final String localeName;
|
||||
|
||||
static AppLocalizations? of(BuildContext context) {
|
||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
|
||||
|
||||
/// A list of this localizations delegate along with the default localizations
|
||||
/// delegates.
|
||||
///
|
||||
/// Returns a list of localizations delegates containing this delegate along with
|
||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||
/// and GlobalWidgetsLocalizations.delegate.
|
||||
///
|
||||
/// 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
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('de'),
|
||||
Locale('en'),
|
||||
Locale('fr'),
|
||||
Locale('it')
|
||||
];
|
||||
|
||||
/// No description provided for @appTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Conduit'**
|
||||
String get appTitle;
|
||||
|
||||
/// No description provided for @initializationFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Initialization Failed'**
|
||||
String get initializationFailed;
|
||||
|
||||
/// No description provided for @retry.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Retry'**
|
||||
String get retry;
|
||||
|
||||
/// No description provided for @back.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Back'**
|
||||
String get back;
|
||||
|
||||
/// No description provided for @you.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You'**
|
||||
String get you;
|
||||
|
||||
/// No description provided for @loadingProfile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Loading profile...'**
|
||||
String get loadingProfile;
|
||||
|
||||
/// No description provided for @unableToLoadProfile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unable to load profile'**
|
||||
String get unableToLoadProfile;
|
||||
|
||||
/// No description provided for @pleaseCheckConnection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please check your connection and try again'**
|
||||
String get pleaseCheckConnection;
|
||||
|
||||
/// No description provided for @account.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Account'**
|
||||
String get account;
|
||||
|
||||
/// No description provided for @signOut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign Out'**
|
||||
String get signOut;
|
||||
|
||||
/// No description provided for @endYourSession.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'End your session'**
|
||||
String get endYourSession;
|
||||
|
||||
/// No description provided for @defaultModel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Default Model'**
|
||||
String get defaultModel;
|
||||
|
||||
/// No description provided for @autoSelect.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Auto-select'**
|
||||
String get autoSelect;
|
||||
|
||||
/// No description provided for @loadingModels.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Loading models...'**
|
||||
String get loadingModels;
|
||||
|
||||
/// No description provided for @failedToLoadModels.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to load models'**
|
||||
String get failedToLoadModels;
|
||||
|
||||
/// No description provided for @availableModels.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Available Models'**
|
||||
String get availableModels;
|
||||
|
||||
/// No description provided for @noResults.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No results'**
|
||||
String get noResults;
|
||||
|
||||
/// No description provided for @searchModels.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Search models...'**
|
||||
String get searchModels;
|
||||
|
||||
/// No description provided for @errorMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Something went wrong. Please try again.'**
|
||||
String get errorMessage;
|
||||
|
||||
/// No description provided for @loginButton.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Login'**
|
||||
String get loginButton;
|
||||
|
||||
/// No description provided for @menuItem.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Settings'**
|
||||
String get menuItem;
|
||||
|
||||
/// Greeting message with a dynamic user name.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Welcome, {name}!'**
|
||||
String dynamicContentWithPlaceholder(String name);
|
||||
|
||||
/// Pluralized count of items.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count, plural, =0{No items} one{1 item} other{{count} items}}'**
|
||||
String itemsCount(int count);
|
||||
|
||||
/// No description provided for @closeButtonSemantic.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Close'**
|
||||
String get closeButtonSemantic;
|
||||
|
||||
/// No description provided for @loadingContent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Loading content'**
|
||||
String get loadingContent;
|
||||
|
||||
/// No description provided for @noItems.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No items'**
|
||||
String get noItems;
|
||||
|
||||
/// No description provided for @noItemsToDisplay.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No items to display'**
|
||||
String get noItemsToDisplay;
|
||||
|
||||
/// No description provided for @loadMore.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Load More'**
|
||||
String get loadMore;
|
||||
|
||||
/// No description provided for @workspace.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Workspace'**
|
||||
String get workspace;
|
||||
|
||||
/// No description provided for @recentFiles.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recent Files'**
|
||||
String get recentFiles;
|
||||
|
||||
/// No description provided for @knowledgeBase.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Knowledge Base'**
|
||||
String get knowledgeBase;
|
||||
|
||||
/// No description provided for @noFilesYet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No files yet'**
|
||||
String get noFilesYet;
|
||||
|
||||
/// No description provided for @uploadDocsPrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Upload documents to reference in your conversations with Conduit'**
|
||||
String get uploadDocsPrompt;
|
||||
|
||||
/// No description provided for @uploadFirstFile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Upload your first file'**
|
||||
String get uploadFirstFile;
|
||||
|
||||
/// No description provided for @knowledgeBaseEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Knowledge base is empty'**
|
||||
String get knowledgeBaseEmpty;
|
||||
|
||||
/// No description provided for @createCollectionsPrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create collections of related documents for easy reference'**
|
||||
String get createCollectionsPrompt;
|
||||
|
||||
/// No description provided for @chooseSourcePhoto.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose your source'**
|
||||
String get chooseSourcePhoto;
|
||||
|
||||
/// No description provided for @takePhoto.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Take a photo'**
|
||||
String get takePhoto;
|
||||
|
||||
/// No description provided for @chooseFromGallery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose from your photos'**
|
||||
String get chooseFromGallery;
|
||||
|
||||
/// No description provided for @document.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Document'**
|
||||
String get document;
|
||||
|
||||
/// No description provided for @documentHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'PDF, Word, or text file'**
|
||||
String get documentHint;
|
||||
|
||||
/// No description provided for @uploadFileTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Upload File'**
|
||||
String get uploadFileTitle;
|
||||
|
||||
/// Temporary message for upcoming upload feature by type
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'File upload for {type} is coming soon!'**
|
||||
String fileUploadComingSoon(String type);
|
||||
|
||||
/// No description provided for @kbCreationComingSoon.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Knowledge base creation is coming soon!'**
|
||||
String get kbCreationComingSoon;
|
||||
|
||||
/// No description provided for @backToServerSetup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Back to server setup'**
|
||||
String get backToServerSetup;
|
||||
|
||||
/// No description provided for @connectedToServer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connected to Server'**
|
||||
String get connectedToServer;
|
||||
|
||||
/// No description provided for @signIn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign In'**
|
||||
String get signIn;
|
||||
|
||||
/// No description provided for @enterCredentials.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter your credentials to access your AI conversations'**
|
||||
String get enterCredentials;
|
||||
|
||||
/// No description provided for @credentials.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Credentials'**
|
||||
String get credentials;
|
||||
|
||||
/// No description provided for @apiKey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'API Key'**
|
||||
String get apiKey;
|
||||
|
||||
/// No description provided for @usernameOrEmail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Username or Email'**
|
||||
String get usernameOrEmail;
|
||||
|
||||
/// No description provided for @password.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Password'**
|
||||
String get password;
|
||||
|
||||
/// No description provided for @signInWithApiKey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign in with API Key'**
|
||||
String get signInWithApiKey;
|
||||
|
||||
/// No description provided for @connectToServer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect to Server'**
|
||||
String get connectToServer;
|
||||
|
||||
/// No description provided for @enterServerAddress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter your Open-WebUI server address to get started'**
|
||||
String get enterServerAddress;
|
||||
|
||||
/// No description provided for @serverUrl.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Server URL'**
|
||||
String get serverUrl;
|
||||
|
||||
/// No description provided for @serverUrlHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'https://your-server.com'**
|
||||
String get serverUrlHint;
|
||||
|
||||
/// No description provided for @enterServerUrlSemantic.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter your server URL or IP address'**
|
||||
String get enterServerUrlSemantic;
|
||||
|
||||
/// No description provided for @headerName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Header Name'**
|
||||
String get headerName;
|
||||
|
||||
/// No description provided for @headerValue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Header Value'**
|
||||
String get headerValue;
|
||||
|
||||
/// No description provided for @headerValueHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'api-key-123 or Bearer token'**
|
||||
String get headerValueHint;
|
||||
|
||||
/// No description provided for @addHeader.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add header'**
|
||||
String get addHeader;
|
||||
|
||||
/// No description provided for @maximumHeadersReached.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maximum headers reached'**
|
||||
String get maximumHeadersReached;
|
||||
|
||||
/// No description provided for @removeHeader.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remove header'**
|
||||
String get removeHeader;
|
||||
|
||||
/// No description provided for @connecting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connecting...'**
|
||||
String get connecting;
|
||||
|
||||
/// No description provided for @connectToServerButton.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect to Server'**
|
||||
String get connectToServerButton;
|
||||
|
||||
/// No description provided for @demoModeActive.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Demo Mode Active'**
|
||||
String get demoModeActive;
|
||||
|
||||
/// No description provided for @skipServerSetupTryDemo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Skip server setup and try the demo'**
|
||||
String get skipServerSetupTryDemo;
|
||||
|
||||
/// No description provided for @enterDemo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter Demo'**
|
||||
String get enterDemo;
|
||||
|
||||
/// No description provided for @demoBadge.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Demo'**
|
||||
String get demoBadge;
|
||||
|
||||
/// No description provided for @serverNotOpenWebUI.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This does not appear to be an Open-WebUI server.'**
|
||||
String get serverNotOpenWebUI;
|
||||
|
||||
/// No description provided for @serverUrlEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Server URL cannot be empty'**
|
||||
String get serverUrlEmpty;
|
||||
|
||||
/// No description provided for @invalidUrlFormat.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid URL format. Please check your input.'**
|
||||
String get invalidUrlFormat;
|
||||
|
||||
/// No description provided for @onlyHttpHttps.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Only HTTP and HTTPS protocols are supported.'**
|
||||
String get onlyHttpHttps;
|
||||
|
||||
/// No description provided for @serverAddressRequired.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Server address is required (e.g., 192.168.1.10 or example.com).'**
|
||||
String get serverAddressRequired;
|
||||
|
||||
/// No description provided for @portRange.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Port must be between 1 and 65535.'**
|
||||
String get portRange;
|
||||
|
||||
/// No description provided for @invalidIpFormat.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid IP address format. Use format like 192.168.1.10.'**
|
||||
String get invalidIpFormat;
|
||||
|
||||
/// No description provided for @couldNotConnectGeneric.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Couldn\'t connect. Double-check the address and try again.'**
|
||||
String get couldNotConnectGeneric;
|
||||
|
||||
/// No description provided for @weCouldntReachServer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'We couldn\'t reach the server. Check your connection and that the server is running.'**
|
||||
String get weCouldntReachServer;
|
||||
|
||||
/// No description provided for @connectionTimedOut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connection timed out. The server might be busy or blocked by a firewall.'**
|
||||
String get connectionTimedOut;
|
||||
|
||||
/// No description provided for @useHttpOrHttpsOnly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use http:// or https:// only.'**
|
||||
String get useHttpOrHttpsOnly;
|
||||
|
||||
/// No description provided for @loginFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Login failed'**
|
||||
String get loginFailed;
|
||||
|
||||
/// No description provided for @invalidCredentials.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid username or password. Please try again.'**
|
||||
String get invalidCredentials;
|
||||
|
||||
/// No description provided for @serverRedirectingHttps.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The server is redirecting requests. Check your server\'s HTTPS configuration.'**
|
||||
String get serverRedirectingHttps;
|
||||
|
||||
/// No description provided for @unableToConnectServer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unable to connect to server. Please check your connection.'**
|
||||
String get unableToConnectServer;
|
||||
|
||||
/// No description provided for @requestTimedOut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The request timed out. Please try again.'**
|
||||
String get requestTimedOut;
|
||||
|
||||
/// No description provided for @genericSignInFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'We couldn\'t sign you in. Check your credentials and server settings.'**
|
||||
String get genericSignInFailed;
|
||||
|
||||
/// No description provided for @skip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Skip'**
|
||||
String get skip;
|
||||
|
||||
/// No description provided for @next.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Next'**
|
||||
String get next;
|
||||
|
||||
/// No description provided for @done.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Done'**
|
||||
String get done;
|
||||
|
||||
/// No description provided for @onboardStartTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Start a conversation'**
|
||||
String get onboardStartTitle;
|
||||
|
||||
/// No description provided for @onboardStartSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose a model, then type below to begin. Tap New Chat anytime.'**
|
||||
String get onboardStartSubtitle;
|
||||
|
||||
/// No description provided for @onboardStartBullet1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap the model name in the top bar to switch models'**
|
||||
String get onboardStartBullet1;
|
||||
|
||||
/// No description provided for @onboardStartBullet2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use New Chat to reset context'**
|
||||
String get onboardStartBullet2;
|
||||
|
||||
/// No description provided for @onboardAttachTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Attach context'**
|
||||
String get onboardAttachTitle;
|
||||
|
||||
/// No description provided for @onboardAttachSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ground responses by adding files or images.'**
|
||||
String get onboardAttachSubtitle;
|
||||
|
||||
/// No description provided for @onboardAttachBullet1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Files: PDFs, docs, datasets'**
|
||||
String get onboardAttachBullet1;
|
||||
|
||||
/// No description provided for @onboardAttachBullet2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Images: photos or screenshots'**
|
||||
String get onboardAttachBullet2;
|
||||
|
||||
/// No description provided for @onboardSpeakTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Speak naturally'**
|
||||
String get onboardSpeakTitle;
|
||||
|
||||
/// No description provided for @onboardSpeakSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap the mic to dictate with live waveform feedback.'**
|
||||
String get onboardSpeakSubtitle;
|
||||
|
||||
/// No description provided for @onboardSpeakBullet1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Stop anytime; partial text is preserved'**
|
||||
String get onboardSpeakBullet1;
|
||||
|
||||
/// No description provided for @onboardSpeakBullet2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Great for quick notes or long prompts'**
|
||||
String get onboardSpeakBullet2;
|
||||
|
||||
/// No description provided for @onboardQuickTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Quick actions'**
|
||||
String get onboardQuickTitle;
|
||||
|
||||
/// No description provided for @onboardQuickSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use the top‑left menu to open the chats list and navigation.'**
|
||||
String get onboardQuickSubtitle;
|
||||
|
||||
/// No description provided for @onboardQuickBullet1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap the menu to open the chats list and navigation'**
|
||||
String get onboardQuickBullet1;
|
||||
|
||||
/// No description provided for @onboardQuickBullet2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Jump instantly to New Chat, Files, or Profile'**
|
||||
String get onboardQuickBullet2;
|
||||
|
||||
/// No description provided for @addAttachment.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add attachment'**
|
||||
String get addAttachment;
|
||||
|
||||
/// No description provided for @tools.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tools'**
|
||||
String get tools;
|
||||
|
||||
/// No description provided for @voiceInput.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Voice input'**
|
||||
String get voiceInput;
|
||||
|
||||
/// No description provided for @messageInputLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Message input'**
|
||||
String get messageInputLabel;
|
||||
|
||||
/// No description provided for @messageInputHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Type your message'**
|
||||
String get messageInputHint;
|
||||
|
||||
/// No description provided for @messageHintText.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Message...'**
|
||||
String get messageHintText;
|
||||
|
||||
/// No description provided for @stopGenerating.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Stop generating'**
|
||||
String get stopGenerating;
|
||||
|
||||
/// No description provided for @send.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Send'**
|
||||
String get send;
|
||||
|
||||
/// No description provided for @sendMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Send message'**
|
||||
String get sendMessage;
|
||||
|
||||
/// No description provided for @file.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'File'**
|
||||
String get file;
|
||||
|
||||
/// No description provided for @photo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Photo'**
|
||||
String get photo;
|
||||
|
||||
/// No description provided for @camera.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Camera'**
|
||||
String get camera;
|
||||
|
||||
/// No description provided for @apiUnavailable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'API service not available'**
|
||||
String get apiUnavailable;
|
||||
|
||||
/// No description provided for @unableToLoadImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unable to load image'**
|
||||
String get unableToLoadImage;
|
||||
|
||||
/// No description provided for @notAnImageFile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not an image file: {fileName}'**
|
||||
String notAnImageFile(String fileName);
|
||||
|
||||
/// No description provided for @failedToLoadImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to load image: {error}'**
|
||||
String failedToLoadImage(String error);
|
||||
|
||||
/// No description provided for @invalidDataUrl.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid data URL format'**
|
||||
String get invalidDataUrl;
|
||||
|
||||
/// No description provided for @failedToDecodeImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to decode image'**
|
||||
String get failedToDecodeImage;
|
||||
|
||||
/// No description provided for @invalidImageFormat.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid image format'**
|
||||
String get invalidImageFormat;
|
||||
|
||||
/// No description provided for @emptyImageData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Empty image data'**
|
||||
String get emptyImageData;
|
||||
|
||||
/// No description provided for @offlineBanner.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You\'re offline. Some features may be limited.'**
|
||||
String get offlineBanner;
|
||||
|
||||
/// No description provided for @featureRequiresInternet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This feature requires an internet connection'**
|
||||
String get featureRequiresInternet;
|
||||
|
||||
/// No description provided for @messagesWillSendWhenOnline.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Messages will be sent when you\'re back online'**
|
||||
String get messagesWillSendWhenOnline;
|
||||
|
||||
/// No description provided for @confirm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Confirm'**
|
||||
String get confirm;
|
||||
|
||||
/// No description provided for @cancel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cancel'**
|
||||
String get cancel;
|
||||
|
||||
/// No description provided for @ok.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'OK'**
|
||||
String get ok;
|
||||
|
||||
/// No description provided for @inputField.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Input field'**
|
||||
String get inputField;
|
||||
|
||||
/// No description provided for @captureDocumentOrImage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Capture a document or image'**
|
||||
String get captureDocumentOrImage;
|
||||
|
||||
/// No description provided for @checkConnection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Check Connection'**
|
||||
String get checkConnection;
|
||||
|
||||
/// No description provided for @openSettings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open Settings'**
|
||||
String get openSettings;
|
||||
|
||||
/// No description provided for @chooseDifferentFile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose Different File'**
|
||||
String get chooseDifferentFile;
|
||||
|
||||
/// No description provided for @goBack.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Go Back'**
|
||||
String get goBack;
|
||||
|
||||
/// No description provided for @technicalDetails.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Technical Details'**
|
||||
String get technicalDetails;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
const _AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) {
|
||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) => <String>['de', 'en', 'fr', 'it'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
|
||||
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'de': return AppLocalizationsDe();
|
||||
case 'en': return AppLocalizationsEn();
|
||||
case 'fr': return AppLocalizationsFr();
|
||||
case 'it': return AppLocalizationsIt();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.'
|
||||
);
|
||||
}
|
||||
444
lib/l10n/app_localizations_de.dart
Normal file
444
lib/l10n/app_localizations_de.dart
Normal file
@@ -0,0 +1,444 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for German (`de`).
|
||||
class AppLocalizationsDe extends AppLocalizations {
|
||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Conduit';
|
||||
|
||||
@override
|
||||
String get initializationFailed => 'Initialisierung fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get retry => 'Erneut versuchen';
|
||||
|
||||
@override
|
||||
String get back => 'Zurück';
|
||||
|
||||
@override
|
||||
String get you => 'Du';
|
||||
|
||||
@override
|
||||
String get loadingProfile => 'Profil wird geladen...';
|
||||
|
||||
@override
|
||||
String get unableToLoadProfile => 'Profil konnte nicht geladen werden';
|
||||
|
||||
@override
|
||||
String get pleaseCheckConnection => 'Bitte überprüfe deine Verbindung und versuche es erneut';
|
||||
|
||||
@override
|
||||
String get account => 'Konto';
|
||||
|
||||
@override
|
||||
String get signOut => 'Abmelden';
|
||||
|
||||
@override
|
||||
String get endYourSession => 'Sitzung beenden';
|
||||
|
||||
@override
|
||||
String get defaultModel => 'Standardmodell';
|
||||
|
||||
@override
|
||||
String get autoSelect => 'Automatische Auswahl';
|
||||
|
||||
@override
|
||||
String get loadingModels => 'Modelle werden geladen...';
|
||||
|
||||
@override
|
||||
String get failedToLoadModels => 'Modelle konnten nicht geladen werden';
|
||||
|
||||
@override
|
||||
String get availableModels => 'Verfügbare Modelle';
|
||||
|
||||
@override
|
||||
String get noResults => 'Keine Ergebnisse';
|
||||
|
||||
@override
|
||||
String get searchModels => 'Modelle suchen...';
|
||||
|
||||
@override
|
||||
String get errorMessage => 'Etwas ist schief gelaufen. Bitte versuche es erneut.';
|
||||
|
||||
@override
|
||||
String get loginButton => 'Anmelden';
|
||||
|
||||
@override
|
||||
String get menuItem => 'Einstellungen';
|
||||
|
||||
@override
|
||||
String dynamicContentWithPlaceholder(String name) {
|
||||
return 'Willkommen, $name!';
|
||||
}
|
||||
|
||||
@override
|
||||
String itemsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Elemente',
|
||||
one: '1 Element',
|
||||
zero: 'Keine Elemente',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get closeButtonSemantic => 'Schließen';
|
||||
|
||||
@override
|
||||
String get loadingContent => 'Inhalt wird geladen';
|
||||
|
||||
@override
|
||||
String get noItems => 'Keine Elemente';
|
||||
|
||||
@override
|
||||
String get noItemsToDisplay => 'Keine Elemente zum Anzeigen';
|
||||
|
||||
@override
|
||||
String get loadMore => 'Mehr laden';
|
||||
|
||||
@override
|
||||
String get workspace => 'Arbeitsbereich';
|
||||
|
||||
@override
|
||||
String get recentFiles => 'Zuletzt verwendete Dateien';
|
||||
|
||||
@override
|
||||
String get knowledgeBase => 'Wissensdatenbank';
|
||||
|
||||
@override
|
||||
String get noFilesYet => 'Noch keine Dateien';
|
||||
|
||||
@override
|
||||
String get uploadDocsPrompt => 'Lade Dokumente hoch, um sie in deinen Unterhaltungen mit Conduit zu verwenden';
|
||||
|
||||
@override
|
||||
String get uploadFirstFile => 'Erste Datei hochladen';
|
||||
|
||||
@override
|
||||
String get knowledgeBaseEmpty => 'Wissensdatenbank ist leer';
|
||||
|
||||
@override
|
||||
String get createCollectionsPrompt => 'Erstelle Sammlungen verwandter Dokumente zur einfachen Referenz';
|
||||
|
||||
@override
|
||||
String get chooseSourcePhoto => 'Quelle auswählen';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Foto aufnehmen';
|
||||
|
||||
@override
|
||||
String get chooseFromGallery => 'Aus Fotos auswählen';
|
||||
|
||||
@override
|
||||
String get document => 'Dokument';
|
||||
|
||||
@override
|
||||
String get documentHint => 'PDF-, Word- oder Textdatei';
|
||||
|
||||
@override
|
||||
String get uploadFileTitle => 'Datei hochladen';
|
||||
|
||||
@override
|
||||
String fileUploadComingSoon(String type) {
|
||||
return 'Dateiupload für $type kommt bald!';
|
||||
}
|
||||
|
||||
@override
|
||||
String get kbCreationComingSoon => 'Erstellung der Wissensdatenbank kommt bald!';
|
||||
|
||||
@override
|
||||
String get backToServerSetup => 'Zur Servereinrichtung zurück';
|
||||
|
||||
@override
|
||||
String get connectedToServer => 'Mit Server verbunden';
|
||||
|
||||
@override
|
||||
String get signIn => 'Anmelden';
|
||||
|
||||
@override
|
||||
String get enterCredentials => 'Gib deine Anmeldedaten ein, um auf deine KI-Unterhaltungen zuzugreifen';
|
||||
|
||||
@override
|
||||
String get credentials => 'Zugangsdaten';
|
||||
|
||||
@override
|
||||
String get apiKey => 'API-Schlüssel';
|
||||
|
||||
@override
|
||||
String get usernameOrEmail => 'Benutzername oder E‑Mail';
|
||||
|
||||
@override
|
||||
String get password => 'Passwort';
|
||||
|
||||
@override
|
||||
String get signInWithApiKey => 'Mit API-Schlüssel anmelden';
|
||||
|
||||
@override
|
||||
String get connectToServer => 'Mit Server verbinden';
|
||||
|
||||
@override
|
||||
String get enterServerAddress => 'Gib die Adresse deines Open-WebUI-Servers ein, um zu beginnen';
|
||||
|
||||
@override
|
||||
String get serverUrl => 'Server-URL';
|
||||
|
||||
@override
|
||||
String get serverUrlHint => 'https://dein-server.com';
|
||||
|
||||
@override
|
||||
String get enterServerUrlSemantic => 'Gib deine Server-URL oder IP-Adresse ein';
|
||||
|
||||
@override
|
||||
String get headerName => 'Header-Name';
|
||||
|
||||
@override
|
||||
String get headerValue => 'Header-Wert';
|
||||
|
||||
@override
|
||||
String get headerValueHint => 'api-key-123 oder Bearer-Token';
|
||||
|
||||
@override
|
||||
String get addHeader => 'Header hinzufügen';
|
||||
|
||||
@override
|
||||
String get maximumHeadersReached => 'Maximale Anzahl erreicht';
|
||||
|
||||
@override
|
||||
String get removeHeader => 'Header entfernen';
|
||||
|
||||
@override
|
||||
String get connecting => 'Verbindung wird hergestellt...';
|
||||
|
||||
@override
|
||||
String get connectToServerButton => 'Mit Server verbinden';
|
||||
|
||||
@override
|
||||
String get demoModeActive => 'Demo-Modus aktiv';
|
||||
|
||||
@override
|
||||
String get skipServerSetupTryDemo => 'Servereinrichtung überspringen und Demo testen';
|
||||
|
||||
@override
|
||||
String get enterDemo => 'Demo starten';
|
||||
|
||||
@override
|
||||
String get demoBadge => 'Demo';
|
||||
|
||||
@override
|
||||
String get serverNotOpenWebUI => 'Dies scheint kein Open-WebUI-Server zu sein.';
|
||||
|
||||
@override
|
||||
String get serverUrlEmpty => 'Server-URL darf nicht leer sein';
|
||||
|
||||
@override
|
||||
String get invalidUrlFormat => 'Ungültiges URL-Format. Bitte Eingabe prüfen.';
|
||||
|
||||
@override
|
||||
String get onlyHttpHttps => 'Nur HTTP- und HTTPS-Protokolle werden unterstützt.';
|
||||
|
||||
@override
|
||||
String get serverAddressRequired => 'Serveradresse erforderlich (z. B. 192.168.1.10 oder example.com).';
|
||||
|
||||
@override
|
||||
String get portRange => 'Port muss zwischen 1 und 65535 liegen.';
|
||||
|
||||
@override
|
||||
String get invalidIpFormat => 'Ungültiges IP-Format. Beispiel: 192.168.1.10.';
|
||||
|
||||
@override
|
||||
String get couldNotConnectGeneric => 'Verbindung fehlgeschlagen. Adresse prüfen und erneut versuchen.';
|
||||
|
||||
@override
|
||||
String get weCouldntReachServer => 'Server nicht erreichbar. Verbindung und Serverstatus prüfen.';
|
||||
|
||||
@override
|
||||
String get connectionTimedOut => 'Zeitüberschreitung. Server eventuell ausgelastet oder blockiert.';
|
||||
|
||||
@override
|
||||
String get useHttpOrHttpsOnly => 'Nur http:// oder https:// verwenden.';
|
||||
|
||||
@override
|
||||
String get loginFailed => 'Anmeldung fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get invalidCredentials => 'Ungültiger Benutzername oder Passwort. Bitte erneut versuchen.';
|
||||
|
||||
@override
|
||||
String get serverRedirectingHttps => 'Server leitet um. HTTPS-Konfiguration prüfen.';
|
||||
|
||||
@override
|
||||
String get unableToConnectServer => 'Verbindung zum Server nicht möglich. Bitte Verbindung prüfen.';
|
||||
|
||||
@override
|
||||
String get requestTimedOut => 'Zeitüberschreitung. Bitte erneut versuchen.';
|
||||
|
||||
@override
|
||||
String get genericSignInFailed => 'Anmeldung nicht möglich. Zugangsdaten und Server prüfen.';
|
||||
|
||||
@override
|
||||
String get skip => 'Überspringen';
|
||||
|
||||
@override
|
||||
String get next => 'Weiter';
|
||||
|
||||
@override
|
||||
String get done => 'Fertig';
|
||||
|
||||
@override
|
||||
String get onboardStartTitle => 'Unterhaltung starten';
|
||||
|
||||
@override
|
||||
String get onboardStartSubtitle => 'Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet1 => 'Modellname oben antippen, um zu wechseln';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet2 => 'Mit Neuer Chat den Kontext zurücksetzen';
|
||||
|
||||
@override
|
||||
String get onboardAttachTitle => 'Kontext anhängen';
|
||||
|
||||
@override
|
||||
String get onboardAttachSubtitle => 'Antworten mit Dateien oder Bildern untermauern.';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet1 => 'Dateien: PDFs, Dokumente, Datensätze';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet2 => 'Bilder: Fotos oder Screenshots';
|
||||
|
||||
@override
|
||||
String get onboardSpeakTitle => 'Natürlich sprechen';
|
||||
|
||||
@override
|
||||
String get onboardSpeakSubtitle => 'Auf das Mikro tippen, um zu diktieren.';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet1 => 'Jederzeit stoppen; Text bleibt erhalten';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet2 => 'Ideal für kurze Notizen oder lange Prompts';
|
||||
|
||||
@override
|
||||
String get onboardQuickTitle => 'Schnellaktionen';
|
||||
|
||||
@override
|
||||
String get onboardQuickSubtitle => 'Links oben das Menü für Chats und Navigation öffnen.';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet1 => 'Menü tippen, um Chats und Navigation zu öffnen';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet2 => 'Schnell zu Neuer Chat, Dateien oder Profil springen';
|
||||
|
||||
@override
|
||||
String get addAttachment => 'Anhang hinzufügen';
|
||||
|
||||
@override
|
||||
String get tools => 'Werkzeuge';
|
||||
|
||||
@override
|
||||
String get voiceInput => 'Spracheingabe';
|
||||
|
||||
@override
|
||||
String get messageInputLabel => 'Nachrichteneingabe';
|
||||
|
||||
@override
|
||||
String get messageInputHint => 'Nachricht eingeben';
|
||||
|
||||
@override
|
||||
String get messageHintText => 'Nachricht...';
|
||||
|
||||
@override
|
||||
String get stopGenerating => 'Generierung stoppen';
|
||||
|
||||
@override
|
||||
String get send => 'Senden';
|
||||
|
||||
@override
|
||||
String get sendMessage => 'Nachricht senden';
|
||||
|
||||
@override
|
||||
String get file => 'Datei';
|
||||
|
||||
@override
|
||||
String get photo => 'Foto';
|
||||
|
||||
@override
|
||||
String get camera => 'Kamera';
|
||||
|
||||
@override
|
||||
String get apiUnavailable => 'API-Dienst nicht verfügbar';
|
||||
|
||||
@override
|
||||
String get unableToLoadImage => 'Bild kann nicht geladen werden';
|
||||
|
||||
@override
|
||||
String notAnImageFile(String fileName) {
|
||||
return 'Keine Bilddatei: $fileName';
|
||||
}
|
||||
|
||||
@override
|
||||
String failedToLoadImage(String error) {
|
||||
return 'Bild konnte nicht geladen werden: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get invalidDataUrl => 'Ungültiges Data-URL-Format';
|
||||
|
||||
@override
|
||||
String get failedToDecodeImage => 'Bild konnte nicht decodiert werden';
|
||||
|
||||
@override
|
||||
String get invalidImageFormat => 'Ungültiges Bildformat';
|
||||
|
||||
@override
|
||||
String get emptyImageData => 'Leere Bilddaten';
|
||||
|
||||
@override
|
||||
String get offlineBanner => 'Du bist offline. Einige Funktionen sind eingeschränkt.';
|
||||
|
||||
@override
|
||||
String get featureRequiresInternet => 'Diese Funktion erfordert eine Internetverbindung';
|
||||
|
||||
@override
|
||||
String get messagesWillSendWhenOnline => 'Nachrichten werden gesendet, sobald du wieder online bist';
|
||||
|
||||
@override
|
||||
String get confirm => 'Bestätigen';
|
||||
|
||||
@override
|
||||
String get cancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get ok => 'OK';
|
||||
|
||||
@override
|
||||
String get inputField => 'Eingabefeld';
|
||||
|
||||
@override
|
||||
String get captureDocumentOrImage => 'Dokument oder Bild aufnehmen';
|
||||
|
||||
@override
|
||||
String get checkConnection => 'Verbindung prüfen';
|
||||
|
||||
@override
|
||||
String get openSettings => 'Einstellungen öffnen';
|
||||
|
||||
@override
|
||||
String get chooseDifferentFile => 'Andere Datei wählen';
|
||||
|
||||
@override
|
||||
String get goBack => 'Zurück';
|
||||
|
||||
@override
|
||||
String get technicalDetails => 'Technische Details';
|
||||
}
|
||||
444
lib/l10n/app_localizations_en.dart
Normal file
444
lib/l10n/app_localizations_en.dart
Normal file
@@ -0,0 +1,444 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Conduit';
|
||||
|
||||
@override
|
||||
String get initializationFailed => 'Initialization Failed';
|
||||
|
||||
@override
|
||||
String get retry => 'Retry';
|
||||
|
||||
@override
|
||||
String get back => 'Back';
|
||||
|
||||
@override
|
||||
String get you => 'You';
|
||||
|
||||
@override
|
||||
String get loadingProfile => 'Loading profile...';
|
||||
|
||||
@override
|
||||
String get unableToLoadProfile => 'Unable to load profile';
|
||||
|
||||
@override
|
||||
String get pleaseCheckConnection => 'Please check your connection and try again';
|
||||
|
||||
@override
|
||||
String get account => 'Account';
|
||||
|
||||
@override
|
||||
String get signOut => 'Sign Out';
|
||||
|
||||
@override
|
||||
String get endYourSession => 'End your session';
|
||||
|
||||
@override
|
||||
String get defaultModel => 'Default Model';
|
||||
|
||||
@override
|
||||
String get autoSelect => 'Auto-select';
|
||||
|
||||
@override
|
||||
String get loadingModels => 'Loading models...';
|
||||
|
||||
@override
|
||||
String get failedToLoadModels => 'Failed to load models';
|
||||
|
||||
@override
|
||||
String get availableModels => 'Available Models';
|
||||
|
||||
@override
|
||||
String get noResults => 'No results';
|
||||
|
||||
@override
|
||||
String get searchModels => 'Search models...';
|
||||
|
||||
@override
|
||||
String get errorMessage => 'Something went wrong. Please try again.';
|
||||
|
||||
@override
|
||||
String get loginButton => 'Login';
|
||||
|
||||
@override
|
||||
String get menuItem => 'Settings';
|
||||
|
||||
@override
|
||||
String dynamicContentWithPlaceholder(String name) {
|
||||
return 'Welcome, $name!';
|
||||
}
|
||||
|
||||
@override
|
||||
String itemsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count items',
|
||||
one: '1 item',
|
||||
zero: 'No items',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get closeButtonSemantic => 'Close';
|
||||
|
||||
@override
|
||||
String get loadingContent => 'Loading content';
|
||||
|
||||
@override
|
||||
String get noItems => 'No items';
|
||||
|
||||
@override
|
||||
String get noItemsToDisplay => 'No items to display';
|
||||
|
||||
@override
|
||||
String get loadMore => 'Load More';
|
||||
|
||||
@override
|
||||
String get workspace => 'Workspace';
|
||||
|
||||
@override
|
||||
String get recentFiles => 'Recent Files';
|
||||
|
||||
@override
|
||||
String get knowledgeBase => 'Knowledge Base';
|
||||
|
||||
@override
|
||||
String get noFilesYet => 'No files yet';
|
||||
|
||||
@override
|
||||
String get uploadDocsPrompt => 'Upload documents to reference in your conversations with Conduit';
|
||||
|
||||
@override
|
||||
String get uploadFirstFile => 'Upload your first file';
|
||||
|
||||
@override
|
||||
String get knowledgeBaseEmpty => 'Knowledge base is empty';
|
||||
|
||||
@override
|
||||
String get createCollectionsPrompt => 'Create collections of related documents for easy reference';
|
||||
|
||||
@override
|
||||
String get chooseSourcePhoto => 'Choose your source';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Take a photo';
|
||||
|
||||
@override
|
||||
String get chooseFromGallery => 'Choose from your photos';
|
||||
|
||||
@override
|
||||
String get document => 'Document';
|
||||
|
||||
@override
|
||||
String get documentHint => 'PDF, Word, or text file';
|
||||
|
||||
@override
|
||||
String get uploadFileTitle => 'Upload File';
|
||||
|
||||
@override
|
||||
String fileUploadComingSoon(String type) {
|
||||
return 'File upload for $type is coming soon!';
|
||||
}
|
||||
|
||||
@override
|
||||
String get kbCreationComingSoon => 'Knowledge base creation is coming soon!';
|
||||
|
||||
@override
|
||||
String get backToServerSetup => 'Back to server setup';
|
||||
|
||||
@override
|
||||
String get connectedToServer => 'Connected to Server';
|
||||
|
||||
@override
|
||||
String get signIn => 'Sign In';
|
||||
|
||||
@override
|
||||
String get enterCredentials => 'Enter your credentials to access your AI conversations';
|
||||
|
||||
@override
|
||||
String get credentials => 'Credentials';
|
||||
|
||||
@override
|
||||
String get apiKey => 'API Key';
|
||||
|
||||
@override
|
||||
String get usernameOrEmail => 'Username or Email';
|
||||
|
||||
@override
|
||||
String get password => 'Password';
|
||||
|
||||
@override
|
||||
String get signInWithApiKey => 'Sign in with API Key';
|
||||
|
||||
@override
|
||||
String get connectToServer => 'Connect to Server';
|
||||
|
||||
@override
|
||||
String get enterServerAddress => 'Enter your Open-WebUI server address to get started';
|
||||
|
||||
@override
|
||||
String get serverUrl => 'Server URL';
|
||||
|
||||
@override
|
||||
String get serverUrlHint => 'https://your-server.com';
|
||||
|
||||
@override
|
||||
String get enterServerUrlSemantic => 'Enter your server URL or IP address';
|
||||
|
||||
@override
|
||||
String get headerName => 'Header Name';
|
||||
|
||||
@override
|
||||
String get headerValue => 'Header Value';
|
||||
|
||||
@override
|
||||
String get headerValueHint => 'api-key-123 or Bearer token';
|
||||
|
||||
@override
|
||||
String get addHeader => 'Add header';
|
||||
|
||||
@override
|
||||
String get maximumHeadersReached => 'Maximum headers reached';
|
||||
|
||||
@override
|
||||
String get removeHeader => 'Remove header';
|
||||
|
||||
@override
|
||||
String get connecting => 'Connecting...';
|
||||
|
||||
@override
|
||||
String get connectToServerButton => 'Connect to Server';
|
||||
|
||||
@override
|
||||
String get demoModeActive => 'Demo Mode Active';
|
||||
|
||||
@override
|
||||
String get skipServerSetupTryDemo => 'Skip server setup and try the demo';
|
||||
|
||||
@override
|
||||
String get enterDemo => 'Enter Demo';
|
||||
|
||||
@override
|
||||
String get demoBadge => 'Demo';
|
||||
|
||||
@override
|
||||
String get serverNotOpenWebUI => 'This does not appear to be an Open-WebUI server.';
|
||||
|
||||
@override
|
||||
String get serverUrlEmpty => 'Server URL cannot be empty';
|
||||
|
||||
@override
|
||||
String get invalidUrlFormat => 'Invalid URL format. Please check your input.';
|
||||
|
||||
@override
|
||||
String get onlyHttpHttps => 'Only HTTP and HTTPS protocols are supported.';
|
||||
|
||||
@override
|
||||
String get serverAddressRequired => 'Server address is required (e.g., 192.168.1.10 or example.com).';
|
||||
|
||||
@override
|
||||
String get portRange => 'Port must be between 1 and 65535.';
|
||||
|
||||
@override
|
||||
String get invalidIpFormat => 'Invalid IP address format. Use format like 192.168.1.10.';
|
||||
|
||||
@override
|
||||
String get couldNotConnectGeneric => 'Couldn\'t connect. Double-check the address and try again.';
|
||||
|
||||
@override
|
||||
String get weCouldntReachServer => 'We couldn\'t reach the server. Check your connection and that the server is running.';
|
||||
|
||||
@override
|
||||
String get connectionTimedOut => 'Connection timed out. The server might be busy or blocked by a firewall.';
|
||||
|
||||
@override
|
||||
String get useHttpOrHttpsOnly => 'Use http:// or https:// only.';
|
||||
|
||||
@override
|
||||
String get loginFailed => 'Login failed';
|
||||
|
||||
@override
|
||||
String get invalidCredentials => 'Invalid username or password. Please try again.';
|
||||
|
||||
@override
|
||||
String get serverRedirectingHttps => 'The server is redirecting requests. Check your server\'s HTTPS configuration.';
|
||||
|
||||
@override
|
||||
String get unableToConnectServer => 'Unable to connect to server. Please check your connection.';
|
||||
|
||||
@override
|
||||
String get requestTimedOut => 'The request timed out. Please try again.';
|
||||
|
||||
@override
|
||||
String get genericSignInFailed => 'We couldn\'t sign you in. Check your credentials and server settings.';
|
||||
|
||||
@override
|
||||
String get skip => 'Skip';
|
||||
|
||||
@override
|
||||
String get next => 'Next';
|
||||
|
||||
@override
|
||||
String get done => 'Done';
|
||||
|
||||
@override
|
||||
String get onboardStartTitle => 'Start a conversation';
|
||||
|
||||
@override
|
||||
String get onboardStartSubtitle => 'Choose a model, then type below to begin. Tap New Chat anytime.';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet1 => 'Tap the model name in the top bar to switch models';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet2 => 'Use New Chat to reset context';
|
||||
|
||||
@override
|
||||
String get onboardAttachTitle => 'Attach context';
|
||||
|
||||
@override
|
||||
String get onboardAttachSubtitle => 'Ground responses by adding files or images.';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet1 => 'Files: PDFs, docs, datasets';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet2 => 'Images: photos or screenshots';
|
||||
|
||||
@override
|
||||
String get onboardSpeakTitle => 'Speak naturally';
|
||||
|
||||
@override
|
||||
String get onboardSpeakSubtitle => 'Tap the mic to dictate with live waveform feedback.';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet1 => 'Stop anytime; partial text is preserved';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet2 => 'Great for quick notes or long prompts';
|
||||
|
||||
@override
|
||||
String get onboardQuickTitle => 'Quick actions';
|
||||
|
||||
@override
|
||||
String get onboardQuickSubtitle => 'Use the top‑left menu to open the chats list and navigation.';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet1 => 'Tap the menu to open the chats list and navigation';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet2 => 'Jump instantly to New Chat, Files, or Profile';
|
||||
|
||||
@override
|
||||
String get addAttachment => 'Add attachment';
|
||||
|
||||
@override
|
||||
String get tools => 'Tools';
|
||||
|
||||
@override
|
||||
String get voiceInput => 'Voice input';
|
||||
|
||||
@override
|
||||
String get messageInputLabel => 'Message input';
|
||||
|
||||
@override
|
||||
String get messageInputHint => 'Type your message';
|
||||
|
||||
@override
|
||||
String get messageHintText => 'Message...';
|
||||
|
||||
@override
|
||||
String get stopGenerating => 'Stop generating';
|
||||
|
||||
@override
|
||||
String get send => 'Send';
|
||||
|
||||
@override
|
||||
String get sendMessage => 'Send message';
|
||||
|
||||
@override
|
||||
String get file => 'File';
|
||||
|
||||
@override
|
||||
String get photo => 'Photo';
|
||||
|
||||
@override
|
||||
String get camera => 'Camera';
|
||||
|
||||
@override
|
||||
String get apiUnavailable => 'API service not available';
|
||||
|
||||
@override
|
||||
String get unableToLoadImage => 'Unable to load image';
|
||||
|
||||
@override
|
||||
String notAnImageFile(String fileName) {
|
||||
return 'Not an image file: $fileName';
|
||||
}
|
||||
|
||||
@override
|
||||
String failedToLoadImage(String error) {
|
||||
return 'Failed to load image: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get invalidDataUrl => 'Invalid data URL format';
|
||||
|
||||
@override
|
||||
String get failedToDecodeImage => 'Failed to decode image';
|
||||
|
||||
@override
|
||||
String get invalidImageFormat => 'Invalid image format';
|
||||
|
||||
@override
|
||||
String get emptyImageData => 'Empty image data';
|
||||
|
||||
@override
|
||||
String get offlineBanner => 'You\'re offline. Some features may be limited.';
|
||||
|
||||
@override
|
||||
String get featureRequiresInternet => 'This feature requires an internet connection';
|
||||
|
||||
@override
|
||||
String get messagesWillSendWhenOnline => 'Messages will be sent when you\'re back online';
|
||||
|
||||
@override
|
||||
String get confirm => 'Confirm';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get ok => 'OK';
|
||||
|
||||
@override
|
||||
String get inputField => 'Input field';
|
||||
|
||||
@override
|
||||
String get captureDocumentOrImage => 'Capture a document or image';
|
||||
|
||||
@override
|
||||
String get checkConnection => 'Check Connection';
|
||||
|
||||
@override
|
||||
String get openSettings => 'Open Settings';
|
||||
|
||||
@override
|
||||
String get chooseDifferentFile => 'Choose Different File';
|
||||
|
||||
@override
|
||||
String get goBack => 'Go Back';
|
||||
|
||||
@override
|
||||
String get technicalDetails => 'Technical Details';
|
||||
}
|
||||
444
lib/l10n/app_localizations_fr.dart
Normal file
444
lib/l10n/app_localizations_fr.dart
Normal file
@@ -0,0 +1,444 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for French (`fr`).
|
||||
class AppLocalizationsFr extends AppLocalizations {
|
||||
AppLocalizationsFr([String locale = 'fr']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Conduit';
|
||||
|
||||
@override
|
||||
String get initializationFailed => 'Échec de l\'initialisation';
|
||||
|
||||
@override
|
||||
String get retry => 'Réessayer';
|
||||
|
||||
@override
|
||||
String get back => 'Retour';
|
||||
|
||||
@override
|
||||
String get you => 'Vous';
|
||||
|
||||
@override
|
||||
String get loadingProfile => 'Chargement du profil...';
|
||||
|
||||
@override
|
||||
String get unableToLoadProfile => 'Impossible de charger le profil';
|
||||
|
||||
@override
|
||||
String get pleaseCheckConnection => 'Veuillez vérifier votre connexion et réessayer';
|
||||
|
||||
@override
|
||||
String get account => 'Compte';
|
||||
|
||||
@override
|
||||
String get signOut => 'Se déconnecter';
|
||||
|
||||
@override
|
||||
String get endYourSession => 'Terminer votre session';
|
||||
|
||||
@override
|
||||
String get defaultModel => 'Modèle par défaut';
|
||||
|
||||
@override
|
||||
String get autoSelect => 'Sélection automatique';
|
||||
|
||||
@override
|
||||
String get loadingModels => 'Chargement des modèles...';
|
||||
|
||||
@override
|
||||
String get failedToLoadModels => 'Échec du chargement des modèles';
|
||||
|
||||
@override
|
||||
String get availableModels => 'Modèles disponibles';
|
||||
|
||||
@override
|
||||
String get noResults => 'Aucun résultat';
|
||||
|
||||
@override
|
||||
String get searchModels => 'Rechercher des modèles...';
|
||||
|
||||
@override
|
||||
String get errorMessage => 'Une erreur s\'est produite. Veuillez réessayer.';
|
||||
|
||||
@override
|
||||
String get loginButton => 'Connexion';
|
||||
|
||||
@override
|
||||
String get menuItem => 'Paramètres';
|
||||
|
||||
@override
|
||||
String dynamicContentWithPlaceholder(String name) {
|
||||
return 'Bienvenue, $name !';
|
||||
}
|
||||
|
||||
@override
|
||||
String itemsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count éléments',
|
||||
one: '1 élément',
|
||||
zero: 'Aucun élément',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get closeButtonSemantic => 'Fermer';
|
||||
|
||||
@override
|
||||
String get loadingContent => 'Chargement du contenu';
|
||||
|
||||
@override
|
||||
String get noItems => 'Aucun élément';
|
||||
|
||||
@override
|
||||
String get noItemsToDisplay => 'Aucun élément à afficher';
|
||||
|
||||
@override
|
||||
String get loadMore => 'Charger plus';
|
||||
|
||||
@override
|
||||
String get workspace => 'Espace de travail';
|
||||
|
||||
@override
|
||||
String get recentFiles => 'Fichiers récents';
|
||||
|
||||
@override
|
||||
String get knowledgeBase => 'Base de connaissances';
|
||||
|
||||
@override
|
||||
String get noFilesYet => 'Pas encore de fichiers';
|
||||
|
||||
@override
|
||||
String get uploadDocsPrompt => 'Importez des documents à utiliser dans vos conversations avec Conduit';
|
||||
|
||||
@override
|
||||
String get uploadFirstFile => 'Importer votre premier fichier';
|
||||
|
||||
@override
|
||||
String get knowledgeBaseEmpty => 'La base de connaissances est vide';
|
||||
|
||||
@override
|
||||
String get createCollectionsPrompt => 'Créez des collections de documents liés pour une référence facile';
|
||||
|
||||
@override
|
||||
String get chooseSourcePhoto => 'Choisir la source';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Prendre une photo';
|
||||
|
||||
@override
|
||||
String get chooseFromGallery => 'Choisir depuis vos photos';
|
||||
|
||||
@override
|
||||
String get document => 'Document';
|
||||
|
||||
@override
|
||||
String get documentHint => 'Fichier PDF, Word ou texte';
|
||||
|
||||
@override
|
||||
String get uploadFileTitle => 'Importer un fichier';
|
||||
|
||||
@override
|
||||
String fileUploadComingSoon(String type) {
|
||||
return 'Le téléversement de fichiers pour $type arrive bientôt !';
|
||||
}
|
||||
|
||||
@override
|
||||
String get kbCreationComingSoon => 'La création de la base de connaissances arrive bientôt !';
|
||||
|
||||
@override
|
||||
String get backToServerSetup => 'Retour à la configuration du serveur';
|
||||
|
||||
@override
|
||||
String get connectedToServer => 'Connecté au serveur';
|
||||
|
||||
@override
|
||||
String get signIn => 'Se connecter';
|
||||
|
||||
@override
|
||||
String get enterCredentials => 'Entrez vos identifiants pour accéder à vos conversations IA';
|
||||
|
||||
@override
|
||||
String get credentials => 'Identifiants';
|
||||
|
||||
@override
|
||||
String get apiKey => 'Clé API';
|
||||
|
||||
@override
|
||||
String get usernameOrEmail => 'Nom d\'utilisateur ou e‑mail';
|
||||
|
||||
@override
|
||||
String get password => 'Mot de passe';
|
||||
|
||||
@override
|
||||
String get signInWithApiKey => 'Se connecter avec une clé API';
|
||||
|
||||
@override
|
||||
String get connectToServer => 'Se connecter au serveur';
|
||||
|
||||
@override
|
||||
String get enterServerAddress => 'Saisissez l\'adresse de votre serveur Open-WebUI pour commencer';
|
||||
|
||||
@override
|
||||
String get serverUrl => 'URL du serveur';
|
||||
|
||||
@override
|
||||
String get serverUrlHint => 'https://votre-serveur.com';
|
||||
|
||||
@override
|
||||
String get enterServerUrlSemantic => 'Saisissez l\'URL ou l\'adresse IP de votre serveur';
|
||||
|
||||
@override
|
||||
String get headerName => 'Nom de l\'en-tête';
|
||||
|
||||
@override
|
||||
String get headerValue => 'Valeur de l\'en-tête';
|
||||
|
||||
@override
|
||||
String get headerValueHint => 'api-key-123 ou jeton Bearer';
|
||||
|
||||
@override
|
||||
String get addHeader => 'Ajouter l\'en-tête';
|
||||
|
||||
@override
|
||||
String get maximumHeadersReached => 'Nombre maximal atteint';
|
||||
|
||||
@override
|
||||
String get removeHeader => 'Supprimer l\'en-tête';
|
||||
|
||||
@override
|
||||
String get connecting => 'Connexion en cours...';
|
||||
|
||||
@override
|
||||
String get connectToServerButton => 'Se connecter au serveur';
|
||||
|
||||
@override
|
||||
String get demoModeActive => 'Mode démo activé';
|
||||
|
||||
@override
|
||||
String get skipServerSetupTryDemo => 'Ignorer la configuration et essayer la démo';
|
||||
|
||||
@override
|
||||
String get enterDemo => 'Entrer en démo';
|
||||
|
||||
@override
|
||||
String get demoBadge => 'Démo';
|
||||
|
||||
@override
|
||||
String get serverNotOpenWebUI => 'Ceci ne semble pas être un serveur Open-WebUI.';
|
||||
|
||||
@override
|
||||
String get serverUrlEmpty => 'L\'URL du serveur ne peut pas être vide';
|
||||
|
||||
@override
|
||||
String get invalidUrlFormat => 'Format d\'URL invalide. Veuillez vérifier votre saisie.';
|
||||
|
||||
@override
|
||||
String get onlyHttpHttps => 'Seuls les protocoles HTTP et HTTPS sont pris en charge.';
|
||||
|
||||
@override
|
||||
String get serverAddressRequired => 'Adresse du serveur requise (ex. 192.168.1.10 ou example.com).';
|
||||
|
||||
@override
|
||||
String get portRange => 'Le port doit être compris entre 1 et 65535.';
|
||||
|
||||
@override
|
||||
String get invalidIpFormat => 'Format d\'IP invalide. Exemple : 192.168.1.10.';
|
||||
|
||||
@override
|
||||
String get couldNotConnectGeneric => 'Connexion impossible. Vérifiez l\'adresse et réessayez.';
|
||||
|
||||
@override
|
||||
String get weCouldntReachServer => 'Impossible d\'atteindre le serveur. Vérifiez la connexion et l\'état du serveur.';
|
||||
|
||||
@override
|
||||
String get connectionTimedOut => 'Délai d\'attente dépassé. Le serveur est peut-être occupé ou bloqué.';
|
||||
|
||||
@override
|
||||
String get useHttpOrHttpsOnly => 'Utilisez uniquement http:// ou https://.';
|
||||
|
||||
@override
|
||||
String get loginFailed => 'Échec de la connexion';
|
||||
|
||||
@override
|
||||
String get invalidCredentials => 'Nom d\'utilisateur ou mot de passe invalide. Réessayez.';
|
||||
|
||||
@override
|
||||
String get serverRedirectingHttps => 'Le serveur redirige les requêtes. Vérifiez la configuration HTTPS.';
|
||||
|
||||
@override
|
||||
String get unableToConnectServer => 'Impossible de se connecter au serveur. Vérifiez votre connexion.';
|
||||
|
||||
@override
|
||||
String get requestTimedOut => 'Délai d\'attente dépassé. Réessayez.';
|
||||
|
||||
@override
|
||||
String get genericSignInFailed => 'Connexion impossible. Vérifiez vos identifiants et le serveur.';
|
||||
|
||||
@override
|
||||
String get skip => 'Ignorer';
|
||||
|
||||
@override
|
||||
String get next => 'Suivant';
|
||||
|
||||
@override
|
||||
String get done => 'Terminé';
|
||||
|
||||
@override
|
||||
String get onboardStartTitle => 'Commencer une conversation';
|
||||
|
||||
@override
|
||||
String get onboardStartSubtitle => 'Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet1 => 'Touchez le nom du modèle en haut pour changer';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet2 => 'Utilisez Nouveau chat pour réinitialiser le contexte';
|
||||
|
||||
@override
|
||||
String get onboardAttachTitle => 'Ajouter du contexte';
|
||||
|
||||
@override
|
||||
String get onboardAttachSubtitle => 'Améliorez les réponses en ajoutant des fichiers ou des images.';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet1 => 'Fichiers : PDF, documents, jeux de données';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet2 => 'Images : photos ou captures d\'écran';
|
||||
|
||||
@override
|
||||
String get onboardSpeakTitle => 'Parlez naturellement';
|
||||
|
||||
@override
|
||||
String get onboardSpeakSubtitle => 'Touchez le micro pour dicter avec retour visuel.';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet1 => 'Arrêtez à tout moment ; le texte partiel est conservé';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet2 => 'Idéal pour des notes rapides ou de longs prompts';
|
||||
|
||||
@override
|
||||
String get onboardQuickTitle => 'Actions rapides';
|
||||
|
||||
@override
|
||||
String get onboardQuickSubtitle => 'Utilisez le menu en haut à gauche pour ouvrir la liste des chats et la navigation.';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet1 => 'Touchez le menu pour ouvrir les chats et la navigation';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet2 => 'Accédez rapidement à Nouveau chat, Fichiers ou Profil';
|
||||
|
||||
@override
|
||||
String get addAttachment => 'Ajouter une pièce jointe';
|
||||
|
||||
@override
|
||||
String get tools => 'Outils';
|
||||
|
||||
@override
|
||||
String get voiceInput => 'Entrée vocale';
|
||||
|
||||
@override
|
||||
String get messageInputLabel => 'Saisie du message';
|
||||
|
||||
@override
|
||||
String get messageInputHint => 'Saisissez votre message';
|
||||
|
||||
@override
|
||||
String get messageHintText => 'Message...';
|
||||
|
||||
@override
|
||||
String get stopGenerating => 'Arrêter la génération';
|
||||
|
||||
@override
|
||||
String get send => 'Envoyer';
|
||||
|
||||
@override
|
||||
String get sendMessage => 'Envoyer le message';
|
||||
|
||||
@override
|
||||
String get file => 'Fichier';
|
||||
|
||||
@override
|
||||
String get photo => 'Photo';
|
||||
|
||||
@override
|
||||
String get camera => 'Appareil photo';
|
||||
|
||||
@override
|
||||
String get apiUnavailable => 'Service API indisponible';
|
||||
|
||||
@override
|
||||
String get unableToLoadImage => 'Impossible de charger l\'image';
|
||||
|
||||
@override
|
||||
String notAnImageFile(String fileName) {
|
||||
return 'Ce n\'est pas un fichier image : $fileName';
|
||||
}
|
||||
|
||||
@override
|
||||
String failedToLoadImage(String error) {
|
||||
return 'Échec du chargement de l\'image : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get invalidDataUrl => 'Format d\'URL de données invalide';
|
||||
|
||||
@override
|
||||
String get failedToDecodeImage => 'Échec du décodage de l\'image';
|
||||
|
||||
@override
|
||||
String get invalidImageFormat => 'Format d\'image invalide';
|
||||
|
||||
@override
|
||||
String get emptyImageData => 'Données d\'image vides';
|
||||
|
||||
@override
|
||||
String get offlineBanner => 'Vous êtes hors ligne. Certaines fonctions peuvent être limitées.';
|
||||
|
||||
@override
|
||||
String get featureRequiresInternet => 'Cette fonctionnalité nécessite une connexion Internet';
|
||||
|
||||
@override
|
||||
String get messagesWillSendWhenOnline => 'Les messages seront envoyés lorsque vous serez de nouveau en ligne';
|
||||
|
||||
@override
|
||||
String get confirm => 'Confirmer';
|
||||
|
||||
@override
|
||||
String get cancel => 'Annuler';
|
||||
|
||||
@override
|
||||
String get ok => 'OK';
|
||||
|
||||
@override
|
||||
String get inputField => 'Champ de saisie';
|
||||
|
||||
@override
|
||||
String get captureDocumentOrImage => 'Capturer un document ou une image';
|
||||
|
||||
@override
|
||||
String get checkConnection => 'Vérifier la connexion';
|
||||
|
||||
@override
|
||||
String get openSettings => 'Ouvrir les réglages';
|
||||
|
||||
@override
|
||||
String get chooseDifferentFile => 'Choisir un autre fichier';
|
||||
|
||||
@override
|
||||
String get goBack => 'Retour';
|
||||
|
||||
@override
|
||||
String get technicalDetails => 'Détails techniques';
|
||||
}
|
||||
444
lib/l10n/app_localizations_it.dart
Normal file
444
lib/l10n/app_localizations_it.dart
Normal file
@@ -0,0 +1,444 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Italian (`it`).
|
||||
class AppLocalizationsIt extends AppLocalizations {
|
||||
AppLocalizationsIt([String locale = 'it']) : super(locale);
|
||||
|
||||
@override
|
||||
String get appTitle => 'Conduit';
|
||||
|
||||
@override
|
||||
String get initializationFailed => 'Inizializzazione non riuscita';
|
||||
|
||||
@override
|
||||
String get retry => 'Riprova';
|
||||
|
||||
@override
|
||||
String get back => 'Indietro';
|
||||
|
||||
@override
|
||||
String get you => 'Tu';
|
||||
|
||||
@override
|
||||
String get loadingProfile => 'Caricamento profilo...';
|
||||
|
||||
@override
|
||||
String get unableToLoadProfile => 'Impossibile caricare il profilo';
|
||||
|
||||
@override
|
||||
String get pleaseCheckConnection => 'Controlla la connessione e riprova';
|
||||
|
||||
@override
|
||||
String get account => 'Account';
|
||||
|
||||
@override
|
||||
String get signOut => 'Esci';
|
||||
|
||||
@override
|
||||
String get endYourSession => 'Termina la sessione';
|
||||
|
||||
@override
|
||||
String get defaultModel => 'Modello predefinito';
|
||||
|
||||
@override
|
||||
String get autoSelect => 'Selezione automatica';
|
||||
|
||||
@override
|
||||
String get loadingModels => 'Caricamento modelli...';
|
||||
|
||||
@override
|
||||
String get failedToLoadModels => 'Impossibile caricare i modelli';
|
||||
|
||||
@override
|
||||
String get availableModels => 'Modelli disponibili';
|
||||
|
||||
@override
|
||||
String get noResults => 'Nessun risultato';
|
||||
|
||||
@override
|
||||
String get searchModels => 'Cerca modelli...';
|
||||
|
||||
@override
|
||||
String get errorMessage => 'Qualcosa è andato storto. Riprova.';
|
||||
|
||||
@override
|
||||
String get loginButton => 'Accedi';
|
||||
|
||||
@override
|
||||
String get menuItem => 'Impostazioni';
|
||||
|
||||
@override
|
||||
String dynamicContentWithPlaceholder(String name) {
|
||||
return 'Benvenuto, $name!';
|
||||
}
|
||||
|
||||
@override
|
||||
String itemsCount(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count elementi',
|
||||
one: '1 elemento',
|
||||
zero: 'Nessun elemento',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get closeButtonSemantic => 'Chiudi';
|
||||
|
||||
@override
|
||||
String get loadingContent => 'Caricamento contenuto';
|
||||
|
||||
@override
|
||||
String get noItems => 'Nessun elemento';
|
||||
|
||||
@override
|
||||
String get noItemsToDisplay => 'Nessun elemento da visualizzare';
|
||||
|
||||
@override
|
||||
String get loadMore => 'Carica altro';
|
||||
|
||||
@override
|
||||
String get workspace => 'Spazio di lavoro';
|
||||
|
||||
@override
|
||||
String get recentFiles => 'File recenti';
|
||||
|
||||
@override
|
||||
String get knowledgeBase => 'Base di conoscenza';
|
||||
|
||||
@override
|
||||
String get noFilesYet => 'Ancora nessun file';
|
||||
|
||||
@override
|
||||
String get uploadDocsPrompt => 'Carica documenti da usare nelle conversazioni con Conduit';
|
||||
|
||||
@override
|
||||
String get uploadFirstFile => 'Carica il tuo primo file';
|
||||
|
||||
@override
|
||||
String get knowledgeBaseEmpty => 'La base di conoscenza è vuota';
|
||||
|
||||
@override
|
||||
String get createCollectionsPrompt => 'Crea raccolte di documenti correlati per un rapido riferimento';
|
||||
|
||||
@override
|
||||
String get chooseSourcePhoto => 'Scegli origine';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Scatta una foto';
|
||||
|
||||
@override
|
||||
String get chooseFromGallery => 'Scegli dalle foto';
|
||||
|
||||
@override
|
||||
String get document => 'Documento';
|
||||
|
||||
@override
|
||||
String get documentHint => 'File PDF, Word o di testo';
|
||||
|
||||
@override
|
||||
String get uploadFileTitle => 'Carica file';
|
||||
|
||||
@override
|
||||
String fileUploadComingSoon(String type) {
|
||||
return 'Il caricamento file per $type arriverà presto!';
|
||||
}
|
||||
|
||||
@override
|
||||
String get kbCreationComingSoon => 'La creazione della base di conoscenza arriverà presto!';
|
||||
|
||||
@override
|
||||
String get backToServerSetup => 'Torna alla configurazione del server';
|
||||
|
||||
@override
|
||||
String get connectedToServer => 'Connesso al server';
|
||||
|
||||
@override
|
||||
String get signIn => 'Accedi';
|
||||
|
||||
@override
|
||||
String get enterCredentials => 'Inserisci le credenziali per accedere alle conversazioni IA';
|
||||
|
||||
@override
|
||||
String get credentials => 'Credenziali';
|
||||
|
||||
@override
|
||||
String get apiKey => 'Chiave API';
|
||||
|
||||
@override
|
||||
String get usernameOrEmail => 'Username o e‑mail';
|
||||
|
||||
@override
|
||||
String get password => 'Password';
|
||||
|
||||
@override
|
||||
String get signInWithApiKey => 'Accedi con chiave API';
|
||||
|
||||
@override
|
||||
String get connectToServer => 'Connetti al server';
|
||||
|
||||
@override
|
||||
String get enterServerAddress => 'Inserisci l\'indirizzo del server Open-WebUI per iniziare';
|
||||
|
||||
@override
|
||||
String get serverUrl => 'URL del server';
|
||||
|
||||
@override
|
||||
String get serverUrlHint => 'https://tuo-server.com';
|
||||
|
||||
@override
|
||||
String get enterServerUrlSemantic => 'Inserisci l\'URL o l\'indirizzo IP del server';
|
||||
|
||||
@override
|
||||
String get headerName => 'Nome header';
|
||||
|
||||
@override
|
||||
String get headerValue => 'Valore header';
|
||||
|
||||
@override
|
||||
String get headerValueHint => 'api-key-123 o token Bearer';
|
||||
|
||||
@override
|
||||
String get addHeader => 'Aggiungi header';
|
||||
|
||||
@override
|
||||
String get maximumHeadersReached => 'Numero massimo raggiunto';
|
||||
|
||||
@override
|
||||
String get removeHeader => 'Rimuovi header';
|
||||
|
||||
@override
|
||||
String get connecting => 'Connessione in corso...';
|
||||
|
||||
@override
|
||||
String get connectToServerButton => 'Connetti al server';
|
||||
|
||||
@override
|
||||
String get demoModeActive => 'Modalità demo attiva';
|
||||
|
||||
@override
|
||||
String get skipServerSetupTryDemo => 'Salta configurazione server e prova la demo';
|
||||
|
||||
@override
|
||||
String get enterDemo => 'Entra in demo';
|
||||
|
||||
@override
|
||||
String get demoBadge => 'Demo';
|
||||
|
||||
@override
|
||||
String get serverNotOpenWebUI => 'Questo non sembra un server Open-WebUI.';
|
||||
|
||||
@override
|
||||
String get serverUrlEmpty => 'L\'URL del server non può essere vuoto';
|
||||
|
||||
@override
|
||||
String get invalidUrlFormat => 'Formato URL non valido. Controlla l\'input.';
|
||||
|
||||
@override
|
||||
String get onlyHttpHttps => 'Sono supportati solo i protocolli HTTP e HTTPS.';
|
||||
|
||||
@override
|
||||
String get serverAddressRequired => 'Indirizzo server richiesto (es. 192.168.1.10 o example.com).';
|
||||
|
||||
@override
|
||||
String get portRange => 'La porta deve essere tra 1 e 65535.';
|
||||
|
||||
@override
|
||||
String get invalidIpFormat => 'Formato IP non valido. Esempio: 192.168.1.10.';
|
||||
|
||||
@override
|
||||
String get couldNotConnectGeneric => 'Impossibile connettersi. Verifica l\'indirizzo e riprova.';
|
||||
|
||||
@override
|
||||
String get weCouldntReachServer => 'Impossibile raggiungere il server. Verifica connessione e stato del server.';
|
||||
|
||||
@override
|
||||
String get connectionTimedOut => 'Tempo scaduto. Il server potrebbe essere occupato o bloccato.';
|
||||
|
||||
@override
|
||||
String get useHttpOrHttpsOnly => 'Usa solo http:// o https://.';
|
||||
|
||||
@override
|
||||
String get loginFailed => 'Accesso non riuscito';
|
||||
|
||||
@override
|
||||
String get invalidCredentials => 'Nome utente o password non validi. Riprova.';
|
||||
|
||||
@override
|
||||
String get serverRedirectingHttps => 'Il server sta reindirizzando. Controlla la configurazione HTTPS.';
|
||||
|
||||
@override
|
||||
String get unableToConnectServer => 'Impossibile connettersi al server. Controlla la connessione.';
|
||||
|
||||
@override
|
||||
String get requestTimedOut => 'Richiesta scaduta. Riprova.';
|
||||
|
||||
@override
|
||||
String get genericSignInFailed => 'Impossibile accedere. Controlla credenziali e server.';
|
||||
|
||||
@override
|
||||
String get skip => 'Salta';
|
||||
|
||||
@override
|
||||
String get next => 'Avanti';
|
||||
|
||||
@override
|
||||
String get done => 'Fatto';
|
||||
|
||||
@override
|
||||
String get onboardStartTitle => 'Inizia una conversazione';
|
||||
|
||||
@override
|
||||
String get onboardStartSubtitle => 'Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet1 => 'Tocca il nome del modello in alto per cambiare';
|
||||
|
||||
@override
|
||||
String get onboardStartBullet2 => 'Usa Nuova chat per azzerare il contesto';
|
||||
|
||||
@override
|
||||
String get onboardAttachTitle => 'Aggiungi contesto';
|
||||
|
||||
@override
|
||||
String get onboardAttachSubtitle => 'Migliora le risposte aggiungendo file o immagini.';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet1 => 'File: PDF, documenti, dataset';
|
||||
|
||||
@override
|
||||
String get onboardAttachBullet2 => 'Immagini: foto o screenshot';
|
||||
|
||||
@override
|
||||
String get onboardSpeakTitle => 'Parla in modo naturale';
|
||||
|
||||
@override
|
||||
String get onboardSpeakSubtitle => 'Tocca il microfono per dettare con feedback visivo.';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet1 => 'Interrompi in qualsiasi momento; il testo parziale viene mantenuto';
|
||||
|
||||
@override
|
||||
String get onboardSpeakBullet2 => 'Ottimo per note rapide o prompt lunghi';
|
||||
|
||||
@override
|
||||
String get onboardQuickTitle => 'Azioni rapide';
|
||||
|
||||
@override
|
||||
String get onboardQuickSubtitle => 'Usa il menu in alto a sinistra per aprire l\'elenco chat e la navigazione.';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet1 => 'Tocca il menu per aprire chat e navigazione';
|
||||
|
||||
@override
|
||||
String get onboardQuickBullet2 => 'Vai subito a Nuova chat, File o Profilo';
|
||||
|
||||
@override
|
||||
String get addAttachment => 'Aggiungi allegato';
|
||||
|
||||
@override
|
||||
String get tools => 'Strumenti';
|
||||
|
||||
@override
|
||||
String get voiceInput => 'Input vocale';
|
||||
|
||||
@override
|
||||
String get messageInputLabel => 'Input messaggio';
|
||||
|
||||
@override
|
||||
String get messageInputHint => 'Scrivi il tuo messaggio';
|
||||
|
||||
@override
|
||||
String get messageHintText => 'Messaggio...';
|
||||
|
||||
@override
|
||||
String get stopGenerating => 'Interrompi generazione';
|
||||
|
||||
@override
|
||||
String get send => 'Invia';
|
||||
|
||||
@override
|
||||
String get sendMessage => 'Invia messaggio';
|
||||
|
||||
@override
|
||||
String get file => 'File';
|
||||
|
||||
@override
|
||||
String get photo => 'Foto';
|
||||
|
||||
@override
|
||||
String get camera => 'Fotocamera';
|
||||
|
||||
@override
|
||||
String get apiUnavailable => 'Servizio API non disponibile';
|
||||
|
||||
@override
|
||||
String get unableToLoadImage => 'Impossibile caricare l\'immagine';
|
||||
|
||||
@override
|
||||
String notAnImageFile(String fileName) {
|
||||
return 'Non è un file immagine: $fileName';
|
||||
}
|
||||
|
||||
@override
|
||||
String failedToLoadImage(String error) {
|
||||
return 'Impossibile caricare l\'immagine: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get invalidDataUrl => 'Formato data URL non valido';
|
||||
|
||||
@override
|
||||
String get failedToDecodeImage => 'Impossibile decodificare l\'immagine';
|
||||
|
||||
@override
|
||||
String get invalidImageFormat => 'Formato immagine non valido';
|
||||
|
||||
@override
|
||||
String get emptyImageData => 'Dati immagine vuoti';
|
||||
|
||||
@override
|
||||
String get offlineBanner => 'Sei offline. Alcune funzioni potrebbero essere limitate.';
|
||||
|
||||
@override
|
||||
String get featureRequiresInternet => 'Questa funzione richiede una connessione Internet';
|
||||
|
||||
@override
|
||||
String get messagesWillSendWhenOnline => 'I messaggi verranno inviati quando tornerai online';
|
||||
|
||||
@override
|
||||
String get confirm => 'Conferma';
|
||||
|
||||
@override
|
||||
String get cancel => 'Annulla';
|
||||
|
||||
@override
|
||||
String get ok => 'OK';
|
||||
|
||||
@override
|
||||
String get inputField => 'Campo di input';
|
||||
|
||||
@override
|
||||
String get captureDocumentOrImage => 'Acquisisci un documento o un\'immagine';
|
||||
|
||||
@override
|
||||
String get checkConnection => 'Controlla connessione';
|
||||
|
||||
@override
|
||||
String get openSettings => 'Apri impostazioni';
|
||||
|
||||
@override
|
||||
String get chooseDifferentFile => 'Scegli un altro file';
|
||||
|
||||
@override
|
||||
String get goBack => 'Indietro';
|
||||
|
||||
@override
|
||||
String get technicalDetails => 'Dettagli tecnici';
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import 'core/auth/auth_state_manager.dart';
|
||||
import 'core/utils/debug_logger.dart';
|
||||
|
||||
import 'features/onboarding/views/onboarding_sheet.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import 'features/chat/views/chat_page.dart';
|
||||
import 'features/navigation/views/splash_launcher_page.dart';
|
||||
|
||||
@@ -85,17 +87,40 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
|
||||
? AppTheme.conduitDarkTheme
|
||||
: AppTheme.conduitLightTheme;
|
||||
|
||||
final locale = ref.watch(localeProvider);
|
||||
|
||||
return AnimatedThemeWrapper(
|
||||
theme: currentTheme,
|
||||
duration: AnimationDuration.medium,
|
||||
child: ErrorBoundary(
|
||||
child: MaterialApp(
|
||||
title: 'Conduit',
|
||||
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
|
||||
theme: AppTheme.conduitLightTheme,
|
||||
darkTheme: AppTheme.conduitDarkTheme,
|
||||
themeMode: themeMode,
|
||||
debugShowCheckedModeBanner: false,
|
||||
navigatorKey: NavigationService.navigatorKey,
|
||||
locale: locale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
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 (deviceLocale == null) return const Locale('en');
|
||||
for (final loc in supported) {
|
||||
if (loc.languageCode == deviceLocale.languageCode) return loc;
|
||||
}
|
||||
return const Locale('en');
|
||||
},
|
||||
builder: (context, child) {
|
||||
// Keep a subtle fade for navigation transitions only
|
||||
final wrapped = OfflineIndicator(
|
||||
@@ -159,7 +184,7 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
|
||||
|
||||
if (authNavState == AuthNavigationState.error) {
|
||||
return _buildErrorState(
|
||||
ref.watch(authErrorProvider3) ?? 'Authentication error',
|
||||
ref.watch(authErrorProvider3) ?? AppLocalizations.of(context)!.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -174,7 +199,7 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
|
||||
loading: () => _buildInitialLoadingSkeleton(context),
|
||||
error: (error, stackTrace) {
|
||||
DebugLogger.error('Server provider error', error);
|
||||
return _buildErrorState('Server connection failed: $error');
|
||||
return _buildErrorState(AppLocalizations.of(context)!.unableToConnectServer);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -268,7 +293,7 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
|
||||
),
|
||||
const SizedBox(height: Spacing.md),
|
||||
Text(
|
||||
'Initialization Failed',
|
||||
AppLocalizations.of(context)!.initializationFailed,
|
||||
style: TextStyle(
|
||||
fontSize: AppTypography.headlineLarge,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -291,7 +316,7 @@ class _ConduitAppState extends ConsumerState<ConduitApp> {
|
||||
backgroundColor: context.conduitTheme.buttonPrimary,
|
||||
foregroundColor: context.conduitTheme.buttonPrimaryText,
|
||||
),
|
||||
child: const Text('Retry'),
|
||||
child: Text(AppLocalizations.of(context)!.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../theme/theme_extensions.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
/// Utility functions for common UI patterns and helpers
|
||||
/// Following Conduit design principles
|
||||
@@ -104,7 +105,7 @@ class UiUtils {
|
||||
behavior: SnackBarBehavior.floating,
|
||||
action: onRetry != null
|
||||
? SnackBarAction(
|
||||
label: 'Try again',
|
||||
label: AppLocalizations.of(context)!.retry,
|
||||
textColor: context.conduitTheme.textInverse,
|
||||
onPressed: onRetry,
|
||||
)
|
||||
@@ -138,7 +139,11 @@ class UiUtils {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(cancelText),
|
||||
child: Text(
|
||||
cancelText.isNotEmpty
|
||||
? cancelText
|
||||
: AppLocalizations.of(context)!.cancel,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
@@ -147,7 +152,11 @@ class UiUtils {
|
||||
foregroundColor: context.conduitTheme.error,
|
||||
)
|
||||
: null,
|
||||
child: Text(confirmText),
|
||||
child: Text(
|
||||
confirmText.isNotEmpty
|
||||
? confirmText
|
||||
: AppLocalizations.of(context)!.confirm,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../theme/theme_extensions.dart';
|
||||
import '../services/brand_service.dart';
|
||||
import '../../core/services/enhanced_accessibility_service.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import '../../core/services/platform_service.dart';
|
||||
import '../../core/services/settings_service.dart';
|
||||
|
||||
@@ -53,7 +54,8 @@ class ConduitButton extends ConsumerWidget {
|
||||
// Build semantic label
|
||||
String semanticLabel = text;
|
||||
if (isLoading) {
|
||||
semanticLabel = 'Loading: $text';
|
||||
final l10n = AppLocalizations.of(context);
|
||||
semanticLabel = '${l10n?.loadingContent ?? 'Loading'}: $text';
|
||||
} else if (isDestructive) {
|
||||
semanticLabel = 'Warning: $text';
|
||||
}
|
||||
@@ -99,7 +101,7 @@ class ConduitButton extends ConsumerWidget {
|
||||
),
|
||||
child: isLoading
|
||||
? Semantics(
|
||||
label: 'Loading',
|
||||
label: AppLocalizations.of(context)?.loadingContent ?? 'Loading',
|
||||
excludeSemantics: true,
|
||||
child: SizedBox(
|
||||
width: IconSize.small,
|
||||
@@ -204,7 +206,7 @@ class ConduitInput extends StatelessWidget {
|
||||
SizedBox(height: Spacing.sm),
|
||||
],
|
||||
Semantics(
|
||||
label: semanticLabel ?? label ?? 'Input field',
|
||||
label: semanticLabel ?? label ?? (AppLocalizations.of(context)?.inputField ?? 'Input field'),
|
||||
textField: true,
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
@@ -772,7 +774,7 @@ class AccessibleFormField extends StatelessWidget {
|
||||
SizedBox(height: isCompact ? Spacing.xs : Spacing.sm),
|
||||
],
|
||||
Semantics(
|
||||
label: semanticLabel ?? label ?? 'Input field',
|
||||
label: semanticLabel ?? label ?? (AppLocalizations.of(context)?.inputField ?? 'Input field'),
|
||||
textField: true,
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'skeleton_loader.dart';
|
||||
import '../theme/theme_extensions.dart';
|
||||
import 'conduit_components.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
/// Improved loading state widget with accessibility and better hierarchy
|
||||
class ImprovedLoadingState extends StatefulWidget {
|
||||
@@ -81,7 +82,7 @@ class _ImprovedLoadingStateState extends State<ImprovedLoadingState>
|
||||
opacity: _fadeAnimation,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
label: widget.message ?? 'Loading content',
|
||||
label: widget.message ?? AppLocalizations.of(context)!.loadingContent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -414,7 +415,7 @@ class LoadingCard extends StatelessWidget {
|
||||
return ConduitCard(
|
||||
isCompact: isCompact,
|
||||
child: ImprovedLoadingState(
|
||||
message: 'Loading...',
|
||||
message: AppLocalizations.of(context)!.loadingContent,
|
||||
isCompact: isCompact,
|
||||
),
|
||||
);
|
||||
@@ -620,7 +621,7 @@ class ErrorStateWidget extends StatelessWidget {
|
||||
Icon(Icons.error_outline, size: 64, color: theme.colorScheme.error),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Oops! Something went wrong',
|
||||
AppLocalizations.of(context)!.errorMessage,
|
||||
style: theme.textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -655,7 +656,7 @@ class ErrorStateWidget extends StatelessWidget {
|
||||
FilledButton.icon(
|
||||
onPressed: onRetry,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Try Again'),
|
||||
label: Text(AppLocalizations.of(context)!.retry),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../services/brand_service.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
/// Standard loading indicators following Conduit design patterns
|
||||
class ConduitLoading {
|
||||
@@ -332,8 +333,8 @@ class LoadingStateWrapper<T> extends StatelessWidget {
|
||||
return asyncValue.when(
|
||||
data: builder,
|
||||
loading: () => showLoadingOverlay
|
||||
? ConduitLoading.overlay(message: 'Loading...')
|
||||
: loadingWidget ?? ConduitLoading.primary(message: 'Loading...'),
|
||||
? ConduitLoading.overlay(message: AppLocalizations.of(context)!.loadingContent)
|
||||
: loadingWidget ?? ConduitLoading.primary(message: AppLocalizations.of(context)!.loadingContent),
|
||||
error: (error, stackTrace) {
|
||||
if (errorBuilder != null) {
|
||||
return errorBuilder!(error, stackTrace);
|
||||
@@ -352,7 +353,7 @@ class LoadingStateWrapper<T> extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: Spacing.md),
|
||||
Text(
|
||||
'Something went wrong',
|
||||
AppLocalizations.of(context)!.errorMessage,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
fontSize: AppTypography.headlineSmall,
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter_highlight/themes/atom-one-light.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:conduit/shared/theme/theme_extensions.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
class ConduitMarkdownConfig {
|
||||
static MarkdownConfig getConfig({
|
||||
@@ -56,7 +57,7 @@ class ConduitMarkdownConfig {
|
||||
builder: (url, attributes) {
|
||||
// Check if it's a base64 data URL
|
||||
if (url.startsWith('data:')) {
|
||||
return _buildBase64Image(url, theme);
|
||||
return _buildBase64Image(url, context, theme);
|
||||
}
|
||||
// Network image
|
||||
return CachedNetworkImage(
|
||||
@@ -94,7 +95,7 @@ class ConduitMarkdownConfig {
|
||||
),
|
||||
const SizedBox(height: Spacing.xs),
|
||||
Text(
|
||||
'Failed to load image',
|
||||
AppLocalizations.of(context)!.failedToLoadImage(''),
|
||||
style: TextStyle(color: theme.error, fontSize: 12),
|
||||
),
|
||||
],
|
||||
@@ -151,7 +152,7 @@ class ConduitMarkdownConfig {
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildBase64Image(String dataUrl, ConduitThemeExtension theme) {
|
||||
static Widget _buildBase64Image(String dataUrl, BuildContext context, ConduitThemeExtension theme) {
|
||||
try {
|
||||
// Extract base64 part from data URL
|
||||
final commaIndex = dataUrl.indexOf(',');
|
||||
@@ -187,7 +188,7 @@ class ConduitMarkdownConfig {
|
||||
Icon(Icons.error_outline, color: theme.error, size: 32),
|
||||
const SizedBox(height: Spacing.xs),
|
||||
Text(
|
||||
'Invalid image data',
|
||||
AppLocalizations.of(context)!.invalidImageFormat,
|
||||
style: TextStyle(color: theme.error, fontSize: 12),
|
||||
),
|
||||
],
|
||||
@@ -206,7 +207,7 @@ class ConduitMarkdownConfig {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Invalid image format',
|
||||
AppLocalizations.of(context)!.invalidImageFormat,
|
||||
style: TextStyle(color: theme.error, fontSize: 12),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../../core/services/connectivity_service.dart';
|
||||
import '../theme/theme_extensions.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
|
||||
class OfflineIndicator extends ConsumerWidget {
|
||||
final Widget child;
|
||||
@@ -70,7 +71,7 @@ class _OfflineBanner extends StatelessWidget {
|
||||
const SizedBox(width: Spacing.xs),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'You\'re offline. Some features may be limited.',
|
||||
AppLocalizations.of(context)!.offlineBanner,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.textInverse,
|
||||
fontSize: AppTypography.labelLarge,
|
||||
@@ -101,7 +102,7 @@ class InlineOfflineIndicator extends ConsumerWidget {
|
||||
|
||||
const InlineOfflineIndicator({
|
||||
super.key,
|
||||
this.message = 'This feature requires an internet connection',
|
||||
this.message = '',
|
||||
this.icon,
|
||||
this.backgroundColor,
|
||||
});
|
||||
@@ -138,7 +139,9 @@ class InlineOfflineIndicator extends ConsumerWidget {
|
||||
const SizedBox(width: Spacing.xs),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
message.isNotEmpty
|
||||
? message
|
||||
: AppLocalizations.of(context)!.featureRequiresInternet,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.warning,
|
||||
fontSize: AppTypography.labelLarge,
|
||||
@@ -174,7 +177,7 @@ class OfflineAwareButton extends ConsumerWidget {
|
||||
|
||||
return Tooltip(
|
||||
message: !enabled
|
||||
? (offlineTooltip ?? 'This action requires an internet connection')
|
||||
? (offlineTooltip ?? AppLocalizations.of(context)!.featureRequiresInternet)
|
||||
: '',
|
||||
child: FilledButton(onPressed: enabled ? onPressed : null, child: child),
|
||||
);
|
||||
@@ -217,7 +220,7 @@ class ChatOfflineOverlay extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
Text(
|
||||
'Messages will be sent when you\'re back online',
|
||||
AppLocalizations.of(context)!.messagesWillSendWhenOnline,
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.warning,
|
||||
fontSize: AppTypography.bodySmall,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'skeleton_loader.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import 'improved_loading_states.dart';
|
||||
|
||||
/// Optimized list widget with virtualization and performance enhancements
|
||||
@@ -127,8 +128,8 @@ class _OptimizedListState<T> extends ConsumerState<OptimizedList<T>> {
|
||||
if (widget.items.isEmpty) {
|
||||
return widget.emptyWidget ??
|
||||
ImprovedEmptyState(
|
||||
title: 'No items',
|
||||
subtitle: widget.emptyMessage ?? 'No items to display',
|
||||
title: AppLocalizations.of(context)!.noItems,
|
||||
subtitle: widget.emptyMessage ?? AppLocalizations.of(context)!.noItemsToDisplay,
|
||||
icon: Icons.inbox_outlined,
|
||||
);
|
||||
}
|
||||
@@ -208,7 +209,10 @@ class _OptimizedListState<T> extends ConsumerState<OptimizedList<T>> {
|
||||
alignment: Alignment.center,
|
||||
child: _isLoadingMore
|
||||
? const CircularProgressIndicator()
|
||||
: TextButton(onPressed: _loadMore, child: const Text('Load More')),
|
||||
: TextButton(
|
||||
onPressed: _loadMore,
|
||||
child: Text(AppLocalizations.of(context)!.loadMore),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -267,11 +271,14 @@ class OptimizedSliverList<T> extends ConsumerWidget {
|
||||
return SliverToBoxAdapter(
|
||||
child:
|
||||
emptyWidget ??
|
||||
ImprovedEmptyState(
|
||||
title: 'No items',
|
||||
subtitle: emptyMessage ?? 'No items to display',
|
||||
icon: Icons.inbox_outlined,
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return ImprovedEmptyState(
|
||||
title: l10n.noItems,
|
||||
subtitle: emptyMessage ?? l10n.noItemsToDisplay,
|
||||
icon: Icons.inbox_outlined,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
13
pubspec.lock
13
pubspec.lock
@@ -366,6 +366,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_native_splash:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -616,6 +621,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
19
pubspec.yaml
19
pubspec.yaml
@@ -9,6 +9,8 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
# State Management
|
||||
flutter_riverpod: ^2.6.1
|
||||
@@ -45,6 +47,7 @@ dependencies:
|
||||
crypto: ^3.0.3
|
||||
package_info_plus: ^8.0.2
|
||||
url_launcher: ^6.3.0
|
||||
intl: ^0.20.2
|
||||
|
||||
# Icons & Theming
|
||||
cupertino_icons: ^1.0.8
|
||||
@@ -69,6 +72,22 @@ dependency_overrides:
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
generate: true
|
||||
# Localization configuration
|
||||
# The default synthetic package is used for generated localizations:
|
||||
# import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
# ARB files live in lib/l10n
|
||||
# You can customize output via the `l10n:` section if needed.
|
||||
#
|
||||
# l10n:
|
||||
# arb-dir: lib/l10n
|
||||
# template-arb-file: app_en.arb
|
||||
# output-localization-file: app_localizations.dart
|
||||
# preferred-supported-locales:
|
||||
# - en
|
||||
# - de
|
||||
# - fr
|
||||
# - it
|
||||
|
||||
assets:
|
||||
- assets/icons/
|
||||
|
||||
Reference in New Issue
Block a user