Compare commits

..

2 Commits

Author SHA1 Message Date
5e2d5944fe Localize app for German and English 2024-12-10 22:37:13 +01:00
2162f2d494 Localize app for German and English 2024-12-10 22:37:07 +01:00
15 changed files with 97 additions and 57 deletions

3
l10n.yaml Normal file
View File

@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

View File

@ -4,6 +4,7 @@ import 'package:calorimeter/storage/storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart';
import 'package:calorimeter/utils/row_with_spacers_widget.dart'; import 'package:calorimeter/utils/row_with_spacers_widget.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class EnterFoodWidget extends StatefulWidget { class EnterFoodWidget extends StatefulWidget {
final Function(BuildContext context, FoodEntryState entry) onAdd; final Function(BuildContext context, FoodEntryState entry) onAdd;
@ -42,8 +43,8 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
return TextFormField( return TextFormField(
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
decoration: const InputDecoration( decoration: InputDecoration(
label: Text("Name"), label: Text(AppLocalizations.of(context)!.name),
), ),
); );
}, },
@ -70,9 +71,11 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
}), }),
TextField( TextField(
textAlign: TextAlign.end, textAlign: TextAlign.end,
decoration: const InputDecoration( decoration: InputDecoration(
label: label: Align(
Align(alignment: Alignment.centerRight, child: Text("Menge")), alignment: Alignment.centerRight,
child: Text(AppLocalizations.of(context)!.amount),
),
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
controller: massController, controller: massController,
@ -80,9 +83,10 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
), ),
TextField( TextField(
textAlign: TextAlign.end, textAlign: TextAlign.end,
decoration: const InputDecoration( decoration: InputDecoration(
label: Align( label: Align(
alignment: Alignment.centerRight, child: Text("kcal pro"))), alignment: Alignment.centerRight,
child: Text(AppLocalizations.of(context)!.kcalper))),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
controller: kcalPerMassController, controller: kcalPerMassController,
onSubmitted: (value) => onSubmitAction(), onSubmitted: (value) => onSubmitAction(),
@ -107,7 +111,8 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
try { try {
massAsNumber = int.parse(massController.text.replaceAll(",", ".")); massAsNumber = int.parse(massController.text.replaceAll(",", "."));
} catch (e) { } catch (e) {
var snackbar = const SnackBar(content: Text("Menge muss eine Zahl sein")); var snackbar = SnackBar(
content: Text(AppLocalizations.of(context)!.errAmountNotANumber));
ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(snackbar); ScaffoldMessenger.of(context).showSnackBar(snackbar);
return; return;
@ -117,8 +122,8 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
kcalPerMassAsNumber = kcalPerMassAsNumber =
int.parse(kcalPerMassController.text.replaceAll(",", ".")); int.parse(kcalPerMassController.text.replaceAll(",", "."));
} catch (e) { } catch (e) {
var snackbar = var snackbar = SnackBar(
const SnackBar(content: Text("'kcal pro 100g' muss eine Zahl sein")); content: Text(AppLocalizations.of(context)!.errKcalNotANumber));
ScaffoldMessenger.of(context).removeCurrentSnackBar(); ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(snackbar); ScaffoldMessenger.of(context).showSnackBar(snackbar);
return; return;

View File

@ -3,6 +3,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart';
import 'package:calorimeter/utils/row_with_spacers_widget.dart'; import 'package:calorimeter/utils/row_with_spacers_widget.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class FoodEntryWidget extends StatefulWidget { class FoodEntryWidget extends StatefulWidget {
final FoodEntryState entry; final FoodEntryState entry;
@ -165,8 +166,8 @@ class _FoodEntryChangeDialogState extends State<FoodEntryChangeDialog> {
child: TextField( child: TextField(
onSubmitted: (val) => _onSubmitAction(), onSubmitted: (val) => _onSubmitAction(),
controller: nameController, controller: nameController,
decoration: const InputDecoration( decoration: InputDecoration(
label: Text("Name"), label: Text(AppLocalizations.of(context)!.name),
), ),
), ),
), ),
@ -177,8 +178,8 @@ class _FoodEntryChangeDialogState extends State<FoodEntryChangeDialog> {
child: TextField( child: TextField(
onSubmitted: (val) => _onSubmitAction(), onSubmitted: (val) => _onSubmitAction(),
controller: massController, controller: massController,
decoration: const InputDecoration( decoration: InputDecoration(
label: Text("Menge"), label: Text(AppLocalizations.of(context)!.amount),
), ),
), ),
), ),
@ -189,8 +190,8 @@ class _FoodEntryChangeDialogState extends State<FoodEntryChangeDialog> {
child: TextField( child: TextField(
onSubmitted: (val) => _onSubmitAction(), onSubmitted: (val) => _onSubmitAction(),
controller: kcalPer100Controller, controller: kcalPer100Controller,
decoration: const InputDecoration( decoration: InputDecoration(
label: Text("kcal pro Menge"), label: Text(AppLocalizations.of(context)!.kcalper),
), ),
), ),
) )

13
lib/l10n/app_de.arb Normal file
View File

@ -0,0 +1,13 @@
{
"ok": "OK",
"name": "Name",
"amount": "Menge in 100 g/ml",
"kcalper": "kcal pro Menge",
"kcalToday": "kcal heute",
"menu": "Menü",
"settings": "Einstellungen",
"yourSettings": "Deine persönlichen Einstellungen",
"dayLimit": "Kalorienlimit pro Tag",
"errAmountNotANumber": "Menge muss eine Zahl sein",
"errKcalNotANumber": "kcal muss eine Zahl sein"
}

13
lib/l10n/app_en.arb Normal file
View File

@ -0,0 +1,13 @@
{
"ok": "OK",
"name": "Name",
"amount": "Amount in 100 grams/ml",
"kcalper": "kcal per amount",
"kcalToday": "kcal today",
"menu": "Menu",
"settings": "Settings",
"yourSettings": "Your personal settings",
"dayLimit": "Calorie limit per day",
"errAmountNotANumber": "Amount must be a number",
"errKcalNotANumber": "kcal must be a number"
}

View File

@ -8,8 +8,8 @@ import 'package:calorimeter/utils/settings_bloc.dart';
import 'package:calorimeter/utils/theme_bloc.dart'; import 'package:calorimeter/utils/theme_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
List<FoodEntryState> entriesForToday = []; List<FoodEntryState> entriesForToday = [];
DateTime timeNow = DateTime.now(); DateTime timeNow = DateTime.now();
@ -17,8 +17,6 @@ DateTime timeNow = DateTime.now();
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
var storage = await FoodStorage.create(); var storage = await FoodStorage.create();
await storage.buildFoodLookupDatabase(); await storage.buildFoodLookupDatabase();
@ -79,6 +77,7 @@ class MainApp extends StatelessWidget {
} }
return MaterialApp.router( return MaterialApp.router(
//locale: Locale('de'),
routerConfig: GoRouter( routerConfig: GoRouter(
routes: [ routes: [
GoRoute( GoRoute(
@ -91,14 +90,8 @@ class MainApp extends StatelessWidget {
), ),
], ],
), ),
localizationsDelegates: const [ localizationsDelegates: AppLocalizations.localizationsDelegates,
GlobalMaterialLocalizations.delegate, supportedLocales: AppLocalizations.supportedLocales,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('de'),
],
theme: ThemeData( theme: ThemeData(
dividerTheme: const DividerThemeData(space: 2), dividerTheme: const DividerThemeData(space: 2),
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(

View File

@ -58,8 +58,10 @@ class PerDatePageViewController extends StatelessWidget {
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Builder(builder: (context) { title: Builder(builder: (context) {
return Text(DateFormat.yMMMMd('de').format( return Text(DateFormat.yMMMMd(
context.watch<PageViewStateProvider>().displayedDate)); Localizations.localeOf(context).toString())
.format(
context.watch<PageViewStateProvider>().displayedDate));
}), }),
actions: const [ThemeSwitcherButton()], actions: const [ThemeSwitcherButton()],
), ),

View File

@ -2,6 +2,7 @@
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */ /* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
import 'package:calorimeter/utils/settings.dart'; import 'package:calorimeter/utils/settings.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class AppDrawer extends StatelessWidget { class AppDrawer extends StatelessWidget {
const AppDrawer({ const AppDrawer({
@ -19,16 +20,17 @@ class AppDrawer extends StatelessWidget {
color: Colors.redAccent, color: Colors.redAccent,
), ),
child: ListTile( child: ListTile(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
title: const Text('Menü')), title: Text(AppLocalizations.of(context)!.menu),
),
), ),
ListTile( ListTile(
title: const Text('Einstellungen'), title: Text(AppLocalizations.of(context)!.settings),
trailing: const Icon(Icons.settings), trailing: const Icon(Icons.settings),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();

View File

@ -15,7 +15,6 @@ class CalendarFAB extends StatelessWidget {
return FloatingActionButton( return FloatingActionButton(
onPressed: () async { onPressed: () async {
var datePicked = await showDatePicker( var datePicked = await showDatePicker(
locale: const Locale('de'),
context: context, context: context,
initialDate: startFromDate, initialDate: startFromDate,
currentDate: DateTimeHelper.now(), currentDate: DateTimeHelper.now(),

View File

@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calorimeter/utils/app_drawer.dart'; import 'package:calorimeter/utils/app_drawer.dart';
import 'package:calorimeter/utils/settings_bloc.dart'; import 'package:calorimeter/utils/settings_bloc.dart';
import 'package:settings_ui/settings_ui.dart'; import 'package:settings_ui/settings_ui.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class SettingsWidget extends StatefulWidget { class SettingsWidget extends StatefulWidget {
const SettingsWidget({super.key}); const SettingsWidget({super.key});
@ -23,33 +24,24 @@ class _SettingsWidgetState extends State<SettingsWidget> {
builder: (context, state) { builder: (context, state) {
return SettingsList(sections: [ return SettingsList(sections: [
SettingsSection( SettingsSection(
title: const Text('Deine persönlichen Einstellungen'), title: Text(AppLocalizations.of(context)!.yourSettings),
tiles: [ tiles: [
SettingsTile.navigation( SettingsTile.navigation(
leading: const Icon(Icons.food_bank), leading: const Icon(Icons.food_bank),
title: const Text('Kalorienlimit pro Tag'), title: Text(AppLocalizations.of(context)!.dayLimit),
value: Text(state.kcalLimit.toString()), value: Text(state.kcalLimit.toString()),
onPressed: (context) async { onPressed: (context) async {
await showDialog( await showDialog(
builder: (ctx) { builder: (ctx) {
return AlertDialog( return AlertDialog(
title: const Text("Kalorienlimit pro Tag"), title: Text(AppLocalizations.of(context)!.dayLimit),
content: TextField(controller: kcalPerDayCtrl), content: TextField(
controller: kcalPerDayCtrl,
onSubmitted: (val) => submitDailyKcal()),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () => submitDailyKcal(),
double setting; child: Text(AppLocalizations.of(context)!.ok))
try {
setting =
double.parse(kcalPerDayCtrl.text);
} catch (e) {
setting = 2000.0;
}
context.read<SettingsDataBloc>().add(
DailyKcalLimitUpdated(kcal: setting));
Navigator.of(context).pop();
},
child: const Text('Ok'))
], ],
); );
}, },
@ -60,7 +52,18 @@ class _SettingsWidgetState extends State<SettingsWidget> {
]); ]);
}), }),
drawer: const AppDrawer(), drawer: const AppDrawer(),
appBar: AppBar(title: const Text('Einstellungen')), appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
); );
} }
void submitDailyKcal() {
double setting;
try {
setting = double.parse(kcalPerDayCtrl.text);
} catch (e) {
setting = 2000.0;
}
context.read<SettingsDataBloc>().add(DailyKcalLimitUpdated(kcal: setting));
Navigator.of(context).pop();
}
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart';
import 'package:calorimeter/utils/settings_bloc.dart'; import 'package:calorimeter/utils/settings_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class SumWidget extends StatelessWidget { class SumWidget extends StatelessWidget {
final DateTime date; final DateTime date;
@ -48,7 +49,7 @@ class SumWidget extends StatelessWidget {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
'kcal heute: ${sum.ceil().toString()}/${settingsState.kcalLimit.ceil()}', '${AppLocalizations.of(context)!.kcalToday}: ${sum.ceil().toString()}/${settingsState.kcalLimit.ceil()}',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyLarge! .bodyLarge!

View File

@ -0,0 +1,2 @@
With Calorimeter you can track your daily calorie intake.
As simple as it gets.

View File

@ -0,0 +1 @@
Track your calories!

1
metadata/en-US/title.txt Normal file
View File

@ -0,0 +1 @@
Calorimeter

View File

@ -30,6 +30,7 @@ dev_dependencies:
flutter: flutter:
uses-material-design: true uses-material-design: true
generate: true
flutter_launcher_icons: flutter_launcher_icons:
android: "launcher_icon" android: "launcher_icon"