[feat]: Add localization support with easy_localization
This commit is contained in:
+304
@@ -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<FDroidApp?> fetchAppDetails(String packageName) async { ... }
|
||||
|
||||
// Bad
|
||||
// This function gets the app
|
||||
Future<FDroidApp?> 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! 🎉
|
||||
@@ -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"
|
||||
}
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
+13
-1
@@ -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(),
|
||||
|
||||
@@ -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<CategoriesScreen>
|
||||
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<CategoriesScreen>
|
||||
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<CategoriesScreen>
|
||||
}
|
||||
|
||||
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()),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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<CategoryAppsScreen> {
|
||||
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<CategoryAppsScreen> {
|
||||
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<CategoryAppsScreen> {
|
||||
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])),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+36
-32
@@ -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<FloridApp> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
final List<NavigationDestination> _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<FloridApp> {
|
||||
bottomNavigationBar: Consumer<AppProvider>(
|
||||
builder: (context, appProvider, child) {
|
||||
final updatableAppsCount = appProvider.getUpdatableApps().length;
|
||||
final destinations = List<NavigationDestination>.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,
|
||||
|
||||
@@ -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<HomeScreen>
|
||||
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<HomeScreen>
|
||||
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<HomeScreen>
|
||||
onPressed: _openLatestScreen,
|
||||
iconAlignment: IconAlignment.end,
|
||||
icon: Icon(Symbols.arrow_forward),
|
||||
label: Text('More'),
|
||||
label: Text('show_more'.tr()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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<LatestScreen>
|
||||
|
||||
Widget _buildBody(LoadingState state, List<FDroidApp> 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<LatestScreen>
|
||||
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<LatestScreen>
|
||||
}
|
||||
|
||||
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<LatestScreen>
|
||||
|
||||
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,
|
||||
),
|
||||
|
||||
@@ -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<LibraryScreen>
|
||||
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<LibraryScreen>
|
||||
}
|
||||
},
|
||||
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<LibraryScreen>
|
||||
_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()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<OnboardingScreen> {
|
||||
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),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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<SearchScreen> {
|
||||
|
||||
// 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<SearchScreen> {
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _performSearch(query),
|
||||
icon: const Icon(Symbols.refresh),
|
||||
label: const Text('Retry'),
|
||||
label: Text('retry'.tr()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user