Internationalization (#131)

Replaced hardcoded English strings with locale-based variables based on the [easy_localization](https://pub.dev/packages/easy_localization) Flutter plugin.
This commit is contained in:
Imran Remtulla
2022-11-26 23:53:11 -05:00
committed by GitHub
parent 086b2b949f
commit 17b1f6e5b0
19 changed files with 595 additions and 265 deletions

View File

@@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/custom_app_bar.dart';
@@ -75,12 +76,15 @@ class _AddAppPageState extends State<AddAppPage> {
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title:
'${pickedSource!.enforceTrackOnly ? 'Source' : 'App'} is Track-Only',
title: tr('xIsTrackOnly', args: [
pickedSource!.enforceTrackOnly
? tr('source')
: tr('app')
]),
items: const [],
defaultValues: const [],
message:
'${pickedSource!.enforceTrackOnly ? 'Apps from this source are \'Track-Only\'.' : 'You have selected the \'Track-Only\' option.'}\n\nThe App will be tracked for updates, but Obtainium will not be able to download or install it.',
'${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}',
);
}) ==
null) {
@@ -100,14 +104,14 @@ class _AddAppPageState extends State<AddAppPage> {
// ignore: use_build_context_synchronously
var apkUrl = await appsProvider.confirmApkUrl(app, context);
if (apkUrl == null) {
throw ObtainiumError('Cancelled');
throw ObtainiumError(tr('cancelled'));
}
app.preferredApkIndex = app.apkUrls.indexOf(apkUrl);
var downloadedApk = await appsProvider.downloadApp(app);
app.id = downloadedApk.appId;
}
if (appsProvider.apps.containsKey(app.id)) {
throw ObtainiumError('App already added');
throw ObtainiumError(tr('appAlreadyAdded'));
}
if (app.trackOnly) {
app.installedVersion = app.latestVersion;
@@ -137,7 +141,7 @@ class _AddAppPageState extends State<AddAppPage> {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[
const CustomAppBar(title: 'Add App'),
CustomAppBar(title: tr('addApp')),
SliverFillRemaining(
child: Padding(
padding: const EdgeInsets.all(16),
@@ -151,7 +155,7 @@ class _AddAppPageState extends State<AddAppPage> {
items: [
[
GeneratedFormItem(
label: 'App Source Url',
label: tr('appSourceURL'),
additionalValidators: [
(value) {
try {
@@ -165,7 +169,7 @@ class _AddAppPageState extends State<AddAppPage> {
? e
: e is ObtainiumError
? e.toString()
: 'Error';
: tr('error');
}
return null;
}
@@ -195,7 +199,7 @@ class _AddAppPageState extends State<AddAppPage> {
!otherAdditionalDataIsValid)
? null
: addApp,
child: const Text('Add'))
child: Text(tr('add')))
],
),
if (sourceProvider.sources
@@ -218,7 +222,7 @@ class _AddAppPageState extends State<AddAppPage> {
items: [
[
GeneratedFormItem(
label: 'Search (Some Sources Only)',
label: tr('searchSomeSourcesLabel'),
required: false),
]
],
@@ -281,7 +285,7 @@ class _AddAppPageState extends State<AddAppPage> {
showError(e, context);
});
},
child: const Text('Search'))
child: Text(tr('search')))
],
),
if (pickedSource != null &&
@@ -301,7 +305,10 @@ class _AddAppPageState extends State<AddAppPage> {
height: 64,
),
Text(
'Additional Options for ${pickedSource?.runtimeType}',
tr('additionalOptsFor', args: [
pickedSource?.runtimeType.toString() ??
tr('source')
]),
style: TextStyle(
color:
Theme.of(context).colorScheme.primary)),
@@ -365,8 +372,8 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox(
height: 48,
),
const Text(
'Supported Sources:',
Text(
tr('supportedSourcesBelow'),
),
const SizedBox(
height: 8,
@@ -379,7 +386,7 @@ class _AddAppPageState extends State<AddAppPage> {
LaunchMode.externalApplication);
},
child: Text(
'${e.runtimeType.toString()}${e.enforceTrackOnly ? ' (Track-Only)' : ''}${e.canSearch ? ' (Searchable)' : ''}',
'${e.runtimeType.toString()}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: const TextStyle(
decoration:
TextDecoration.underline,

View File

@@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/custom_app_bar.dart';
@@ -191,7 +192,7 @@ class AppsPageState extends State<AppsPage> {
});
},
child: CustomScrollView(slivers: <Widget>[
const CustomAppBar(title: 'Apps'),
CustomAppBar(title: tr('appsString')),
if (appsProvider.loadingApps || sortedApps.isEmpty)
SliverFillRemaining(
child: Center(
@@ -199,8 +200,8 @@ class AppsPageState extends State<AppsPage> {
? const CircularProgressIndicator()
: Text(
appsProvider.apps.isEmpty
? 'No Apps'
: 'No Apps for Filter',
? tr('noApps')
: tr('noAppsForFilter'),
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
))),
@@ -244,14 +245,19 @@ class AppsPageState extends State<AppsPage> {
? FontWeight.bold
: FontWeight.normal),
),
subtitle: Text('By ${sortedApps[index].app.author}',
subtitle: Text(tr('byX', args: [sortedApps[index].app.author]),
style: TextStyle(
fontWeight: sortedApps[index].app.pinned
? FontWeight.bold
: FontWeight.normal)),
trailing: sortedApps[index].downloadProgress != null
? Text(
'Downloading - ${sortedApps[index].downloadProgress?.toInt()}%')
? Text(tr('percentProgress', args: [
sortedApps[index]
.downloadProgress
?.toInt()
.toString() ??
'100'
]))
: (sortedApps[index].app.installedVersion != null &&
sortedApps[index].app.installedVersion !=
sortedApps[index].app.latestVersion
@@ -260,8 +266,8 @@ class AppsPageState extends State<AppsPage> {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(appsProvider.areDownloadsRunning()
? 'Please Wait...'
: 'Update Available${sortedApps[index].app.trackOnly ? ' (Est.)' : ''}'),
? tr('pleaseWait')
: '${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}'),
SourceProvider()
.getSource(sortedApps[index].app.url)
.changeLogPageFromStandardUrl(
@@ -292,7 +298,7 @@ class AppsPageState extends State<AppsPage> {
child: SizedBox(
width: 80,
child: Text(
'${sortedApps[index].app.installedVersion ?? 'Not Installed'} ${sortedApps[index].app.trackOnly == true ? '(Estimate)' : ''}',
'${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.trackOnly == true ? ' ${tr('estimateInBrackets')}' : ''}',
overflow: TextOverflow.fade,
textAlign: TextAlign.end,
)))),
@@ -327,8 +333,8 @@ class AppsPageState extends State<AppsPage> {
color: Theme.of(context).colorScheme.primary,
),
tooltip: selectedApps.isEmpty
? 'Select All'
: 'Deselect ${selectedApps.length.toString()}'),
? tr('selectAll')
: tr('deselectN', args: [selectedApps.length.toString()])),
const VerticalDivider(),
Expanded(
child: Row(
@@ -343,12 +349,15 @@ class AppsPageState extends State<AppsPage> {
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: 'Remove Selected Apps?',
title: tr('removeSelectedAppsQuestion'),
items: const [],
defaultValues: const [],
initValid: true,
message:
'${selectedApps.length} App${selectedApps.length == 1 ? '' : 's'} will be removed from Obtainium but remain installed. You still need to uninstall ${selectedApps.length == 1 ? 'it' : 'them'} manually.',
message: tr(
'xWillBeRemovedButRemainInstalled',
args: [
plural('apps', selectedApps.length)
]),
);
}).then((values) {
if (values != null) {
@@ -357,7 +366,7 @@ class AppsPageState extends State<AppsPage> {
}
});
},
tooltip: 'Remove Selected Apps',
tooltip: tr('removeSelectedApps'),
icon: const Icon(Icons.delete_outline_outlined),
),
IconButton(
@@ -373,16 +382,20 @@ class AppsPageState extends State<AppsPage> {
List<String> defaultValues = [];
if (existingUpdateIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem(
label:
'Update ${existingUpdateIdsAllOrSelected.length} App${existingUpdateIdsAllOrSelected.length == 1 ? '' : 's'}',
label: tr('updateX', args: [
plural('apps',
existingUpdateIdsAllOrSelected.length)
]),
type: FormItemType.bool,
key: 'updates'));
defaultValues.add('true');
}
if (newInstallIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem(
label:
'Install ${newInstallIdsAllOrSelected.length} new App${newInstallIdsAllOrSelected.length == 1 ? '' : 's'}',
label: tr('installX', args: [
plural('apps',
newInstallIdsAllOrSelected.length)
]),
type: FormItemType.bool,
key: 'installs'));
defaultValues
@@ -390,8 +403,10 @@ class AppsPageState extends State<AppsPage> {
}
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem(
label:
'Mark ${trackOnlyUpdateIdsAllOrSelected.length} Track-Only\nApp${trackOnlyUpdateIdsAllOrSelected.length == 1 ? '' : 's'} as Updated',
label: tr('markXTrackOnlyAsUpdated', args: [
plural('apps',
trackOnlyUpdateIdsAllOrSelected.length)
]),
type: FormItemType.bool,
key: 'trackonlies'));
defaultValues
@@ -405,8 +420,8 @@ class AppsPageState extends State<AppsPage> {
newInstallIdsAllOrSelected.length +
trackOnlyUpdateIdsAllOrSelected.length;
return GeneratedFormModal(
title:
'Change $totalApps App${totalApps == 1 ? '' : 's'}',
title: tr('changeX',
args: [plural('apps', totalApps)]),
items: formInputs.map((e) => [e]).toList(),
defaultValues: defaultValues,
initValid: true,
@@ -459,8 +474,9 @@ class AppsPageState extends State<AppsPage> {
}
});
},
tooltip:
'Install/Update${selectedApps.isEmpty ? ' ' : ' Selected '}Apps',
tooltip: selectedApps.isEmpty
? tr('installUpdateApps')
: tr('installUpdateSelectedApps'),
icon: const Icon(
Icons.file_download_outlined,
)),
@@ -492,11 +508,15 @@ class AppsPageState extends State<AppsPage> {
(BuildContext
ctx) {
return AlertDialog(
title: Text(
'Mark ${selectedApps.length} Selected Apps as Updated?'),
content:
const Text(
'Only applies to installed but out of date Apps.'),
title: Text(tr(
'markXSelectedAppsAsUpdated',
args: [
selectedApps
.length
.toString()
])),
content: Text(
tr('onlyAppliesToInstalledAndOutdatedApps')),
actions: [
TextButton(
onPressed:
@@ -504,8 +524,8 @@ class AppsPageState extends State<AppsPage> {
Navigator.of(context)
.pop();
},
child: const Text(
'No')),
child: Text(
tr('no'))),
TextButton(
onPressed:
() {
@@ -523,8 +543,8 @@ class AppsPageState extends State<AppsPage> {
Navigator.of(context)
.pop();
},
child: const Text(
'Yes'))
child: Text(
tr('yes')))
],
);
}).whenComplete(() {
@@ -534,7 +554,7 @@ class AppsPageState extends State<AppsPage> {
});
},
tooltip:
'Mark Selected Apps as Updated',
tr('markSelectedAppsUpdated'),
icon: const Icon(Icons.done)),
IconButton(
onPressed: () {
@@ -549,8 +569,12 @@ class AppsPageState extends State<AppsPage> {
}).toList());
Navigator.of(context).pop();
},
tooltip:
'${selectedApps.where((element) => element.pinned).isEmpty ? 'Pin to' : 'Unpin from'} top',
tooltip: selectedApps
.where((element) =>
element.pinned)
.isEmpty
? tr('pinToTop')
: tr('unpinFromTop'),
icon: Icon(selectedApps
.where((element) =>
element.pinned)
@@ -568,11 +592,11 @@ class AppsPageState extends State<AppsPage> {
urls = urls.substring(
0, urls.length - 1);
Share.share(urls,
subject:
'${selectedApps.length} Selected App URLs from Obtainium');
subject: tr(
'selectedAppURLsFromObtainium'));
Navigator.of(context).pop();
},
tooltip: 'Share Selected App URLs',
tooltip: tr('shareSelectedAppURLs'),
icon: const Icon(Icons.share),
),
IconButton(
@@ -581,13 +605,19 @@ class AppsPageState extends State<AppsPage> {
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title:
'Reset Install Status for Selected Apps?',
title: tr(
'resetInstallStatusForSelectedAppsQuestion'),
items: const [],
defaultValues: const [],
initValid: true,
message:
'The install status of ${selectedApps.length} App${selectedApps.length == 1 ? '' : 's'} will be reset.\n\nThis can help when the App version shown in Obtainium is incorrect due to failed updates or other issues.',
message: tr(
'installStatusOfXWillBeResetExplanation',
args: [
plural(
'app',
selectedApps
.length)
]),
);
}).then((values) {
if (values != null) {
@@ -601,7 +631,7 @@ class AppsPageState extends State<AppsPage> {
Navigator.of(context).pop();
});
},
tooltip: 'Reset Install Status',
tooltip: tr('resetInstallStatus'),
icon: const Icon(
Icons.restore_page_outlined),
),
@@ -610,7 +640,7 @@ class AppsPageState extends State<AppsPage> {
);
});
},
tooltip: 'More',
tooltip: tr('more'),
icon: const Icon(Icons.more_horiz),
),
],
@@ -628,8 +658,8 @@ class AppsPageState extends State<AppsPage> {
});
},
tooltip: currentFilterIsUpdatesOnly
? 'Remove Out-of-Date App Filter'
: 'Show Out-of-Date Apps Only',
? tr('removeOutdatedFilter')
: tr('showOutdatedOnly'),
icon: Icon(
currentFilterIsUpdatesOnly
? Icons.update_disabled_rounded
@@ -641,7 +671,7 @@ class AppsPageState extends State<AppsPage> {
? const SizedBox()
: TextButton.icon(
label: Text(
filter == null ? 'Filter' : 'Filter *',
filter == null ? tr('filter') : tr('filterActive'),
style: TextStyle(
fontWeight: filter == null
? FontWeight.normal
@@ -652,22 +682,22 @@ class AppsPageState extends State<AppsPage> {
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: 'Filter Apps',
title: tr('filterApps'),
items: [
[
GeneratedFormItem(
label: 'App Name', required: false),
label: tr('appName'), required: false),
GeneratedFormItem(
label: 'Author', required: false)
label: tr('author'), required: false)
],
[
GeneratedFormItem(
label: 'Up to Date Apps',
label: tr('upToDateApps'),
type: FormItemType.bool)
],
[
GeneratedFormItem(
label: 'Non-Installed Apps',
label: tr('nonInstalledApps'),
type: FormItemType.bool)
]
],

View File

@@ -1,4 +1,5 @@
import 'package:animations/animations.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/pages/add_app.dart';
@@ -25,12 +26,12 @@ class _HomePageState extends State<HomePage> {
List<int> selectedIndexHistory = [];
List<NavigationPageItem> pages = [
NavigationPageItem(tr('appsString'), Icons.apps,
AppsPage(key: GlobalKey<AppsPageState>())),
NavigationPageItem(tr('addApp'), Icons.add, const AddAppPage()),
NavigationPageItem(
'Apps', Icons.apps, AppsPage(key: GlobalKey<AppsPageState>())),
NavigationPageItem('Add App', Icons.add, const AddAppPage()),
NavigationPageItem(
'Import/Export', Icons.import_export, const ImportExportPage()),
NavigationPageItem('Settings', Icons.settings, const SettingsPage())
tr('importExport'), Icons.import_export, const ImportExportPage()),
NavigationPageItem(tr('settings'), Icons.settings, const SettingsPage())
];
@override

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/custom_app_bar.dart';
@@ -41,7 +42,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[
const CustomAppBar(title: 'Import/Export'),
CustomAppBar(title: tr('importExport')),
SliverFillRemaining(
child: Padding(
padding:
@@ -63,10 +64,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
.exportApps()
.then((String path) {
showError(
'Exported to $path', context);
tr('exportedTo', args: [path]),
context);
});
},
child: const Text('Obtainium Export'))),
child: Text(tr('obtainiumExport')))),
const SizedBox(
width: 16,
),
@@ -91,13 +93,15 @@ class _ImportExportPageState extends State<ImportExportPage> {
jsonDecode(data);
} catch (e) {
throw ObtainiumError(
'Invalid input');
tr('invalidInput'));
}
appsProvider
.importApps(data)
.then((value) {
showError(
'$value App${value == 1 ? '' : 's'} Imported',
tr('importedX', args: [
plural('apps', value)
]),
context);
});
} else {
@@ -111,7 +115,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
});
});
},
child: const Text('Obtainium Import')))
child: Text(tr('obtainiumImport'))))
],
),
if (importInProgress)
@@ -138,11 +142,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: 'Import from URL List',
title: tr('importFromURLList'),
items: [
[
GeneratedFormItem(
label: 'App URL List',
label: tr('appURLList'),
max: 7,
additionalValidators: [
(String? value) {
@@ -159,7 +163,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
.getSource(
lines[i]);
} catch (e) {
return 'Line ${i + 1}: $e';
return '${tr('line')} ${i + 1}: $e';
}
}
}
@@ -182,7 +186,9 @@ class _ImportExportPageState extends State<ImportExportPage> {
.then((errors) {
if (errors.isEmpty) {
showError(
'Imported ${urls.length} Apps',
tr('importedX', args: [
plural('apps', urls.length)
]),
context);
} else {
showDialog(
@@ -203,8 +209,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
}
});
},
child: const Text(
'Import from URL List',
child: Text(
tr('importFromURLList'),
)),
...sourceProvider.sources
.where((element) => element.canSearch)
@@ -224,13 +230,17 @@ class _ImportExportPageState extends State<ImportExportPage> {
builder:
(BuildContext ctx) {
return GeneratedFormModal(
title:
'Search ${source.runtimeType}',
title: tr('searchX',
args: [
source
.runtimeType
.toString()
]),
items: [
[
GeneratedFormItem(
label:
'${source.runtimeType} Search Query')
label: tr(
'searchQuery'))
]
],
defaultValues: const [],
@@ -272,7 +282,13 @@ class _ImportExportPageState extends State<ImportExportPage> {
if (errors.isEmpty) {
// ignore: use_build_context_synchronously
showError(
'Imported ${selectedUrls.length} Apps',
tr('importedX',
args: [
plural(
'app',
selectedUrls
.length)
]),
context);
} else {
showDialog(
@@ -291,7 +307,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
}
} else {
throw ObtainiumError(
'No results found');
tr('noResults'));
}
}
}()
@@ -303,8 +319,9 @@ class _ImportExportPageState extends State<ImportExportPage> {
});
});
},
child: Text(
'Search ${source.runtimeType}'))
child: Text(tr('searchX', args: [
source.runtimeType.toString()
])))
]))
.toList(),
...sourceProvider.massUrlSources
@@ -323,8 +340,10 @@ class _ImportExportPageState extends State<ImportExportPage> {
builder:
(BuildContext ctx) {
return GeneratedFormModal(
title:
'Import ${source.name}',
title: tr('importX',
args: [
source.name
]),
items:
source
.requiredArgs
@@ -363,7 +382,13 @@ class _ImportExportPageState extends State<ImportExportPage> {
if (errors.isEmpty) {
// ignore: use_build_context_synchronously
showError(
'Imported ${selectedUrls.length} Apps',
tr('importedX',
args: [
plural(
'app',
selectedUrls
.length)
]),
context);
} else {
showDialog(
@@ -390,17 +415,17 @@ class _ImportExportPageState extends State<ImportExportPage> {
});
});
},
child: Text('Import ${source.name}'))
child: Text(
tr('importX', args: [source.name])))
]))
.toList(),
const Spacer(),
const Divider(
height: 32,
),
const Text(
'Imported Apps may incorrectly show as "Not Installed".\nTo fix this, re-install them through Obtainium.\nThis should not affect App data.\n\nOnly affects URL and third-party import methods.',
Text(tr('importedAppsIdDisclaimer'),
textAlign: TextAlign.center,
style: TextStyle(
style: const TextStyle(
fontStyle: FontStyle.italic, fontSize: 12)),
const SizedBox(
height: 8,
@@ -427,16 +452,19 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
Widget build(BuildContext context) {
return AlertDialog(
scrollable: true,
title: const Text('Import Errors'),
title: Text(tr('importErrors')),
content:
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Text(
'${widget.urlsLength - widget.errors.length} of ${widget.urlsLength} Apps imported.',
tr('importedXOfYApps', args: [
(widget.urlsLength - widget.errors.length).toString(),
widget.urlsLength.toString()
]),
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 16),
Text(
'The following URLs had errors:',
tr('followingURLsHadErrors'),
style: Theme.of(context).textTheme.bodyLarge,
),
...widget.errors.map((e) {
@@ -459,7 +487,7 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
onPressed: () {
Navigator.of(context).pop(null);
},
child: const Text('Okay'))
child: Text(tr('okay')))
],
);
}
@@ -505,8 +533,8 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
Widget build(BuildContext context) {
return AlertDialog(
scrollable: true,
title:
Text(widget.onlyOneSelectionAllowed ? 'Select URL' : 'Select URLs'),
title: Text(
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
content: Column(children: [
...urlWithDescriptionSelections.keys.map((urlWithD) {
return Row(children: [
@@ -564,7 +592,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel')),
child: Text(tr('cancel'))),
TextButton(
onPressed:
urlWithDescriptionSelections.values.where((b) => b).isEmpty
@@ -577,8 +605,14 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
.toList());
},
child: Text(widget.onlyOneSelectionAllowed
? 'Pick'
: 'Import ${urlWithDescriptionSelections.values.where((b) => b).length} URLs'))
? tr('pick')
: tr('importX', args: [
plural(
'url',
urlWithDescriptionSelections.values
.where((b) => b)
.length)
])))
],
);
}

View File

@@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart';
@@ -26,20 +27,20 @@ class _SettingsPageState extends State<SettingsPage> {
}
var themeDropdown = DropdownButtonFormField(
decoration: const InputDecoration(labelText: 'Theme'),
decoration: InputDecoration(labelText: tr('theme')),
value: settingsProvider.theme,
items: const [
items: [
DropdownMenuItem(
value: ThemeSettings.dark,
child: Text('Dark'),
child: Text(tr('dark')),
),
DropdownMenuItem(
value: ThemeSettings.light,
child: Text('Light'),
child: Text(tr('light')),
),
DropdownMenuItem(
value: ThemeSettings.system,
child: Text('Follow System'),
child: Text(tr('followSystem')),
)
],
onChanged: (value) {
@@ -49,16 +50,16 @@ class _SettingsPageState extends State<SettingsPage> {
});
var colourDropdown = DropdownButtonFormField(
decoration: const InputDecoration(labelText: 'Colour'),
decoration: InputDecoration(labelText: tr('colour')),
value: settingsProvider.colour,
items: const [
items: [
DropdownMenuItem(
value: ColourSettings.basic,
child: Text('Obtainium'),
child: Text(tr('obtainium')),
),
DropdownMenuItem(
value: ColourSettings.materialYou,
child: Text('Material You'),
child: Text(tr('materialYou')),
)
],
onChanged: (value) {
@@ -68,20 +69,20 @@ class _SettingsPageState extends State<SettingsPage> {
});
var sortDropdown = DropdownButtonFormField(
decoration: const InputDecoration(labelText: 'App Sort By'),
decoration: InputDecoration(labelText: tr('appSortBy')),
value: settingsProvider.sortColumn,
items: const [
items: [
DropdownMenuItem(
value: SortColumnSettings.authorName,
child: Text('Author/Name'),
child: Text(tr('authorName')),
),
DropdownMenuItem(
value: SortColumnSettings.nameAuthor,
child: Text('Name/Author'),
child: Text(tr('nameAuthor')),
),
DropdownMenuItem(
value: SortColumnSettings.added,
child: Text('As Added'),
child: Text(tr('asAdded')),
)
],
onChanged: (value) {
@@ -91,16 +92,16 @@ class _SettingsPageState extends State<SettingsPage> {
});
var orderDropdown = DropdownButtonFormField(
decoration: const InputDecoration(labelText: 'App Sort Order'),
decoration: InputDecoration(labelText: tr('appSortOrder')),
value: settingsProvider.sortOrder,
items: const [
items: [
DropdownMenuItem(
value: SortOrderSettings.ascending,
child: Text('Ascending'),
child: Text(tr('ascending')),
),
DropdownMenuItem(
value: SortOrderSettings.descending,
child: Text('Descending'),
child: Text(tr('descending')),
),
],
onChanged: (value) {
@@ -110,8 +111,7 @@ class _SettingsPageState extends State<SettingsPage> {
});
var intervalDropdown = DropdownButtonFormField(
decoration: const InputDecoration(
labelText: 'Background Update Checking Interval'),
decoration: InputDecoration(labelText: tr('bgUpdateCheckInterval')),
value: settingsProvider.updateInterval,
items: updateIntervals.map((e) {
int displayNum = (e < 60
@@ -120,15 +120,13 @@ class _SettingsPageState extends State<SettingsPage> {
? e / 60
: e / 1440)
.round();
var displayUnit = (e < 60
? 'Minute'
: e < 1440
? 'Hour'
: 'Day');
String display = e == 0
? 'Never - Manual Only'
: '$displayNum $displayUnit${displayNum == 1 ? '' : 's'}';
? tr('neverManualOnly')
: (e < 60
? plural('minute', displayNum)
: e < 1440
? plural('hour', displayNum)
: plural('day', displayNum));
return DropdownMenuItem(value: e, child: Text(display));
}).toList(),
onChanged: (value) {
@@ -167,7 +165,7 @@ class _SettingsPageState extends State<SettingsPage> {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[
const CustomAppBar(title: 'Settings'),
CustomAppBar(title: tr('settings')),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
@@ -177,7 +175,7 @@ class _SettingsPageState extends State<SettingsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Appearance',
tr('appearance'),
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
@@ -200,7 +198,7 @@ class _SettingsPageState extends State<SettingsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Show Source Webpage in App View'),
Text(tr('showWebInAppView')),
Switch(
value: settingsProvider.showAppWebpage,
onChanged: (value) {
@@ -212,7 +210,7 @@ class _SettingsPageState extends State<SettingsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Pin Updates to Top of Apps View'),
Text(tr('pinUpdates')),
Switch(
value: settingsProvider.pinUpdates,
onChanged: (value) {
@@ -225,7 +223,7 @@ class _SettingsPageState extends State<SettingsPage> {
),
height16,
Text(
'Updates',
tr('updates'),
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
@@ -234,7 +232,7 @@ class _SettingsPageState extends State<SettingsPage> {
height: 48,
),
Text(
'Source-Specific',
tr('sourceSpecific'),
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
@@ -256,15 +254,15 @@ class _SettingsPageState extends State<SettingsPage> {
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.code),
label: const Text(
'App Source',
label: Text(
tr('appSource'),
),
),
TextButton.icon(
onPressed: () {
context.read<LogsProvider>().get().then((logs) {
if (logs.isEmpty) {
showError(ObtainiumError('No Logs'), context);
showError(ObtainiumError(tr('noLogs')), context);
} else {
showDialog(
context: context,
@@ -275,7 +273,7 @@ class _SettingsPageState extends State<SettingsPage> {
});
},
icon: const Icon(Icons.bug_report_outlined),
label: const Text('App Logs')),
label: Text(tr('appLogs'))),
],
),
height16,
@@ -306,7 +304,7 @@ class _LogsDialogState extends State<LogsDialog> {
.then((value) {
setState(() {
String l = value.map((e) => e.toString()).join('\n\n');
logString = l.isNotEmpty ? l : 'No Logs';
logString = l.isNotEmpty ? l : tr('noLogs');
});
});
}
@@ -317,7 +315,7 @@ class _LogsDialogState extends State<LogsDialog> {
return AlertDialog(
scrollable: true,
title: const Text('Obtainium App Logs'),
title: Text(tr('appLogs')),
content: Column(
children: [
DropdownButtonFormField(
@@ -325,7 +323,7 @@ class _LogsDialogState extends State<LogsDialog> {
items: days
.map((e) => DropdownMenuItem(
value: e,
child: Text('$e Day${e == 1 ? '' : 's'}'),
child: Text(plural('day', e)),
))
.toList(),
onChanged: (d) {
@@ -342,13 +340,13 @@ class _LogsDialogState extends State<LogsDialog> {
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close')),
child: Text(tr('close'))),
TextButton(
onPressed: () {
Share.share(logString ?? '', subject: 'Obtainium App Logs');
Share.share(logString ?? '', subject: tr('appLogs'));
Navigator.of(context).pop();
},
child: const Text('Share'))
child: Text(tr('share')))
],
);
}