From 129dca1f8d286874ef4c06c20781d7b38564dc54 Mon Sep 17 00:00:00 2001 From: nahnah Date: Mon, 26 Jan 2026 23:31:20 +0000 Subject: [PATCH] [feat]: Add localization support with easy_localization --- CONTRIBUTING.md | 304 ++++++++++++++++++++++++++ LOCALIZATION.md | 31 +++ assets/translations/en.json | 90 ++++++++ lib/main.dart | 14 +- lib/screens/categories_screen.dart | 19 +- lib/screens/category_apps_screen.dart | 9 +- lib/screens/florid_app.dart | 68 +++--- lib/screens/home_screen.dart | 7 +- lib/screens/latest_screen.dart | 17 +- lib/screens/library_screen.dart | 19 +- lib/screens/onboarding_screen.dart | 3 +- lib/screens/search_screen.dart | 7 +- pubspec.lock | 29 +++ pubspec.yaml | 4 + 14 files changed, 551 insertions(+), 70 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 LOCALIZATION.md create mode 100644 assets/translations/en.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5177366 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,304 @@ +# Contributing to Florid + +Thank you for your interest in contributing to Florid! We welcome contributions from everyone. This guide will help you get started. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) + - [Reporting Bugs](#reporting-bugs) + - [Suggesting Features](#suggesting-features) + - [Contributing Code](#contributing-code) + - [Contributing Translations](#contributing-translations) +- [Development Setup](#development-setup) +- [Pull Request Process](#pull-request-process) +- [Style Guidelines](#style-guidelines) +- [Localization Guidelines](#localization-guidelines) + +## Code of Conduct + +By participating in this project, you agree to maintain a respectful and inclusive environment for everyone. + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check existing issues to avoid duplicates. When creating a bug report, include: + +- **Clear title and description** +- **Steps to reproduce** the issue +- **Expected behavior** vs **actual behavior** +- **Screenshots** if applicable +- **Device information** (Android version, device model) +- **App version** you're using + +### Suggesting Features + +Feature suggestions are welcome! Please: + +- **Check existing feature requests** first +- **Describe the feature** in detail +- **Explain the use case** and why it would be valuable +- **Consider implementation complexity** + +### Contributing Code + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Make your changes +4. Test thoroughly +5. Commit with clear messages (`git commit -m 'Add amazing feature'`) +6. Push to your branch (`git push origin feature/amazing-feature`) +7. Open a Pull Request + +### Contributing Translations + +We use `easy_localization` for internationalization. Contributing translations is easy and highly appreciated! + +#### Adding a New Language + +1. **Create a translation file:** + - Navigate to `assets/translations/` + - Create a new JSON file named with the language code (e.g., `fr.json` for French, `de.json` for German) + - Copy the structure from `en.json` + +2. **Translate all keys:** + + ```json + { + "app_name": "Florid", + "welcome": "Your translation here", + "search": "Your translation here", + ... + } + ``` + +3. **Update main.dart:** + Add your locale to the supported locales list: + + ```dart + EasyLocalization( + supportedLocales: const [ + Locale('en'), + Locale('es'), + Locale('fr'), // Your new language + ], + // ... + ) + ``` + +4. **Test your translations:** + - Change your device language to the new language + - Launch the app and verify all strings appear correctly + - Check that text fits in UI elements (some languages use longer words) + +#### Improving Existing Translations + +1. Open the relevant JSON file in `assets/translations/` +2. Update the translation values +3. Ensure translations are: + - **Accurate** and contextually appropriate + - **Natural** in the target language + - **Consistent** with app terminology +4. Test the changes in the app + +#### Translation Guidelines + +- **Keep keys unchanged** - Only modify the values, never the keys +- **Maintain consistency** - Use the same terms throughout for repeated concepts +- **Consider context** - Some words have different meanings in different contexts +- **Test thoroughly** - Verify translations in the actual UI +- **Be concise** - Mobile UIs have limited space +- **Use native conventions** - Follow target language conventions for dates, numbers, etc. + +See [LOCALIZATION.md](LOCALIZATION.md) for detailed localization documentation. + +## Development Setup + +### Prerequisites + +- Flutter SDK (3.38.7 or higher) +- Dart SDK (3.9.2 or higher) +- Android Studio or VS Code with Flutter extensions +- Android device or emulator for testing + +### Setup Steps + +1. **Clone the repository:** + + ```bash + git clone https://github.com/yourusername/florid.git + cd florid + ``` + +2. **Install dependencies:** + + ```bash + flutter pub get + ``` + +3. **Run the app:** + ```bash + flutter run + ``` + +### Project Structure + +``` +lib/ +├── models/ # Data models +├── providers/ # State management (Provider) +├── screens/ # UI screens +├── services/ # API and business logic +├── themes/ # App themes +├── utils/ # Utility functions +├── widgets/ # Reusable widgets +└── main.dart # App entry point + +assets/ +└── translations/ # Translation JSON files +``` + +## Pull Request Process + +1. **Update documentation** if you've made changes to APIs or added features +2. **Add/update tests** for new functionality +3. **Follow the style guidelines** below +4. **Ensure the app builds** without errors +5. **Test on a real device** when possible +6. **Update CHANGELOG.md** with notable changes +7. **Link any related issues** in the PR description + +### PR Checklist + +- [ ] Code follows the project style guidelines +- [ ] Self-review of code completed +- [ ] Comments added for complex logic +- [ ] Documentation updated if needed +- [ ] No new warnings generated +- [ ] Translations added/updated if UI text changed +- [ ] Tested on Android device/emulator + +## Style Guidelines + +### Dart Code Style + +- Follow [Effective Dart](https://dart.dev/guides/language/effective-dart) guidelines +- Use `flutter analyze` to check for issues +- Format code with `dart format .` +- Maximum line length: 80 characters (flexible for readability) + +### Widget Organization + +```dart +class MyWidget extends StatelessWidget { + // 1. Final fields + final String title; + + // 2. Constructor + const MyWidget({super.key, required this.title}); + + // 3. Build method + @override + Widget build(BuildContext context) { + // ... + } + + // 4. Helper methods + void _helperMethod() { + // ... + } +} +``` + +### Naming Conventions + +- **Classes**: `PascalCase` (e.g., `AppDetailsScreen`) +- **Files**: `snake_case` (e.g., `app_details_screen.dart`) +- **Variables/Functions**: `camelCase` (e.g., `downloadApp`) +- **Constants**: `camelCase` (e.g., `maxRetries`) +- **Private members**: prefix with `_` (e.g., `_privateMethod`) + +### Comments + +- Use `///` for public API documentation +- Use `//` for inline comments +- Explain **why**, not **what** (code should be self-documenting) + +```dart +// Good +/// Fetches app details from the repository. +/// Returns null if the app is not found or network error occurs. +Future fetchAppDetails(String packageName) async { ... } + +// Bad +// This function gets the app +Future fetchAppDetails(String packageName) async { ... } +``` + +### UI/UX Guidelines + +- **Responsive Design**: Test on different screen sizes +- **Accessibility**: Use semantic labels and ensure good contrast +- **Performance**: Avoid unnecessary rebuilds, use `const` constructors +- **Material Design**: Follow Material 3 guidelines +- **Animations**: Keep animations smooth and purposeful (avoid excessive animation) + +## Localization Guidelines + +### Adding New Strings + +When adding new UI text: + +1. **Never hardcode strings** in UI code +2. **Add to all translation files** (at minimum `en.json` and `es.json`) +3. **Use descriptive keys** with underscores: + + ```json + { + "error_network_title": "Network Error", + "error_network_message": "Please check your internet connection", + "button_retry": "Retry" + } + ``` + +4. **Use the string in code:** + ```dart + Text('error_network_title'.tr()) + ``` + +### Translation Key Naming + +Follow this pattern: `[category]_[context]_[element]` + +Examples: + +- `error_network_title` +- `settings_theme_dark` +- `dialog_delete_confirm` +- `button_download` +- `label_version_name` + +### Context for Translators + +When adding strings that might be ambiguous, add a comment in the PR: + +``` +Added "bank" key - refers to river bank, not financial institution +``` + +## Questions? + +If you have questions or need help: + +- Open an issue with the `question` label +- Check existing issues and discussions +- Review the documentation in the repository + +## License + +By contributing to Florid, you agree that your contributions will be licensed under the same license as the project. + +--- + +Thank you for contributing to Florid! 🎉 diff --git a/LOCALIZATION.md b/LOCALIZATION.md new file mode 100644 index 0000000..2f5edd6 --- /dev/null +++ b/LOCALIZATION.md @@ -0,0 +1,31 @@ +# Localization Setup with easy_localization + +This project uses the `easy_localization` package for internationalization. + +## Setup + +The localization is already set up with the following configuration: + +### Supported Languages + +- English (en) - Default + +### Translation Files + +Translation files are located in `assets/translations/`: + +- `en.json` - English translations +- `es.json` - Spanish translations (for example) + +## Usage + +### 1. Translations with Parameters + +Add to your JSON files: + +```json +{ + "welcome": "YOUR_TRANSLATION", + "items_count": "YOUR_TRANSLATION" +} +``` diff --git a/assets/translations/en.json b/assets/translations/en.json new file mode 100644 index 0000000..35f5d55 --- /dev/null +++ b/assets/translations/en.json @@ -0,0 +1,90 @@ +{ + "app_name": "Florid", + "welcome": "Welcome to Florid", + "search": "Search", + "settings": "Settings", + "home": "Home", + "categories": "Categories", + "updates": "Updates", + "installed": "Installed", + "download": "Download", + "install": "Install", + "uninstall": "Uninstall", + "open": "Open", + "cancel": "Cancel", + "update_available": "Update Available", + "downloading": "Downloading...", + "install_permission_required": "Install permission is required", + "storage_permission_required": "Storage permission is required", + "cancel_download": "Cancel Download", + "version": "Version", + "size": "Size", + "description": "Description", + "permissions": "Permissions", + "screenshots": "Screenshots", + "no_version_available": "No Version Available", + "app_information": "App Information", + "package_name": "Package Name", + "license": "License", + "added": "Added", + "last_updated": "Last Updated", + "version_information": "Version Information", + "version_name": "Version Name", + "version_code": "Version Code", + "min_sdk": "Min SDK", + "target_sdk": "Target SDK", + "all_versions": "All Versions", + "latest": "Latest", + "released": "Released", + "loading": "Loading...", + "error": "Error", + "retry": "Retry", + "share": "Share", + "website": "Website", + "source_code": "Source Code", + "issue_tracker": "Issue Tracker", + "whats_new": "What's New", + "show_more": "Show more", + "show_less": "Show less", + "downloads_stats": "Downloads stats", + "last_day": "Last day", + "last_30_days": "Last 30 days", + "last_365_days": "Last 365 days", + "not_available": "Not available", + "download_failed": "Download failed", + "installation_failed": "Installation failed", + "uninstall_failed": "Uninstall failed", + "open_failed": "Failed to open", + "device": "Device", + "recently_updated": "Recently Updated", + "refresh": "Refresh", + "about": "About", + "refreshing_data": "Refreshing data...", + "data_refreshed": "Data refreshed", + "refresh_failed": "Refresh failed", + "loading_latest_apps": "Loading latest apps...", + "latest_apps": "Latest Apps", + "no_apps_found": "No apps found", + "searching": "Searching...", + "setup_failed": "Setup failed", + "back": "Back", + "allow": "Allow", + "manage_repositories": "Manage Repositories", + "enable_disable": "Enable/Disable", + "edit": "Edit", + "delete": "Delete", + "delete_repository": "Delete Repository", + "delete_repository_confirm": "Are you sure you want to remove \"{}\"?", + "updating_repository": "Updating Repository", + "touch_grass_message": "Now is a great time to touch grass!", + "add_repository": "Add Repository", + "add": "Add", + "save": "Save", + "enter_repository_name": "Please enter a repository name", + "enter_repository_url": "Please enter a repository URL", + "edit_repository": "Edit Repository", + "loading_apps": "Loading apps...", + "no_apps_in_category": "No apps found in {}", + "loading_categories": "Loading categories...", + "no_categories_found": "No categories found" +} diff --git a/lib/main.dart b/lib/main.dart index 5b505a3..397151e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:easy_localization/easy_localization.dart'; import 'package:florid/providers/settings_provider.dart'; import 'package:florid/screens/florid_app.dart'; import 'package:florid/themes/app_themes.dart'; @@ -17,11 +18,19 @@ import 'services/izzy_stats_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + await EasyLocalization.ensureInitialized(); // Initialize notification service and request permission // await NotificationService().init(); - runApp(const MainApp()); + runApp( + EasyLocalization( + supportedLocales: const [Locale('en')], + path: 'assets/translations', + fallbackLocale: const Locale('en'), + child: const MainApp(), + ), + ); } class MainApp extends StatelessWidget { @@ -88,6 +97,9 @@ class MainApp extends StatelessWidget { return MaterialApp( title: 'Florid - F-Droid Client', debugShowCheckedModeBanner: false, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, theme: settings.themeStyle == ThemeStyle.florid ? AppThemes.floridLightTheme() : AppThemes.materialLightTheme(), diff --git a/lib/screens/categories_screen.dart b/lib/screens/categories_screen.dart index 18d2051..d9fb02e 100644 --- a/lib/screens/categories_screen.dart +++ b/lib/screens/categories_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -56,13 +57,13 @@ class _CategoriesScreenState extends State String? error, ) { if (state == LoadingState.loading && categories.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - CircularProgressIndicator(year2023: false), - SizedBox(height: 16), - Text('Loading categories...'), + const CircularProgressIndicator(year2023: false), + const SizedBox(height: 16), + Text('loading_categories'.tr()), ], ), ); @@ -95,7 +96,7 @@ class _CategoriesScreenState extends State ElevatedButton.icon( onPressed: _loadData, icon: const Icon(Symbols.refresh), - label: const Text('Retry'), + label: Text('retry'.tr()), ), ], ), @@ -103,13 +104,13 @@ class _CategoriesScreenState extends State } if (categories.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Symbols.category, size: 64, color: Colors.grey), - SizedBox(height: 16), - Text('No categories found'), + const Icon(Symbols.category, size: 64, color: Colors.grey), + const SizedBox(height: 16), + Text('no_categories_found'.tr()), ], ), ); diff --git a/lib/screens/category_apps_screen.dart b/lib/screens/category_apps_screen.dart index e43bc52..ed5830d 100644 --- a/lib/screens/category_apps_screen.dart +++ b/lib/screens/category_apps_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:florid/providers/settings_provider.dart'; import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -75,13 +76,13 @@ class _CategoryAppsScreenState extends State { final error = appProvider.categoryAppsError; if (state == LoadingState.loading && apps.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(year2023: false), SizedBox(height: 16), - Text('Loading apps...'), + Text('loading_apps'.tr()), ], ), ); @@ -114,7 +115,7 @@ class _CategoryAppsScreenState extends State { ElevatedButton.icon( onPressed: _loadData, icon: const Icon(Symbols.refresh), - label: const Text('Retry'), + label: Text('retry'.tr()), ), ], ), @@ -128,7 +129,7 @@ class _CategoryAppsScreenState extends State { children: [ const Icon(Symbols.apps, size: 64, color: Colors.grey), const SizedBox(height: 16), - Text('No apps found in ${widget.category}'), + Text('no_apps_in_category'.tr(args: [widget.category])), ], ), ); diff --git a/lib/screens/florid_app.dart b/lib/screens/florid_app.dart index ec609d1..291a469 100644 --- a/lib/screens/florid_app.dart +++ b/lib/screens/florid_app.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:florid/screens/library_screen.dart'; import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -44,24 +45,6 @@ class _FloridAppState extends State { super.dispose(); } - final List _destinations = const [ - NavigationDestination( - icon: Icon(Symbols.newsstand_rounded), - selectedIcon: Icon(Symbols.newsstand_rounded, fill: 1, weight: 600), - label: 'Library', - ), - NavigationDestination( - icon: Icon(Symbols.search), - selectedIcon: Icon(Symbols.search, fill: 1, weight: 600), - label: 'Search', - ), - NavigationDestination( - icon: Icon(Symbols.mobile_3_rounded), - selectedIcon: Icon(Symbols.mobile_3_rounded, fill: 1, weight: 600), - label: 'Device', - ), - ]; - @override Widget build(BuildContext context) { return Scaffold( @@ -69,22 +52,43 @@ class _FloridAppState extends State { bottomNavigationBar: Consumer( builder: (context, appProvider, child) { final updatableAppsCount = appProvider.getUpdatableApps().length; - final destinations = List.from(_destinations); - // Add badge to Device tab if there are updates - if (updatableAppsCount > 0) { - destinations[2] = NavigationDestination( - icon: Badge.count( - count: updatableAppsCount, - child: Icon(Symbols.mobile_3_rounded), + // Build destinations with translations + final destinations = [ + NavigationDestination( + icon: const Icon(Symbols.newsstand_rounded), + selectedIcon: const Icon( + Symbols.newsstand_rounded, + fill: 1, + weight: 600, ), - selectedIcon: Badge.count( - count: updatableAppsCount, - child: Icon(Symbols.mobile_3_rounded, fill: 1, weight: 600), - ), - label: 'Device', - ); - } + label: 'home'.tr(), + ), + NavigationDestination( + icon: const Icon(Symbols.search), + selectedIcon: const Icon(Symbols.search, fill: 1, weight: 600), + label: 'search'.tr(), + ), + NavigationDestination( + icon: updatableAppsCount > 0 + ? Badge.count( + count: updatableAppsCount, + child: const Icon(Symbols.mobile_3_rounded), + ) + : const Icon(Symbols.mobile_3_rounded), + selectedIcon: updatableAppsCount > 0 + ? Badge.count( + count: updatableAppsCount, + child: const Icon( + Symbols.mobile_3_rounded, + fill: 1, + weight: 600, + ), + ) + : const Icon(Symbols.mobile_3_rounded, fill: 1, weight: 600), + label: 'device'.tr(), + ), + ]; return NavigationBar( selectedIndex: _currentIndex, diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index d589027..806e938 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -100,7 +101,7 @@ class _HomeScreenState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Recently Updated', + 'recently_updated'.tr(), style: Theme.of(context).textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.w600), ), @@ -108,7 +109,7 @@ class _HomeScreenState extends State onPressed: _openRecentlyUpdatedScreen, iconAlignment: IconAlignment.end, icon: Icon(Symbols.arrow_forward), - label: Text('More'), + label: Text('show_more'.tr()), ), ], ), @@ -186,7 +187,7 @@ class _HomeScreenState extends State onPressed: _openLatestScreen, iconAlignment: IconAlignment.end, icon: Icon(Symbols.arrow_forward), - label: Text('More'), + label: Text('show_more'.tr()), ), ], ), diff --git a/lib/screens/latest_screen.dart b/lib/screens/latest_screen.dart index c9c9df6..b011535 100644 --- a/lib/screens/latest_screen.dart +++ b/lib/screens/latest_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -59,13 +60,13 @@ class _LatestScreenState extends State Widget _buildBody(LoadingState state, List apps, String? error) { if (state == LoadingState.loading && apps.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(year2023: false), SizedBox(height: 16), - Text('Loading latest apps...'), + Text('loading_latest_apps'.tr()), ], ), ); @@ -98,7 +99,7 @@ class _LatestScreenState extends State ElevatedButton.icon( onPressed: _loadData, icon: const Icon(Symbols.refresh), - label: const Text('Retry'), + label: Text('retry'.tr()), ), ], ), @@ -106,13 +107,13 @@ class _LatestScreenState extends State } if (apps.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Symbols.apps, size: 64, color: Colors.grey), - SizedBox(height: 16), - Text('No apps found'), + const Icon(Symbols.apps, size: 64, color: Colors.grey), + const SizedBox(height: 16), + Text('no_apps_found'.tr()), ], ), ); @@ -120,7 +121,7 @@ class _LatestScreenState extends State return Scaffold( appBar: AppBar( - title: const Text('Latest Apps'), + title: Text('latest_apps'.tr()), backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, surfaceTintColor: Theme.of(context).colorScheme.surfaceContainerLow, ), diff --git a/lib/screens/library_screen.dart b/lib/screens/library_screen.dart index a635030..d9bb5af 100644 --- a/lib/screens/library_screen.dart +++ b/lib/screens/library_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:florid/providers/app_provider.dart'; import 'package:florid/providers/repositories_provider.dart'; import 'package:florid/screens/categories_screen.dart'; @@ -36,7 +37,7 @@ class _LibraryScreenState extends State Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Florid'), + title: Text('app_name'.tr()), backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, surfaceTintColor: Theme.of(context).colorScheme.surfaceContainerLow, actions: [ @@ -55,19 +56,19 @@ class _LibraryScreenState extends State } }, itemBuilder: (context) => [ - const PopupMenuItem( + PopupMenuItem( value: 'refresh', child: ListTile( - leading: Icon(Symbols.refresh), - title: Text('Refresh'), + leading: const Icon(Symbols.refresh), + title: Text('refresh'.tr()), contentPadding: EdgeInsets.zero, ), ), - const PopupMenuItem( + PopupMenuItem( value: 'settings', child: ListTile( - leading: Icon(Symbols.settings), - title: Text('Settings'), + leading: const Icon(Symbols.settings), + title: Text('settings'.tr()), contentPadding: EdgeInsets.zero, ), ), @@ -88,8 +89,8 @@ class _LibraryScreenState extends State _tabController.animateTo(index); }, items: [ - FloridTabBarItem(icon: Symbols.home, label: 'Home'), - FloridTabBarItem(icon: Symbols.category, label: 'Categories'), + FloridTabBarItem(icon: Symbols.home, label: 'home'.tr()), + FloridTabBarItem(icon: Symbols.category, label: 'categories'.tr()), ], ), ), diff --git a/lib/screens/onboarding_screen.dart b/lib/screens/onboarding_screen.dart index 413ac22..96a382d 100644 --- a/lib/screens/onboarding_screen.dart +++ b/lib/screens/onboarding_screen.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -227,7 +228,7 @@ class _OnboardingScreenState extends State { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Setup failed: $e'), + content: Text('${'setup_failed'.tr()}: $e'), action: SnackBarAction(label: 'Retry', onPressed: _performSetup), ), ); diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index c354425..23e0805 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:florid/utils/menu_actions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -202,13 +203,13 @@ class _SearchScreenState extends State { // Show loading if (state == LoadingState.loading) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(year2023: false), SizedBox(height: 16), - Text('Searching...'), + Text('searching'.tr()), ], ), ); @@ -242,7 +243,7 @@ class _SearchScreenState extends State { ElevatedButton.icon( onPressed: () => _performSearch(query), icon: const Icon(Symbols.refresh), - label: const Text('Retry'), + label: Text('retry'.tr()), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 19ea40b..be3e991 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -273,6 +273,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed" + url: "https://pub.dev" + source: hosted + version: "3.0.8" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" fake_async: dependency: transitive description: @@ -366,6 +382,11 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_native_splash: dependency: "direct main" description: @@ -472,6 +493,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + intl: + dependency: transitive + 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 518168f..efdb442 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,9 @@ dependencies: flutter_native_splash: ^2.4.7 flutter_animate: ^4.5.2 + # Localization + easy_localization: ^3.0.8 + dev_dependencies: flutter_test: sdk: flutter @@ -102,6 +105,7 @@ flutter: - assets/Foreground.png - assets/Splash.png - assets/repositories.json + - assets/translations/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg