From dc5c2f6565f22c80123392ed7c21c752de9637ee Mon Sep 17 00:00:00 2001 From: Asad Date: Sat, 30 May 2026 03:55:22 +0500 Subject: [PATCH 1/4] run build --- android/app/build.gradle.kts | 9 +- android/gradle.properties | 2 + pubspec.lock | 80 +++------- pubspec.yaml | 2 +- test/auth_ui_test.mocks.dart | 182 +++++++++++++++++++++++ test/checkout_view_model_test.mocks.dart | 111 ++++++++++++++ 6 files changed, 321 insertions(+), 65 deletions(-) create mode 100644 test/auth_ui_test.mocks.dart create mode 100644 test/checkout_view_model_test.mocks.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9391dfcf..69b04e7e 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,12 +1,5 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import java.util.Properties -import java.io.FileInputStream -val localProperties = Properties() -val localPropertiesFile = rootDir.resolve("local.properties") -if (localPropertiesFile.exists()) { - localProperties.load(FileInputStream(localPropertiesFile)) -} plugins { id("com.android.application") // START: FlutterFire Configuration @@ -20,7 +13,7 @@ plugins { android { namespace = "uk.org.cherry.app" compileSdk = flutter.compileSdkVersion - ndkVersion = localProperties.getProperty("flutter.ndkVersion") + ndkVersion = flutter.ndkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/android/gradle.properties b/android/gradle.properties index f018a618..39d030bf 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,5 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# Project on D: with Pub cache on C: breaks Kotlin incremental compilation on Windows. +kotlin.incremental=false diff --git a/pubspec.lock b/pubspec.lock index e04f18b0..8c027a78 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: cd6add6f846f35fb79f3c315296703c1a24f3cfd7f4739d91a74961c1c7e9f1b + sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc" url: "https://pub.dev" source: hosted - version: "100.0.0" + version: "96.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "6ba98576948803398b69e3a444df24eacdbe12ed699c7014e120ea38552debbf" + sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "10.2.0" ansicolor: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -193,14 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.5" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" - source: hosted - version: "1.0.0" code_builder: dependency: transitive description: @@ -261,10 +253,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60" + sha256: "15a7db352c8fc6a4d2bc475ba901c25b39fe7157541da4c16eacce6f8be83e49" url: "https://pub.dev" source: hosted - version: "3.1.9" + version: "3.1.5" dio: dependency: "direct main" description: @@ -564,10 +556,10 @@ packages: dependency: transitive description: name: google_sign_in_ios - sha256: ac1e4c1205267cb7999d1d81333fccffdfda29e853f434bbaf71525498bb6950 + sha256: "234fc2830b55d1bbeb7e05662967691f5994143ff43dc70d3f139d1bbb3b8fb2" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.2.5" google_sign_in_platform_interface: dependency: transitive description: @@ -592,14 +584,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - hooks: - dependency: transitive - description: - name: hooks - sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 - url: "https://pub.dev" - source: hosted - version: "1.0.2" html: dependency: transitive description: @@ -668,10 +652,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" url: "https://pub.dev" source: hosted - version: "0.8.13+6" + version: "0.8.13+3" image_picker_linux: dependency: transitive description: @@ -796,18 +780,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: "direct overridden" description: @@ -828,18 +812,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: "3762486c07f0faba043abf7da4f51f8564d2119659b1d8a708d34bff74ed5035" - url: "https://pub.dev" - source: hosted - version: "5.6.5" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 url: "https://pub.dev" source: hosted - version: "0.17.6" + version: "5.6.4" nested: dependency: transitive description: @@ -848,14 +824,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.dev" - source: hosted - version: "9.3.0" package_config: dependency: transitive description: @@ -900,10 +868,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -1169,10 +1137,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.7" typed_data: dependency: transitive description: @@ -1286,5 +1254,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.11.0 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.6" diff --git a/pubspec.yaml b/pubspec.yaml index a11620d1..aec26d2f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - mockito: ^5.6.5 + mockito: ^5.5.0 flutter_lints: ^6.0.0 json_serializable: ^6.13.2 build_runner: ^2.15.0 diff --git a/test/auth_ui_test.mocks.dart b/test/auth_ui_test.mocks.dart new file mode 100644 index 00000000..eb705071 --- /dev/null +++ b/test/auth_ui_test.mocks.dart @@ -0,0 +1,182 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cherry_mvp/test/auth_ui_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; + +import 'package:cherry_mvp/core/models/model.dart' as _i6; +import 'package:cherry_mvp/core/router/nav_provider.dart' as _i8; +import 'package:cherry_mvp/core/utils/utils.dart' as _i2; +import 'package:cherry_mvp/features/login/login_model.dart' as _i7; +import 'package:cherry_mvp/features/login/login_repository.dart' as _i4; +import 'package:flutter/material.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +class _FakeResult_0 extends _i1.SmartFake implements _i2.Result { + _FakeResult_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeGlobalKey_1> + extends _i1.SmartFake + implements _i3.GlobalKey { + _FakeGlobalKey_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [LoginRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockLoginRepository extends _i1.Mock implements _i4.LoginRepository { + @override + _i5.Future<_i2.Result<_i6.UserCredentials?>> login( + _i7.LoginRequest? request, + ) => + (super.noSuchMethod( + Invocation.method(#login, [request]), + returnValue: _i5.Future<_i2.Result<_i6.UserCredentials?>>.value( + _FakeResult_0<_i6.UserCredentials?>( + this, + Invocation.method(#login, [request]), + ), + ), + returnValueForMissingStub: + _i5.Future<_i2.Result<_i6.UserCredentials?>>.value( + _FakeResult_0<_i6.UserCredentials?>( + this, + Invocation.method(#login, [request]), + ), + ), + ) + as _i5.Future<_i2.Result<_i6.UserCredentials?>>); + + @override + _i5.Future<_i2.Result<_i6.UserCredentials?>> signInWithSocial( + _i4.SocialLoginType? type, + ) => + (super.noSuchMethod( + Invocation.method(#signInWithSocial, [type]), + returnValue: _i5.Future<_i2.Result<_i6.UserCredentials?>>.value( + _FakeResult_0<_i6.UserCredentials?>( + this, + Invocation.method(#signInWithSocial, [type]), + ), + ), + returnValueForMissingStub: + _i5.Future<_i2.Result<_i6.UserCredentials?>>.value( + _FakeResult_0<_i6.UserCredentials?>( + this, + Invocation.method(#signInWithSocial, [type]), + ), + ), + ) + as _i5.Future<_i2.Result<_i6.UserCredentials?>>); + + @override + _i5.Future<_i2.Result> logout() => + (super.noSuchMethod( + Invocation.method(#logout, []), + returnValue: _i5.Future<_i2.Result>.value( + _FakeResult_0(this, Invocation.method(#logout, [])), + ), + returnValueForMissingStub: _i5.Future<_i2.Result>.value( + _FakeResult_0(this, Invocation.method(#logout, [])), + ), + ) + as _i5.Future<_i2.Result>); +} + +/// A class which mocks [NavigationProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockNavigationProvider extends _i1.Mock + implements _i8.NavigationProvider { + @override + _i3.GlobalKey<_i3.NavigatorState> get navigatorKey => + (super.noSuchMethod( + Invocation.getter(#navigatorKey), + returnValue: _FakeGlobalKey_1<_i3.NavigatorState>( + this, + Invocation.getter(#navigatorKey), + ), + returnValueForMissingStub: _FakeGlobalKey_1<_i3.NavigatorState>( + this, + Invocation.getter(#navigatorKey), + ), + ) + as _i3.GlobalKey<_i3.NavigatorState>); + + @override + _i5.Future navigateTo(String? routeName, {Object? arguments}) => + (super.noSuchMethod( + Invocation.method( + #navigateTo, + [routeName], + {#arguments: arguments}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future replaceWith(String? routeName, {Object? arguments}) => + (super.noSuchMethod( + Invocation.method( + #replaceWith, + [routeName], + {#arguments: arguments}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future navigateToAndRemoveUntil( + String? routeName, + _i3.RoutePredicate? predicate, { + Object? arguments, + }) => + (super.noSuchMethod( + Invocation.method( + #navigateToAndRemoveUntil, + [routeName, predicate], + {#arguments: arguments}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + void goBack([Object? arguments]) => super.noSuchMethod( + Invocation.method(#goBack, [arguments]), + returnValueForMissingStub: null, + ); + + @override + _i5.Future showPurchaseSecurity() => + (super.noSuchMethod( + Invocation.method(#showPurchaseSecurity, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); +} diff --git a/test/checkout_view_model_test.mocks.dart b/test/checkout_view_model_test.mocks.dart new file mode 100644 index 00000000..6333b086 --- /dev/null +++ b/test/checkout_view_model_test.mocks.dart @@ -0,0 +1,111 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in cherry_mvp/test/checkout_view_model_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:cherry_mvp/core/router/nav_provider.dart' as _i3; +import 'package:flutter/material.dart' as _i1; +import 'package:mockito/mockito.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member + +class _FakeGlobalKey_0> + extends _i2.SmartFake + implements _i1.GlobalKey { + _FakeGlobalKey_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +/// A class which mocks [NavigationProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockNavigationProvider extends _i2.Mock + implements _i3.NavigationProvider { + @override + _i1.GlobalKey<_i1.NavigatorState> get navigatorKey => + (super.noSuchMethod( + Invocation.getter(#navigatorKey), + returnValue: _FakeGlobalKey_0<_i1.NavigatorState>( + this, + Invocation.getter(#navigatorKey), + ), + returnValueForMissingStub: _FakeGlobalKey_0<_i1.NavigatorState>( + this, + Invocation.getter(#navigatorKey), + ), + ) + as _i1.GlobalKey<_i1.NavigatorState>); + + @override + _i4.Future navigateTo(String? routeName, {Object? arguments}) => + (super.noSuchMethod( + Invocation.method( + #navigateTo, + [routeName], + {#arguments: arguments}, + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future replaceWith(String? routeName, {Object? arguments}) => + (super.noSuchMethod( + Invocation.method( + #replaceWith, + [routeName], + {#arguments: arguments}, + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future navigateToAndRemoveUntil( + String? routeName, + _i1.RoutePredicate? predicate, { + Object? arguments, + }) => + (super.noSuchMethod( + Invocation.method( + #navigateToAndRemoveUntil, + [routeName, predicate], + {#arguments: arguments}, + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + void goBack([Object? arguments]) => super.noSuchMethod( + Invocation.method(#goBack, [arguments]), + returnValueForMissingStub: null, + ); + + @override + _i4.Future showPurchaseSecurity() => + (super.noSuchMethod( + Invocation.method(#showPurchaseSecurity, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} From 3804cf53bcbad4cef2a70f2e02a8855aa5b04e22 Mon Sep 17 00:00:00 2001 From: Asad Date: Sat, 30 May 2026 05:25:39 +0500 Subject: [PATCH 2/4] feat: add Flutter localization setup using gen-l10n --- lib/features/auth/username_setup_page.dart | 16 +++++---- lib/features/checkout/checkout_page.dart | 9 +++-- .../checkout/widgets/delivery_options.dart | 6 ++-- .../widgets/share_location_dialog.dart | 6 ++-- lib/features/discover/discover_page.dart | 3 +- .../widgets/discover_selection_bar.dart | 8 +++-- lib/features/home/widgets/bottom_nav_bar.dart | 12 ++++--- lib/features/login/login_page.dart | 3 +- lib/features/login/widgets/login_form.dart | 19 ++++++----- .../not_you_page/not_you_page.dart | 11 +++--- lib/features/products/product_page.dart | 10 +++--- .../products/widgets/seller_information.dart | 8 ++--- lib/features/register/register_page.dart | 3 +- lib/features/register/verify_email_page.dart | 6 ++-- .../register/widgets/register_form.dart | 34 ++++++++++--------- .../widgets/settings_category_group.dart | 10 +++--- lib/features/welcome/welcome_page.dart | 8 +++-- lib/features/welcome/widgets/signup_card.dart | 12 ++++--- lib/main.dart | 9 +++++ pubspec.lock | 13 +++++++ pubspec.yaml | 4 +++ test/auth_ui_test.dart | 18 +++++++--- test/widget_test.dart | 9 +++++ 23 files changed, 154 insertions(+), 83 deletions(-) diff --git a/lib/features/auth/username_setup_page.dart b/lib/features/auth/username_setup_page.dart index 3d777be9..ace049e2 100644 --- a/lib/features/auth/username_setup_page.dart +++ b/lib/features/auth/username_setup_page.dart @@ -2,6 +2,7 @@ import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/core/router/router.dart'; import 'package:cherry_mvp/core/services/services.dart'; import 'package:cherry_mvp/core/utils/utils.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -70,7 +71,7 @@ class _UsernameSetupPageState extends State { if (!isTakenResult.isSuccess) { setState(() { _isSubmitting = false; - _errorText = isTakenResult.error ?? AppStrings.usernameSaveFailed; + _errorText = isTakenResult.error ?? AppLocalizations.of(context)!.authUsernameSaveFailed; }); return; } @@ -78,7 +79,7 @@ class _UsernameSetupPageState extends State { if (isTakenResult.value == true) { setState(() { _isSubmitting = false; - _errorText = AppStrings.usernameTakenError; + _errorText = AppLocalizations.of(context)!.authUsernameTakenError; }); return; } @@ -90,7 +91,7 @@ class _UsernameSetupPageState extends State { if (!saveResult.isSuccess) { setState(() { _isSubmitting = false; - _errorText = saveResult.error ?? AppStrings.usernameSaveFailed; + _errorText = saveResult.error ?? AppLocalizations.of(context)!.authUsernameSaveFailed; }); return; } @@ -101,6 +102,7 @@ class _UsernameSetupPageState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return WillPopScope( onWillPop: () async { await _handleBack(); @@ -129,14 +131,14 @@ class _UsernameSetupPageState extends State { ), const SizedBox(height: 22), Text( - AppStrings.usernameSetupTitle, + l10n.authUsernameSetupTitle, style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: 8), Text( - AppStrings.usernameSetupSubtitle, + l10n.authUsernameSetupSubtitle, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -151,7 +153,7 @@ class _UsernameSetupPageState extends State { }); }, decoration: InputDecoration( - hintText: AppStrings.usernameSetupHint, + hintText: l10n.authUsernameSetupHint, prefixIcon: const Icon(Icons.person_outline), errorText: _errorText, ), @@ -164,7 +166,7 @@ class _UsernameSetupPageState extends State { ? const Center(child: CircularProgressIndicator()) : FilledButton( onPressed: _isValid ? _saveUsername : null, - child: const Text(AppStrings.nextButton), + child: Text(l10n.commonNext), ), ), ], diff --git a/lib/features/checkout/checkout_page.dart b/lib/features/checkout/checkout_page.dart index 7af7a83b..28b6e04e 100644 --- a/lib/features/checkout/checkout_page.dart +++ b/lib/features/checkout/checkout_page.dart @@ -10,6 +10,7 @@ import 'package:cherry_mvp/features/checkout/payment_type.dart'; import 'package:cherry_mvp/features/checkout/widgets/basket_list_item.dart'; import 'package:cherry_mvp/features/checkout/widgets/delivery_options.dart'; import 'package:cherry_mvp/features/checkout/widgets/select_payment_type_bottom_sheet.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class CheckoutPage extends StatefulWidget { const CheckoutPage({super.key}); @@ -46,14 +47,15 @@ class _CheckoutPageState extends State { final status = vm.createOrderStatus.type; if (status == StatusType.failure) { + final l10n = AppLocalizations.of(context)!; Fluttertoast.showToast( - msg: vm.createOrderStatus.message ?? "oops! Something went wrong", + msg: vm.createOrderStatus.message ?? l10n.checkoutSomethingWentWrongToast, ); vm.resetCreateOrderStatus(); } if (status == StatusType.success) { - Fluttertoast.showToast(msg: "Payment Successful"); + Fluttertoast.showToast(msg: AppLocalizations.of(context)!.checkoutPaymentSuccessfulToast); vm.resetCreateOrderStatus(); vm.gotoCheckoutComplete(); } @@ -61,6 +63,7 @@ class _CheckoutPageState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final basket = context.read(); return Scaffold( @@ -216,7 +219,7 @@ class _CheckoutPageState extends State { color: Colors.white, ), ) - : Text(AppStrings.checkoutPay), + : Text(l10n.checkoutPayButton), ); }, ), diff --git a/lib/features/checkout/widgets/delivery_options.dart b/lib/features/checkout/widgets/delivery_options.dart index 69c1fc6b..72522774 100644 --- a/lib/features/checkout/widgets/delivery_options.dart +++ b/lib/features/checkout/widgets/delivery_options.dart @@ -17,6 +17,7 @@ import 'package:cherry_mvp/features/checkout/widgets/select_payment_type_bottom_ import 'package:cherry_mvp/features/checkout/widgets/share_location_dialog.dart'; import 'package:cherry_mvp/features/checkout/widgets/shipping_address_widget.dart'; import 'package:cherry_mvp/features/checkout/widgets/shipping_list_item.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class DeliveryOptions extends StatefulWidget { const DeliveryOptions({super.key}); @@ -62,6 +63,7 @@ class _DeliveryOptionsState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final basket = context.read(); return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -232,7 +234,7 @@ class _DeliveryOptionsState extends State { onChanged: (value) { if (postcodeController.text.isEmpty && context.read().selectedInpost == null) { Fluttertoast.showToast( - msg: "Postcode required", + msg: l10n.checkoutPostcodeRequiredToast, backgroundColor: AppColors.red, textColor: AppColors.white, ); @@ -343,7 +345,7 @@ class _DeliveryOptionsState extends State { _delivery = null; }); }, - child: Text("Change pickup point"), + child: Text(l10n.checkoutChangePickupPoint), ), ), CheckboxListTile( diff --git a/lib/features/checkout/widgets/share_location_dialog.dart b/lib/features/checkout/widgets/share_location_dialog.dart index 126a242f..b5749baa 100644 --- a/lib/features/checkout/widgets/share_location_dialog.dart +++ b/lib/features/checkout/widgets/share_location_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/features/checkout/checkout_view_model.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class ShareLocationDialog extends StatelessWidget { final String postcode; @@ -9,6 +10,7 @@ class ShareLocationDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, @@ -26,12 +28,12 @@ class ShareLocationDialog extends StatelessWidget { height: 43, child: OutlinedButton( onPressed: () => Navigator.pop(context), - child: Text(AppStrings.cancel), + child: Text(l10n.commonCancel), ), ), FilledButton( onPressed: () => context.read().onConfirmLocation(postcode), - child: Text("Ok"), + child: Text(l10n.commonOk), ), ], ); diff --git a/lib/features/discover/discover_page.dart b/lib/features/discover/discover_page.dart index e94e7150..8480c16c 100644 --- a/lib/features/discover/discover_page.dart +++ b/lib/features/discover/discover_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:cherry_mvp/features/discover/discover_viewmodel.dart'; import 'package:cherry_mvp/features/discover/widgets/discover_charity_list.dart'; @@ -21,7 +22,7 @@ class DiscoverPage extends StatelessWidget { child: CustomScrollView( slivers: [ SliverAppBar( - title: Text('Discover'), + title: Text(AppLocalizations.of(context)!.navDiscoverTitle), floating: true, primary: false, snap: true, diff --git a/lib/features/discover/widgets/discover_selection_bar.dart b/lib/features/discover/widgets/discover_selection_bar.dart index 4bca4df2..ace74f02 100644 --- a/lib/features/discover/widgets/discover_selection_bar.dart +++ b/lib/features/discover/widgets/discover_selection_bar.dart @@ -1,3 +1,4 @@ +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; class DiscoverSelectionBar extends StatefulWidget { @@ -10,6 +11,7 @@ class DiscoverSelectionBar extends StatefulWidget { class _DiscoverSelectionBarState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, @@ -27,7 +29,7 @@ class _DiscoverSelectionBarState extends State { child: Padding( padding: EdgeInsets.symmetric(horizontal: 4, vertical: 8), child: Text( - "Popular", + l10n.navPopularSegment, style: Theme.of(context).textTheme.titleSmall?.copyWith( color: Theme.of(context).colorScheme.primary, ), @@ -40,7 +42,7 @@ class _DiscoverSelectionBarState extends State { child: Padding( padding: EdgeInsets.symmetric(horizontal: 4, vertical: 8), child: Text( - "Smaller Charities", + l10n.navSmallerCharitiesSegment, style: Theme.of(context).textTheme.titleSmall?.copyWith( color: Theme.of(context).colorScheme.secondary, ), @@ -52,7 +54,7 @@ class _DiscoverSelectionBarState extends State { child: Padding( padding: EdgeInsets.symmetric(horizontal: 4, vertical: 8), child: Text( - "Local to you", + l10n.navLocalToYouSegment, style: Theme.of(context).textTheme.titleSmall?.copyWith( color: Theme.of(context).colorScheme.secondary, ), diff --git a/lib/features/home/widgets/bottom_nav_bar.dart b/lib/features/home/widgets/bottom_nav_bar.dart index e78587af..c3ccaa0b 100644 --- a/lib/features/home/widgets/bottom_nav_bar.dart +++ b/lib/features/home/widgets/bottom_nav_bar.dart @@ -1,5 +1,6 @@ import 'package:cherry_mvp/core/config/app_images.dart'; import 'package:cherry_mvp/features/search/widgets/search.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; /// A reusable navigation bar widget that be use across the app. @@ -19,6 +20,7 @@ class CherryBottomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final selectedColor = this.selectedColor ?? Theme.of(context).colorScheme.primary; final unselectedColor = @@ -53,7 +55,7 @@ class CherryBottomNavBar extends StatelessWidget { height: 24, color: selectedIndex == 0 ? selectedColor : unselectedColor, ), - label: 'Home', + label: l10n.navHome, ), BottomNavigationBarItem( icon: Image.asset( @@ -62,7 +64,7 @@ class CherryBottomNavBar extends StatelessWidget { height: 24, color: selectedIndex == 1 ? selectedColor : unselectedColor, ), - label: 'Inbox', + label: l10n.navInbox, ), BottomNavigationBarItem( icon: Image.asset( @@ -71,11 +73,11 @@ class CherryBottomNavBar extends StatelessWidget { height: 24, color: selectedIndex == 2 ? selectedColor : unselectedColor, ), - label: 'Give', + label: l10n.navGive, ), BottomNavigationBarItem( icon: Search(), - label: 'Search', + label: l10n.navSearch, ), BottomNavigationBarItem( icon: Image.asset( @@ -84,7 +86,7 @@ class CherryBottomNavBar extends StatelessWidget { height: 24, color: selectedIndex == 4 ? selectedColor : unselectedColor, ), - label: 'Profile', + label: l10n.navProfile, ), ], ), diff --git a/lib/features/login/login_page.dart b/lib/features/login/login_page.dart index 3194aada..94beb9ac 100644 --- a/lib/features/login/login_page.dart +++ b/lib/features/login/login_page.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/features/login/login_viewmodel.dart'; import 'package:cherry_mvp/features/login/widgets/login_form.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class LoginPage extends StatelessWidget { const LoginPage({super.key}); @@ -19,7 +20,7 @@ class LoginPage extends StatelessWidget { ), onPressed: () => context.read().goBack(), ), - title: const Text('Login'), + title: Text(AppLocalizations.of(context)!.authLoginButton), ), body: const LoginForm(), ); diff --git a/lib/features/login/widgets/login_form.dart b/lib/features/login/widgets/login_form.dart index 3d8b5c28..31b31d59 100644 --- a/lib/features/login/widgets/login_form.dart +++ b/lib/features/login/widgets/login_form.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; import 'package:cherry_mvp/features/login/login_viewmodel.dart'; -import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/core/utils/utils.dart'; import 'package:cherry_mvp/core/router/router.dart'; import 'package:cherry_mvp/features/welcome/widgets/auth_form_shell.dart'; import 'package:cherry_mvp/features/shared_widgets/labeled_input_field.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class LoginForm extends StatefulWidget { const LoginForm({super.key}); @@ -63,6 +63,7 @@ class LoginFormState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final navigator = Provider.of(context, listen: false); return AuthFormShell( @@ -78,8 +79,8 @@ class LoginFormState extends State { // Email Field LabeledInputField( - label: 'Email', - hint: 'Enter your email', + label: l10n.authEmailLabel, + hint: l10n.authEmailHint, controller: _emailController, validator: validateEmail, prefixIcon: Icons.email, @@ -90,8 +91,8 @@ class LoginFormState extends State { // Password Field LabeledInputField( - label: 'Password', - hint: 'Enter your password', + label: l10n.authPasswordLabel, + hint: l10n.authPasswordHint, controller: _passwordController, validator: validatePassword, prefixIcon: Icons.lock, @@ -124,13 +125,13 @@ class LoginFormState extends State { _passwordController.text, ); if (viewModel.status.type == StatusType.success) { - Fluttertoast.showToast(msg: "Login Successful"); + Fluttertoast.showToast(msg: l10n.authLoginSuccessToast); } else { Fluttertoast.showToast(msg: viewModel.status.message ?? ""); } } }, - child: const Text("Submit"), + child: Text(l10n.commonSubmit), ), ), ], @@ -144,7 +145,7 @@ class LoginFormState extends State { width: double.infinity, child: TextButton( onPressed: () => navigator.replaceWith(AppRoutes.register), - child: Text(AppStrings.createAccount), + child: Text(l10n.authCreateAccountButton), ), ), @@ -154,7 +155,7 @@ class LoginFormState extends State { onPressed: () {}, child: Center( child: Text( - AppStrings.forgotPassword, + l10n.authForgotPassword, style: Theme.of(context).textTheme.labelSmall, ), ), diff --git a/lib/features/password_flow/not_you_page/not_you_page.dart b/lib/features/password_flow/not_you_page/not_you_page.dart index 2bac7cf0..a7742cce 100644 --- a/lib/features/password_flow/not_you_page/not_you_page.dart +++ b/lib/features/password_flow/not_you_page/not_you_page.dart @@ -1,6 +1,6 @@ import 'package:cherry_mvp/core/config/app_images.dart'; import 'package:cherry_mvp/core/config/app_spacing.dart'; -import 'package:cherry_mvp/core/config/app_strings.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; class NotYouPage extends StatelessWidget { @@ -8,6 +8,7 @@ class NotYouPage extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( resizeToAvoidBottomInset: true, body: LayoutBuilder( @@ -52,18 +53,18 @@ class NotYouPage extends StatelessWidget { // First Text Align( alignment: Alignment.centerLeft, - child: Text(AppStrings.userCheck, + child: Text(l10n.authNotYouLink, style: Theme.of(context).textTheme.headlineMedium), ), SizedBox(height: AppSpacing.medium), Align( alignment: Alignment.centerLeft, - child: Text(AppStrings.forgotPasswordInstruction), + child: Text(l10n.authForgotPasswordInstruction), ), SizedBox(height: AppSpacing.large), TextField( decoration: InputDecoration( - hintText: AppStrings.email, + hintText: l10n.authEmailLabel, ), ), SizedBox(height: AppSpacing.large), @@ -72,7 +73,7 @@ class NotYouPage extends StatelessWidget { height: 60, child: OutlinedButton( onPressed: () {}, - child: const Text(AppStrings.sendEmail), + child: Text(l10n.authSendEmailButton), ), ), SizedBox(height: AppSpacing.large), diff --git a/lib/features/products/product_page.dart b/lib/features/products/product_page.dart index 8367bba2..97e4026b 100644 --- a/lib/features/products/product_page.dart +++ b/lib/features/products/product_page.dart @@ -13,19 +13,21 @@ import 'package:cherry_mvp/features/products/widgets/product_highlight_title.dar import 'package:cherry_mvp/features/products/widgets/product_information.dart'; import 'package:cherry_mvp/features/products/widgets/seller_information.dart'; import 'package:cherry_mvp/features/products/widgets/product_header_carousel.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class ProductPage extends StatelessWidget { const ProductPage({super.key}); @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final viewModel = Provider.of(context); final product = viewModel.product; if (product == null) { return Scaffold( appBar: AppBar(), - body: const Center(child: Text('No product selected')), + body: Center(child: Text(l10n.productNoProductSelected)), ); } @@ -69,7 +71,7 @@ class ProductPage extends StatelessWidget { ), const Divider(thickness: 8), ListTile( - title: Text(AppStrings.productPageDescription), + title: Text(l10n.productDescriptionHeading), titleTextStyle: Theme.of(context).textTheme.titleSmall, subtitle: Text(product.description), subtitleTextStyle: TextStyle( @@ -121,7 +123,7 @@ class ProductPage extends StatelessWidget { height: 56, child: OutlinedButton( onPressed: () {}, - child: Text(AppStrings.productPageMakeOffer, textAlign: TextAlign.center), + child: Text(l10n.productMakeOfferButton, textAlign: TextAlign.center), ), ), ), @@ -137,7 +139,7 @@ class ProductPage extends StatelessWidget { AppRoutes.checkout, ); }, - child: Text(AppStrings.productPageBuyNow, textAlign: TextAlign.center), + child: Text(l10n.productBuyNowButton, textAlign: TextAlign.center), ), ), ), diff --git a/lib/features/products/widgets/seller_information.dart b/lib/features/products/widgets/seller_information.dart index 5073bbf2..9e7d554f 100644 --- a/lib/features/products/widgets/seller_information.dart +++ b/lib/features/products/widgets/seller_information.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:cherry_mvp/core/config/app_colors.dart'; -import 'package:cherry_mvp/core/config/app_images.dart'; -import 'package:cherry_mvp/core/config/app_strings.dart'; -import 'package:cherry_mvp/core/models/user_section.dart'; +import 'package:cherry_mvp/core/config/app_images.dart';import 'package:cherry_mvp/core/models/user_section.dart'; import 'package:cherry_mvp/core/widgets/star_rating.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class SellerInformation extends StatelessWidget { final UserInformation user; @@ -97,7 +95,7 @@ class SellerInformation extends StatelessWidget { shape: StadiumBorder(), ), onPressed: onAskSeller, - child: Text(AppStrings.askSeller, textAlign: TextAlign.center), + child: Text(AppLocalizations.of(context)!.productAskSellerButton, textAlign: TextAlign.center), ), ), ], diff --git a/lib/features/register/register_page.dart b/lib/features/register/register_page.dart index 33d47b22..877b03c1 100644 --- a/lib/features/register/register_page.dart +++ b/lib/features/register/register_page.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/features/register/register_viewmodel.dart'; import 'package:cherry_mvp/features/register/widgets/register_form.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class RegisterPage extends StatelessWidget { const RegisterPage({super.key}); @@ -19,7 +20,7 @@ class RegisterPage extends StatelessWidget { ), onPressed: () => context.read().goBack(), ), - title: const Text('Register'), + title: Text(AppLocalizations.of(context)!.authRegisterTitle), ), body: const RegisterForm(), ); diff --git a/lib/features/register/verify_email_page.dart b/lib/features/register/verify_email_page.dart index 2504e106..0867067d 100644 --- a/lib/features/register/verify_email_page.dart +++ b/lib/features/register/verify_email_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:cherry_mvp/core/services/firebase_auth_service.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class VerifyEmailPage extends StatefulWidget { const VerifyEmailPage({super.key}); @@ -30,16 +31,17 @@ class _VerifyEmailPageState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; return Scaffold( body: Center( child: Column( spacing: 50, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Check your email inbox for a Verification email'), + Text(l10n.authVerifyEmailBody), ElevatedButton( onPressed: () async => await _firebaseAuthService.sendVerificationEmail(), - child: Text('Resend Verification Email'), + child: Text(l10n.authResendVerificationEmail), ), ], ), diff --git a/lib/features/register/widgets/register_form.dart b/lib/features/register/widgets/register_form.dart index 45895ddc..81801d14 100644 --- a/lib/features/register/widgets/register_form.dart +++ b/lib/features/register/widgets/register_form.dart @@ -9,6 +9,7 @@ import 'package:cherry_mvp/core/utils/utils.dart'; import 'package:cherry_mvp/features/register/register_viewmodel.dart'; import 'package:cherry_mvp/features/shared_widgets/labeled_input_field.dart'; import 'package:cherry_mvp/features/welcome/widgets/auth_form_shell.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class RegisterForm extends StatefulWidget { const RegisterForm({super.key}); @@ -112,6 +113,7 @@ class _RegisterFormState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final navigator = Provider.of(context, listen: false); return AuthFormShell( @@ -151,8 +153,8 @@ class _RegisterFormState extends State { // Username Field LabeledInputField( - label: 'Username', - hint: 'Enter your username', + label: l10n.authUsernameLabel, + hint: l10n.authUsernameHint, controller: _userNameController, validator: validateUsername, prefixIcon: Icons.person, @@ -161,8 +163,8 @@ class _RegisterFormState extends State { // FirstName Field LabeledInputField( - label: 'First Name', - hint: 'Enter your first name', + label: l10n.authFirstNameLabel, + hint: l10n.authFirstNameHint, controller: _firstNameController, validator: validateFirstName, prefixIcon: Icons.person, @@ -171,8 +173,8 @@ class _RegisterFormState extends State { // Email Field LabeledInputField( - label: 'Email', - hint: 'Enter your email', + label: l10n.authEmailLabel, + hint: l10n.authEmailHint, controller: _emailController, validator: validateEmail, prefixIcon: Icons.email, @@ -182,8 +184,8 @@ class _RegisterFormState extends State { // Phone Number LabeledInputField( - label: 'Phone Number', - hint: 'Enter your phone number', + label: l10n.authPhoneLabel, + hint: l10n.authPhoneHint, controller: _phoneNumberController, validator: validatePhoneNumber, prefixIcon: Icons.phone, @@ -193,8 +195,8 @@ class _RegisterFormState extends State { // Password Field LabeledInputField( - label: 'Password', - hint: 'Enter your password', + label: l10n.authPasswordLabel, + hint: l10n.authPasswordHint, controller: _passwordController, validator: validatePassword, prefixIcon: Icons.lock, @@ -204,8 +206,8 @@ class _RegisterFormState extends State { // Confirm Password Field LabeledInputField( - label: 'Confirm Password', - hint: 'Confirm your password', + label: l10n.authConfirmPasswordLabel, + hint: l10n.authConfirmPasswordHint, controller: _confirmPasswordController, validator: (value) => validateConfirmPassword(value, _passwordController.text), prefixIcon: Icons.lock, @@ -236,11 +238,11 @@ class _RegisterFormState extends State { if (viewModel.status.type == StatusType.failure) { Fluttertoast.showToast(msg: viewModel.status.message ?? ""); } else if (viewModel.status.type == StatusType.success) { - Fluttertoast.showToast(msg: "Registration Successful"); + Fluttertoast.showToast(msg: l10n.authRegisterSuccessToast); } } }, - child: const Text("Submit"), + child: Text(l10n.commonSubmit), ), ), ], @@ -256,9 +258,9 @@ class _RegisterFormState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text("Already have an account? "), + Text(l10n.authAlreadyHaveAccountPrompt), Text( - "Login", + l10n.authLoginButton, style: TextStyle( color: Theme.of(context).colorScheme.primary, ), diff --git a/lib/features/settings/widgets/settings_category_group.dart b/lib/features/settings/widgets/settings_category_group.dart index a3f18c73..3d0a93ac 100644 --- a/lib/features/settings/widgets/settings_category_group.dart +++ b/lib/features/settings/widgets/settings_category_group.dart @@ -8,6 +8,7 @@ import 'package:cherry_mvp/core/config/app_colors.dart'; import 'package:cherry_mvp/core/config/app_strings.dart'; import 'package:cherry_mvp/features/auth/auth_view_model.dart'; import 'package:cherry_mvp/features/login/login_viewmodel.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; class SettingsCategoryGroup extends StatelessWidget { const SettingsCategoryGroup({ @@ -40,16 +41,17 @@ class SettingsCategoryGroup extends StatelessWidget { title: item.title, trailing: item.trailing, onTap: () async { + final l10n = AppLocalizations.of(context)!; final shouldLogout = await showDialog( context: context, builder: (ctx) => AlertDialog( - title: const Text('Confirm Sign out'), - content: const Text('Are you sure you want to sign out?'), + title: Text(l10n.authConfirmSignOutTitle), + content: Text(l10n.authConfirmSignOutBody), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: Text( - 'Cancel', + l10n.commonCancel, style: Theme.of(context).textTheme.labelLarge?.copyWith( color: AppColors.black, ), @@ -59,7 +61,7 @@ class SettingsCategoryGroup extends StatelessWidget { TextButton( onPressed: () => Navigator.pop(ctx, true), child: Text( - 'Sign out', + l10n.authSignOutButton, style: Theme.of(context).textTheme.labelLarge?.copyWith( color: AppColors.red, fontWeight: FontWeight.bold, diff --git a/lib/features/welcome/welcome_page.dart b/lib/features/welcome/welcome_page.dart index 2fbed294..295a94b9 100644 --- a/lib/features/welcome/welcome_page.dart +++ b/lib/features/welcome/welcome_page.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:cherry_mvp/features/welcome/widgets/signup_card.dart'; import 'package:flutter/material.dart'; import 'package:cherry_mvp/core/config/config.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; enum AuthMode { login, signup } @@ -43,6 +44,7 @@ class _WelcomePageState extends State { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final double cardHeight = 400; return Scaffold( body: Stack( @@ -70,7 +72,7 @@ class _WelcomePageState extends State { ), const SizedBox(height: 8), Text( - AppStrings.giveInStyle, + l10n.authGiveInStyleTagline, style: Theme.of(context).textTheme.headlineLarge?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -119,7 +121,7 @@ class _WelcomePageState extends State { onPressed: () { toggleCard(AuthMode.login); }, - child: Text(AppStrings.login), + child: Text(l10n.authLoginButton), ), ), @@ -134,7 +136,7 @@ class _WelcomePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - AppStrings.createAccount, + l10n.authCreateAccountButton, style: Theme.of(context) .textTheme .labelSmall diff --git a/lib/features/welcome/widgets/signup_card.dart b/lib/features/welcome/widgets/signup_card.dart index 52621be6..20039860 100644 --- a/lib/features/welcome/widgets/signup_card.dart +++ b/lib/features/welcome/widgets/signup_card.dart @@ -5,6 +5,7 @@ import 'package:cherry_mvp/core/router/router.dart'; import 'package:cherry_mvp/core/utils/status.dart'; import 'package:cherry_mvp/features/login/login_viewmodel.dart'; import 'package:cherry_mvp/features/welcome/welcome_page.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'dart:io'; @@ -16,6 +17,7 @@ class AuthCard extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; final navigator = Provider.of(context, listen: false); final loginViewModel = Provider.of(context, listen: false); @@ -41,7 +43,7 @@ class AuthCard extends StatelessWidget { children: [ Expanded( child: Text( - isLogin ? AppStrings.login : AppStrings.register, + isLogin ? l10n.authLoginButton : l10n.authRegisterButton, style: Theme.of(context).textTheme.titleMedium, ), ), @@ -56,14 +58,14 @@ class AuthCard extends StatelessWidget { : [ if (Platform.isIOS) ...[ SocialLoginButton( - label: AppStrings.continueWithApple, + label: l10n.authContinueWithApple, iconAsset: AppImages.authAppleIcon, onPressed: () => loginViewModel.signInWithApple(), ), const SizedBox(height: 10), ], SocialLoginButton( - label: AppStrings.continueWithGoogle, + label: l10n.authContinueWithGoogle, iconAsset: AppImages.authGoogleIcon, onPressed: () => loginViewModel.signInWithGoogle(), ), @@ -80,7 +82,7 @@ class AuthCard extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( - AppStrings.or, + l10n.commonOr, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -104,7 +106,7 @@ class AuthCard extends StatelessWidget { : () { isLogin ? navigator.navigateTo(AppRoutes.login) : navigator.navigateTo(AppRoutes.register); }, - child: const Text(AppStrings.continueWithEmail), + child: Text(l10n.authContinueWithEmail), ), ], ), diff --git a/lib/main.dart b/lib/main.dart index 588e568f..29fd2b25 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'core/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'core/router/router.dart'; @@ -62,6 +64,13 @@ class MyApp extends StatelessWidget { builder: (context, navigatorService, themeNotifier, child) { return MaterialApp( debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, navigatorKey: navigatorService.navigatorKey, onGenerateRoute: AppRoutes.generateRoute, theme: buildTheme(), diff --git a/pubspec.lock b/pubspec.lock index 8c027a78..aea4c4ab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -454,6 +454,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_native_splash: dependency: "direct dev" description: @@ -688,6 +693,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" io: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index aec26d2f..b9e9b1ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,9 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + intl: any firebase_core: ^4.7.0 firebase_auth: ^6.4.0 firebase_storage: ^13.3.0 @@ -45,6 +48,7 @@ dependency_overrides: meta: ^1.18.2 flutter: + generate: true uses-material-design: true assets: - assets/images/ diff --git a/test/auth_ui_test.dart b/test/auth_ui_test.dart index c89c694f..ede6261a 100644 --- a/test/auth_ui_test.dart +++ b/test/auth_ui_test.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:provider/provider.dart'; -import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/core/router/router.dart'; import 'package:cherry_mvp/features/login/login_repository.dart'; import 'package:cherry_mvp/features/login/login_viewmodel.dart'; import 'package:cherry_mvp/features/welcome/welcome_page.dart'; import 'package:cherry_mvp/features/welcome/widgets/signup_card.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; @GenerateNiceMocks([MockSpec(), MockSpec()]) import 'auth_ui_test.mocks.dart'; @@ -29,8 +30,15 @@ void main() { ChangeNotifierProvider.value(value: viewModel), Provider.value(value: mockNavigator), ], - child: const MaterialApp( - home: Scaffold( + child: MaterialApp( + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: const Scaffold( body: AuthCard( onClose: _dummyOnClose, mode: AuthMode.login, @@ -44,8 +52,8 @@ void main() { await tester.pumpWidget(createTestWidget()); // Check for allowed providers - expect(find.text(AppStrings.continueWithEmail), findsOneWidget); - expect(find.text(AppStrings.continueWithGoogle), findsOneWidget); + expect(find.text('Continue with email'), findsOneWidget); + expect(find.text('Continue with Google'), findsOneWidget); // Note: Apple only shows on iOS, so in a standard test environment (likely linux/mac), // it may or may not show depending on the Platform override. diff --git a/test/widget_test.dart b/test/widget_test.dart index ca3c87bf..54ba1c51 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,6 +1,8 @@ import 'package:cherry_mvp/core/config/config.dart'; import 'package:cherry_mvp/features/categories/widget/category_error_widget.dart'; +import 'package:cherry_mvp/l10n/app_localizations.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -10,6 +12,13 @@ void main() { await tester.pumpWidget( MaterialApp( + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, home: CategoryErrorWidget( errorMessage: 'Network timeout', onRetry: () { From 2e5d811a0ae090aea5ea801914f91fa55c5d9f2e Mon Sep 17 00:00:00 2001 From: Asad Date: Sat, 30 May 2026 05:25:43 +0500 Subject: [PATCH 3/4] feat: add Flutter localization setup using gen-l10n --- l10n.yaml | 3 + lib/l10n/app_en.arb | 67 ++++ lib/l10n/app_localizations.dart | 507 +++++++++++++++++++++++++++++ lib/l10n/app_localizations_en.dart | 202 ++++++++++++ 4 files changed, 779 insertions(+) create mode 100644 l10n.yaml create mode 100644 lib/l10n/app_en.arb create mode 100644 lib/l10n/app_localizations.dart create mode 100644 lib/l10n/app_localizations_en.dart diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 00000000..15338f2d --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 00000000..f9676dd0 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,67 @@ +{ + "@@locale": "en", + "authLoginButton": "Login", + "authRegisterButton": "Sign Up", + "authCreateAccountButton": "Create Account", + "authLoginTitle": "Login", + "authRegisterTitle": "Register", + "authGiveInStyleTagline": "Give in style", + "authContinueWithEmail": "Continue with email", + "authContinueWithApple": "Continue with Apple", + "authContinueWithGoogle": "Continue with Google", + "authForgotPassword": "Forgot Password?", + "authEmailLabel": "Email", + "authEmailHint": "Enter your email", + "authPasswordLabel": "Password", + "authPasswordHint": "Enter your password", + "authUsernameLabel": "Username", + "authUsernameHint": "Enter your username", + "authFirstNameLabel": "First Name", + "authFirstNameHint": "Enter your first name", + "authPhoneLabel": "Phone Number", + "authPhoneHint": "Enter your phone number", + "authConfirmPasswordLabel": "Confirm Password", + "authConfirmPasswordHint": "Confirm your password", + "authLoginSuccessToast": "Login Successful", + "authRegisterSuccessToast": "Registration Successful", + "authAlreadyHaveAccountPrompt": "Already have an account? ", + "authVerifyEmailBody": "Check your email inbox for a Verification email", + "authResendVerificationEmail": "Resend Verification Email", + "authForgotPasswordInstruction": "Please type your email and we'll get in touch", + "authSendEmailButton": "Send email", + "authNotYouLink": "Not you", + "authUsernameSetupTitle": "Who is this?", + "authUsernameSetupSubtitle": "Enter the name you will like to go by.", + "authUsernameSetupHint": "Username", + "authUsernameTakenError": "Username is already taken.", + "authUsernameSaveFailed": "Could not save username. Please try again.", + "authConfirmSignOutTitle": "Confirm Sign out", + "authConfirmSignOutBody": "Are you sure you want to sign out?", + "authSignOutButton": "Sign out", + "commonOr": "or", + "navHome": "Home", + "navInbox": "Inbox", + "navGive": "Give", + "navSearch": "Search", + "navProfile": "Profile", + "navDiscoverTitle": "Discover", + "navPopularSegment": "Popular", + "navSmallerCharitiesSegment": "Smaller Charities", + "navLocalToYouSegment": "Local to you", + "commonSubmit": "Submit", + "commonCancel": "Cancel", + "commonContinue": "Continue", + "commonRetry": "Retry", + "commonOk": "Ok", + "commonNext": "Next", + "productDescriptionHeading": "Description", + "productMakeOfferButton": "Make Offer", + "productBuyNowButton": "Buy Now", + "productAskSellerButton": "Ask seller", + "productNoProductSelected": "No product selected", + "checkoutPayButton": "Pay", + "checkoutSomethingWentWrongToast": "oops! Something went wrong", + "checkoutPaymentSuccessfulToast": "Payment Successful", + "checkoutPostcodeRequiredToast": "Postcode required", + "checkoutChangePickupPoint": "Change pickup point" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 00000000..d6386885 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,507 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [Locale('en')]; + + /// No description provided for @authLoginButton. + /// + /// In en, this message translates to: + /// **'Login'** + String get authLoginButton; + + /// No description provided for @authRegisterButton. + /// + /// In en, this message translates to: + /// **'Sign Up'** + String get authRegisterButton; + + /// No description provided for @authCreateAccountButton. + /// + /// In en, this message translates to: + /// **'Create Account'** + String get authCreateAccountButton; + + /// No description provided for @authLoginTitle. + /// + /// In en, this message translates to: + /// **'Login'** + String get authLoginTitle; + + /// No description provided for @authRegisterTitle. + /// + /// In en, this message translates to: + /// **'Register'** + String get authRegisterTitle; + + /// No description provided for @authGiveInStyleTagline. + /// + /// In en, this message translates to: + /// **'Give in style'** + String get authGiveInStyleTagline; + + /// No description provided for @authContinueWithEmail. + /// + /// In en, this message translates to: + /// **'Continue with email'** + String get authContinueWithEmail; + + /// No description provided for @authContinueWithApple. + /// + /// In en, this message translates to: + /// **'Continue with Apple'** + String get authContinueWithApple; + + /// No description provided for @authContinueWithGoogle. + /// + /// In en, this message translates to: + /// **'Continue with Google'** + String get authContinueWithGoogle; + + /// No description provided for @authForgotPassword. + /// + /// In en, this message translates to: + /// **'Forgot Password?'** + String get authForgotPassword; + + /// No description provided for @authEmailLabel. + /// + /// In en, this message translates to: + /// **'Email'** + String get authEmailLabel; + + /// No description provided for @authEmailHint. + /// + /// In en, this message translates to: + /// **'Enter your email'** + String get authEmailHint; + + /// No description provided for @authPasswordLabel. + /// + /// In en, this message translates to: + /// **'Password'** + String get authPasswordLabel; + + /// No description provided for @authPasswordHint. + /// + /// In en, this message translates to: + /// **'Enter your password'** + String get authPasswordHint; + + /// No description provided for @authUsernameLabel. + /// + /// In en, this message translates to: + /// **'Username'** + String get authUsernameLabel; + + /// No description provided for @authUsernameHint. + /// + /// In en, this message translates to: + /// **'Enter your username'** + String get authUsernameHint; + + /// No description provided for @authFirstNameLabel. + /// + /// In en, this message translates to: + /// **'First Name'** + String get authFirstNameLabel; + + /// No description provided for @authFirstNameHint. + /// + /// In en, this message translates to: + /// **'Enter your first name'** + String get authFirstNameHint; + + /// No description provided for @authPhoneLabel. + /// + /// In en, this message translates to: + /// **'Phone Number'** + String get authPhoneLabel; + + /// No description provided for @authPhoneHint. + /// + /// In en, this message translates to: + /// **'Enter your phone number'** + String get authPhoneHint; + + /// No description provided for @authConfirmPasswordLabel. + /// + /// In en, this message translates to: + /// **'Confirm Password'** + String get authConfirmPasswordLabel; + + /// No description provided for @authConfirmPasswordHint. + /// + /// In en, this message translates to: + /// **'Confirm your password'** + String get authConfirmPasswordHint; + + /// No description provided for @authLoginSuccessToast. + /// + /// In en, this message translates to: + /// **'Login Successful'** + String get authLoginSuccessToast; + + /// No description provided for @authRegisterSuccessToast. + /// + /// In en, this message translates to: + /// **'Registration Successful'** + String get authRegisterSuccessToast; + + /// No description provided for @authAlreadyHaveAccountPrompt. + /// + /// In en, this message translates to: + /// **'Already have an account? '** + String get authAlreadyHaveAccountPrompt; + + /// No description provided for @authVerifyEmailBody. + /// + /// In en, this message translates to: + /// **'Check your email inbox for a Verification email'** + String get authVerifyEmailBody; + + /// No description provided for @authResendVerificationEmail. + /// + /// In en, this message translates to: + /// **'Resend Verification Email'** + String get authResendVerificationEmail; + + /// No description provided for @authForgotPasswordInstruction. + /// + /// In en, this message translates to: + /// **'Please type your email and we\'ll get in touch'** + String get authForgotPasswordInstruction; + + /// No description provided for @authSendEmailButton. + /// + /// In en, this message translates to: + /// **'Send email'** + String get authSendEmailButton; + + /// No description provided for @authNotYouLink. + /// + /// In en, this message translates to: + /// **'Not you'** + String get authNotYouLink; + + /// No description provided for @authUsernameSetupTitle. + /// + /// In en, this message translates to: + /// **'Who is this?'** + String get authUsernameSetupTitle; + + /// No description provided for @authUsernameSetupSubtitle. + /// + /// In en, this message translates to: + /// **'Enter the name you will like to go by.'** + String get authUsernameSetupSubtitle; + + /// No description provided for @authUsernameSetupHint. + /// + /// In en, this message translates to: + /// **'Username'** + String get authUsernameSetupHint; + + /// No description provided for @authUsernameTakenError. + /// + /// In en, this message translates to: + /// **'Username is already taken.'** + String get authUsernameTakenError; + + /// No description provided for @authUsernameSaveFailed. + /// + /// In en, this message translates to: + /// **'Could not save username. Please try again.'** + String get authUsernameSaveFailed; + + /// No description provided for @authConfirmSignOutTitle. + /// + /// In en, this message translates to: + /// **'Confirm Sign out'** + String get authConfirmSignOutTitle; + + /// No description provided for @authConfirmSignOutBody. + /// + /// In en, this message translates to: + /// **'Are you sure you want to sign out?'** + String get authConfirmSignOutBody; + + /// No description provided for @authSignOutButton. + /// + /// In en, this message translates to: + /// **'Sign out'** + String get authSignOutButton; + + /// No description provided for @commonOr. + /// + /// In en, this message translates to: + /// **'or'** + String get commonOr; + + /// No description provided for @navHome. + /// + /// In en, this message translates to: + /// **'Home'** + String get navHome; + + /// No description provided for @navInbox. + /// + /// In en, this message translates to: + /// **'Inbox'** + String get navInbox; + + /// No description provided for @navGive. + /// + /// In en, this message translates to: + /// **'Give'** + String get navGive; + + /// No description provided for @navSearch. + /// + /// In en, this message translates to: + /// **'Search'** + String get navSearch; + + /// No description provided for @navProfile. + /// + /// In en, this message translates to: + /// **'Profile'** + String get navProfile; + + /// No description provided for @navDiscoverTitle. + /// + /// In en, this message translates to: + /// **'Discover'** + String get navDiscoverTitle; + + /// No description provided for @navPopularSegment. + /// + /// In en, this message translates to: + /// **'Popular'** + String get navPopularSegment; + + /// No description provided for @navSmallerCharitiesSegment. + /// + /// In en, this message translates to: + /// **'Smaller Charities'** + String get navSmallerCharitiesSegment; + + /// No description provided for @navLocalToYouSegment. + /// + /// In en, this message translates to: + /// **'Local to you'** + String get navLocalToYouSegment; + + /// No description provided for @commonSubmit. + /// + /// In en, this message translates to: + /// **'Submit'** + String get commonSubmit; + + /// No description provided for @commonCancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get commonCancel; + + /// No description provided for @commonContinue. + /// + /// In en, this message translates to: + /// **'Continue'** + String get commonContinue; + + /// No description provided for @commonRetry. + /// + /// In en, this message translates to: + /// **'Retry'** + String get commonRetry; + + /// No description provided for @commonOk. + /// + /// In en, this message translates to: + /// **'Ok'** + String get commonOk; + + /// No description provided for @commonNext. + /// + /// In en, this message translates to: + /// **'Next'** + String get commonNext; + + /// No description provided for @productDescriptionHeading. + /// + /// In en, this message translates to: + /// **'Description'** + String get productDescriptionHeading; + + /// No description provided for @productMakeOfferButton. + /// + /// In en, this message translates to: + /// **'Make Offer'** + String get productMakeOfferButton; + + /// No description provided for @productBuyNowButton. + /// + /// In en, this message translates to: + /// **'Buy Now'** + String get productBuyNowButton; + + /// No description provided for @productAskSellerButton. + /// + /// In en, this message translates to: + /// **'Ask seller'** + String get productAskSellerButton; + + /// No description provided for @productNoProductSelected. + /// + /// In en, this message translates to: + /// **'No product selected'** + String get productNoProductSelected; + + /// No description provided for @checkoutPayButton. + /// + /// In en, this message translates to: + /// **'Pay'** + String get checkoutPayButton; + + /// No description provided for @checkoutSomethingWentWrongToast. + /// + /// In en, this message translates to: + /// **'oops! Something went wrong'** + String get checkoutSomethingWentWrongToast; + + /// No description provided for @checkoutPaymentSuccessfulToast. + /// + /// In en, this message translates to: + /// **'Payment Successful'** + String get checkoutPaymentSuccessfulToast; + + /// No description provided for @checkoutPostcodeRequiredToast. + /// + /// In en, this message translates to: + /// **'Postcode required'** + String get checkoutPostcodeRequiredToast; + + /// No description provided for @checkoutChangePickupPoint. + /// + /// In en, this message translates to: + /// **'Change pickup point'** + String get checkoutChangePickupPoint; +} + +class _AppLocalizationsDelegate extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 00000000..81269226 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,202 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get authLoginButton => 'Login'; + + @override + String get authRegisterButton => 'Sign Up'; + + @override + String get authCreateAccountButton => 'Create Account'; + + @override + String get authLoginTitle => 'Login'; + + @override + String get authRegisterTitle => 'Register'; + + @override + String get authGiveInStyleTagline => 'Give in style'; + + @override + String get authContinueWithEmail => 'Continue with email'; + + @override + String get authContinueWithApple => 'Continue with Apple'; + + @override + String get authContinueWithGoogle => 'Continue with Google'; + + @override + String get authForgotPassword => 'Forgot Password?'; + + @override + String get authEmailLabel => 'Email'; + + @override + String get authEmailHint => 'Enter your email'; + + @override + String get authPasswordLabel => 'Password'; + + @override + String get authPasswordHint => 'Enter your password'; + + @override + String get authUsernameLabel => 'Username'; + + @override + String get authUsernameHint => 'Enter your username'; + + @override + String get authFirstNameLabel => 'First Name'; + + @override + String get authFirstNameHint => 'Enter your first name'; + + @override + String get authPhoneLabel => 'Phone Number'; + + @override + String get authPhoneHint => 'Enter your phone number'; + + @override + String get authConfirmPasswordLabel => 'Confirm Password'; + + @override + String get authConfirmPasswordHint => 'Confirm your password'; + + @override + String get authLoginSuccessToast => 'Login Successful'; + + @override + String get authRegisterSuccessToast => 'Registration Successful'; + + @override + String get authAlreadyHaveAccountPrompt => 'Already have an account? '; + + @override + String get authVerifyEmailBody => 'Check your email inbox for a Verification email'; + + @override + String get authResendVerificationEmail => 'Resend Verification Email'; + + @override + String get authForgotPasswordInstruction => 'Please type your email and we\'ll get in touch'; + + @override + String get authSendEmailButton => 'Send email'; + + @override + String get authNotYouLink => 'Not you'; + + @override + String get authUsernameSetupTitle => 'Who is this?'; + + @override + String get authUsernameSetupSubtitle => 'Enter the name you will like to go by.'; + + @override + String get authUsernameSetupHint => 'Username'; + + @override + String get authUsernameTakenError => 'Username is already taken.'; + + @override + String get authUsernameSaveFailed => 'Could not save username. Please try again.'; + + @override + String get authConfirmSignOutTitle => 'Confirm Sign out'; + + @override + String get authConfirmSignOutBody => 'Are you sure you want to sign out?'; + + @override + String get authSignOutButton => 'Sign out'; + + @override + String get commonOr => 'or'; + + @override + String get navHome => 'Home'; + + @override + String get navInbox => 'Inbox'; + + @override + String get navGive => 'Give'; + + @override + String get navSearch => 'Search'; + + @override + String get navProfile => 'Profile'; + + @override + String get navDiscoverTitle => 'Discover'; + + @override + String get navPopularSegment => 'Popular'; + + @override + String get navSmallerCharitiesSegment => 'Smaller Charities'; + + @override + String get navLocalToYouSegment => 'Local to you'; + + @override + String get commonSubmit => 'Submit'; + + @override + String get commonCancel => 'Cancel'; + + @override + String get commonContinue => 'Continue'; + + @override + String get commonRetry => 'Retry'; + + @override + String get commonOk => 'Ok'; + + @override + String get commonNext => 'Next'; + + @override + String get productDescriptionHeading => 'Description'; + + @override + String get productMakeOfferButton => 'Make Offer'; + + @override + String get productBuyNowButton => 'Buy Now'; + + @override + String get productAskSellerButton => 'Ask seller'; + + @override + String get productNoProductSelected => 'No product selected'; + + @override + String get checkoutPayButton => 'Pay'; + + @override + String get checkoutSomethingWentWrongToast => 'oops! Something went wrong'; + + @override + String get checkoutPaymentSuccessfulToast => 'Payment Successful'; + + @override + String get checkoutPostcodeRequiredToast => 'Postcode required'; + + @override + String get checkoutChangePickupPoint => 'Change pickup point'; +} From be664f200e8a310bfe7958b79771a0e32af0f504 Mon Sep 17 00:00:00 2001 From: Asad Date: Wed, 3 Jun 2026 15:20:14 +0500 Subject: [PATCH 4/4] feat: add Flutter localization setup using gen-l10n --- lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.dart b/lib/main.dart index 29fd2b25..bbab2f42 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,6 +63,7 @@ class MyApp extends StatelessWidget { return Consumer2( builder: (context, navigatorService, themeNotifier, child) { return MaterialApp( + debugShowCheckedModeBanner: false, localizationsDelegates: const [ AppLocalizations.delegate,