feat: localisation with en, de, fr and it
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user