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
@@ -7,6 +7,7 @@ import '../../../core/api/api_exception.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/api_providers.dart';
import '../../../core/forms/form_options.dart';
import '../../../core/l10n/l10n.dart';
import '../../../core/ui/async_state_views.dart';
import '../../auth/data/auth_providers.dart';
import '../data/recipe_providers.dart';
@@ -152,20 +153,20 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
String? _validateIngredients() {
if (_ingredients.isEmpty) {
return 'Minst en ingrediens krävs.';
return context.l10n.recipeEditMinIngredients;
}
for (final ingredient in _ingredients) {
if (ingredient.productId == null) {
return 'Välj produkt för alla ingredienser.';
return context.l10n.recipeEditSelectProduct;
}
final quantity = double.tryParse(
ingredient.quantityCtrl.text.trim().replaceAll(',', '.'),
);
if (quantity == null || quantity < 0) {
return 'Ange giltig mängd för alla ingredienser.';
return context.l10n.recipeEditValidQuantity;
}
if (ingredient.unit.trim().isEmpty) {
return 'Välj enhet för alla ingredienser.';
return context.l10n.recipeEditSelectUnit;
}
}
return null;
@@ -237,7 +238,7 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
return Scaffold(
appBar: AppBar(
title: const Text('Redigera recept'),
title: Text(context.l10n.recipeEditTitle),
actions: [
if (_initialized)
TextButton(
@@ -248,12 +249,12 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
width: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Spara'),
: Text(context.l10n.saveAction),
),
],
),
body: recipeAsync.when(
loading: () => const LoadingStateView(label: 'Laddar recept...'),
loading: () => LoadingStateView(label: context.l10n.recipeDetailLoading),
error: (error, _) => ErrorStateView(
message: mapErrorToUserMessage(error, context),
onRetry: () => ref.invalidate(recipeDetailProvider(widget.recipeId)),
@@ -276,27 +277,27 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
children: [
TextFormField(
controller: _nameCtrl,
decoration: const InputDecoration(labelText: 'Receptnamn'),
decoration: InputDecoration(labelText: context.l10n.recipeEditNameLabel),
validator: (v) =>
(v == null || v.trim().isEmpty) ? 'Ange ett receptnamn.' : null,
(v == null || v.trim().isEmpty) ? context.l10n.recipeEditNameRequired : null,
),
const SizedBox(height: 16),
TextFormField(
controller: _descCtrl,
decoration:
const InputDecoration(labelText: 'Beskrivning (valfritt)'),
InputDecoration(labelText: context.l10n.recipeEditDescriptionLabel),
maxLines: 3,
),
const SizedBox(height: 16),
TextFormField(
controller: _servingsCtrl,
decoration:
const InputDecoration(labelText: 'Antal portioner (valfritt)'),
InputDecoration(labelText: context.l10n.recipeEditServingsLabel),
keyboardType: TextInputType.number,
validator: (v) {
if (v == null || v.trim().isEmpty) return null;
if (int.tryParse(v.trim()) == null) {
return 'Ange ett heltal.';
return context.l10n.recipeEditServingsInvalid;
}
return null;
},
@@ -305,7 +306,7 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
TextFormField(
controller: _instructionsCtrl,
decoration:
const InputDecoration(labelText: 'Tillvägagångssätt (valfritt)'),
InputDecoration(labelText: context.l10n.recipeEditInstructionsLabel),
maxLines: 10,
textAlignVertical: TextAlignVertical.top,
),
@@ -314,20 +315,20 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
children: [
Expanded(
child: Text(
'Ingredienser',
context.l10n.recipeEditIngredientsLabel,
style: Theme.of(context).textTheme.titleMedium,
),
),
OutlinedButton.icon(
onPressed: _isSaving ? null : _addIngredient,
icon: const Icon(Icons.add),
label: const Text('Lägg till'),
label: Text(context.l10n.addAction),
),
],
),
const SizedBox(height: 8),
Text(
'Välj produkt, mängd och enhet för varje ingrediens.',
context.l10n.recipeEditIngredientsHint,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
@@ -338,10 +339,10 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
child: LinearProgressIndicator(),
),
if (_ingredients.isEmpty)
const Card(
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Inga ingredienser tillagda än.'),
padding: const EdgeInsets.all(16),
child: Text(context.l10n.recipeEditNoIngredients),
),
),
...List.generate(
@@ -366,7 +367,7 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
width: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Spara ändringar'),
: Text(context.l10n.recipeEditSaveChanges),
),
const SizedBox(height: 40),
],
@@ -387,14 +388,14 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
children: [
Expanded(
child: Text(
'Ingrediens ${index + 1}',
'${context.l10n.recipeEditIngredientPrefix}${index + 1}',
style: Theme.of(context).textTheme.titleSmall,
),
),
IconButton(
onPressed: _isSaving ? null : () => _removeIngredient(index),
icon: const Icon(Icons.delete_outline),
tooltip: 'Ta bort ingrediens',
tooltip: context.l10n.recipeEditRemoveIngredient,
),
],
),
@@ -440,19 +441,19 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
Expanded(
child: TextFormField(
controller: ingredient.quantityCtrl,
decoration: const InputDecoration(
labelText: 'Mängd *',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: context.l10n.quantityLabel,
border: const OutlineInputBorder(),
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Ange mängd';
return context.l10n.quantityHint;
}
if (double.tryParse(value.trim().replaceAll(',', '.')) ==
null) {
return 'Ogiltigt tal';
return context.l10n.invalidNumber;
}
return null;
},
@@ -463,9 +464,9 @@ class _RecipeEditScreenState extends ConsumerState<RecipeEditScreen> {
child: DropdownButtonFormField<String>(
initialValue: ingredient.unit.trim().isEmpty ? null : ingredient.unit,
isExpanded: true,
decoration: const InputDecoration(
labelText: 'Enhet *',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: context.l10n.unitLabel,
border: const OutlineInputBorder(),
),
items: unitOptions
.map(