feat(localization): Implement Swedish localization and error messages

- Added localization support for Swedish and English languages.
- Integrated localized strings for user messages in the API error mapper.
- Updated UI components to use localized strings for labels and messages.
- Ensured all error messages are context-aware and utilize the localization framework.
- Created regression test to prevent common ASCII fallbacks in Swedish UI text.
This commit is contained in:
Nils-Johan Gynther
2026-04-22 19:16:23 +02:00
parent 37472f6c43
commit 2e117718a7
26 changed files with 315 additions and 96 deletions
@@ -19,7 +19,7 @@ class AuthRepository {
if (data is! Map<String, dynamic>) {
throw const ApiException(
type: ApiErrorType.unknown,
message: 'Ogiltigt svar fran servern.',
message: 'Ogiltigt svar från servern.',
);
}
@@ -38,7 +38,7 @@ class AuthRepository {
} catch (_) {
throw const ApiException(
type: ApiErrorType.network,
message: 'Kunde inte na servern.',
message: 'Kunde inte nå servern.',
);
}
}
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/l10n/l10n.dart';
import '../data/auth_providers.dart';
class LoginScreen extends ConsumerStatefulWidget {
@@ -41,9 +42,10 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
Widget build(BuildContext context) {
final authState = ref.watch(authStateProvider);
final isLoading = authState is AsyncLoading;
final l10n = context.l10n;
return Scaffold(
appBar: AppBar(title: const Text('Logga in')),
appBar: AppBar(title: Text(l10n.loginTitle)),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
@@ -57,8 +59,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
children: [
TextFormField(
controller: _usernameCtrl,
decoration:
const InputDecoration(labelText: 'Användarnamn'),
decoration: InputDecoration(labelText: l10n.usernameLabel),
textInputAction: TextInputAction.next,
autofocus: true,
enabled: !isLoading,
@@ -66,7 +67,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
FocusScope.of(context).requestFocus(_passwordFocus),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Ange ditt användarnamn.';
return l10n.usernameRequired;
}
return null;
},
@@ -75,14 +76,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
TextFormField(
controller: _passwordCtrl,
focusNode: _passwordFocus,
decoration: const InputDecoration(labelText: 'Lösenord'),
decoration: InputDecoration(labelText: l10n.passwordLabel),
obscureText: true,
textInputAction: TextInputAction.done,
enabled: !isLoading,
onFieldSubmitted: (_) => _submit(),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Ange ditt lösenord.';
return l10n.passwordRequired;
}
return null;
},
@@ -93,13 +94,13 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
else
FilledButton(
onPressed: _submit,
child: const Text('Logga in'),
child: Text(l10n.loginAction),
),
if (authState is AsyncError)
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text(
mapErrorToUserMessage(authState.error!),
mapErrorToUserMessage(authState.error!, context),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.error),