Add Swedish localization for various app actions and inventory management strings

This commit is contained in:
Nils-Johan Gynther
2026-05-02 15:42:00 +02:00
parent 4e81f56225
commit 2563738fcf
24 changed files with 4510 additions and 366 deletions
@@ -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),
),
],
);