diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..4e6692e --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/lib/app/voucher_vault_app.dart b/lib/app/voucher_vault_app.dart index c71a43a..9fe7b01 100644 --- a/lib/app/voucher_vault_app.dart +++ b/lib/app/voucher_vault_app.dart @@ -6,6 +6,8 @@ import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:vouchervault/app/app.dart'; import 'package:vouchervault/auth/auth.dart'; import 'package:vouchervault/vouchers/vouchers.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'voucher_vault_app.g.dart'; @@ -41,7 +43,12 @@ Widget __app() => AtomBuilder((context, watch, child) { ), navigatorObservers: [routeObserver], localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, FormBuilderLocalizations.delegate, ], + supportedLocales: AppLocalizations.supportedLocales, ); }); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..a26e7ed --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,24 @@ +{ + "vouchers": "Vouchers", + "edit": "Edit", + "close": "Close", + "copiedToClipboard": "Copied to clipboard", + "areYouSure": "Are you sure ?", + "confirmRemoveVoucher": "That you want to remove this voucher ?", + "cancel": "Cancel", + "remove": "Remove", + "howMuchSpend": "How much did you spend ?", + "amount": "Amount", + "ok": "OK", + "addVoucher": "Add voucher", + "editVoucher": "Edit voucher", + "create": "Create", + "update": "Update", + "scanBarcode": "Scan barcode", + "description": "Description", + "code": "Code", + "expires": "Expires", + "removeOnceExpired": "Remove once expired", + "balance": "Balance", + "notes": "Notes" +} \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb new file mode 100644 index 0000000..1c55985 --- /dev/null +++ b/lib/l10n/app_fr.arb @@ -0,0 +1,25 @@ +{ + "vouchers": "Mes bons", + "edit": "Modifier", + "close": "Fermer", + "copiedToClipboard": "Copié dans le presse-papier", + "areYouSure": "Etes-vous sûr ?", + "confirmRemoveVoucher": "De vouloir supprimer ce bon ?", + "cancel": "Annuler", + "remove": "Supprimer", + "howMuchSpend": "Quel montant avez-vous depensé ?", + "amount": "Montant", + "ok": "Valider", + "addVoucher": "Ajouter un bon", + "editVoucher": "Modifier ce bon", + "create": "Créer", + "update": "Modifier", + "scanBarcode": "Scanner un code barre", + "toggleFlash": "Flash", + "description": "Description", + "code": "Code", + "expires": "Date d'expiration", + "removeOnceExpired": "Supprimer une fois expiré", + "balance": "Solde", + "notes": "Notes" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index e7890b4..62c7d71 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:vouchervault/app/app.dart'; import 'package:vouchervault/vouchers/vouchers.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; void main({IList? vouchers}) async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/shared/voucher_details/voucher_details.dart b/lib/shared/voucher_details/voucher_details.dart index d71dafe..9d53381 100644 --- a/lib/shared/voucher_details/voucher_details.dart +++ b/lib/shared/voucher_details/voucher_details.dart @@ -5,9 +5,12 @@ import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:vouchervault/app/app.dart'; import 'package:vouchervault/lib/lib.dart'; import 'package:vouchervault/vouchers/models/voucher.dart'; +import 'package:intl/intl.dart'; part 'voucher_details.g.dart'; +final formatCurrency = NumberFormat.simpleCurrency(); + List buildVoucherDetails( BuildContext context, Voucher voucher, { @@ -15,6 +18,7 @@ List buildVoucherDetails( Option space = const None(), bool includeNotes = false, }) => + intersperse(SizedBox( height: space.p(O.getOrElse(() => AppTheme.space1)), ))([ @@ -25,11 +29,11 @@ List buildVoucherDetails( formatExpires(dt), ) ])), - ...voucher.balanceOption.p(O.map(millisToString)).p(ifSomeList((b) => [ + ...voucher.balanceDoubleOption.p(ifSomeList((b) => [ _VoucherDetailRow( textColor, Icons.account_balance, - '\$$b', + formatCurrency.format(b), ), ])), ...voucher.notesOption diff --git a/lib/voucher_form/barcode_scanner/widgets/barcode_button.dart b/lib/voucher_form/barcode_scanner/widgets/barcode_button.dart index 7fc3251..dcc1a0d 100644 --- a/lib/voucher_form/barcode_scanner/widgets/barcode_button.dart +++ b/lib/voucher_form/barcode_scanner/widgets/barcode_button.dart @@ -7,6 +7,7 @@ import 'package:fpdt/option.dart' as O; import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:vouchervault/app/app.dart'; import 'package:vouchervault/lib/lib.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'barcode_button.g.dart'; @@ -45,6 +46,6 @@ Widget _barcodeButton( child: Center( child: _barcodeWidget(barcodeType, optionOfString(data)) .p(O.alt(() => _autoSizeText(data))) - .p(O.getOrElse(() => const Text('Scan barcode'))), + .p(O.getOrElse(() => Text(AppLocalizations.of(context)!.scanBarcode))), ), ); diff --git a/lib/voucher_form/barcode_scanner/widgets/scanner_dialog.dart b/lib/voucher_form/barcode_scanner/widgets/scanner_dialog.dart index 02f83bd..2e18696 100644 --- a/lib/voucher_form/barcode_scanner/widgets/scanner_dialog.dart +++ b/lib/voucher_form/barcode_scanner/widgets/scanner_dialog.dart @@ -10,6 +10,7 @@ import 'package:fpdt/reader_task_either.dart' as RTE; import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:vouchervault/app/app.dart'; import 'package:vouchervault/voucher_form/voucher_form.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'scanner_dialog.g.dart'; @@ -101,9 +102,10 @@ Widget __previewDialog( onPressed: onPressedPicker, ), SizedBox(width: AppTheme.space3), - ElevatedButton( - onPressed: onPressedFlash, - child: const Text('Toggle flash'), + IconButton( + color: Colors.white, + onPressed: onPressedFlash, + icon: const Icon(Icons.flash_on) // TODO: Toggle icon ? ), SizedBox(width: AppTheme.space3), ElevatedButton( @@ -112,7 +114,7 @@ Widget __previewDialog( foregroundColor: Colors.black, ), onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), ], ), diff --git a/lib/voucher_form/widgets/dialog.dart b/lib/voucher_form/widgets/dialog.dart index 0178d2b..fcc5a75 100644 --- a/lib/voucher_form/widgets/dialog.dart +++ b/lib/voucher_form/widgets/dialog.dart @@ -8,6 +8,7 @@ import 'package:vouchervault/shared/scaffold/app_scaffold.dart'; import 'package:vouchervault/voucher_form/voucher_form.dart'; import 'package:vouchervault/vouchers/vouchers.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'dialog.g.dart'; @@ -18,12 +19,12 @@ Widget _voucherFormDialog( }) { final formKey = useMemoized(() => GlobalKey()); final title = initialValue.p(O.fold( - () => 'Add voucher', - (_) => 'Edit voucher', + () => AppLocalizations.of(context)!.addVoucher, + (_) => AppLocalizations.of(context)!.editVoucher, )); final action = initialValue.p(O.fold( - () => 'Create', - (_) => 'Update', + () => AppLocalizations.of(context)!.create, + (_) => AppLocalizations.of(context)!.update, )); return AppScaffold( diff --git a/lib/voucher_form/widgets/form.dart b/lib/voucher_form/widgets/form.dart index 506a5b5..c19c685 100644 --- a/lib/voucher_form/widgets/form.dart +++ b/lib/voucher_form/widgets/form.dart @@ -11,6 +11,7 @@ import 'package:vouchervault/voucher_form/voucher_form.dart'; import 'package:vouchervault/vouchers/vouchers.dart' show Voucher, VoucherCodeType, VoucherColor; import 'package:vouchervault/vouchers/vouchers.dart' as V; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'form.g.dart'; @@ -54,9 +55,9 @@ Widget voucherForm( FormBuilderTextField( name: 'description', textCapitalization: TextCapitalization.words, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Description', + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.description, ), validator: FormBuilderValidators.required(), valueTransformer: optionOfString.c(O.toNullable), @@ -68,7 +69,7 @@ Widget voucherForm( validator: FormBuilderValidators.required(), builder: (field) => BarcodeScannerField( launchScannerImmediately: true, - labelText: 'Code', + labelText: AppLocalizations.of(context)!.code, onChange: field.didChange, errorText: optionOfString(field.errorText), initialValue: field.value ?? '', @@ -112,8 +113,8 @@ Widget voucherForm( lastDate: DateTime.now().add(const Duration(days: 365 * 100)), resetIcon: null, decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'Expires', + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.expires, suffixIcon: _resetIconButton(formKey, 'expires'), ), valueTransformer: O @@ -124,7 +125,7 @@ Widget voucherForm( FormBuilderSwitch( name: 'removeOnceExpired', title: Text( - 'Remove once expired', + AppLocalizations.of(context)!.removeOnceExpired, style: theme.textTheme.bodyLarge, ), decoration: const InputDecoration( @@ -140,8 +141,8 @@ Widget voucherForm( decimal: true, ), decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: 'Balance', + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.balance, suffixIcon: _resetIconButton(formKey, 'balanceMilliunits'), ), ), @@ -152,9 +153,9 @@ Widget voucherForm( minLines: 2, maxLines: null, textCapitalization: TextCapitalization.sentences, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Notes', + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.notes, ), ), SizedBox(height: AppTheme.space3), diff --git a/lib/vouchers/dialog/voucher_dialog.dart b/lib/vouchers/dialog/voucher_dialog.dart index b914519..e0e7cd8 100644 --- a/lib/vouchers/dialog/voucher_dialog.dart +++ b/lib/vouchers/dialog/voucher_dialog.dart @@ -11,6 +11,7 @@ import 'package:vouchervault/vouchers/vouchers.dart' as V; import 'package:vouchervault/vouchers/vouchers.dart' show Voucher, VoucherCodeType; import 'package:vouchervault/shared/voucher_details/voucher_details.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; export 'voucher_dialog_container.dart'; export 'voucher_spend_dialog.dart'; @@ -88,7 +89,7 @@ Widget _voucherDialog( SizedBox(width: AppTheme.space3), ElevatedButton( onPressed: onEdit, - child: const Text('Edit'), + child: Text(AppLocalizations.of(context)!.edit), ), SizedBox(width: AppTheme.space3), ElevatedButton( @@ -97,7 +98,7 @@ Widget _voucherDialog( foregroundColor: Colors.black, ), onPressed: onClose, - child: const Text('Close'), + child: Text(AppLocalizations.of(context)!.close), ), ], ), diff --git a/lib/vouchers/dialog/voucher_dialog_container.dart b/lib/vouchers/dialog/voucher_dialog_container.dart index 28aaec7..01555be 100644 --- a/lib/vouchers/dialog/voucher_dialog_container.dart +++ b/lib/vouchers/dialog/voucher_dialog_container.dart @@ -12,6 +12,7 @@ import 'package:vouchervault/hooks/hooks.dart'; import 'package:vouchervault/lib/navigator.dart'; import 'package:vouchervault/voucher_form/voucher_form.dart'; import 'package:vouchervault/vouchers/vouchers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'voucher_dialog_container.g.dart'; @@ -33,7 +34,7 @@ Widget _voucherDialogContainer( final onTapBarcode = useCallback( () => v.code.p(O.map((code) { Clipboard.setData(ClipboardData(text: code)); - Fluttertoast.showToast(msg: 'Copied to clipboard'); + Fluttertoast.showToast(msg: AppLocalizations.of(context)!.copiedToClipboard); })), [v.code], ); @@ -87,16 +88,16 @@ TaskOption _showRemoveDialog( showDialogTO( context: context, builder: (context) => AlertDialog( - title: const Text('Are you sure?'), - content: const Text('That you want to remove this voucher?'), + title: Text(AppLocalizations.of(context)!.areYouSure), + content: Text(AppLocalizations.of(context)!.confirmRemoveVoucher), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => onPressed(context), - child: const Text('Remove'), + child: Text(AppLocalizations.of(context)!.remove), ), ], ), diff --git a/lib/vouchers/dialog/voucher_spend_dialog.dart b/lib/vouchers/dialog/voucher_spend_dialog.dart index d173abc..0753802 100644 --- a/lib/vouchers/dialog/voucher_spend_dialog.dart +++ b/lib/vouchers/dialog/voucher_spend_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:functional_widget_annotation/functional_widget_annotation.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'voucher_spend_dialog.g.dart'; @@ -10,12 +11,12 @@ Widget _voucherSpendDialog(BuildContext context) { void submit() => Navigator.pop(context, amount.value); return AlertDialog( - title: const Text('How much did you spend?'), + title: Text(AppLocalizations.of(context)!.howMuchSpend), content: TextField( autofocus: true, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Amount', + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: AppLocalizations.of(context)!.amount, ), keyboardType: const TextInputType.numberWithOptions(signed: true), onChanged: (s) => amount.value = s, @@ -24,11 +25,11 @@ Widget _voucherSpendDialog(BuildContext context) { actions: [ TextButton( onPressed: () => Navigator.pop(context, null), - child: const Text('Cancel'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: submit, - child: const Text('OK'), + child: Text(AppLocalizations.of(context)!.ok), ), ], ); diff --git a/lib/vouchers/menu/vouchers_menu.dart b/lib/vouchers/menu/vouchers_menu.dart index 730bddd..30c1236 100644 --- a/lib/vouchers/menu/vouchers_menu.dart +++ b/lib/vouchers/menu/vouchers_menu.dart @@ -3,6 +3,7 @@ import 'package:functional_widget_annotation/functional_widget_annotation.dart'; part 'vouchers_menu.g.dart'; +// TODO: i18n enum VouchersMenuAction { import(label: "Import"), export(label: "Export"), diff --git a/lib/vouchers/vouchers_screen.dart b/lib/vouchers/vouchers_screen.dart index 31296aa..9e1d4d1 100644 --- a/lib/vouchers/vouchers_screen.dart +++ b/lib/vouchers/vouchers_screen.dart @@ -9,12 +9,13 @@ import 'package:vouchervault/lib/navigator.dart'; import 'package:vouchervault/shared/scaffold/scaffold.dart'; import 'package:vouchervault/voucher_form/voucher_form.dart'; import 'package:vouchervault/vouchers/vouchers.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; part 'vouchers_screen.g.dart'; @swidget Widget _vouchersScreen(BuildContext context) => AppScaffold( - title: 'Vouchers', + title: AppLocalizations.of(context)!.vouchers, actions: const [VouchersMenuContainer()], slivers: [ SliverPadding( diff --git a/pubspec.yaml b/pubspec.yaml index 73c6c66..c500843 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: vouchervault -description: A new Flutter project. +description: An app for storing vouchers and loyalty cards publish_to: "none" version: 1.2.0 @@ -55,6 +55,7 @@ dev_dependencies: flutter: uses-material-design: true + generate: true fonts: - family: Alegreya Sans