From e0fba11f25446bcac85dca6135b2fa14fbff29d5 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Mon, 12 May 2025 00:02:19 +0200 Subject: [PATCH] Add i18n via slang --- .gitignore | 3 + assets/i18n/en.i18n.json | 137 +++++++++ build.yaml | 9 + lib/main.dart | 2 + lib/ui/pages/account/account.dart | 13 +- lib/ui/pages/account/balance_graph_card.dart | 3 +- lib/ui/pages/account/breakdown_card.dart | 15 +- lib/ui/pages/account/delete_account.dart | 14 +- lib/ui/pages/account/total_balance_card.dart | 7 +- .../account/upcoming_transactions_card.dart | 42 ++- lib/ui/pages/beneficiary_list.dart | 7 +- lib/ui/pages/budgets/add_budget.dart | 11 +- lib/ui/pages/budgets/add_budget_item.dart | 13 +- lib/ui/pages/budgets/budget_details.dart | 54 +++- lib/ui/pages/budgets/budgets.dart | 3 +- lib/ui/pages/budgets/edit_budget.dart | 9 +- lib/ui/pages/settings.dart | 24 +- lib/ui/pages/template_list.dart | 21 +- lib/ui/pages/transaction_details.dart | 7 +- lib/ui/widgets/add_expense_category.dart | 7 +- lib/ui/widgets/add_recurring_transaction.dart | 291 ------------------ lib/ui/widgets/add_template.dart | 74 +++-- lib/ui/widgets/add_transaction.dart | 37 ++- pubspec.lock | 56 +++- pubspec.yaml | 3 + 25 files changed, 447 insertions(+), 415 deletions(-) create mode 100644 assets/i18n/en.i18n.json create mode 100644 build.yaml delete mode 100644 lib/ui/widgets/add_recurring_transaction.dart diff --git a/.gitignore b/.gitignore index 79c113f..7da805a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Build artifacts +lib/i18n/* \ No newline at end of file diff --git a/assets/i18n/en.i18n.json b/assets/i18n/en.i18n.json new file mode 100644 index 0000000..1abce3c --- /dev/null +++ b/assets/i18n/en.i18n.json @@ -0,0 +1,137 @@ +{ + "common": { + "beneficiary": { + "addBeneficiary": { + "title": "Add beneficiary", + "body": "The beneficiary '$name' does not exist. Do you want to add it?" + }, + "nameWithAccount": "$name (Account)" + }, + "transaction": { + "directionSend": "Send", + "directionReceive": "Receive", + "beneficiaryTextfieldHintSend": "Payee", + "beneficiaryTextfieldHintReceive": "Payer" + }, + "amount": "Amount", + "date": "Date", + "expenseCategory": { + "name": "Expense category", + "none": "None" + }, + "templateName": "Template name", + "period": { + "days": "Days", + "weeks": "Weeks", + "months": "Months", + "years": "Years", + "daysNumber": "$number days", + "weeksNumber": "$number weeks", + "monthsNumber": "$number months", + "yearsNumber": "$number years" + } + }, + "pages": { + "accounts": { + "title": "Accounts", + "accountSelector": { + "none": "None" + }, + "addAccount": { + "accountName": "Acount name" + }, + "expenseBreakdown": { + "title": "Expense Breakdown", + "noActiveAccount": "No account active", + "noExpensesAvailable": "No expenses available", + "availableFunds": "Available funds: $amount" + }, + "totalBalance": { + "title": "Total Balance", + "loading": "..." + }, + "upcomingTransactions": { + "title": "Upcoming Transactions", + "noUpcomingTransactions": "No upcoming transactions", + "items": { + "title": "$name ($amount)", + "dueIn": "Due in $number days" + } + }, + "deleteAccount": { + "title": "Delete Account", + "content": "Are you sure you want to delete the account '$name'? This will delete all related data as well!" + } + }, + "budgets": { + "addBudget": { + "budgetNameHint": "Budget name", + "income": "Income", + "includeOtherSpendings": "Include other spendings" + }, + "addBudgetItem": { + "amountHint": "Amount" + }, + "noBudgets": "No budgets", + "details": { + "noBudgetSelected": "No budget selected", + "noBudgetItems": "No budget items", + "budgetItems": "Budget items", + "items": { + "title": "$name ($amount)", + "loading": "...", + "over": "$amount over", + "remaining": "$amount left" + }, + "daysLeft": "Days left", + "budgetLeft": "Budget left", + "totalBudget": "Budget total", + "budgetBreakdown": { + "title": "Budget breakdown", + "noSpendingAvailable": "No spending available" + }, + "spendingBreakdown": { + "title": "Spending Breakdown" + } + } + }, + "transactions": { + "balance": "Account Balance", + "details": { + "noTransactionSelected": "No transaction selected" + }, + "addTransaction": { + "useTemplate": "Use template" + } + }, + "templates": { + "removeTemplate": { + "title": "Remove Template", + "body": "Are you sure you want to remove the template '$name'?" + }, + "addTemplate": { + "isRecurring": "Is recurring" + }, + "nonRecurring": { + "title": "Non-recurring" + }, + "recurring": { + "title": "Recurring" + } + }, + "settings": { + "colorSchemes": { + "title": "Color Scheme", + "dark": "Dark", + "light": "Light", + "system": "System" + } + } + }, + "modals": { + "add": "Add", + "delete": "Delete", + "cancel": "Cancel", + "save": "Save" + } +} \ No newline at end of file diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..abd6157 --- /dev/null +++ b/build.yaml @@ -0,0 +1,9 @@ +targets: + $default: + builders: + slang_build_runner: + options: + input_directory: assets/i18n + output_directory: lib/i18n + fallback_strategy: base_locale + base_locale: en \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 1ee236a..7f4d818 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:isar/isar.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/screen.dart'; import 'package:okane/ui/navigation.dart'; import 'package:okane/ui/pages/budgets/budget_details.dart'; @@ -15,6 +16,7 @@ import 'package:okane/ui/state/settings.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + LocaleSettings.useDeviceLocale(); final settings = SettingsCubit(); await settings.loadSettings(); diff --git a/lib/ui/pages/account/account.dart b/lib/ui/pages/account/account.dart index 3c9cb05..d41a3ff 100644 --- a/lib/ui/pages/account/account.dart +++ b/lib/ui/pages/account/account.dart @@ -10,6 +10,7 @@ import 'package:okane/ui/pages/account/total_balance_card.dart'; import 'package:okane/ui/pages/account/upcoming_transactions_card.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; +import 'package:okane/i18n/strings.g.dart'; class AccountListPage extends StatefulWidget { final bool isPage; @@ -33,7 +34,7 @@ class AccountListPageState extends State { Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( - "Accounts", + t.pages.accounts.title, style: Theme.of(context).textTheme.titleLarge, ), ), @@ -104,7 +105,10 @@ class AccountListPageState extends State { ), ); }, - child: Text(bloc.activeAccount?.name ?? "None"), + child: Text( + bloc.activeAccount?.name ?? + t.pages.accounts.accountSelector.none, + ), ), ], ), @@ -207,7 +211,8 @@ class AccountListPageState extends State { child: TextField( controller: _accountNameController, decoration: InputDecoration( - hintText: "Account name", + hintText: + t.pages.accounts.addAccount.accountName, ), ), ), @@ -229,7 +234,7 @@ class AccountListPageState extends State { _accountNameController.text = ""; Navigator.of(context).pop(); }, - child: Text("Add"), + child: Text(t.modals.add), ), ], ), diff --git a/lib/ui/pages/account/balance_graph_card.dart b/lib/ui/pages/account/balance_graph_card.dart index 3baaad2..8d21f48 100644 --- a/lib/ui/pages/account/balance_graph_card.dart +++ b/lib/ui/pages/account/balance_graph_card.dart @@ -3,6 +3,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; @@ -67,7 +68,7 @@ class AccountBalanceGraphCard extends StatelessWidget { padding: const EdgeInsets.all(16), child: Column( children: [ - Text("Account balance"), + Text(t.pages.transactions.balance), SizedBox( height: 150, child: FutureBuilder( diff --git a/lib/ui/pages/account/breakdown_card.dart b/lib/ui/pages/account/breakdown_card.dart index 98f3ebf..6d7350d 100644 --- a/lib/ui/pages/account/breakdown_card.dart +++ b/lib/ui/pages/account/breakdown_card.dart @@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/transaction.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/piechart.dart'; @@ -75,7 +76,7 @@ class BreakdownCard extends StatelessWidget { Widget _buildCard(Widget child, String? subtitle) { return ResponsiveCard( - titleText: "Expense Breakdown", + titleText: t.pages.accounts.expenseBreakdown.title, subtitleText: subtitle, child: child, ); @@ -91,7 +92,9 @@ class BreakdownCard extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (bloc.activeAccount == null) { - return _buildCenterText("No account active"); + return _buildCenterText( + t.pages.accounts.expenseBreakdown.noActiveAccount, + ); } return FutureBuilder( @@ -125,7 +128,9 @@ class BreakdownCard extends StatelessWidget { ) .toList(); if (sectionData.isEmpty) { - return _buildCenterText("No expenses available"); + return _buildCenterText( + t.pages.accounts.expenseBreakdown.noExpensesAvailable, + ); } return _buildCard( OkanePieChart( @@ -141,7 +146,9 @@ class BreakdownCard extends StatelessWidget { ) .toList(), ), - "Available money: ${formatCurrency(data.usable)}", + t.pages.accounts.expenseBreakdown.availableFunds( + amount: formatCurrency(data.usable), + ), ); }, ); diff --git a/lib/ui/pages/account/delete_account.dart b/lib/ui/pages/account/delete_account.dart index cf2c7cd..23a5124 100644 --- a/lib/ui/pages/account/delete_account.dart +++ b/lib/ui/pages/account/delete_account.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/account.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; class DeleteAccountPopup extends StatelessWidget { @@ -22,7 +23,7 @@ class DeleteAccountPopup extends StatelessWidget { return BlocBuilder( builder: (context, state) => AlertDialog( - title: Text("Delete Account"), + title: Text(t.pages.accounts.deleteAccount.title), content: state.isDeletingAccount ? Row( @@ -37,7 +38,9 @@ class DeleteAccountPopup extends StatelessWidget { ], ) : Text( - "Are you sure you want to delete the account '${account.name!}'? This will delete all transactions as well.", + t.pages.accounts.deleteAccount.content( + name: account.name!, + ), ), actions: [ TextButton( @@ -48,7 +51,10 @@ class DeleteAccountPopup extends StatelessWidget { await GetIt.I.get().deleteAccount(account); afterDelete(); }, - child: Text("Delete", style: TextStyle(color: Colors.red)), + child: Text( + t.modals.delete, + style: TextStyle(color: Colors.red), + ), ), TextButton( onPressed: @@ -57,7 +63,7 @@ class DeleteAccountPopup extends StatelessWidget { : () { onCancel(); }, - child: Text("Cancel"), + child: Text(t.modals.cancel), ), ], ), diff --git a/lib/ui/pages/account/total_balance_card.dart b/lib/ui/pages/account/total_balance_card.dart index 2d629ec..cec4ce9 100644 --- a/lib/ui/pages/account/total_balance_card.dart +++ b/lib/ui/pages/account/total_balance_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:okane/database/collections/account.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/piechart_card.dart'; @@ -24,14 +25,16 @@ class TotalBalanceCard extends StatelessWidget { return BlocBuilder( builder: (context, state) { return ResponsiveCard( - titleText: "Total Balance", + titleText: t.pages.accounts.totalBalance.title, child: Padding( padding: EdgeInsets.all(16), child: FutureBuilder( future: _getTotalBalance(state.accounts), builder: (context, snapshot) { return Text( - snapshot.hasData ? formatCurrency(snapshot.data!) : "...", + snapshot.hasData + ? formatCurrency(snapshot.data!) + : t.pages.accounts.totalBalance.loading, style: Theme.of(context).textTheme.bodyLarge, ); }, diff --git a/lib/ui/pages/account/upcoming_transactions_card.dart b/lib/ui/pages/account/upcoming_transactions_card.dart index 7498728..5d65ddd 100644 --- a/lib/ui/pages/account/upcoming_transactions_card.dart +++ b/lib/ui/pages/account/upcoming_transactions_card.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/recurrent.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/add_transaction.dart'; @@ -28,25 +29,40 @@ class UpcomingTransactionsCard extends StatelessWidget { upcoming.isEmpty ? [ Text( - "No upcoming transactions", + t + .pages + .accounts + .upcomingTransactions + .noUpcomingTransactions, style: Theme.of(context).textTheme.bodyLarge, ), ] : upcoming .map( - (t) => ListTile( + (transaction) => ListTile( title: Text( - "${t.template.value!.name} (${t.template.value!.amount}€)", + t.pages.accounts.upcomingTransactions.items.title( + name: transaction.template.value!.name, + amount: + "${formatCurrency(transaction.template.value!.amount)}€", + ), ), subtitle: Text( - "Due in ${today.difference(t.lastExecution ?? today).inDays} days", + t.pages.accounts.upcomingTransactions.items.dueIn( + number: + today + .difference( + transaction.lastExecution ?? today, + ) + .inDays, + ), ), leading: Icon( - t.template.value!.amount < 0 + transaction.template.value!.amount < 0 ? Icons.remove : Icons.add, color: - t.template.value!.amount < 0 + transaction.template.value!.amount < 0 ? Colors.red : Colors.green, ), @@ -58,12 +74,14 @@ class UpcomingTransactionsCard extends StatelessWidget { builder: (context) => AddTransactionWidget( activeAccountItem: bloc.activeAccount!, - template: t.template.value!, - onAdd: (transaction) async { + template: transaction.template.value!, + onAdd: (newTransaction) async { // Update the recurring template - print(transaction.date); - t.lastExecution = transaction.date; - await upsertRecurringTransaction(t); + transaction.lastExecution = + newTransaction.date; + await upsertRecurringTransaction( + transaction, + ); Navigator.of(context).pop(); }, ), @@ -74,7 +92,7 @@ class UpcomingTransactionsCard extends StatelessWidget { ) .toList(); return ResponsiveCard( - titleText: "Upcoming Transactions", + titleText: t.pages.accounts.upcomingTransactions.title, child: Padding( padding: const EdgeInsets.all(16), child: Column(children: transactions), diff --git a/lib/ui/pages/beneficiary_list.dart b/lib/ui/pages/beneficiary_list.dart index 56a144c..73acf5b 100644 --- a/lib/ui/pages/beneficiary_list.dart +++ b/lib/ui/pages/beneficiary_list.dart @@ -20,11 +20,8 @@ class BeneficiaryListPage extends StatelessWidget { leading: ImageWrapper(title: item.name, path: item.imagePath), // TODO: Allow deleting beneficiaries trailing: IconButton( - onPressed: null, - icon: Icon( - Icons.delete, - color: Colors.grey, - ), + onPressed: null, + icon: Icon(Icons.delete, color: Colors.grey), ), title: Text(item.name), ); diff --git a/lib/ui/pages/budgets/add_budget.dart b/lib/ui/pages/budgets/add_budget.dart index 39cad06..cf2fe0e 100644 --- a/lib/ui/pages/budgets/add_budget.dart +++ b/lib/ui/pages/budgets/add_budget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/budget.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; class AddBudgetPopup extends StatefulWidget { @@ -23,12 +24,16 @@ class AddBudgetState extends State { mainAxisSize: MainAxisSize.min, children: [ TextField( - decoration: InputDecoration(hintText: "Budget name"), + decoration: InputDecoration( + hintText: t.pages.budgets.addBudget.budgetNameHint, + ), controller: _budgetNameEditController, ), TextField( - decoration: InputDecoration(hintText: "Income"), + decoration: InputDecoration( + hintText: t.pages.budgets.addBudget.income, + ), controller: _budgetIncomeEditController, keyboardType: TextInputType.numberWithOptions( signed: false, @@ -57,7 +62,7 @@ class AddBudgetState extends State { await upsertBudget(budget); widget.onDone(); }, - child: Text("Add"), + child: Text(t.modals.add), ), ], ), diff --git a/lib/ui/pages/budgets/add_budget_item.dart b/lib/ui/pages/budgets/add_budget_item.dart index cf103c4..a5e1c9c 100644 --- a/lib/ui/pages/budgets/add_budget_item.dart +++ b/lib/ui/pages/budgets/add_budget_item.dart @@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/budget.dart'; import 'package:okane/database/collections/expense_category.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/add_expense_category.dart'; @@ -32,7 +33,7 @@ class AddBudgetItemState extends State { children: [ Row( children: [ - Text("Expense category"), + Text(t.common.expenseCategory.name), OutlinedButton( onPressed: () async { @@ -46,13 +47,17 @@ class AddBudgetItemState extends State { setState(() => _expenseCategory = category); }, - child: Text(_expenseCategory?.name ?? "None"), + child: Text( + _expenseCategory?.name ?? t.common.expenseCategory.none, + ), ), ], ), TextField( - decoration: InputDecoration(hintText: "Amount"), + decoration: InputDecoration( + hintText: t.pages.budgets.addBudgetItem.amountHint, + ), controller: _budgetItemAmountEditController, keyboardType: TextInputType.numberWithOptions( signed: false, @@ -91,7 +96,7 @@ class AddBudgetItemState extends State { await upsertBudget(widget.budget); widget.onDone(); }, - child: Text("Add"), + child: Text(t.modals.add), ), ], ), diff --git a/lib/ui/pages/budgets/budget_details.dart b/lib/ui/pages/budgets/budget_details.dart index ad84e17..abd7553 100644 --- a/lib/ui/pages/budgets/budget_details.dart +++ b/lib/ui/pages/budgets/budget_details.dart @@ -1,14 +1,12 @@ -import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/budget.dart'; import 'package:okane/database/database.dart'; -import 'package:okane/ui/pages/account/breakdown_card.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/pages/budgets/add_budget_item.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; -import 'package:okane/ui/widgets/piechart.dart'; import 'package:okane/ui/widgets/piechart_card.dart'; class BudgetDetailsPage extends StatelessWidget { @@ -55,13 +53,13 @@ class BudgetDetailsPage extends StatelessWidget { BlocBuilder( builder: (context, state) { if (state.activeBudget == null) { - return Text("No budget selected"); + return Text(t.pages.budgets.details.noBudgetSelected); } if (state.activeBudget!.items.isEmpty) { return Row( children: [ - Text("No budget items added"), + Text(t.pages.budgets.details.noBudgetItems), Padding( padding: EdgeInsets.only(left: 16), child: IconButton( @@ -92,7 +90,7 @@ class BudgetDetailsPage extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - "Budget items", + t.pages.budgets.details.budgetItems, style: Theme.of(context).textTheme.titleMedium, ), ListView.builder( @@ -105,9 +103,14 @@ class BudgetDetailsPage extends StatelessWidget { final amount = formatCurrency(item.amount); return ListTile( title: Text( - "${item.expenseCategory.value!.name} ($amount)", + t.pages.budgets.details.items.title( + name: item.expenseCategory.value!.name, + amount: amount, + ), + ), + subtitle: Text( + t.pages.budgets.details.items.loading, ), - subtitle: Text("..."), ); }, ), @@ -167,7 +170,7 @@ class BudgetDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "Days left", + t.pages.budgets.details.daysLeft, textAlign: TextAlign.center, style: Theme.of( @@ -199,7 +202,7 @@ class BudgetDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "Budget left", + t.pages.budgets.details.budgetLeft, textAlign: TextAlign.center, style: Theme.of( @@ -233,7 +236,7 @@ class BudgetDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "Budget total", + t.pages.budgets.details.totalBudget, textAlign: TextAlign.center, style: Theme.of( @@ -276,14 +279,21 @@ class BudgetDetailsPage extends StatelessWidget { ), ) .toList(), - titleText: "Budget breakdown", + titleText: + t.pages.budgets.details.budgetBreakdown.title, ), ), Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: PieChartCard( - fallbackText: "No spending available", + fallbackText: + t + .pages + .budgets + .details + .budgetBreakdown + .noSpendingAvailable, valueConverter: formatCurrency, items: spending.entries @@ -295,7 +305,13 @@ class BudgetDetailsPage extends StatelessWidget { ), ) .toList(), - titleText: "Spending Breakdown", + titleText: + t + .pages + .budgets + .details + .spendingBreakdown + .title, ), ), ], @@ -306,7 +322,7 @@ class BudgetDetailsPage extends StatelessWidget { child: Row( children: [ Text( - "Budget items", + t.pages.budgets.details.budgetItems, style: Theme.of(context).textTheme.titleMedium, ), Padding( @@ -337,8 +353,12 @@ class BudgetDetailsPage extends StatelessWidget { : item.amount + spent; final subtitleText = left < 0 - ? "${formatCurrency(left)} over" - : "${formatCurrency(left)} left"; + ? t.pages.budgets.details.items.over( + amount: formatCurrency(left), + ) + : t.pages.budgets.details.items.remaining( + amount: formatCurrency(left), + ); return ListTile( title: Text( "${item.expenseCategory.value!.name} ($amount)", diff --git a/lib/ui/pages/budgets/budgets.dart b/lib/ui/pages/budgets/budgets.dart index 0d7b38c..392026d 100644 --- a/lib/ui/pages/budgets/budgets.dart +++ b/lib/ui/pages/budgets/budgets.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/screen.dart'; import 'package:okane/ui/pages/budgets/add_budget.dart'; import 'package:okane/ui/pages/budgets/edit_budget.dart'; @@ -19,7 +20,7 @@ class BudgetListPage extends StatelessWidget { if (state.budgets.isEmpty) { return Column( crossAxisAlignment: CrossAxisAlignment.center, - children: [Text("No budgets")], + children: [Text(t.pages.budgets.noBudgets)], ); } diff --git a/lib/ui/pages/budgets/edit_budget.dart b/lib/ui/pages/budgets/edit_budget.dart index 060d347..dad29a6 100644 --- a/lib/ui/pages/budgets/edit_budget.dart +++ b/lib/ui/pages/budgets/edit_budget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:okane/database/collections/budget.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; class EditBudgetPopup extends StatefulWidget { final Budget budget; @@ -36,12 +37,14 @@ class EditBudgetState extends State { mainAxisSize: MainAxisSize.min, children: [ TextField( - decoration: InputDecoration(hintText: "Name"), + decoration: InputDecoration( + hintText: t.pages.budgets.addBudget.budgetNameHint, + ), controller: _budgetNameEditController, ), Row( children: [ - Text("Include other spendings"), + Text(t.pages.budgets.addBudget.includeOtherSpendings), Switch( value: _includeOtherSpendings, onChanged: (value) { @@ -71,7 +74,7 @@ class EditBudgetState extends State { await upsertBudget(widget.budget); widget.onDone(); }, - child: Text("Save"), + child: Text(t.modals.save), ), ], ), diff --git a/lib/ui/pages/settings.dart b/lib/ui/pages/settings.dart index 2531ed4..b0f2a1f 100644 --- a/lib/ui/pages/settings.dart +++ b/lib/ui/pages/settings.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/settings.dart'; import 'package:okane/ui/utils.dart'; @@ -14,11 +15,17 @@ class SettingsPage extends StatelessWidget { BlocBuilder( builder: (context, state) => ListTile( - title: Text("Color Scheme"), + title: Text(t.pages.settings.colorSchemes.title), subtitle: switch (state.settings.colorScheme) { - ColorSchemeSettings.dark => Text("Dark"), - ColorSchemeSettings.light => Text("Light"), - ColorSchemeSettings.system => Text("System"), + ColorSchemeSettings.dark => Text( + t.pages.settings.colorSchemes.dark, + ), + ColorSchemeSettings.light => Text( + t.pages.settings.colorSchemes.light, + ), + ColorSchemeSettings.system => Text( + t.pages.settings.colorSchemes.system, + ), }, onTap: () async { final colorScheme = await showDialogOrModal( @@ -35,9 +42,12 @@ class SettingsPage extends StatelessWidget { ? Icon(Icons.check) : null, title: Text(switch (s) { - ColorSchemeSettings.dark => "Dark", - ColorSchemeSettings.light => "Light", - ColorSchemeSettings.system => "System", + ColorSchemeSettings.dark => + t.pages.settings.colorSchemes.dark, + ColorSchemeSettings.light => + t.pages.settings.colorSchemes.light, + ColorSchemeSettings.system => + t.pages.settings.colorSchemes.system, }), onTap: () { Navigator.of(context).pop(s); diff --git a/lib/ui/pages/template_list.dart b/lib/ui/pages/template_list.dart index 3b52c2a..7e87447 100644 --- a/lib/ui/pages/template_list.dart +++ b/lib/ui/pages/template_list.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/add_template.dart'; @@ -25,7 +26,9 @@ class TemplateListState extends State { children: [ CustomScrollView( slivers: [ - SliverToBoxAdapter(child: Text("Non-recurring")), + SliverToBoxAdapter( + child: Text(t.pages.templates.nonRecurring.title), + ), SliverList.builder( itemCount: nonRecurringTemplates.length, itemBuilder: (context, index) { @@ -38,8 +41,10 @@ class TemplateListState extends State { onPressed: () async { final result = await confirm( context, - "Remove Template", - "Are you sure you want to remove the template '${template.name}'", + t.pages.templates.removeTemplate.title, + t.pages.templates.removeTemplate.body( + name: template.name, + ), ); if (!result) { return; @@ -51,7 +56,9 @@ class TemplateListState extends State { ); }, ), - SliverToBoxAdapter(child: Text("Recurring")), + SliverToBoxAdapter( + child: Text(t.pages.templates.recurring.title), + ), SliverList.builder( itemCount: state.recurringTransactions.length, itemBuilder: (context, index) { @@ -63,8 +70,10 @@ class TemplateListState extends State { onPressed: () async { final result = await confirm( context, - "Remove Template", - "Are you sure you want to remove the template '${template.template.value!.name}'", + t.pages.templates.removeTemplate.title, + t.pages.templates.removeTemplate.body( + name: template.template.value!.name, + ), ); if (!result) { return; diff --git a/lib/ui/pages/transaction_details.dart b/lib/ui/pages/transaction_details.dart index ac37e52..9049706 100644 --- a/lib/ui/pages/transaction_details.dart +++ b/lib/ui/pages/transaction_details.dart @@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/beneficiary.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/image_wrapper.dart'; @@ -69,7 +70,9 @@ class TransactionDetailsPage extends StatelessWidget { child: BlocBuilder( builder: (context, state) { if (state.activeTransaction == null) { - return Text("No transaction selected"); + return Text( + t.pages.transactions.details.noTransactionSelected, + ); } return Padding( @@ -162,7 +165,7 @@ class TransactionDetailsPage extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Text("Expense category"), + Text(t.common.expenseCategory.name), Padding( padding: EdgeInsets.only(left: 16), child: Chip( diff --git a/lib/ui/widgets/add_expense_category.dart b/lib/ui/widgets/add_expense_category.dart index c9a94b3..ae464ac 100644 --- a/lib/ui/widgets/add_expense_category.dart +++ b/lib/ui/widgets/add_expense_category.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:okane/database/collections/expense_category.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; class AddExpenseCategory extends StatefulWidget { @@ -39,7 +40,9 @@ class AddExpenseCategoryState extends State { ), TextField( - decoration: InputDecoration(hintText: "Category name"), + decoration: InputDecoration( + hintText: t.common.expenseCategory.name, + ), controller: _categoryNameController, ), Row( @@ -54,7 +57,7 @@ class AddExpenseCategoryState extends State { _categoryNameController.text = ""; Navigator.of(context).pop(category); }, - child: Text("Add"), + child: Text(t.modals.add), ), ], ), diff --git a/lib/ui/widgets/add_recurring_transaction.dart b/lib/ui/widgets/add_recurring_transaction.dart deleted file mode 100644 index f178ed3..0000000 --- a/lib/ui/widgets/add_recurring_transaction.dart +++ /dev/null @@ -1,291 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_picker_plus/picker.dart'; -import 'package:get_it/get_it.dart'; -import 'package:okane/database/collections/account.dart'; -import 'package:okane/database/collections/beneficiary.dart'; -import 'package:okane/database/collections/recurrent.dart'; -import 'package:okane/database/collections/template.dart'; -import 'package:okane/database/database.dart'; -import 'package:okane/ui/state/core.dart'; -import 'package:okane/ui/transaction.dart'; -import 'package:okane/ui/utils.dart'; -import 'package:searchfield/searchfield.dart'; - -enum Period { days, weeks, months, years } - -class AddRecurringTransactionTemplateWidget extends StatefulWidget { - final VoidCallback onAdd; - - final Account activeAccountItem; - - const AddRecurringTransactionTemplateWidget({ - super.key, - required this.activeAccountItem, - required this.onAdd, - }); - - @override - State createState() => - _AddRecurringTransactionTemplateWidgetState(); -} - -class _AddRecurringTransactionTemplateWidgetState - extends State { - final TextEditingController _beneficiaryTextController = - TextEditingController(); - final TextEditingController _amountTextController = TextEditingController(); - final TextEditingController _templateNameController = TextEditingController(); - - List beneficiaries = []; - - SearchFieldListItem? _selectedBeneficiary; - - TransactionDirection _selectedDirection = TransactionDirection.send; - - Period _selectedPeriod = Period.months; - int _periodSize = 1; - - String getBeneficiaryName(Beneficiary item) { - return switch (item.type) { - BeneficiaryType.account => "${item.name} (Account)", - BeneficiaryType.other => item.name, - }; - } - - Future _submit(BuildContext context) async { - final beneficiaryName = _beneficiaryTextController.text; - if (_selectedBeneficiary == null && beneficiaryName.isEmpty) { - return; - } - if (_templateNameController.text.isEmpty) { - return; - } - - Beneficiary? beneficiary = _selectedBeneficiary?.item; - if (beneficiary == null || - getBeneficiaryName(beneficiary) != beneficiaryName) { - // Add a new beneficiary, if none was selected - final result = await showDialog( - context: context, - builder: - (context) => AlertDialog( - title: const Text("Add Beneficiary"), - content: Text( - "The beneficiary '$beneficiaryName' does not exist. Do you want to add it?", - ), - actions: [ - TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), - child: const Text('Add'), - onPressed: () => Navigator.of(context).pop(true), - ), - TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), - child: const Text('Cancel'), - onPressed: () => Navigator.of(context).pop(false), - ), - ], - ), - ); - if (result == null || !result) { - return; - } - - beneficiary = - Beneficiary() - ..name = beneficiaryName - ..type = BeneficiaryType.other; - await upsertBeneficiary(beneficiary); - } - - final days = switch (_selectedPeriod) { - Period.days => _periodSize, - Period.weeks => _periodSize * 7, - Period.months => _periodSize * 31, - Period.years => _periodSize * 365, - }; - final factor = switch (_selectedDirection) { - TransactionDirection.send => -1, - TransactionDirection.receive => 1, - }; - final amount = factor * double.parse(_amountTextController.text).abs(); - final template = - TransactionTemplate() - ..name = _templateNameController.text - ..beneficiary.value = beneficiary - ..account.value = widget.activeAccountItem - ..recurring = true - ..amount = amount; - await upsertTransactionTemplate(template); - - final transaction = - RecurringTransaction() - ..lastExecution = null - ..template.value = template - ..account.value = widget.activeAccountItem - ..days = days; - await upsertRecurringTransaction(transaction); - - _periodSize = 1; - _selectedPeriod = Period.weeks; - _amountTextController.text = ""; - _templateNameController.text = ""; - widget.onAdd(); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: ListView( - shrinkWrap: true, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: TextField( - controller: _templateNameController, - decoration: InputDecoration(label: Text("Template name")), - ), - ), - - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: SegmentedButton( - segments: [ - ButtonSegment( - value: TransactionDirection.send, - label: Text("Send"), - icon: Icon(Icons.remove), - ), - ButtonSegment( - value: TransactionDirection.receive, - label: Text("Receive"), - icon: Icon(Icons.add), - ), - ], - selected: {_selectedDirection}, - multiSelectionEnabled: false, - onSelectionChanged: (selection) { - setState(() => _selectedDirection = selection.first); - }, - ), - ), - - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: SearchField( - suggestions: - beneficiaries - .where((el) { - final bloc = GetIt.I.get(); - if (el.type == BeneficiaryType.account) { - return el.account.value?.id != bloc.activeAccount?.id; - } - return true; - }) - .map((el) { - return SearchFieldListItem( - getBeneficiaryName(el), - item: el, - ); - }) - .toList(), - hint: switch (_selectedDirection) { - TransactionDirection.send => "Payee", - TransactionDirection.receive => "Payer", - }, - controller: _beneficiaryTextController, - selectedValue: _selectedBeneficiary, - onSuggestionTap: (beneficiary) { - setState(() => _selectedBeneficiary = beneficiary); - }, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: TextField( - controller: _amountTextController, - keyboardType: TextInputType.numberWithOptions( - signed: false, - decimal: false, - ), - decoration: InputDecoration( - hintText: "Amount", - icon: Icon(Icons.euro), - ), - ), - ), - - Padding( - padding: EdgeInsets.only(left: 16, right: 16, top: 16), - child: SegmentedButton( - segments: [ - ButtonSegment(value: Period.days, label: Text("Days")), - ButtonSegment(value: Period.weeks, label: Text("Weeks")), - ButtonSegment(value: Period.months, label: Text("Months")), - ButtonSegment(value: Period.years, label: Text("Years")), - ], - selected: {_selectedPeriod}, - multiSelectionEnabled: false, - onSelectionChanged: (selection) { - setState(() => _selectedPeriod = selection.first); - }, - ), - ), - Text.rich( - TextSpan( - text: "Repeat every ", - children: [ - WidgetSpan( - child: TextButton( - onPressed: () { - Picker( - adapter: NumberPickerAdapter( - data: [ - NumberPickerColumn( - begin: 1, - end: 999, - initValue: _periodSize, - ), - ], - ), - hideHeader: true, - selectedTextStyle: TextStyle(color: Colors.blue), - onConfirm: (Picker picker, List value) { - setState(() { - _periodSize = (value.first as int) + 1; - }); - }, - ).showDialog(context); - }, - child: Text(_periodSize.toString()), - ), - ), - TextSpan( - text: switch (_selectedPeriod) { - Period.days => " days", - Period.weeks => " weeks", - Period.months => " months", - Period.years => " years", - }, - ), - ], - ), - ), - - Align( - alignment: Alignment.centerRight, - child: OutlinedButton( - onPressed: () => _submit(context), - child: Text("Add"), - ), - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/add_template.dart b/lib/ui/widgets/add_template.dart index b0baa82..2f6f6cd 100644 --- a/lib/ui/widgets/add_template.dart +++ b/lib/ui/widgets/add_template.dart @@ -7,6 +7,7 @@ import 'package:okane/database/collections/expense_category.dart'; import 'package:okane/database/collections/recurrent.dart'; import 'package:okane/database/collections/template.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/transaction.dart'; import 'package:okane/ui/utils.dart'; @@ -51,7 +52,9 @@ class _AddTransactionTemplateWidgetState String getBeneficiaryName(Beneficiary item) { return switch (item.type) { - BeneficiaryType.account => "${item.name} (Account)", + BeneficiaryType.account => t.common.beneficiary.nameWithAccount( + name: item.name, + ), BeneficiaryType.other => item.name, }; } @@ -59,7 +62,6 @@ class _AddTransactionTemplateWidgetState Future _submit(BuildContext context) async { final beneficiaryName = _beneficiaryTextController.text; if (_selectedBeneficiary == null && beneficiaryName.isEmpty) { - print("No beneficiary"); return; } if (_templateNameController.text.isEmpty) { @@ -74,23 +76,23 @@ class _AddTransactionTemplateWidgetState context: context, builder: (context) => AlertDialog( - title: const Text("Add Beneficiary"), + title: Text(t.common.beneficiary.addBeneficiary.title), content: Text( - "The beneficiary '$beneficiaryName' does not exist. Do you want to add it?", + t.common.beneficiary.addBeneficiary.body(name: beneficiaryName), ), actions: [ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), - child: const Text('Add'), + child: Text(t.modals.add), onPressed: () => Navigator.of(context).pop(true), ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), - child: const Text('Cancel'), + child: Text(t.modals.cancel), onPressed: () => Navigator.of(context).pop(false), ), ], @@ -151,7 +153,7 @@ class _AddTransactionTemplateWidgetState padding: const EdgeInsets.symmetric(vertical: 8), child: TextField( controller: _templateNameController, - decoration: InputDecoration(label: Text("Template name")), + decoration: InputDecoration(label: Text(t.common.templateName)), ), ), @@ -161,12 +163,12 @@ class _AddTransactionTemplateWidgetState segments: [ ButtonSegment( value: TransactionDirection.send, - label: Text("Send"), + label: Text(t.common.transaction.directionSend), icon: Icon(Icons.remove), ), ButtonSegment( value: TransactionDirection.receive, - label: Text("Receive"), + label: Text(t.common.transaction.directionReceive), icon: Icon(Icons.add), ), ], @@ -201,8 +203,10 @@ class _AddTransactionTemplateWidgetState }) .toList(), hint: switch (_selectedDirection) { - TransactionDirection.send => "Payee", - TransactionDirection.receive => "Payer", + TransactionDirection.send => + t.common.transaction.beneficiaryTextfieldHintSend, + TransactionDirection.receive => + t.common.transaction.beneficiaryTextfieldHintReceive, }, controller: _beneficiaryTextController, selectedValue: _selectedBeneficiary, @@ -222,7 +226,7 @@ class _AddTransactionTemplateWidgetState decimal: false, ), decoration: InputDecoration( - hintText: "Amount", + hintText: t.common.amount, icon: Icon(Icons.euro), ), ), @@ -230,7 +234,7 @@ class _AddTransactionTemplateWidgetState Row( children: [ - Text("Expense category"), + Text(t.common.expenseCategory.name), Padding( padding: EdgeInsets.only(left: 16), @@ -246,7 +250,9 @@ class _AddTransactionTemplateWidgetState setState(() => _expenseCategory = category); }, - child: Text(_expenseCategory?.name ?? "None"), + child: Text( + _expenseCategory?.name ?? t.common.expenseCategory.none, + ), ), ), ], @@ -254,7 +260,7 @@ class _AddTransactionTemplateWidgetState Row( children: [ - Text("Is recurring"), + Text(t.pages.templates.addTemplate.isRecurring), Padding( padding: EdgeInsets.only(left: 16), child: Switch( @@ -272,10 +278,22 @@ class _AddTransactionTemplateWidgetState padding: EdgeInsets.only(left: 16, right: 16, top: 16), child: SegmentedButton( segments: [ - ButtonSegment(value: Period.days, label: Text("Days")), - ButtonSegment(value: Period.weeks, label: Text("Weeks")), - ButtonSegment(value: Period.months, label: Text("Months")), - ButtonSegment(value: Period.years, label: Text("Years")), + ButtonSegment( + value: Period.days, + label: Text(t.common.period.days), + ), + ButtonSegment( + value: Period.weeks, + label: Text(t.common.period.weeks), + ), + ButtonSegment( + value: Period.months, + label: Text(t.common.period.months), + ), + ButtonSegment( + value: Period.years, + label: Text(t.common.period.years), + ), ], selected: {_selectedPeriod}, multiSelectionEnabled: false, @@ -311,10 +329,18 @@ class _AddTransactionTemplateWidgetState child: Center( child: Text( switch (_selectedPeriod) { - Period.days => "$_periodSize days", - Period.weeks => "$_periodSize weeks", - Period.months => "$_periodSize months", - Period.years => "$_periodSize years", + Period.days => t.common.period.daysNumber( + number: _periodSize, + ), + Period.weeks => t.common.period.weeksNumber( + number: _periodSize, + ), + Period.months => t.common.period.monthsNumber( + number: _periodSize, + ), + Period.years => t.common.period.yearsNumber( + number: _periodSize, + ), }, style: TextStyle( color: _isRecurring ? Colors.black : Colors.grey, @@ -341,7 +367,7 @@ class _AddTransactionTemplateWidgetState alignment: Alignment.centerRight, child: OutlinedButton( onPressed: () => _submit(context), - child: Text("Add"), + child: Text(t.modals.add), ), ), ], diff --git a/lib/ui/widgets/add_transaction.dart b/lib/ui/widgets/add_transaction.dart index e965222..f36b01e 100644 --- a/lib/ui/widgets/add_transaction.dart +++ b/lib/ui/widgets/add_transaction.dart @@ -7,6 +7,7 @@ import 'package:okane/database/collections/expense_category.dart'; import 'package:okane/database/collections/template.dart'; import 'package:okane/database/collections/transaction.dart'; import 'package:okane/database/database.dart'; +import 'package:okane/i18n/strings.g.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/transaction.dart'; import 'package:okane/ui/utils.dart'; @@ -67,7 +68,9 @@ class _AddTransactionWidgetState extends State { String getBeneficiaryName(Beneficiary item) { return switch (item.type) { - BeneficiaryType.account => "${item.name} (Account)", + BeneficiaryType.account => t.common.beneficiary.nameWithAccount( + name: item.name, + ), BeneficiaryType.other => item.name, }; } @@ -86,23 +89,23 @@ class _AddTransactionWidgetState extends State { context: context, builder: (context) => AlertDialog( - title: const Text("Add Beneficiary"), + title: Text(t.common.beneficiary.addBeneficiary.title), content: Text( - "The beneficiary '$beneficiaryName' does not exist. Do you want to add it?", + t.common.beneficiary.addBeneficiary.body(name: beneficiaryName), ), actions: [ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), - child: const Text('Add'), + child: Text(t.modals.add), onPressed: () => Navigator.of(context).pop(true), ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), - child: const Text('Cancel'), + child: Text(t.modals.cancel), onPressed: () => Navigator.of(context).pop(false), ), ], @@ -177,7 +180,7 @@ class _AddTransactionWidgetState extends State { template.beneficiary.value!, ); }, - child: Text("Use template"), + child: Text(t.pages.transactions.addTransaction.useTemplate), ), Padding( @@ -186,12 +189,12 @@ class _AddTransactionWidgetState extends State { segments: [ ButtonSegment( value: TransactionDirection.send, - label: Text("Send"), + label: Text(t.common.transaction.directionSend), icon: Icon(Icons.remove), ), ButtonSegment( value: TransactionDirection.receive, - label: Text("Receive"), + label: Text(t.common.transaction.directionReceive), icon: Icon(Icons.add), ), ], @@ -226,8 +229,10 @@ class _AddTransactionWidgetState extends State { }) .toList(), hint: switch (_selectedDirection) { - TransactionDirection.send => "Payee", - TransactionDirection.receive => "Payer", + TransactionDirection.send => + t.common.transaction.beneficiaryTextfieldHintSend, + TransactionDirection.receive => + t.common.transaction.beneficiaryTextfieldHintReceive, }, controller: _beneficiaryTextController, selectedValue: _selectedBeneficiary, @@ -247,7 +252,7 @@ class _AddTransactionWidgetState extends State { decimal: false, ), decoration: InputDecoration( - hintText: "Amount", + hintText: t.common.amount, icon: Icon(Icons.euro), ), ), @@ -258,7 +263,7 @@ class _AddTransactionWidgetState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Text("Date"), + Text(t.common.date), Padding( padding: EdgeInsets.only(left: 16), child: OutlinedButton( @@ -289,7 +294,7 @@ class _AddTransactionWidgetState extends State { Row( children: [ - Text("Expense category"), + Text(t.common.expenseCategory.name), Padding( padding: EdgeInsets.only(left: 16), child: OutlinedButton( @@ -304,7 +309,9 @@ class _AddTransactionWidgetState extends State { setState(() => _expenseCategory = category); }, - child: Text(_expenseCategory?.name ?? "None"), + child: Text( + _expenseCategory?.name ?? t.common.expenseCategory.none, + ), ), ), ], @@ -314,7 +321,7 @@ class _AddTransactionWidgetState extends State { alignment: Alignment.centerRight, child: OutlinedButton( onPressed: () => _submit(context), - child: Text("Add"), + child: Text(t.modals.add), ), ), ], diff --git a/pubspec.lock b/pubspec.lock index 9e138c8..e565e15 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csv: + dependency: transitive + description: + name: csv + sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c + url: "https://pub.dev" + source: hosted + version: "6.0.0" cupertino_icons: dependency: "direct main" description: @@ -237,10 +245,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "8986dec4581b4bcd4b6df5d75a2ea0bede3db802f500635d05fa8be298f9467f" + sha256: a222f231db4f822fc49e3b753674bda630e981873c84bf8604bceeb77fce0b24 url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "10.1.7" fixnum: dependency: transitive description: @@ -416,6 +424,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json2yaml: + dependency: transitive + description: + name: json2yaml + sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51 + url: "https://pub.dev" + source: hosted + version: "3.0.1" json_annotation: dependency: "direct main" description: @@ -636,10 +652,10 @@ packages: dependency: "direct main" description: name: searchfield - sha256: "223fca0828ec95f45501db93feac7b120b93600760c0d8c04039fb2eeed9cc20" + sha256: "98fa29165366ec178e86a370918b084c9830cdf6663126fbd11b8c6f77cdcd0f" url: "https://pub.dev" source: hosted - version: "1.2.7" + version: "1.2.9" shared_preferences: dependency: "direct main" description: @@ -717,6 +733,30 @@ packages: description: flutter source: sdk version: "0.0.0" + slang: + dependency: "direct main" + description: + name: slang + sha256: a466773de768eb95bdf681e0a92e7c8010d44bb247b62130426c83ece33aeaed + url: "https://pub.dev" + source: hosted + version: "3.32.0" + slang_build_runner: + dependency: "direct dev" + description: + name: slang_build_runner + sha256: b2e0c63f3c801a4aa70b4ca43173893d6eb7d5a421fc9d97ad983527397631b3 + url: "https://pub.dev" + source: hosted + version: "3.32.0" + slang_flutter: + dependency: "direct main" + description: + name: slang_flutter + sha256: "1a98e878673996902fa5ef0b61ce5c245e41e4d25640d18af061c6aab917b0c7" + url: "https://pub.dev" + source: hosted + version: "3.32.0" source_gen: dependency: transitive description: @@ -849,10 +889,10 @@ packages: dependency: transitive description: name: web_socket - sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: transitive description: @@ -865,10 +905,10 @@ packages: dependency: transitive description: name: win32 - sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f + sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" url: "https://pub.dev" source: hosted - version: "5.12.0" + version: "5.13.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c99da1e..9086d92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,8 @@ dependencies: shared_preferences: ^2.5.3 json_annotation: ^4.9.0 more: 4.5.0 + slang: ^3.0.0 + slang_flutter: ^3.0.0 dev_dependencies: flutter_test: @@ -35,6 +37,7 @@ dev_dependencies: freezed: 2.5.0 isar_generator: ^3.1.0+1 json_serializable: ^6.4.0 + slang_build_runner: ^3.0.0 flutter: uses-material-design: true