feat: localisation with en, de, fr and it

This commit is contained in:
cogwheel0
2025-08-23 20:09:43 +05:30
parent b898adbe40
commit a852ce7848
36 changed files with 3912 additions and 203 deletions

View File

@@ -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,

View File

@@ -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),
),
],
],

View File

@@ -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,

View File

@@ -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),
),
),

View File

@@ -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,

View File

@@ -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,
);
}),
);
}