adding multilingual support

This commit is contained in:
Azmat-7860
2026-10-03 15:04:48 +05:30
parent 3785ea1e45
commit 0d7785b242
51 changed files with 3007 additions and 250 deletions
@@ -1,6 +1,7 @@
import 'dart:typed_data';
import 'dart:io';
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'package:file_picker/file_picker.dart';
import 'base_field.dart';
import '../utils/entity_field_store.dart';
@@ -63,7 +64,9 @@ class AudioUploadField extends BaseField {
}
},
icon: const Icon(Icons.audiotrack_rounded),
label: const Text('Add Audio'),
label: Builder(
builder: (ctx) =>
Text(AppLocalizations.of(ctx)!.addAudio)),
),
],
),
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
@@ -255,7 +256,7 @@ class _AutocompleteMultiSelectWidgetState
const SizedBox(width: 8),
Expanded(
child: Text(
'No results',
AppLocalizations.of(context)!.noResults,
style: TextStyle(
color:
widget.colorScheme.onSurfaceVariant),
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'package:barcode_widget/barcode_widget.dart';
import 'base_field.dart';
@@ -73,19 +74,22 @@ class BarcodeField extends BaseField {
),
),
const SizedBox(width: 8),
IconButton(
tooltip: 'Scan Barcode',
icon: Icon(Icons.document_scanner_rounded,
color: colorScheme.primary),
onPressed: scanner == null
? null
: () async {
final val = await scanner!.call();
if (val != null) {
controller.text = val;
onChanged?.call();
}
},
Builder(
builder: (ctx) => IconButton(
tooltip:
AppLocalizations.of(ctx)!.scanBarcode,
icon: Icon(Icons.document_scanner_rounded,
color: colorScheme.primary),
onPressed: scanner == null
? null
: () async {
final val = await scanner!.call();
if (val != null) {
controller.text = val;
onChanged?.call();
}
},
),
),
],
),
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
/// Read-only Data Grid field
@@ -96,7 +97,7 @@ class _DataGridFieldWidgetState extends State<_DataGridFieldWidget> {
}
final data = snapshot.data ?? const <Map<String, dynamic>>[];
if (data.isEmpty) {
return Text('No data available',
return Text(AppLocalizations.of(context)!.noDataAvailable,
style: TextStyle(color: colorScheme.onSurfaceVariant));
}
final columns = data.first.keys.toList();
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
@@ -152,7 +153,8 @@ class _DynamicDropdownWidgetState extends State<_DynamicDropdownWidget> {
children: [
TextField(
decoration: InputDecoration(
hintText: 'Search ${widget.label.toLowerCase()}',
hintText: AppLocalizations.of(context)!
.searchEntity(widget.label),
prefixIcon: const Icon(Icons.search),
border: const OutlineInputBorder(),
),
@@ -162,7 +164,8 @@ class _DynamicDropdownWidgetState extends State<_DynamicDropdownWidget> {
Flexible(
child: filtered.isEmpty
? Center(
child: Text('No results',
child: Text(
AppLocalizations.of(context)!.noResults,
style: TextStyle(color: cs.onSurfaceVariant)),
)
: ListView.separated(
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../../../shared/widgets/inputs/modern_text_field.dart';
@@ -146,7 +147,8 @@ class _DynamicMultiSelectWidgetState extends State<_DynamicMultiSelectWidget> {
children: [
TextField(
decoration: InputDecoration(
hintText: 'Search ${widget.label.toLowerCase()}',
hintText: AppLocalizations.of(context)!
.searchEntity(widget.label),
prefixIcon: const Icon(Icons.search),
border: const OutlineInputBorder(),
),
@@ -156,7 +158,8 @@ class _DynamicMultiSelectWidgetState extends State<_DynamicMultiSelectWidget> {
Flexible(
child: filtered.isEmpty
? Center(
child: Text('No results',
child: Text(
AppLocalizations.of(context)!.noResults,
style: TextStyle(color: cs.onSurfaceVariant)),
)
: ListView.separated(
@@ -188,7 +191,8 @@ class _DynamicMultiSelectWidgetState extends State<_DynamicMultiSelectWidget> {
widget.onChanged?.call();
Navigator.of(context).pop();
},
child: const Text('Clear'),
child: Text(
AppLocalizations.of(context)!.clearBtn),
),
const Spacer(),
ElevatedButton(
@@ -199,7 +203,8 @@ class _DynamicMultiSelectWidgetState extends State<_DynamicMultiSelectWidget> {
widget.onChanged?.call();
Navigator.of(context).pop();
},
child: const Text('Done'),
child: Text(
AppLocalizations.of(context)!.doneBtn),
),
],
),
@@ -1,6 +1,7 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../utils/entity_field_store.dart';
@@ -62,7 +63,9 @@ class FileUploadField extends BaseField {
}
},
icon: const Icon(Icons.attach_file_rounded),
label: const Text('Add Files'),
label: Builder(
builder: (ctx) =>
Text(AppLocalizations.of(ctx)!.addFiles)),
),
],
),
@@ -1,6 +1,7 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../utils/entity_field_store.dart';
@@ -57,7 +58,9 @@ class ImageUploadField extends BaseField {
}
},
icon: const Icon(Icons.image_rounded),
label: const Text('Add Image'),
label: Builder(
builder: (ctx) =>
Text(AppLocalizations.of(ctx)!.addImage)),
),
],
),
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../ui/entity_form.dart';
import 'dart:convert';
@@ -553,7 +554,9 @@ class _OneToManyWidgetState extends State<_OneToManyWidget> {
child: ElevatedButton.icon(
onPressed: () => _addRow(),
icon: Icon(Icons.add, color: cs.onPrimary),
label: Text('Add ${widget.label}',
label: Text(
AppLocalizations.of(context)!
.addEntity(widget.label),
style: TextStyle(color: cs.onPrimary)),
style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../l10n/app_localizations.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../core/providers/dynamic_theme_provider.dart';
import '../ui/entity_form.dart';
@@ -147,7 +147,8 @@ class RelatedEntityInsertField extends BaseField {
setLocal(() => isSubmitting = false);
}
},
submitButtonText: 'Create',
submitButtonText: AppLocalizations.of(context)!
.createEntity(relationTitle),
isLoading: isSubmitting,
),
),
@@ -156,7 +157,8 @@ class RelatedEntityInsertField extends BaseField {
onPressed: isSubmitting
? null
: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'),
child: Text(
AppLocalizations.of(dialogContext)!.cancel),
),
],
);
@@ -402,7 +404,8 @@ class _RelatedEntityInsertWidgetState
ElevatedButton.icon(
onPressed: _creating ? null : _openInsertDialog,
icon: Icon(Icons.add_rounded, color: cs.onPrimary, size: 18),
label: Text('Insert',
label: Text(
AppLocalizations.of(context)!.insertBtn,
style: TextStyle(
color: cs.onPrimary, fontWeight: FontWeight.w600)),
style: ElevatedButton.styleFrom(
@@ -491,7 +494,8 @@ class _RelatedEntityInsertWidgetState
TextButton(
onPressed:
isSubmitting ? null : () => Navigator.of(context).pop(),
child: const Text('Cancel'),
child: Text(
AppLocalizations.of(context)!.cancel),
),
],
);
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../ui/entity_form.dart';
@@ -143,7 +144,8 @@ class _ValueListPickerWidgetState extends State<_ValueListPickerWidget> {
strokeWidth: 2, color: cs.primary),
),
const SizedBox(width: 12),
Text('Loading...',
Text(
AppLocalizations.of(context)!.loading,
style: TextStyle(color: cs.onSurfaceVariant)),
],
),
@@ -152,7 +154,7 @@ class _ValueListPickerWidgetState extends State<_ValueListPickerWidget> {
if (snapshot.hasError || _loadError != null) {
final String msg = snapshot.error?.toString() ??
_loadError ??
'Failed to load';
AppLocalizations.of(context)!.errorLoadingData;
return Padding(
padding: const EdgeInsets.all(16),
child: Container(
@@ -186,13 +188,15 @@ class _ValueListPickerWidgetState extends State<_ValueListPickerWidget> {
Icon(Icons.inbox_rounded,
color: cs.onSurfaceVariant, size: 36),
const SizedBox(height: 8),
Text('No data found',
Text(
AppLocalizations.of(context)!.noDataFound,
style: TextStyle(color: cs.onSurfaceVariant)),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close_rounded),
label: const Text('Close'),
label: Text(
AppLocalizations.of(context)!.close),
style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
foregroundColor: cs.onPrimary,
@@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import '../../../l10n/app_localizations.dart';
import 'base_field.dart';
import '../utils/entity_field_store.dart';
@@ -63,7 +64,9 @@ class VideoUploadField extends BaseField {
}
},
icon: const Icon(Icons.video_library_rounded),
label: const Text('Add Video'),
label: Builder(
builder: (ctx) =>
Text(AppLocalizations.of(ctx)!.addVideo)),
),
],
),
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../l10n/app_localizations.dart';
import '../../../core/providers/dynamic_theme_provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../shared/widgets/app_bar/modern_app_bar.dart';
@@ -408,7 +409,7 @@ class _EntityDetailsState extends State<EntityDetails>
children: [
Expanded(
child: ModernButton(
text: 'Edit',
text: AppLocalizations.of(context)!.edit,
type: ModernButtonType.secondary,
size: ModernButtonSize.large,
onPressed: () => widget.onEdit(widget.entity),
@@ -418,7 +419,7 @@ class _EntityDetailsState extends State<EntityDetails>
const SizedBox(width: UIConstants.spacing16),
Expanded(
child: ModernButton(
text: 'Delete',
text: AppLocalizations.of(context)!.delete,
type: ModernButtonType.danger,
size: ModernButtonSize.large,
onPressed: () => _showDeleteDialog(colorScheme),
@@ -386,6 +386,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../l10n/app_localizations.dart';
import '../../../core/providers/dynamic_theme_provider.dart';
import '../fields/base_field.dart';
import '../fields/dependent_dropdown_field.dart';
@@ -562,7 +563,9 @@ class _EntityFormState extends State<EntityForm> {
final pass = passwordByGroup[gid] ?? '';
if (confirm.isNotEmpty && confirm != pass) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Passwords do not match')),
SnackBar(
content: Text(AppLocalizations.of(context)!
.passwordsDoNotMatch)),
);
return;
}
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/providers/dynamic_theme_provider.dart';
import '../../../core/constants/ui_constants.dart';
import '../../../l10n/app_localizations.dart';
import '../../../shared/widgets/app_bar/modern_app_bar.dart';
import 'entity_card.dart';
@@ -106,7 +107,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
TextButton(
onPressed: widget.onInsertAction,
child: Text(
widget.insertActionLabel ?? 'Unknown',
widget.insertActionLabel ?? AppLocalizations.of(context)!.unknown,
style: TextStyle(
color: colorScheme.primary,
fontWeight: FontWeight.w600,
@@ -117,7 +118,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
IconButton(
onPressed: widget.onAddNew,
icon: Icon(Icons.add_rounded, color: colorScheme.primary),
tooltip: 'Add New',
tooltip: AppLocalizations.of(context)!.addNew,
),
],
),
@@ -175,7 +176,8 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
fontWeight: FontWeight.w500,
),
decoration: InputDecoration(
hintText: 'Search ${widget.title.toLowerCase()}...',
hintText: AppLocalizations.of(context)!
.searchEntity(widget.title.toLowerCase()),
hintStyle: TextStyle(
color: colorScheme.onSurface.withOpacity(0.6),
fontSize: 16,
@@ -214,7 +216,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
color: colorScheme.onSurface.withOpacity(0.8),
size: 22,
),
tooltip: 'Refresh',
tooltip: AppLocalizations.of(context)!.refresh,
),
],
),
@@ -245,7 +247,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
CircularProgressIndicator(color: colorScheme.primary, strokeWidth: 3),
const SizedBox(height: UIConstants.spacing16),
Text(
'Loading ${widget.title.toLowerCase()}...',
AppLocalizations.of(context)!.loadingEntity(widget.title.toLowerCase()),
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 16,
@@ -277,7 +279,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
),
const SizedBox(height: UIConstants.spacing16),
Text(
'Error Loading Data',
AppLocalizations.of(context)!.errorLoadingData,
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 18,
@@ -286,7 +288,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
),
const SizedBox(height: UIConstants.spacing8),
Text(
widget.errorMessage ?? 'Something went wrong',
widget.errorMessage ?? AppLocalizations.of(context)!.somethingWentWrong,
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 14,
@@ -299,7 +301,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
onPressed: widget.onRefresh,
icon: Icon(Icons.refresh_rounded, color: colorScheme.onPrimary),
label: Text(
'Retry',
AppLocalizations.of(context)!.retry,
style: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w600,
@@ -344,7 +346,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
),
const SizedBox(height: UIConstants.spacing16),
Text(
'No ${widget.title.toLowerCase()} found',
AppLocalizations.of(context)!.noEntityFound(widget.title.toLowerCase()),
style: TextStyle(
color: colorScheme.onSurface,
fontSize: 18,
@@ -354,8 +356,9 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
const SizedBox(height: UIConstants.spacing8),
Text(
widget.searchQuery.isNotEmpty
? 'Try adjusting your search terms'
: 'Start by adding your first ${widget.title.toLowerCase()}',
? AppLocalizations.of(context)!.adjustSearchTerms
: AppLocalizations.of(context)!
.startByAdding(widget.title.toLowerCase()),
style: TextStyle(
color: colorScheme.onSurface.withOpacity(0.7),
fontSize: 14,
@@ -369,7 +372,7 @@ class _EntityListState extends State<EntityList> with TickerProviderStateMixin {
onPressed: widget.onAddNew,
icon: Icon(Icons.add_rounded, color: colorScheme.onPrimary),
label: Text(
'Add ${widget.title}',
AppLocalizations.of(context)!.addEntity(widget.title),
style: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w600,