Add i18n via slang

This commit is contained in:
2025-05-12 00:02:19 +02:00
parent 99ab2f006d
commit e0fba11f25
25 changed files with 447 additions and 415 deletions

View File

@@ -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<AccountListPage> {
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<AccountListPage> {
),
);
},
child: Text(bloc.activeAccount?.name ?? "None"),
child: Text(
bloc.activeAccount?.name ??
t.pages.accounts.accountSelector.none,
),
),
],
),
@@ -207,7 +211,8 @@ class AccountListPageState extends State<AccountListPage> {
child: TextField(
controller: _accountNameController,
decoration: InputDecoration(
hintText: "Account name",
hintText:
t.pages.accounts.addAccount.accountName,
),
),
),
@@ -229,7 +234,7 @@ class AccountListPageState extends State<AccountListPage> {
_accountNameController.text = "";
Navigator.of(context).pop();
},
child: Text("Add"),
child: Text(t.modals.add),
),
],
),

View File

@@ -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(

View File

@@ -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<CoreCubit, CoreState>(
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),
),
);
},
);

View File

@@ -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<CoreCubit, CoreState>(
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<CoreCubit>().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),
),
],
),

View File

@@ -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<CoreCubit, CoreState>(
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,
);
},

View File

@@ -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),

View File

@@ -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),
);

View File

@@ -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<AddBudgetPopup> {
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<AddBudgetPopup> {
await upsertBudget(budget);
widget.onDone();
},
child: Text("Add"),
child: Text(t.modals.add),
),
],
),

View File

@@ -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<AddBudgetItemPopup> {
children: [
Row(
children: [
Text("Expense category"),
Text(t.common.expenseCategory.name),
OutlinedButton(
onPressed: () async {
@@ -46,13 +47,17 @@ class AddBudgetItemState extends State<AddBudgetItemPopup> {
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<AddBudgetItemPopup> {
await upsertBudget(widget.budget);
widget.onDone();
},
child: Text("Add"),
child: Text(t.modals.add),
),
],
),

View File

@@ -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<CoreCubit, CoreState>(
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)",

View File

@@ -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)],
);
}

View File

@@ -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<EditBudgetPopup> {
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<EditBudgetPopup> {
await upsertBudget(widget.budget);
widget.onDone();
},
child: Text("Save"),
child: Text(t.modals.save),
),
],
),

View File

@@ -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<SettingsCubit, SettingsWrapper>(
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);

View File

@@ -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<TemplateListPage> {
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<TemplateListPage> {
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<TemplateListPage> {
);
},
),
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<TemplateListPage> {
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;

View File

@@ -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<CoreCubit, CoreState>(
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(