Add Swedish localization for various app actions and inventory management strings
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../data/admin_repository.dart';
|
||||
import '../domain/ai_model_info.dart';
|
||||
|
||||
@@ -60,7 +61,7 @@ class _AdminAiPanelState extends ConsumerState<AdminAiPanel> {
|
||||
children: [
|
||||
Text(_error!, style: TextStyle(color: theme.colorScheme.error)),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(onPressed: _load, child: const Text('Försök igen')),
|
||||
FilledButton(onPressed: _load, child: Text(context.l10n.retryAction)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -70,7 +71,7 @@ class _AdminAiPanelState extends ConsumerState<AdminAiPanel> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Översikt över AI-funktioner som backend exponerar.',
|
||||
context.l10n.adminAiDescription,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -98,7 +99,7 @@ class _AdminAiPanelState extends ConsumerState<AdminAiPanel> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('Sida: ${model.path}', style: theme.textTheme.bodySmall),
|
||||
Text('${context.l10n.adminPagePrefix}${model.path}', style: theme.textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../data/admin_repository.dart';
|
||||
import '../domain/pending_product.dart';
|
||||
|
||||
@@ -74,7 +75,7 @@ class _AdminPendingProductsPanelState
|
||||
children: [
|
||||
Text(_error!, style: TextStyle(color: theme.colorScheme.error)),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(onPressed: _load, child: const Text('Försök igen')),
|
||||
FilledButton(onPressed: _load, child: Text(context.l10n.retryAction)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -84,7 +85,7 @@ class _AdminPendingProductsPanelState
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Inga väntande produktförslag.',
|
||||
context.l10n.adminNoPendingProducts,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
@@ -106,10 +107,10 @@ class _AdminPendingProductsPanelState
|
||||
children: [
|
||||
if (product.displayName != product.name)
|
||||
Text(product.name, style: theme.textTheme.bodySmall),
|
||||
Text('Kategori: ${product.categoryPath ?? '—'}'),
|
||||
Text('Föreslagen av: ${product.ownerUsername ?? '—'}'),
|
||||
Text('${context.l10n.adminCategoryPrefix}${product.categoryPath ?? '—'}'),
|
||||
Text('${context.l10n.adminSuggestedByPrefix}${product.ownerUsername ?? '—'}'),
|
||||
Text(
|
||||
'Datum: ${product.createdAt == null ? '—' : MaterialLocalizations.of(context).formatShortDate(product.createdAt!)}',
|
||||
'${context.l10n.adminDatePrefix}${product.createdAt == null ? '—' : MaterialLocalizations.of(context).formatShortDate(product.createdAt!)}',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
@@ -122,13 +123,13 @@ class _AdminPendingProductsPanelState
|
||||
onPressed: isProcessing
|
||||
? null
|
||||
: () => _handleAction(product, 'active'),
|
||||
child: const Text('Godkänn'),
|
||||
child: Text(context.l10n.adminApproveAction),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: isProcessing
|
||||
? null
|
||||
: () => _handleAction(product, 'rejected'),
|
||||
child: const Text('Avvisa'),
|
||||
child: Text(context.l10n.adminRejectAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -143,7 +144,7 @@ class _AdminPendingProductsPanelState
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Godkänn eller avvisa väntande produktförslag direkt från profilsidan.',
|
||||
context.l10n.adminPendingDescription,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../data/admin_repository.dart';
|
||||
import '../domain/admin_ai_categorize_result.dart';
|
||||
import '../domain/admin_category_node.dart';
|
||||
@@ -78,17 +79,17 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
String _sortLabel(_ProductSort sort) {
|
||||
switch (sort) {
|
||||
case _ProductSort.newest:
|
||||
return 'Sortera: Nyast';
|
||||
return context.l10n.adminSortNewest;
|
||||
case _ProductSort.oldest:
|
||||
return 'Sortera: Äldst';
|
||||
return context.l10n.adminSortOldest;
|
||||
case _ProductSort.nameAsc:
|
||||
return 'Sortera: Namn A-Ö';
|
||||
return context.l10n.adminSortNameAsc;
|
||||
case _ProductSort.nameDesc:
|
||||
return 'Sortera: Namn Ö-A';
|
||||
return context.l10n.adminSortNameDesc;
|
||||
case _ProductSort.categoryAsc:
|
||||
return 'Sortera: Kategori A-Ö';
|
||||
return context.l10n.adminSortCategoryAsc;
|
||||
case _ProductSort.categoryDesc:
|
||||
return 'Sortera: Kategori Ö-A';
|
||||
return context.l10n.adminSortCategoryDesc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
await _load();
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Produkter uppdaterade.')),
|
||||
SnackBar(content: Text(context.l10n.adminProductsUpdated)),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -148,7 +149,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
if (!mounted) return;
|
||||
if (suggestions.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Inga AI-förslag att visa.')),
|
||||
SnackBar(content: Text(context.l10n.adminNoAiSuggestions)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -175,7 +176,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
await _load();
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('AI-förslag tillämpade på ${selectedProductIds.length} produkter.')),
|
||||
SnackBar(content: Text(context.l10n.adminAiApplied(selectedProductIds.length))),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -203,12 +204,12 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
builder: (dialogContext) {
|
||||
return StatefulBuilder(
|
||||
builder: (dialogContext, setDialogState) => AlertDialog(
|
||||
title: const Text('Slå ihop produkter'),
|
||||
title: Text(context.l10n.adminMergeProductsTitle),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Välj vilken produkt som ska flyttas in i den andra:'),
|
||||
Text(context.l10n.adminMergeProductsHint),
|
||||
const SizedBox(height: 12),
|
||||
SegmentedButton<int>(
|
||||
segments: [
|
||||
@@ -236,11 +237,11 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: const Text('Avbryt'),
|
||||
child: Text(context.l10n.cancelAction),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: const Text('Slå ihop'),
|
||||
child: Text(context.l10n.adminMergeAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -260,7 +261,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
await _load();
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Produkter sammanslagna.')),
|
||||
SnackBar(content: Text(context.l10n.adminProductsMerged)),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -275,16 +276,16 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: const Text('Ta bort produkt'),
|
||||
content: Text('Ta bort ${product.displayName}? Produkten kan återställas senare.'),
|
||||
title: Text(context.l10n.adminDeleteProductTitle),
|
||||
content: Text(context.l10n.adminDeleteProductConfirm(product.displayName)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: const Text('Avbryt'),
|
||||
child: Text(context.l10n.cancelAction),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: const Text('Ta bort'),
|
||||
child: Text(context.l10n.deleteAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -297,7 +298,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
await _load();
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Produkt borttagen.')),
|
||||
SnackBar(content: Text(context.l10n.adminProductDeleted)),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -432,7 +433,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
children: [
|
||||
Text(_error!, style: TextStyle(color: theme.colorScheme.error)),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(onPressed: _load, child: const Text('Försök igen')),
|
||||
FilledButton(onPressed: _load, child: Text(context.l10n.retryAction)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -442,10 +443,10 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Sök produkt',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.adminSearchProduct,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (value) => setState(() => _search = value),
|
||||
),
|
||||
@@ -470,7 +471,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: const Text('Visa raderade'),
|
||||
label: Text(context.l10n.adminShowDeleted),
|
||||
selected: _showDeletedOnly,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
@@ -483,7 +484,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: const Text('Endast okategoriserade'),
|
||||
label: Text(context.l10n.adminShowUncategorized),
|
||||
selected: _showUncategorizedOnly,
|
||||
onSelected: _showDeletedOnly
|
||||
? null
|
||||
@@ -494,14 +495,14 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
width: 260,
|
||||
child: DropdownButtonFormField<String>(
|
||||
initialValue: _bulkCategoryValue,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Bulk: sätt kategori',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.adminBulkSetCategory,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String>(
|
||||
DropdownMenuItem<String>(
|
||||
value: '__remove__',
|
||||
child: Text('Ta bort kategori'),
|
||||
child: Text(context.l10n.adminRemoveCategory),
|
||||
),
|
||||
...categoryOptions.map(
|
||||
(option) => DropdownMenuItem<String>(
|
||||
@@ -555,13 +556,13 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
width: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: Text('Återställ valda (${_selectedIds.length})'),
|
||||
: Text(context.l10n.adminRestoreSelected(_selectedIds.length))
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (filtered.isEmpty)
|
||||
const Text('Inga produkter matchar filtret.')
|
||||
Text(context.l10n.adminNoProductsFound)
|
||||
else
|
||||
...filtered.map(
|
||||
(product) => Card(
|
||||
@@ -609,15 +610,15 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
'row-category-${product.id}-${_rowCategoryFor(product)}',
|
||||
),
|
||||
initialValue: _rowCategoryFor(product),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Kategori (inline)',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.adminInlineCategory,
|
||||
border: const OutlineInputBorder(),
|
||||
isDense: true,
|
||||
),
|
||||
items: [
|
||||
const DropdownMenuItem<String>(
|
||||
DropdownMenuItem<String>(
|
||||
value: '__remove__',
|
||||
child: Text('Ingen kategori'),
|
||||
child: Text(context.l10n.adminNoCategory),
|
||||
),
|
||||
...categoryOptions.map(
|
||||
(option) => DropdownMenuItem<String>(
|
||||
@@ -645,7 +646,7 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.save_outlined),
|
||||
label: const Text('Spara'),
|
||||
label: Text(context.l10n.saveAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -657,13 +658,13 @@ class _AdminProductsPanelState extends ConsumerState<AdminProductsPanel> {
|
||||
TextButton.icon(
|
||||
onPressed: () => _restoreProduct(product),
|
||||
icon: const Icon(Icons.restore),
|
||||
label: const Text('Återställ'),
|
||||
label: Text(context.l10n.adminRestoreAction),
|
||||
)
|
||||
else
|
||||
TextButton.icon(
|
||||
onPressed: () => _removeProduct(product),
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
label: const Text('Ta bort'),
|
||||
label: Text(context.l10n.deleteAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -707,7 +708,7 @@ class _AiApplyDialogState extends State<_AiApplyDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('AI-förslag'),
|
||||
title: Text(context.l10n.adminAiSuggestionsTitle),
|
||||
content: SizedBox(
|
||||
width: 700,
|
||||
child: SingleChildScrollView(
|
||||
@@ -744,13 +745,13 @@ class _AiApplyDialogState extends State<_AiApplyDialog> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Avbryt'),
|
||||
child: Text(context.l10n.cancelAction),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: _selected.isEmpty
|
||||
? null
|
||||
: () => Navigator.pop(context, _selected),
|
||||
child: Text('Tillämpa (${_selected.length})'),
|
||||
child: Text(context.l10n.adminApplySelected(_selected.length)),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../data/admin_repository.dart';
|
||||
import '../domain/user_admin.dart';
|
||||
|
||||
@@ -47,8 +48,8 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
final newRole = user.isAdmin ? 'user' : 'admin';
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
'Ändra roll',
|
||||
'Ändra ${user.username} till $newRole?',
|
||||
context.l10n.adminChangeRole,
|
||||
context.l10n.adminChangeRoleConfirm(user.username, newRole),
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
try {
|
||||
@@ -65,8 +66,8 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
final newValue = !user.isPremium;
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
newValue ? 'Ge Premium' : 'Ta bort Premium',
|
||||
'${newValue ? 'Ge' : 'Ta bort'} Premium för ${user.username}?',
|
||||
newValue ? context.l10n.adminGivePremium : context.l10n.adminRemovePremium,
|
||||
context.l10n.adminPremiumConfirm(newValue ? context.l10n.adminGivePremium : context.l10n.adminRemovePremium, user.username),
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
try {
|
||||
@@ -85,8 +86,8 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
final newValue = !user.canShareRecipes;
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
newValue ? 'Tillåt receptdelning' : 'Blockera receptdelning',
|
||||
'${newValue ? 'Tillåt' : 'Blockera'} receptdelning för ${user.username}?',
|
||||
newValue ? context.l10n.adminAllowSharing : context.l10n.adminBlockSharing,
|
||||
context.l10n.adminSharingConfirm(newValue ? context.l10n.adminAllowSharing : context.l10n.adminBlockSharing, user.username),
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
try {
|
||||
@@ -104,8 +105,8 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
Future<void> _resetPassword(UserAdmin user) async {
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
'Återställ lösenord',
|
||||
'Generera ett tillfälligt lösenord för ${user.username}?',
|
||||
context.l10n.adminResetPassword,
|
||||
context.l10n.adminResetPasswordConfirm(user.username),
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
try {
|
||||
@@ -115,12 +116,12 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: const Text('Tillfälligt lösenord'),
|
||||
title: Text(context.l10n.adminTempPasswordTitle),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Lösenord för ${user.username}:'),
|
||||
Text(context.l10n.adminTempPasswordForUser(user.username)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
@@ -135,7 +136,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy),
|
||||
tooltip: 'Kopiera',
|
||||
tooltip: context.l10n.adminCopyAction,
|
||||
onPressed: () => Clipboard.setData(
|
||||
ClipboardData(text: tempPw),
|
||||
),
|
||||
@@ -152,7 +153,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Stäng'),
|
||||
child: Text(context.l10n.adminCloseAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -169,37 +170,37 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
final newEmail = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text('Ändra e-post för ${user.username}'),
|
||||
title: Text(context.l10n.adminEmailEditTitle(user.username)),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'E-post',
|
||||
border: OutlineInputBorder(),
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.adminEmailLabel,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: const Text('Avbryt'),
|
||||
child: Text(context.l10n.cancelAction),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(dialogContext, controller.text.trim()),
|
||||
child: const Text('Spara'),
|
||||
child: Text(context.l10n.saveAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (newEmail == null || newEmail.isEmpty || !mounted) return;
|
||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(newEmail)) {
|
||||
_showError('Ogiltig e-postadress.');
|
||||
_showError(context.l10n.adminEmailInvalid);
|
||||
return;
|
||||
}
|
||||
await ref.read(adminRepositoryProvider).updateEmail(user.id, newEmail);
|
||||
if (!mounted) return;
|
||||
_load();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('E-post uppdaterad.')),
|
||||
SnackBar(content: Text(context.l10n.adminEmailUpdated)),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -212,8 +213,8 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
Future<void> _deleteUser(UserAdmin user) async {
|
||||
final confirmed = await _confirm(
|
||||
context,
|
||||
'Ta bort användare',
|
||||
'Ta bort ${user.username} permanent? Detta går inte att ångra.',
|
||||
context.l10n.adminDeleteUser,
|
||||
context.l10n.adminDeleteUserConfirm,
|
||||
destructive: true,
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
@@ -243,7 +244,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
if (!mounted) return;
|
||||
_load();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Användare ${result['username']} skapad.')),
|
||||
SnackBar(content: Text(context.l10n.adminUserCreated(result['username']!))),
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
@@ -283,7 +284,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
foregroundColor: Theme.of(ctx).colorScheme.error,
|
||||
)
|
||||
: null,
|
||||
child: const Text('Bekräfta'),
|
||||
child: Text(context.l10n.adminConfirmAction),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -304,7 +305,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
children: [
|
||||
Text(_error!, style: TextStyle(color: theme.colorScheme.error)),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(onPressed: _load, child: const Text('Försök igen')),
|
||||
FilledButton(onPressed: _load, child: Text(context.l10n.retryAction)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -317,11 +318,11 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
FilledButton.icon(
|
||||
onPressed: _createUser,
|
||||
icon: const Icon(Icons.person_add_outlined),
|
||||
label: const Text('Ny användare'),
|
||||
label: Text(context.l10n.adminNewUser),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
const Text('Inga användare hittades.'),
|
||||
Text(context.l10n.adminNoUsers),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -357,7 +358,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Hantera användare direkt från profilsidan.',
|
||||
context.l10n.adminUsersDescription,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
@@ -372,7 +373,7 @@ class _AdminUsersPanelState extends ConsumerState<AdminUsersPanel> {
|
||||
FilledButton.icon(
|
||||
onPressed: _createUser,
|
||||
icon: const Icon(Icons.person_add_outlined),
|
||||
label: const Text('Ny användare'),
|
||||
label: Text(context.l10n.adminNewUser),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
list,
|
||||
@@ -432,29 +433,29 @@ class _UserTile extends StatelessWidget {
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
labelStyle: theme.textTheme.labelSmall,
|
||||
tooltip: user.isAdmin ? 'Nedgradera till user' : 'Uppgradera till admin',
|
||||
tooltip: user.isAdmin ? context.l10n.adminDowngradeToUser : context.l10n.adminUpgradeToAdmin,
|
||||
onPressed: onChangeRole,
|
||||
),
|
||||
ActionChip(
|
||||
label: Text(user.isPremium ? 'Premium' : 'Free'),
|
||||
label: Text(user.isPremium ? context.l10n.adminPremiumLabel : context.l10n.adminFreeLabel),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
labelStyle: theme.textTheme.labelSmall,
|
||||
backgroundColor: user.isPremium
|
||||
? theme.colorScheme.tertiaryContainer
|
||||
: theme.colorScheme.surfaceContainerHighest,
|
||||
tooltip: user.isPremium ? 'Ta bort Premium' : 'Ge Premium',
|
||||
tooltip: user.isPremium ? context.l10n.adminRemovePremium : context.l10n.adminGivePremium,
|
||||
onPressed: onTogglePremium,
|
||||
),
|
||||
ActionChip(
|
||||
label: Text(user.canShareRecipes ? 'Delning: På' : 'Delning: Av'),
|
||||
label: Text(user.canShareRecipes ? context.l10n.adminSharingOn : context.l10n.adminSharingOff),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
labelStyle: theme.textTheme.labelSmall,
|
||||
backgroundColor: user.canShareRecipes
|
||||
? theme.colorScheme.secondaryContainer
|
||||
: theme.colorScheme.errorContainer,
|
||||
tooltip: user.canShareRecipes ? 'Blockera receptdelning' : 'Tillåt receptdelning',
|
||||
tooltip: user.canShareRecipes ? context.l10n.adminBlockSharing : context.l10n.adminAllowSharing,
|
||||
onPressed: onToggleRecipeSharing,
|
||||
),
|
||||
],
|
||||
@@ -489,34 +490,34 @@ class _UserTile extends StatelessWidget {
|
||||
PopupMenuItem(
|
||||
value: 'role',
|
||||
child: Text(
|
||||
user.isAdmin ? 'Nedgradera till user' : 'Uppgradera till admin',
|
||||
user.isAdmin ? context.l10n.adminDowngradeToUser : context.l10n.adminUpgradeToAdmin,
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'premium',
|
||||
child: Text(user.isPremium ? 'Ta bort Premium' : 'Ge Premium'),
|
||||
child: Text(user.isPremium ? context.l10n.adminRemovePremium : context.l10n.adminGivePremium),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'sharing',
|
||||
child: Text(
|
||||
user.canShareRecipes
|
||||
? 'Blockera receptdelning'
|
||||
: 'Tillåt receptdelning',
|
||||
? context.l10n.adminBlockSharing
|
||||
: context.l10n.adminAllowSharing,
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: 'email',
|
||||
child: Text('Ändra e-post'),
|
||||
child: Text(context.l10n.adminEmailAction),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: 'reset',
|
||||
child: Text('Återställ lösenord'),
|
||||
child: Text(context.l10n.adminResetPassword),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text(
|
||||
'Ta bort',
|
||||
context.l10n.deleteAction,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
),
|
||||
@@ -553,7 +554,7 @@ class _CreateUserDialogState extends State<_CreateUserDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Skapa användare'),
|
||||
title: Text(context.l10n.adminCreateUserTitle),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
@@ -562,19 +563,19 @@ class _CreateUserDialogState extends State<_CreateUserDialog> {
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _usernameCtrl,
|
||||
decoration: const InputDecoration(labelText: 'Användarnamn'),
|
||||
decoration: InputDecoration(labelText: context.l10n.profileUsernameLabel),
|
||||
validator: (v) =>
|
||||
(v == null || v.length < 2) ? 'Minst 2 tecken' : null,
|
||||
(v == null || v.length < 2) ? context.l10n.adminMinChars2 : null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _emailCtrl,
|
||||
decoration: const InputDecoration(labelText: 'E-post'),
|
||||
decoration: InputDecoration(labelText: context.l10n.adminEmailLabel),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return 'Obligatoriskt';
|
||||
if (v == null || v.isEmpty) return context.l10n.required;
|
||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(v)) {
|
||||
return 'Ogiltig e-post';
|
||||
return context.l10n.adminEmailInvalid;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -583,7 +584,7 @@ class _CreateUserDialogState extends State<_CreateUserDialog> {
|
||||
TextFormField(
|
||||
controller: _passwordCtrl,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Lösenord',
|
||||
labelText: context.l10n.adminPasswordLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscure
|
||||
@@ -595,15 +596,15 @@ class _CreateUserDialogState extends State<_CreateUserDialog> {
|
||||
),
|
||||
obscureText: _obscure,
|
||||
validator: (v) =>
|
||||
(v == null || v.length < 8) ? 'Minst 8 tecken' : null,
|
||||
(v == null || v.length < 8) ? context.l10n.adminMinChars8 : null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _role,
|
||||
decoration: const InputDecoration(labelText: 'Roll'),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'user', child: Text('Användare')),
|
||||
DropdownMenuItem(value: 'admin', child: Text('Admin')),
|
||||
decoration: InputDecoration(labelText: context.l10n.adminRoleLabel),
|
||||
items: [
|
||||
DropdownMenuItem(value: 'user', child: Text(context.l10n.adminUserRole)),
|
||||
DropdownMenuItem(value: 'admin', child: Text(context.l10n.adminAdminRole)),
|
||||
],
|
||||
onChanged: (v) => setState(() => _role = v ?? 'user'),
|
||||
),
|
||||
@@ -614,7 +615,7 @@ class _CreateUserDialogState extends State<_CreateUserDialog> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Avbryt'),
|
||||
child: Text(context.l10n.cancelAction),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
@@ -627,7 +628,7 @@ class _CreateUserDialogState extends State<_CreateUserDialog> {
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Text('Skapa'),
|
||||
child: Text(context.l10n.adminCreateAction),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user