mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-27 21:30:23 +02:00
i18n: extract hardcoded strings into l10n keys
Move hardcoded UI strings across multiple screens and the notification service into ARB-backed l10n keys so they can be translated via Crowdin. Adds 62 new keys covering sort labels, dialog copy, metadata error snackbars, folder-picker errors, home-tab error states, extensions home feed selector, and all notification titles/bodies. NotificationService now caches an AppLocalizations instance (injected from MainShell via didChangeDependencies) and falls back to English literals when no locale is available.
This commit is contained in:
@@ -3124,7 +3124,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: FilterChip(
|
||||
label: const Text('All'),
|
||||
label: Text(context.l10n.historyFilterAll),
|
||||
selected: selectedFilter == null,
|
||||
onSelected: (_) {
|
||||
ref.read(trackProvider.notifier).setSearchFilter(null);
|
||||
@@ -4212,7 +4212,7 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
|
||||
|
||||
if (result == null) {
|
||||
setState(() {
|
||||
_error = 'Failed to load album';
|
||||
_error = context.l10n.errorLoadAlbum;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
@@ -4221,7 +4221,7 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
|
||||
final trackList = result['tracks'] as List<dynamic>?;
|
||||
if (trackList == null) {
|
||||
setState(() {
|
||||
_error = 'No tracks found';
|
||||
_error = context.l10n.errorNoTracksFound;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
@@ -4243,7 +4243,7 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_error = 'Error: $e';
|
||||
_error = context.l10n.snackbarError(e.toString());
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -4376,7 +4376,7 @@ class _ExtensionPlaylistScreenState
|
||||
|
||||
if (result == null) {
|
||||
setState(() {
|
||||
_error = 'Failed to load playlist';
|
||||
_error = context.l10n.errorLoadPlaylist;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
@@ -4385,7 +4385,7 @@ class _ExtensionPlaylistScreenState
|
||||
final trackList = result['tracks'] as List<dynamic>?;
|
||||
if (trackList == null) {
|
||||
setState(() {
|
||||
_error = 'No tracks found';
|
||||
_error = context.l10n.errorNoTracksFound;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
@@ -4402,7 +4402,7 @@ class _ExtensionPlaylistScreenState
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_error = 'Error: $e';
|
||||
_error = context.l10n.snackbarError(e.toString());
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -4528,7 +4528,7 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen> {
|
||||
|
||||
if (result == null) {
|
||||
setState(() {
|
||||
_error = 'Failed to load artist';
|
||||
_error = context.l10n.errorLoadArtist;
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
@@ -4562,7 +4562,7 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen> {
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_error = 'Error: $e';
|
||||
_error = context.l10n.snackbarError(e.toString());
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:spotiflac_android/screens/settings/settings_tab.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/services/shell_navigation_service.dart';
|
||||
import 'package:spotiflac_android/services/share_intent_service.dart';
|
||||
import 'package:spotiflac_android/services/notification_service.dart';
|
||||
import 'package:spotiflac_android/services/update_checker.dart';
|
||||
import 'package:spotiflac_android/widgets/update_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
@@ -47,6 +48,12 @@ class _MainShellState extends ConsumerState<MainShell>
|
||||
final GlobalKey<NavigatorState> _repoTabNavigatorKey =
|
||||
ShellNavigationService.repoTabNavigatorKey;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
NotificationService().updateStrings(context.l10n);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
@@ -2174,13 +2174,13 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
),
|
||||
FilterChip(
|
||||
label: const Text('A-Z'),
|
||||
label: Text(context.l10n.sortAlphaAsc),
|
||||
selected: tempSortMode == 'a-z',
|
||||
onSelected: (_) =>
|
||||
setSheetState(() => tempSortMode = 'a-z'),
|
||||
),
|
||||
FilterChip(
|
||||
label: const Text('Z-A'),
|
||||
label: Text(context.l10n.sortAlphaDesc),
|
||||
selected: tempSortMode == 'z-a',
|
||||
onSelected: (_) =>
|
||||
setSheetState(() => tempSortMode = 'z-a'),
|
||||
@@ -5461,18 +5461,18 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Cancel download?'),
|
||||
title: Text(context.l10n.cancelDownloadTitle),
|
||||
content: Text(
|
||||
'This will cancel the active download for "${item.track.name}".',
|
||||
context.l10n.cancelDownloadContent(item.track.name),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: const Text('Keep'),
|
||||
child: Text(context.l10n.cancelDownloadKeep),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(context.l10n.dialogCancel),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1359,7 +1359,9 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
if (ctx.mounted) {
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to open folder picker: $e'),
|
||||
content: Text(
|
||||
ctx.l10n.snackbarFolderPickerFailed(e.toString()),
|
||||
),
|
||||
backgroundColor: Theme.of(ctx).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
|
||||
@@ -735,7 +735,7 @@ class _SearchProviderSelector extends ConsumerWidget {
|
||||
(entry) => ListTile(
|
||||
leading: Icon(Icons.search, color: colorScheme.tertiary),
|
||||
title: Text(entry.value),
|
||||
subtitle: Text('Search with ${entry.value}'),
|
||||
subtitle: Text(ctx.l10n.extensionsSearchWith(entry.value)),
|
||||
trailing: settings.searchProvider == entry.key
|
||||
? Icon(Icons.check_circle, color: colorScheme.primary)
|
||||
: Icon(Icons.circle_outlined, color: colorScheme.outline),
|
||||
@@ -791,7 +791,7 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
|
||||
final hasAnyProvider = homeFeedProviders.isNotEmpty;
|
||||
|
||||
String currentProviderName = 'Auto';
|
||||
String currentProviderName = context.l10n.extensionsHomeFeedAuto;
|
||||
if (settings.homeFeedProvider != null &&
|
||||
settings.homeFeedProvider!.isNotEmpty) {
|
||||
final ext = homeFeedProviders
|
||||
@@ -828,7 +828,7 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Home Feed Provider',
|
||||
context.l10n.extensionsHomeFeedProvider,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: !hasAnyProvider ? colorScheme.outline : null,
|
||||
),
|
||||
@@ -836,7 +836,7 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
!hasAnyProvider
|
||||
? 'No extensions with home feed'
|
||||
? context.l10n.extensionsNoHomeFeedExtensions
|
||||
: currentProviderName,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
@@ -883,7 +883,7 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Text(
|
||||
'Home Feed Provider',
|
||||
ctx.l10n.extensionsHomeFeedProvider,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
@@ -892,7 +892,7 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
|
||||
child: Text(
|
||||
'Choose which extension provides the home feed on the main screen',
|
||||
ctx.l10n.extensionsHomeFeedDescription,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
@@ -900,8 +900,8 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.auto_awesome, color: colorScheme.primary),
|
||||
title: const Text('Auto'),
|
||||
subtitle: const Text('Automatically select the best available'),
|
||||
title: Text(ctx.l10n.extensionsHomeFeedAuto),
|
||||
subtitle: Text(ctx.l10n.extensionsHomeFeedAutoSubtitle),
|
||||
trailing:
|
||||
(settings.homeFeedProvider == null ||
|
||||
settings.homeFeedProvider!.isEmpty)
|
||||
@@ -917,7 +917,9 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
(ext) => ListTile(
|
||||
leading: Icon(Icons.extension, color: colorScheme.secondary),
|
||||
title: Text(ext.displayName),
|
||||
subtitle: Text('Use ${ext.displayName} home feed'),
|
||||
subtitle: Text(
|
||||
ctx.l10n.extensionsHomeFeedUse(ext.displayName),
|
||||
),
|
||||
trailing: settings.homeFeedProvider == ext.id
|
||||
? Icon(Icons.check_circle, color: colorScheme.primary)
|
||||
: Icon(Icons.circle_outlined, color: colorScheme.outline),
|
||||
|
||||
@@ -307,9 +307,9 @@ class OptionsSettingsPage extends ConsumerWidget {
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context); // Close loading dialog
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error: $e')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarError(e.toString()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +339,9 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to open folder picker: $e'),
|
||||
content: Text(
|
||||
context.l10n.snackbarFolderPickerFailed(e.toString()),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
@@ -430,9 +432,9 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
if (mounted) context.go('/tutorial');
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error: $e')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarError(e.toString()))),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
@@ -5025,9 +5025,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
if (ffmpegResult == null) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Failed to save metadata via FFmpeg'),
|
||||
),
|
||||
SnackBar(content: Text(context.l10n.metadataSaveFailedFfmpeg)),
|
||||
);
|
||||
}
|
||||
setState(() => _saving = false);
|
||||
@@ -5038,9 +5036,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
final ok = await PlatformBridge.writeTempToSaf(ffmpegResult, safUri);
|
||||
if (!ok && mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Failed to write metadata back to storage'),
|
||||
),
|
||||
SnackBar(content: Text(context.l10n.metadataSaveFailedStorage)),
|
||||
);
|
||||
setState(() => _saving = false);
|
||||
return;
|
||||
@@ -5094,7 +5090,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Edit Metadata',
|
||||
context.l10n.trackEditMetadata,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -5107,7 +5103,10 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
else
|
||||
FilledButton(onPressed: _save, child: const Text('Save')),
|
||||
FilledButton(
|
||||
onPressed: _save,
|
||||
child: Text(context.l10n.dialogSave),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user