Skip to content

Commit 7ec910f

Browse files
committed
feat: implement internationalization (i18n)
- Translated UI elements to English - Added Arabic translation file
1 parent 4fed2d9 commit 7ec910f

File tree

11 files changed

+565
-143
lines changed

11 files changed

+565
-143
lines changed

lib/account/view/account_linking_page.dart

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
44
import 'package:ht_main/account/bloc/account_linking_bloc.dart';
5+
import 'package:ht_main/l10n/l10n.dart'; // Added import
56

67
/// {@template account_linking_page} // Renamed template
78
/// Page widget for the Account Linking feature.
@@ -50,20 +51,21 @@ class _AccountLinkingViewState extends State<_AccountLinkingView> {
5051

5152
@override
5253
Widget build(BuildContext context) {
54+
final l10n = context.l10n;
5355
final textTheme = Theme.of(context).textTheme;
5456

5557
return Scaffold(
56-
appBar: AppBar(title: const Text('Link Your Account')),
58+
appBar: AppBar(title: Text(l10n.accountLinkingPageTitle)),
5759
body: BlocConsumer<AccountLinkingBloc, AccountLinkingState>(
58-
// Renamed Bloc and State
5960
listener: (context, state) {
6061
if (state.status == AccountLinkingStatus.failure) {
61-
// Renamed Status enum
6262
ScaffoldMessenger.of(context)
6363
..hideCurrentSnackBar()
6464
..showSnackBar(
6565
SnackBar(
66-
content: Text(state.errorMessage ?? 'An error occurred'),
66+
content: Text(
67+
state.errorMessage ?? l10n.accountLinkingGenericError,
68+
),
6769
backgroundColor: Theme.of(context).colorScheme.error,
6870
),
6971
);
@@ -72,9 +74,7 @@ class _AccountLinkingViewState extends State<_AccountLinkingView> {
7274
ScaffoldMessenger.of(context)
7375
..hideCurrentSnackBar()
7476
..showSnackBar(
75-
const SnackBar(
76-
content: Text('Check your email for the sign-in link!'),
77-
),
77+
SnackBar(content: Text(l10n.accountLinkingEmailSentSuccess)),
7878
);
7979
// Optionally clear email field or navigate away
8080
_emailController.clear();
@@ -92,16 +92,13 @@ class _AccountLinkingViewState extends State<_AccountLinkingView> {
9292
crossAxisAlignment: CrossAxisAlignment.stretch,
9393
children: [
9494
Text(
95-
'Create or Link Account to Save Progress',
95+
l10n.accountLinkingHeadline,
9696
style: textTheme.headlineSmall,
9797
textAlign: TextAlign.center,
9898
),
9999
const SizedBox(height: 16),
100100
Text(
101-
"""
102-
Signing up or linking allows you to access your information
103-
across multiple devices and ensures your progress isn't lost.
104-
""", // Updated text
101+
l10n.accountLinkingBody,
105102
style: textTheme.bodyMedium,
106103
textAlign: TextAlign.center,
107104
),
@@ -112,7 +109,7 @@ class _AccountLinkingViewState extends State<_AccountLinkingView> {
112109
icon: const Icon(
113110
Icons.g_mobiledata,
114111
), // Placeholder, use a Google icon asset
115-
label: const Text('Continue with Google'),
112+
label: Text(l10n.accountLinkingContinueWithGoogleButton),
116113
onPressed:
117114
isLoading
118115
? null
@@ -126,17 +123,17 @@ class _AccountLinkingViewState extends State<_AccountLinkingView> {
126123
// --- Email Link Sign-In ---
127124
TextFormField(
128125
controller: _emailController,
129-
decoration: const InputDecoration(
130-
labelText: 'Enter your email',
131-
hintText: '[email protected]',
126+
decoration: InputDecoration(
127+
labelText: l10n.accountLinkingEmailInputLabel,
128+
hintText: l10n.accountLinkingEmailInputHint,
132129
),
133130
keyboardType: TextInputType.emailAddress,
134131
autocorrect: false,
135132
validator: (value) {
136133
if (value == null ||
137134
value.isEmpty ||
138135
!value.contains('@')) {
139-
return 'Please enter a valid email address';
136+
return l10n.accountLinkingEmailValidationError;
140137
}
141138
return null;
142139
},
@@ -156,7 +153,7 @@ class _AccountLinkingViewState extends State<_AccountLinkingView> {
156153
);
157154
}
158155
},
159-
child: const Text('Send Sign-In Link'),
156+
child: Text(l10n.accountLinkingSendLinkButton),
160157
),
161158

162159
// --- Loading Indicator ---

lib/account/view/account_page.dart

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:ht_authentication_repository/ht_authentication_repository.dart';
77
import 'package:ht_main/account/bloc/account_bloc.dart';
88
import 'package:ht_main/app/bloc/app_bloc.dart'; // Needed for AppBloc and AppState access
99
// Remove direct import of app_state.dart when using part of
10+
import 'package:ht_main/l10n/l10n.dart'; // Added import
1011
import 'package:ht_main/router/routes.dart'; // Needed for route names
1112

1213
/// {@template account_page}
@@ -43,6 +44,7 @@ class _AccountView extends StatelessWidget {
4344

4445
@override
4546
Widget build(BuildContext context) {
47+
final l10n = context.l10n;
4648
// Watch AppBloc for user details and authentication status
4749
final user = context.watch<AppBloc>().state.user;
4850
final status = user.authenticationStatus; // Use status from User model
@@ -51,7 +53,7 @@ class _AccountView extends StatelessWidget {
5153
final isAnonymous = status == AuthenticationStatus.anonymous;
5254

5355
return Scaffold(
54-
appBar: AppBar(title: const Text('Account')),
56+
appBar: AppBar(title: Text(l10n.accountPageTitle)),
5557
body: ListView(
5658
// Use ListView for potential scrolling if content grows
5759
padding: const EdgeInsets.all(16),
@@ -75,6 +77,7 @@ class _AccountView extends StatelessWidget {
7577

7678
/// Builds the header section displaying user avatar, name, and status.
7779
Widget _buildUserHeader(BuildContext context, User user, bool isAnonymous) {
80+
final l10n = context.l10n;
7881
final theme = Theme.of(context);
7982
final textTheme = theme.textTheme;
8083

@@ -100,14 +103,19 @@ class _AccountView extends StatelessWidget {
100103
),
101104
const SizedBox(height: 16),
102105
Text(
103-
isAnonymous ? '(Anonymous)' : user.displayName ?? 'No Name',
106+
isAnonymous
107+
? l10n.accountAnonymousUser
108+
: user.displayName ?? l10n.accountNoNameUser,
104109
style: textTheme.titleLarge,
105110
textAlign: TextAlign.center,
106111
),
107112
const SizedBox(height: 4),
108113
Text(
109114
// Convert enum to user-friendly string
110-
_authenticationStatusToString(user.authenticationStatus),
115+
_authenticationStatusToString(
116+
context,
117+
user.authenticationStatus,
118+
), // Pass context
111119
style: textTheme.bodyMedium?.copyWith(
112120
color: theme.colorScheme.secondary,
113121
),
@@ -119,9 +127,10 @@ class _AccountView extends StatelessWidget {
119127

120128
/// Builds the ListTile for navigating to Settings.
121129
Widget _buildSettingsTile(BuildContext context) {
130+
final l10n = context.l10n;
122131
return ListTile(
123132
leading: const Icon(Icons.settings_outlined),
124-
title: const Text('Settings'),
133+
title: Text(l10n.accountSettingsTile),
125134
trailing: const Icon(Icons.chevron_right),
126135
onTap: () {
127136
// Navigate to the existing settings route
@@ -132,10 +141,11 @@ class _AccountView extends StatelessWidget {
132141

133142
/// Builds the ListTile for logging out (for authenticated users).
134143
Widget _buildLogoutTile(BuildContext context) {
144+
final l10n = context.l10n;
135145
return ListTile(
136146
leading: Icon(Icons.logout, color: Theme.of(context).colorScheme.error),
137147
title: Text(
138-
'Sign Out',
148+
l10n.accountSignOutTile,
139149
style: TextStyle(color: Theme.of(context).colorScheme.error),
140150
),
141151
onTap: () {
@@ -148,11 +158,12 @@ class _AccountView extends StatelessWidget {
148158

149159
/// Builds the ListTile for navigating to Backup/Account Linking (for anonymous users).
150160
Widget _buildBackupTile(BuildContext context) {
161+
final l10n = context.l10n;
151162
return ListTile(
152163
leading: const Icon(
153164
Icons.cloud_upload_outlined,
154165
), // Or Icons.link, Icons.save
155-
title: const Text('Create Account to Save Data'),
166+
title: Text(l10n.accountBackupTile),
156167
trailing: const Icon(Icons.chevron_right),
157168
onTap: () {
158169
// Navigate to the account linking page
@@ -162,14 +173,18 @@ class _AccountView extends StatelessWidget {
162173
}
163174

164175
/// Helper to convert AuthenticationStatus enum to a display string.
165-
String _authenticationStatusToString(AuthenticationStatus status) {
176+
String _authenticationStatusToString(
177+
BuildContext context,
178+
AuthenticationStatus status,
179+
) {
180+
final l10n = context.l10n;
166181
switch (status) {
167182
case AuthenticationStatus.authenticated:
168-
return 'Authenticated';
183+
return l10n.accountStatusAuthenticated;
169184
case AuthenticationStatus.anonymous:
170-
return 'Anonymous Session';
185+
return l10n.accountStatusAnonymous;
171186
case AuthenticationStatus.unauthenticated:
172-
return 'Not Signed In';
187+
return l10n.accountStatusUnauthenticated;
173188
}
174189
}
175190
}

lib/authentication/view/authentication_page.dart

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
55
import 'package:flutter_bloc/flutter_bloc.dart';
66
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
77
import 'package:ht_main/authentication/bloc/authentication_bloc.dart';
8+
import 'package:ht_main/l10n/l10n.dart'; // Added import
89

910
class AuthenticationPage extends StatelessWidget {
1011
const AuthenticationPage({super.key});
@@ -58,8 +59,8 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
5859
ScaffoldMessenger.of(context)
5960
..hideCurrentSnackBar()
6061
..showSnackBar(
61-
const SnackBar(
62-
content: Text('Check your email for the sign-in link.'),
62+
SnackBar(
63+
content: Text(context.l10n.authenticationEmailSentSuccess),
6364
),
6465
);
6566
// Optionally clear email field or navigate
@@ -70,6 +71,7 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
7071
final isLoading =
7172
state is AuthenticationLoading ||
7273
state is AuthenticationLinkSending;
74+
final l10n = context.l10n; // Added l10n variable
7375

7476
return Padding(
7577
padding: const EdgeInsets.all(16), // Use AppSpacing later
@@ -82,7 +84,7 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
8284
crossAxisAlignment: CrossAxisAlignment.stretch,
8385
children: [
8486
Text(
85-
'Sign In / Register', // Updated title
87+
l10n.authenticationPageTitle,
8688
style:
8789
Theme.of(
8890
context,
@@ -92,9 +94,9 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
9294
const SizedBox(height: 32), // Use AppSpacing later
9395
TextFormField(
9496
controller: _emailController,
95-
decoration: const InputDecoration(
96-
labelText: 'Email', // Needs localization
97-
border: OutlineInputBorder(),
97+
decoration: InputDecoration(
98+
labelText: l10n.authenticationEmailLabel,
99+
border: const OutlineInputBorder(),
98100
),
99101
keyboardType: TextInputType.emailAddress,
100102
autocorrect: false,
@@ -128,20 +130,21 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
128130
strokeWidth: 2,
129131
),
130132
)
131-
: const Text(
132-
'Send Sign-In Link',
133-
), // Needs localization
133+
: Text(l10n.authenticationSendLinkButton),
134134
),
135135
const SizedBox(height: 16), // Use AppSpacing later
136136
// Add divider for clarity
137-
const Row(
137+
Row(
138+
// Removed const
138139
children: [
139-
Expanded(child: Divider()),
140+
const Expanded(
141+
child: Divider(),
142+
), // Added const back here
140143
Padding(
141-
padding: EdgeInsets.symmetric(horizontal: 8),
142-
child: Text('OR'), // Needs localization
144+
padding: const EdgeInsets.symmetric(horizontal: 8),
145+
child: Text(l10n.authenticationOrDivider),
143146
),
144-
Expanded(child: Divider()),
147+
const Expanded(child: Divider()),
145148
],
146149
),
147150
const SizedBox(height: 16), // Use AppSpacing later
@@ -157,9 +160,7 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
157160
);
158161
},
159162
// Consider adding Google icon
160-
child: const Text(
161-
'Sign In with Google',
162-
), // Needs localization
163+
child: Text(l10n.authenticationGoogleSignInButton),
163164
),
164165
const SizedBox(height: 16), // Use AppSpacing later
165166
OutlinedButton(
@@ -172,9 +173,7 @@ class __AuthenticationViewState extends State<_AuthenticationView> {
172173
const AuthenticationAnonymousSignInRequested(),
173174
);
174175
},
175-
child: const Text(
176-
'Continue Anonymously',
177-
), // Needs localization
176+
child: Text(l10n.authenticationAnonymousSignInButton),
178177
),
179178
],
180179
), // Column

lib/headline-details/view/headline_details_page.dart

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:ht_headlines_client/ht_headlines_client.dart'; // Import for Headline
44
import 'package:ht_headlines_repository/ht_headlines_repository.dart';
55
import 'package:ht_main/headline-details/bloc/headline_details_bloc.dart';
6+
import 'package:ht_main/l10n/l10n.dart'; // Added import
67
import 'package:ht_main/shared/widgets/failure_state_widget.dart';
78
import 'package:ht_main/shared/widgets/initial_state_widget.dart';
89
import 'package:ht_main/shared/widgets/loading_state_widget.dart';
@@ -37,6 +38,7 @@ class _HeadlineDetailsView extends StatelessWidget {
3738

3839
@override
3940
Widget build(BuildContext context) {
41+
final l10n = context.l10n;
4042
return Scaffold(
4143
appBar: AppBar(
4244
leading: IconButton(
@@ -57,15 +59,15 @@ class _HeadlineDetailsView extends StatelessWidget {
5759
body: BlocBuilder<HeadlineDetailsBloc, HeadlineDetailsState>(
5860
builder: (context, state) {
5961
return switch (state) {
60-
HeadlineDetailsInitial _ => const InitialStateWidget(
62+
HeadlineDetailsInitial _ => InitialStateWidget(
6163
icon: Icons.article,
62-
headline: 'Waiting for Headline',
63-
subheadline: 'Please wait...',
64+
headline: l10n.headlineDetailsInitialHeadline,
65+
subheadline: l10n.headlineDetailsInitialSubheadline,
6466
),
65-
HeadlineDetailsLoading _ => const LoadingStateWidget(
67+
HeadlineDetailsLoading _ => LoadingStateWidget(
6668
icon: Icons.downloading,
67-
headline: 'Loading Headline',
68-
subheadline: 'Fetching data...',
69+
headline: l10n.headlineDetailsLoadingHeadline,
70+
subheadline: l10n.headlineDetailsLoadingSubheadline,
6971
),
7072
final HeadlineDetailsFailure state => FailureStateWidget(
7173
message: state.message,
@@ -87,6 +89,7 @@ class _HeadlineDetailsView extends StatelessWidget {
8789
}
8890

8991
Widget _buildLoaded(BuildContext context, Headline headline) {
92+
final l10n = context.l10n;
9093
return SingleChildScrollView(
9194
child: Padding(
9295
padding: const EdgeInsets.all(16),
@@ -110,7 +113,7 @@ class _HeadlineDetailsView extends StatelessWidget {
110113
errorBuilder:
111114
(context, error, stackTrace) => const Icon(Icons.error),
112115
),
113-
const SizedBox(height: 16), // Keep this
116+
const SizedBox(height: 16),
114117
Text(headline.title, style: Theme.of(context).textTheme.titleLarge),
115118
const SizedBox(height: 8),
116119
Column(
@@ -126,9 +129,7 @@ class _HeadlineDetailsView extends StatelessWidget {
126129
),
127130
],
128131
),
129-
const SizedBox(
130-
height: 8,
131-
), // Add spacing between metadata items
132+
const SizedBox(height: 8),
132133
],
133134
if (headline.categories != null &&
134135
headline.categories!.isNotEmpty) ...[
@@ -190,7 +191,7 @@ class _HeadlineDetailsView extends StatelessWidget {
190191
// Removed custom padding
191192
),
192193
child: Text(
193-
'Continue Reading',
194+
l10n.headlineDetailsContinueReadingButton,
194195
style: Theme.of(context).textTheme.labelLarge,
195196
),
196197
),

0 commit comments

Comments
 (0)