From 8d3ca62304c9a90b1eb8915a83daf0717a42313b Mon Sep 17 00:00:00 2001 From: mark-cachy Date: Wed, 29 Apr 2026 19:25:48 +0200 Subject: [PATCH] localization --- analysis_options.yaml | 15 ++ l10n.yaml | 3 + ...string_extensions.dart => extensions.dart} | 7 + .../customers/data/customer_repository.dart | 2 +- .../customers/models/customer_model.dart | 2 +- lib/features/home/ui/home_screen.dart | 9 +- .../products/models/brand_model.dart | 2 +- .../products/models/model_model.dart | 2 +- .../services/blocs/service_files_bloc.dart | 2 +- .../services/blocs/services_cubit.dart | 2 +- .../services/data/services_repository.dart | 2 +- .../services/models/service_model.dart | 2 +- lib/l10n/app_en.arb | 13 ++ lib/l10n/app_it.arb | 20 ++ lib/l10n/app_localizations.dart | 188 ++++++++++++++++++ lib/l10n/app_localizations_en.dart | 39 ++++ lib/l10n/app_localizations_it.dart | 39 ++++ lib/main.dart | 6 + pubspec.lock | 21 +- pubspec.yaml | 5 + 20 files changed, 361 insertions(+), 20 deletions(-) create mode 100644 l10n.yaml rename lib/core/utils/{string_extensions.dart => extensions.dart} (86%) create mode 100644 lib/l10n/app_en.arb create mode 100644 lib/l10n/app_it.arb create mode 100644 lib/l10n/app_localizations.dart create mode 100644 lib/l10n/app_localizations_en.dart create mode 100644 lib/l10n/app_localizations_it.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b3034..d78598d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,16 @@ include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + # Escludiamo i file generati per le lingue, così il linter non ci entra proprio + - "lib/generated/**" + - "lib/l10n/*.dart" + - "**/*.g.dart" # Già che ci siamo escludiamo tutti i file generati (tipo quelli di JsonSerializable) + - "**/*.freezed.dart" + +linter: + rules: + diagnostic_describe_all_properties: false + public_member_api_docs: false + # Ti consiglio di aggiungere anche questa se usi molto i file generati + avoid_relative_lib_imports: true \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..aba5d99 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_it.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/lib/core/utils/string_extensions.dart b/lib/core/utils/extensions.dart similarity index 86% rename from lib/core/utils/string_extensions.dart rename to lib/core/utils/extensions.dart index 060d8df..27b2e53 100644 --- a/lib/core/utils/string_extensions.dart +++ b/lib/core/utils/extensions.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:flux/l10n/app_localizations.dart'; + extension MyStringExtensions on String? { // Gestiamo anche il nullable per sicurezza String myFormat() { @@ -40,3 +43,7 @@ extension MyStringExtensions on String? { .join('.'); // Ritorna tutto tranne l'ultima parte } } + +extension LocalizationExtension on BuildContext { + AppLocalizations get l10n => AppLocalizations.of(this)!; +} diff --git a/lib/features/customers/data/customer_repository.dart b/lib/features/customers/data/customer_repository.dart index d2a8f7a..daf61c0 100644 --- a/lib/features/customers/data/customer_repository.dart +++ b/lib/features/customers/data/customer_repository.dart @@ -1,7 +1,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/customers/models/customer_file_model.dart'; import 'package:get_it/get_it.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; diff --git a/lib/features/customers/models/customer_model.dart b/lib/features/customers/models/customer_model.dart index fa55090..d1fe55e 100644 --- a/lib/features/customers/models/customer_model.dart +++ b/lib/features/customers/models/customer_model.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/customers/models/customer_file_model.dart'; class CustomerModel extends Equatable { diff --git a/lib/features/home/ui/home_screen.dart b/lib/features/home/ui/home_screen.dart index 9f9abac..e254aeb 100644 --- a/lib/features/home/ui/home_screen.dart +++ b/lib/features/home/ui/home_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/theme/theme.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/home/ui/quick_actions_widget.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; import 'package:go_router/go_router.dart'; @@ -57,25 +58,25 @@ class HomeScreen extends StatelessWidget { ), delegate: SliverChildListDelegate([ _buildDashboardWidget( - title: 'Contratti in Scadenza', + title: context.l10n.expiring_contracts, icon: Icons.assignment_late_outlined, color: Colors.orange, context: context, ), _buildDashboardWidget( - title: 'Sticky Notes', + title: context.l10n.sticky_notes, icon: Icons.sticky_note_2_outlined, color: Colors.yellow.shade700, context: context, ), _buildDashboardWidget( - title: 'I miei Task', + title: context.l10n.my_tasks, icon: Icons.check_box_outlined, color: Colors.green, context: context, ), _buildDashboardWidget( - title: 'Ultimi Servizi', + title: context.l10n.latestServices, icon: Icons.design_services_outlined, color: Colors.blue, context: context, diff --git a/lib/features/master_data/products/models/brand_model.dart b/lib/features/master_data/products/models/brand_model.dart index 72e02b1..f6c33c6 100644 --- a/lib/features/master_data/products/models/brand_model.dart +++ b/lib/features/master_data/products/models/brand_model.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; class BrandModel extends Equatable { final String? id; diff --git a/lib/features/master_data/products/models/model_model.dart b/lib/features/master_data/products/models/model_model.dart index 4859356..aff7e36 100644 --- a/lib/features/master_data/products/models/model_model.dart +++ b/lib/features/master_data/products/models/model_model.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; class ModelModel extends Equatable { final String? id; diff --git a/lib/features/services/blocs/service_files_bloc.dart b/lib/features/services/blocs/service_files_bloc.dart index 0298ef2..02f1922 100644 --- a/lib/features/services/blocs/service_files_bloc.dart +++ b/lib/features/services/blocs/service_files_bloc.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/services/data/services_repository.dart'; import 'package:flux/features/services/models/service_file_model.dart'; import 'package:flux/features/services/models/service_model.dart'; diff --git a/lib/features/services/blocs/services_cubit.dart b/lib/features/services/blocs/services_cubit.dart index 3417802..a9fb3a6 100644 --- a/lib/features/services/blocs/services_cubit.dart +++ b/lib/features/services/blocs/services_cubit.dart @@ -3,7 +3,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/services/data/services_repository.dart'; import 'package:flux/features/services/models/energy_service_model.dart'; import 'package:flux/features/services/models/entertainment_service_model.dart'; diff --git a/lib/features/services/data/services_repository.dart b/lib/features/services/data/services_repository.dart index 624c19d..9c91fc0 100644 --- a/lib/features/services/data/services_repository.dart +++ b/lib/features/services/data/services_repository.dart @@ -1,7 +1,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/customers/data/customer_repository.dart'; import 'package:flux/features/customers/models/customer_file_model.dart'; import 'package:flux/features/services/models/service_file_model.dart'; diff --git a/lib/features/services/models/service_model.dart b/lib/features/services/models/service_model.dart index df58125..a4d15f6 100644 --- a/lib/features/services/models/service_model.dart +++ b/lib/features/services/models/service_model.dart @@ -1,5 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:flux/core/utils/string_extensions.dart'; +import 'package:flux/core/utils/extensions.dart'; import 'package:flux/features/services/models/energy_service_model.dart'; import 'package:flux/features/services/models/entertainment_service_model.dart'; import 'package:flux/features/services/models/fin_service_model.dart'; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..84c90f5 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,13 @@ +{ + "@@locale": "en", + "welcomeBack": "Welcome back, {name}! 👋", + "latestServices": "Latest Services", + "masterData": "Master Data", + "settings": "Settings", + "newService": "Service", + "expiring_contracts": "Expiring Contracts", + "sticky_notes": "Sticky Notes", + "my_tasks": "My Tasks", + "latest_service_tickets": "Latest service tickets" + +} \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb new file mode 100644 index 0000000..b6ab9cc --- /dev/null +++ b/lib/l10n/app_it.arb @@ -0,0 +1,20 @@ +{ + "@@locale": "it", + "welcomeBack": "Bentornato, {name}! 👋", + "@welcomeBack": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "latestServices": "Ultimi Servizi", + "masterData": "Anagrafiche", + "settings": "Impostazioni", + "newService": "Servizio", + "expiring_contracts": "Contratti in scadenza", + "sticky_notes": "Sticky Notes", + "my_tasks": "Mie Attività", + "latest_service_tickets": "Ultime assistenze" + +} \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..58547fa --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,188 @@ +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'; +import 'app_localizations_it.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'), + Locale('it'), + ]; + + /// No description provided for @welcomeBack. + /// + /// In it, this message translates to: + /// **'Bentornato, {name}! 👋'** + String welcomeBack(String name); + + /// No description provided for @latestServices. + /// + /// In it, this message translates to: + /// **'Ultimi Servizi'** + String get latestServices; + + /// No description provided for @masterData. + /// + /// In it, this message translates to: + /// **'Anagrafiche'** + String get masterData; + + /// No description provided for @settings. + /// + /// In it, this message translates to: + /// **'Impostazioni'** + String get settings; + + /// No description provided for @newService. + /// + /// In it, this message translates to: + /// **'Servizio'** + String get newService; + + /// No description provided for @expiring_contracts. + /// + /// In it, this message translates to: + /// **'Contratti in scadenza'** + String get expiring_contracts; + + /// No description provided for @sticky_notes. + /// + /// In it, this message translates to: + /// **'Sticky Notes'** + String get sticky_notes; + + /// No description provided for @my_tasks. + /// + /// In it, this message translates to: + /// **'Mie Attività'** + String get my_tasks; + + /// No description provided for @latest_service_tickets. + /// + /// In it, this message translates to: + /// **'Ultime assistenze'** + String get latest_service_tickets; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'it'].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(); + case 'it': + return AppLocalizationsIt(); + } + + 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 0000000..f5dea85 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,39 @@ +// 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 welcomeBack(String name) { + return 'Welcome back, $name! 👋'; + } + + @override + String get latestServices => 'Latest Services'; + + @override + String get masterData => 'Master Data'; + + @override + String get settings => 'Settings'; + + @override + String get newService => 'Service'; + + @override + String get expiring_contracts => 'Expiring Contracts'; + + @override + String get sticky_notes => 'Sticky Notes'; + + @override + String get my_tasks => 'My Tasks'; + + @override + String get latest_service_tickets => 'Latest service tickets'; +} diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart new file mode 100644 index 0000000..434a39d --- /dev/null +++ b/lib/l10n/app_localizations_it.dart @@ -0,0 +1,39 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Italian (`it`). +class AppLocalizationsIt extends AppLocalizations { + AppLocalizationsIt([String locale = 'it']) : super(locale); + + @override + String welcomeBack(String name) { + return 'Bentornato, $name! 👋'; + } + + @override + String get latestServices => 'Ultimi Servizi'; + + @override + String get masterData => 'Anagrafiche'; + + @override + String get settings => 'Impostazioni'; + + @override + String get newService => 'Servizio'; + + @override + String get expiring_contracts => 'Contratti in scadenza'; + + @override + String get sticky_notes => 'Sticky Notes'; + + @override + String get my_tasks => 'Mie Attività'; + + @override + String get latest_service_tickets => 'Ultime assistenze'; +} diff --git a/lib/main.dart b/lib/main.dart index 93b8891..3e64a34 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flux/features/auth/bloc/auth_cubit.dart'; +import 'package:flux/l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -152,6 +153,11 @@ class _FluxAppState extends State { darkTheme: fluxDarkTheme, themeMode: themeState.currentTheme.themeMode, routerConfig: _router, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + locale: const Locale( + 'it', + ), // Per ora forziamo l'italiano, poi lo renderemo dinamico! ); }, ); diff --git a/pubspec.lock b/pubspec.lock index b1c4635..30588e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -254,6 +254,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_plugin_android_lifecycle: dependency: transitive description: @@ -316,10 +321,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: db9df7a5898d894eeda4c78143f35c30a243558be439518972366880b80bf88e + sha256: "4e9391085e524954a51e3625b7c9c7e9851dc3f376603208bb45c24b9a66255d" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.1.0" gotrue: dependency: transitive description: @@ -364,10 +369,10 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: @@ -1041,10 +1046,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "81da85e9ca8885ade47f9685b953cb098970d11be4821ac765580a6607ea4373" + sha256: "6409a25046024f0f8c5d8a59fec314081e81f9d436b66ca4015a8b49772bf445" url: "https://pub.dev" source: hosted - version: "1.1.21" + version: "1.2.0" vector_graphics_codec: dependency: transitive description: @@ -1073,10 +1078,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.1.0" + version: "15.2.0" web: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 732b141..1bef9ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,8 @@ dependencies: file_picker: ^11.0.2 flutter: sdk: flutter + flutter_localizations: + sdk: flutter flutter_bloc: ^9.1.1 flutter_dotenv: ^6.0.0 flutter_svg: ^2.2.4 @@ -26,6 +28,8 @@ dependencies: qr_flutter: ^4.1.0 shared_preferences: ^2.5.5 supabase_flutter: ^2.12.2 + + dev_dependencies: flutter_test: @@ -34,6 +38,7 @@ dev_dependencies: flutter: uses-material-design: true + generate: true assets: - assets/images/