Merge pull request #2524 from KRTirtho/dev

Release 4.0.1
This commit is contained in:
Kingkor Roy Tirtho 2025-03-15 17:21:27 +06:00 committed by GitHub
commit 723b6b1f38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 43830 additions and 327 deletions

View File

@ -1,3 +1,3 @@
{
"flutterSdkVersion": "3.29.0"
"flutterSdkVersion": "3.29.2"
}

2
.fvmrc
View File

@ -1,4 +1,4 @@
{
"flutter": "3.29.0",
"flutter": "3.29.2",
"flavors": {}
}

View File

@ -4,7 +4,7 @@ on:
pull_request:
env:
FLUTTER_VERSION: 3.29.0
FLUTTER_VERSION: 3.29.2
jobs:
lint:

View File

@ -20,7 +20,7 @@ on:
description: Dry run without uploading to release
env:
FLUTTER_VERSION: 3.29.0
FLUTTER_VERSION: 3.29.2
FLUTTER_CHANNEL: master
permissions:

View File

@ -28,5 +28,5 @@
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
"*.dart": "${capture}.g.dart,${capture}.freezed.dart"
},
"dart.flutterSdkPath": ".fvm/versions/3.29.0"
"dart.flutterSdkPath": ".fvm/versions/3.29.2"
}

View File

@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [4.0.1](https://github.com/krtirtho/spotube/compare/v4.0.0...v4.0.1) (2025-03-15)
### Bug Fixes
- **android**: navigation overlaying in app navigation
- add to playlist not working in smaller screen devices
- language picker search broken
- **generate_playlist**: create playlist not adding tracks nor navigating to playlist page
- **desktop**: double titlebar in local library folders and massive space in overlay player
- lastfm form broken in other locales #2447
- spotify login broken due to new totp requirement #2494
- spotify authentication 429 errors
### Features
- **local_library**: add support for x-flac, opus and x-wav
- **translation**: add tagalog language support #2504
- **translation**: add tamil translation for spotube #2501
## [4.0.0](https://github.com/krtirtho/spotube/compare/v3.9.0...v4.0.0) (2025-03-07)
## Changes

View File

@ -325,8 +325,9 @@ If you are concerned, you can [read the reason of choosing this license](https:/
1. [youtube_explode_dart](https://github.com/Hexer10/youtube_explode_dart) - A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
1. [http_parser](https://pub.dev/packages/http_parser) - A platform-independent package for parsing and serializing HTTP formats.
1. [collection](https://pub.dev/packages/collection) - Collections and utilities functions and classes related to collections.
1. [otp_util](https://github.com/dushiling) - otp_util is a dart package to generate and verify one-time passwords,it It provides two methods TOPT and HOTP.They are Time-based OTPs and Counter-based OTPs.
1. [dio_http2_adapter](https://github.com/cfug/dio) - An adapter that combines HTTP/2 and dio. Supports reusing connections, header compression, etc.
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
1. [crypto](https://pub.dev/packages/crypto) - Implementations of SHA, MD5, and HMAC cryptographic functions.
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
1. [flutter_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.

View File

@ -1,4 +1,5 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-dir: lib/l10n/generated
untranslated-messages-file: untranslated_messages.json
synthetic-package: false

View File

@ -13,12 +13,6 @@ class FontFamily {
/// Font family: BootstrapIcons
static const String bootstrapIcons = 'BootstrapIcons';
/// Font family: GeistMono
static const String geistMono = 'GeistMono';
/// Font family: GeistSans
static const String geistSans = 'GeistSans';
/// Font family: RadixIcons
static const String radixIcons = 'RadixIcons';
}

View File

@ -625,10 +625,10 @@ abstract class LanguageLocals {
// name: "Swedish",
// nativeName: "svenska",
// ),
// "ta": const ISOLanguageName(
// name: "Tamil",
// nativeName: "தமிழ்",
// ),
"ta": const ISOLanguageName(
name: "Tamil",
nativeName: "தமிழ்",
),
// "te": const ISOLanguageName(
// name: "Telugu",
// nativeName: "తెలుగు",
@ -653,10 +653,10 @@ abstract class LanguageLocals {
// name: "Turkmen",
// nativeName: "Türkmen, Түркмен",
// ),
// "tl": const ISOLanguageName(
// name: "Tagalog",
// nativeName: "Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔",
// ),
"tl": const ISOLanguageName(
name: "Tagalog",
nativeName: "Wikang Tagalog",
),
// "tn": const ISOLanguageName(
// name: "Tswana",
// nativeName: "Setswana",

View File

@ -75,17 +75,17 @@ class AppRouter extends RootStackRouter {
path: "local",
page: UserLocalLibraryRoute.page,
),
AutoRoute(
path: "local/folder",
page: LocalLibraryRoute.page,
// parentNavigatorKey: shellRouteNavigatorKey,
),
AutoRoute(
path: "downloads",
page: UserDownloadsRoute.page,
),
],
),
AutoRoute(
path: "local/folder",
page: LocalLibraryRoute.page,
// parentNavigatorKey: shellRouteNavigatorKey,
),
AutoRoute(
path: "library/generate",
page: PlaylistGeneratorRoute.page,

View File

@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:spotube/l10n/l10n.dart';
class SideBarTiles {
final IconData icon;

View File

@ -28,7 +28,7 @@ class AdaptiveMenuButton<T> extends MenuButton {
/// or equal to 640px
/// In smaller screen, a [IconButton] with a [showModalBottomSheet] is shown
class AdaptivePopSheetList<T> extends StatelessWidget {
final List<AdaptiveMenuButton<T>> children;
final List<AdaptiveMenuButton<T>> Function(BuildContext context) items;
final Widget? icon;
final Widget? child;
final bool useRootNavigator;
@ -43,7 +43,7 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
const AdaptivePopSheetList({
super.key,
required this.children,
required this.items,
this.icon,
this.child,
this.useRootNavigator = true,
@ -59,7 +59,8 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
Future<void> showDropdownMenu(BuildContext context, Offset position) async {
final mediaQuery = MediaQuery.of(context);
final childrenModified = children.map((s) {
List<MenuButton> childrenModified(BuildContext context) =>
items(context).map((s) {
if (s.onPressed == null) {
return MenuButton(
key: s.key,
@ -92,7 +93,7 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
position: position,
builder: (context) {
return DropdownMenu(
children: childrenModified,
children: childrenModified(context),
);
},
).future;
@ -109,11 +110,12 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
),
backgroundColor: context.theme.colorScheme.card,
builder: (context) {
final children = childrenModified(context);
return ListView.builder(
itemCount: childrenModified.length,
itemCount: children.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final data = childrenModified[index];
final data = children[index];
return Button(
enabled: data.enabled,

View File

@ -1,126 +0,0 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class AnimateGradient extends HookWidget {
const AnimateGradient({
super.key,
required this.primaryColors,
required this.secondaryColors,
this.child,
this.primaryBegin,
this.primaryEnd,
this.secondaryBegin,
this.secondaryEnd,
AnimationController? controller,
this.duration = const Duration(seconds: 4),
this.animateAlignments = true,
this.reverse = true,
}) : assert(primaryColors.length >= 2),
assert(primaryColors.length == secondaryColors.length),
_controller = controller;
/// [controller]: pass this to have a fine control over the [Animation]
final AnimationController? _controller;
/// [duration]: Time to switch between [Gradient].
/// By default its value is [Duration(seconds:4)]
final Duration duration;
/// [primaryColors]: These will be the starting colors of the [Animation].
final List<Color> primaryColors;
/// [secondaryColors]: These Colors are those in which the [primaryColors] will transition into.
final List<Color> secondaryColors;
/// [primaryBegin]: This is begin [Alignment] for [primaryColors].
/// By default its value is [Alignment.topLeft]
final Alignment? primaryBegin;
/// [primaryBegin]: This is end [Alignment] for [primaryColors].
/// By default its value is [Alignment.topRight]
final Alignment? primaryEnd;
/// [secondaryBegin]: This is begin [Alignment] for [secondaryColors].
/// By default its value is [Alignment.bottomLeft]
final Alignment? secondaryBegin;
/// [secondaryEnd]: This is end [Alignment] for [secondaryColors].
/// By default its value is [Alignment.bottomRight]
final Alignment? secondaryEnd;
/// [animateAlignments]: set to false if you don't want to animate the alignments.
/// This can provide you way cooler animations
final bool animateAlignments;
/// [reverse]: set it to false if you don't want to reverse the animation.
/// using that it will go into one direction only
final bool reverse;
final Widget? child;
@override
Widget build(BuildContext context) {
// ignore: no_leading_underscores_for_local_identifiers
final __controller = useAnimationController(
duration: duration,
)..repeat(reverse: reverse);
final controller = _controller ?? __controller;
final animation = useMemoized(
() => CurvedAnimation(
parent: controller,
curve: Curves.easeInOut,
),
[controller]);
final colorTween = useMemoized(
() => primaryColors.map((color) {
return ColorTween(
begin: color,
end: color,
);
}).toList(),
[primaryColors]);
final colors = useMemoized(
() => colorTween.map((color) {
return color.evaluate(animation)!;
}).toList(),
[colorTween, animation]);
final begin = useMemoized(
() => AlignmentTween(
begin: primaryBegin ?? Alignment.topLeft,
end: primaryEnd ?? Alignment.topRight,
),
[primaryBegin, primaryEnd]);
final end = useMemoized(
() => AlignmentTween(
begin: secondaryBegin ?? Alignment.bottomLeft,
end: secondaryEnd ?? Alignment.bottomRight,
),
[secondaryBegin, secondaryEnd]);
return AnimatedBuilder(
animation: animation,
child: useMemoized(() => child, [child]),
builder: (BuildContext context, Widget? child) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: animateAlignments
? begin.evaluate(animation)
: (primaryBegin as Alignment),
end: animateAlignments
? end.evaluate(animation)
: primaryEnd as Alignment,
colors: colors,
),
),
child: child,
);
},
);
}
}

View File

@ -1,24 +0,0 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';
class SpotubePage<T> extends MaterialPage<T> {
const SpotubePage({required super.child});
}
// class SpotubeSlidePage extends CustomTransitionPage {
// SpotubeSlidePage({
// required super.child,
// super.key,
// }) : super(
// reverseTransitionDuration: const Duration(milliseconds: 150),
// transitionDuration: const Duration(milliseconds: 150),
// transitionsBuilder: (context, animation, secondaryAnimation, child) {
// return SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(1, 0),
// end: Offset.zero,
// ).animate(animation),
// child: child,
// );
// },
// );
// }

View File

@ -166,7 +166,7 @@ class TrackPresentationActionsSection extends HookConsumerWidget {
},
icon: const Icon(SpotubeIcons.moreVertical),
variance: ButtonVariance.outline,
children: [
items: (context) => [
AdaptiveMenuButton(
value: "download",
leading: const Icon(SpotubeIcons.download),

View File

@ -23,7 +23,7 @@ class SortTracksDropdown extends StatelessWidget {
onSelected: onChanged,
tooltip: context.l10n.sort_tracks,
icon: const Icon(SpotubeIcons.sort),
children: [
items: (context) => [
AdaptiveMenuButton(
value: SortBy.none,
enabled: value != SortBy.none,

View File

@ -90,13 +90,27 @@ class TrackOptions extends HookConsumerWidget {
BuildContext context,
Track track,
) {
showDialog(
/// showDialog doesn't work for some reason. So we have to
/// manually push a Dialog Route in the Navigator to get it working
Navigator.push(
context,
DialogRoute(
alignment: Alignment.bottomCenter,
transitionBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
context: context,
builder: (context) => PlaylistAddTrackDialog(
barrierColor: Colors.black.withValues(alpha: 0.5),
builder: (context) {
return Center(
child: PlaylistAddTrackDialog(
tracks: [track],
openFromPlaylist: playlistId,
),
);
},
),
);
}
void actionStartRadio(
@ -352,7 +366,7 @@ class TrackOptions extends HookConsumerWidget {
),
),
],
children: [
items: (context) => [
if (isLocalTrack)
AdaptiveMenuButton(
value: TrackOptionValue.delete,

View File

@ -1,5 +1,5 @@
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:spotube/l10n/l10n.dart';
extension AppLocale on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this)!;

428
lib/l10n/app_ta.arb Normal file
View File

@ -0,0 +1,428 @@
{
"guest": "விருந்தினர்",
"browse": "உலாவு",
"search": "தேடுக",
"library": "நூலகம்",
"lyrics": "பாடல் வரிகள்",
"settings": "அமைப்புகள்",
"genre_categories_filter": "வகைகள் அல்லது பாணிகளை வடிகட்டுக...",
"genre": "பாணி",
"personalized": "தனிப்பயனாக்கப்பட்ட",
"featured": "சிறப்பிடம் பெற்ற",
"new_releases": "புதிய வெளியீடுகள்",
"songs": "பாடல்கள்",
"playing_track": "{track} இயங்குகிறது",
"queue_clear_alert": "இது தற்போதைய வரிசையை அழிக்கும். {track_length} பாடல்கள் நீக்கப்படும்\nதொடர விரும்புகிறீர்களா?",
"load_more": "மேலும் ஏற்றுக",
"playlists": "பாடல் பட்டியல்கள்",
"artists": "கலைஞர்கள்",
"albums": "ஆல்பங்கள்",
"tracks": "பாடல்கள்",
"downloads": "பதிவிறக்கங்கள்",
"filter_playlists": "உங்கள் பாடல் பட்டியல்களை வடிகட்டுக...",
"liked_tracks": "விரும்பிய பாடல்கள்",
"liked_tracks_description": "உங்கள் விரும்பிய பாடல்கள் அனைத்தும்",
"playlist": "பாடல் பட்டியல்",
"create_a_playlist": "பாடல் பட்டியலை உருவாக்குக",
"update_playlist": "பாடல் பட்டியலைப் புதுப்பிக்க",
"create": "உருவாக்கு",
"cancel": "ரத்து செய்",
"update": "புதுப்பி",
"playlist_name": "பாடல் பட்டியல் பெயர்",
"name_of_playlist": "பாடல் பட்டியலின் பெயர்",
"description": "விளக்கம்",
"public": "பொது",
"collaborative": "கூட்டு",
"search_local_tracks": "உள்ளூர் பாடல்களைத் தேடுக...",
"play": "இயக்கு",
"delete": "அழி",
"none": "எதுவுமில்லை",
"sort_a_z": "A-Z வரிசைப்படுத்து",
"sort_z_a": "Z-A வரிசைப்படுத்து",
"sort_artist": "கலைஞர் மூலம் வரிசைப்படுத்து",
"sort_album": "ஆல்பம் மூலம் வரிசைப்படுத்து",
"sort_duration": "கால அளவு மூலம் வரிசைப்படுத்து",
"sort_tracks": "பாடல்களை வரிசைப்படுத்து",
"currently_downloading": "தற்போது பதிவிறக்குகிறது ({tracks_length})",
"cancel_all": "அனைத்தையும் ரத்து செய்",
"filter_artist": "கலைஞர்களை வடிகட்டுக...",
"followers": "{followers} பின்தொடர்பவர்கள்",
"add_artist_to_blacklist": "கலைஞரை தடைப்பட்டியலில் சேர்க்க",
"top_tracks": "சிறந்த பாடல்கள்",
"fans_also_like": "ரசிகர்கள் விரும்புவது",
"loading": "ஏற்றுகிறது...",
"artist": "கலைஞர்",
"blacklisted": "தடைப்பட்டியலில் உள்ளது",
"following": "பின்தொடர்கிறது",
"follow": "பின்தொடர்",
"artist_url_copied": "கலைஞர் URL கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது",
"added_to_queue": "{tracks} பாடல்கள் வரிசையில் சேர்க்கப்பட்டன",
"filter_albums": "ஆல்பங்களை வடிகட்டுக...",
"synced": "ஒத்திசைக்கப்பட்டது",
"plain": "சாதாரண",
"shuffle": "கலக்கு",
"search_tracks": "பாடல்களைத் தேடுக...",
"released": "வெளியிடப்பட்டது",
"error": "பிழை {error}",
"title": "தலைப்பு",
"time": "நேரம்",
"more_actions": "மேலும் செயல்கள்",
"download_count": "பதிவிறக்கு ({count})",
"add_count_to_playlist": "({count}) பாடல் பட்டியலில் சேர்",
"add_count_to_queue": "({count}) வரிசையில் சேர்",
"play_count_next": "({count}) அடுத்து இயக்கு",
"album": "ஆல்பம்",
"copied_to_clipboard": "{data} கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது",
"add_to_following_playlists": "{track} பின்வரும் பாடல் பட்டியல்களில் சேர்",
"add": "சேர்",
"added_track_to_queue": "{track} வரிசையில் சேர்க்கப்பட்டது",
"add_to_queue": "வரிசையில் சேர்",
"track_will_play_next": "{track} அடுத்து இயக்கப்படும்",
"play_next": "அடுத்து இயக்கு",
"removed_track_from_queue": "{track} வரிசையிலிருந்து நீக்கப்பட்டது",
"remove_from_queue": "வரிசையிலிருந்து நீக்கு",
"remove_from_favorites": "பிடித்தவையிலிருந்து நீக்கு",
"save_as_favorite": "பிடித்தவையாக சேமி",
"add_to_playlist": "பாடல் பட்டியலில் சேர்",
"remove_from_playlist": "பாடல் பட்டியலிலிருந்து நீக்கு",
"add_to_blacklist": "தடைப்பட்டியலில் சேர்",
"remove_from_blacklist": "தடைப்பட்டியலிலிருந்து நீக்கு",
"share": "பகிர்",
"mini_player": "சிறிய இயக்கி",
"slide_to_seek": "முன்னோக்கி அல்லது பின்னோக்கி செல்ல சறுக்கவும்",
"shuffle_playlist": "பாடல் பட்டியலை கலக்கு",
"unshuffle_playlist": "பாடல் பட்டியலை கலக்காதே",
"previous_track": "முந்தைய பாடல்",
"next_track": "அடுத்த பாடல்",
"pause_playback": "இயக்கத்தை நிறுத்து",
"resume_playback": "இயக்கத்தை தொடர்",
"loop_track": "பாடலை சுழற்று",
"no_loop": "சுழற்சி இல்லை",
"repeat_playlist": "பாடல் பட்டியலை மீண்டும் இயக்கு",
"queue": "வரிசை",
"alternative_track_sources": "மாற்று பாடல் மூலங்கள்",
"download_track": "பாடலைப் பதிவிறக்கு",
"tracks_in_queue": "வரிசையில் {tracks} பாடல்கள்",
"clear_all": "அனைத்தையும் அழி",
"show_hide_ui_on_hover": "மேலே வரும்போது UI ஐக் காட்டு/மறை",
"always_on_top": "எப்போதும் மேலே",
"exit_mini_player": "சிறிய இயக்கியிலிருந்து வெளியேறு",
"download_location": "பதிவிறக்க இடம்",
"local_library": "உள்ளூர் நூலகம்",
"add_library_location": "நூலகத்தில் சேர்",
"remove_library_location": "நூலகத்திலிருந்து நீக்கு",
"account": "கணக்கு",
"login_with_spotify": "உங்கள் Spotify கணக்கில் உள்நுழைக",
"connect_with_spotify": "Spotify உடன் இணைக்கவும்",
"logout": "வெளியேறு",
"logout_of_this_account": "இந்த கணக்கிலிருந்து வெளியேறு",
"language_region": "மொழி & பிராந்தியம்",
"language": "மொழி",
"system_default": "கணினி இயல்புநிலை",
"market_place_region": "சந்தை பிராந்தியம்",
"recommendation_country": "பரிந்துரை நாடு",
"appearance": "தோற்றம்",
"layout_mode": "அமைப்பு முறை",
"override_layout_settings": "தளவமைப்பு அமைப்புகளை மாற்றியமை",
"adaptive": "தகவமைப்பு",
"compact": "சுருக்கமான",
"extended": "விரிவான",
"theme": "தீம்",
"dark": "இருள்",
"light": "வெளிர்",
"system": "கணினி வழி",
"accent_color": "அழுத்த நிறம்",
"sync_album_color": "ஆல்பம் நிறத்தை ஒத்திசை",
"sync_album_color_description": "ஆல்பம் படத்தின் முக்கிய நிறத்தை அழுத்த நிறமாகப் பயன்படுத்துகிறது",
"playback": "பின்னணி",
"audio_quality": "ஒலி தரம்",
"high": "உயர்",
"low": "குறைந்த",
"pre_download_play": "முன்பதிவிறக்கம் மற்றும் இயக்கம்",
"pre_download_play_description": "ஒலியை ஸ்ட்ரீம் செய்வதற்குப் பதிலாக, பைட்டுகளைப் பதிவிறக்கி இயக்கவும் (அதிக பேண்ட்விட்த் பயனர்களுக்கு பரிந்துரைக்கப்படுகிறது)",
"skip_non_music": "இசையல்லாத பகுதிகளைத் தவிர் (SponsorBlock)",
"blacklist_description": "தடைசெய்யப்பட்ட பாடல்கள் மற்றும் கலைஞர்கள்",
"wait_for_download_to_finish": "தற்போதைய பதிவிறக்கம் முடியும் வரை காத்திருக்கவும்",
"desktop": "கணினி",
"close_behavior": "மூடும் நடத்தை",
"close": "மூடு",
"minimize_to_tray": "ட்ரேயை குறைக்கவும்",
"show_tray_icon": "ட்ரே ஐகானைக் காட்டு",
"about": "பற்றி",
"u_love_spotube": "நீங்கள் Spotube ஐ நேசிக்கிறீர்கள் என்பது எங்களுக்குத் தெரியும்",
"check_for_updates": "புதுப்பிப்புகளைச் சரிபார்",
"about_spotube": "Spotube பற்றி",
"blacklist": "தடைப்பட்டியல்",
"please_sponsor": "தயவுசெய்து ஆதரவு/நன்கொடை அளியுங்கள்",
"spotube_description": "Spotube, ஒரு லேசான, பல தளங்களில் இயங்கும், அனைவருக்கும் இலவசமான spotify கிளையன்ட்",
"version": "பதிப்பு",
"build_number": "கட்டமைப்பு எண்",
"founder": "நிறுவனர்",
"repository": "களஞ்சியம்",
"bug_issues": "பிழை_சிக்கல்கள்",
"made_with": "வங்காளதேசத்திலிருந்து🇧🇩 ❤️ உருவாக்கப்பட்டது",
"kingkor_roy_tirtho": "கிங்கர் ராய் திர்தோ",
"copyright": "© 2021-{current_year} கிங்கர் ராய் திர்தோ",
"license": "உரிமம்",
"add_spotify_credentials": "தொடங்குவதற்கு உங்கள் spotify சான்றுகளைச் சேர்க்கவும்",
"credentials_will_not_be_shared_disclaimer": "கவலைப்பட வேண்டாம், உங்கள் சான்றுகள் எதுவும் சேகரிக்கப்படாது அல்லது யாருடனும் பகிரப்படாது",
"know_how_to_login": "இதை எப்படி செய்வது என்று தெரியவில்லையா?",
"follow_step_by_step_guide": "படிப்படியான வழிகாட்டியைப் பின்பற்றவும்",
"spotify_cookie": "Spotify {name} நட்புநிரல்",
"cookie_name_cookie": "{name} நட்புநிரல்",
"fill_in_all_fields": "அனைத்து களங்களையும் நிரப்பவும்",
"submit": "சமர்ப்பி",
"exit": "வெளியேறு",
"previous": "முந்தைய",
"next": "அடுத்து",
"done": "முடிந்தது",
"step_1": "முதல் படி",
"first_go_to": "முதலில், செல்லவேண்டியது",
"login_if_not_logged_in": "நீங்கள் உள்நுழையவில்லை என்றால் உள்நுழைக/பதிவுசெய்க",
"step_2": "இரண்டாம் படி",
"step_2_steps": "1. நீங்கள் உள்நுழைந்தவுடன், F12 ஐ அழுத்தவும் அல்லது வலது கிளிக் செய்து > ஆய்வு செய்யவும் உலாவி டெவ்டூல்களைத் திறக்கவும்.\n2. பின்னர் \"பயன்பாடு\" தாவலுக்குச் செல்லவும் (Chrome, Edge, Brave போன்றவை) அல்லது \"சேமிப்பகம்\" தாவல் (Firefox, Palemoon போன்றவை)\n3. \"குக்கிகள்\" பிரிவுக்குச் சென்று பின்னர் \"https://accounts.spotify.com\" பிரிவுக்குச் செல்லவும்",
"step_3": "மூன்றாம் படி",
"step_3_steps": "\"sp_dc\" நட்புநிரலின் மதிப்பை நகலெடுக்கவும்",
"success_emoji": "வெற்றி🥳",
"success_message": "இப்போது நீங்கள் உங்கள் Spotify கணக்கில் வெற்றிகரமாக உள்நுழைந்துள்ளீர்கள். நல்லது, நண்பரே!",
"step_4": "நான்காம் படி",
"step_4_steps": "நகலெடுக்கப்பட்ட \"sp_dc\" மதிப்பை ஒட்டவும்",
"something_went_wrong": "ஏதோ தவறு நடந்துவிட்டது",
"piped_instance": "Piped சேவையகம் நிகழ்வு",
"piped_description": "பாடல் பொருத்தத்திற்குப் பயன்படுத்த வேண்டிய Piped சேவையகம் நிகழ்வு",
"piped_warning": "அவற்றில் சில நன்றாக வேலை செய்யாமல் இருக்கலாம். எனவே உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்",
"invidious_instance": "Invidious சேவையக நிகழ்வு",
"invidious_description": "பாடல் பொருத்தத்திற்குப் பயன்படுத்த வேண்டிய Invidious சேவையக நிகழ்வு",
"invidious_warning": "அவற்றில் சில நன்றாக வேலை செய்யாமல் இருக்கலாம். எனவே உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்",
"generate": "உருவாக்கு",
"track_exists": "பாடல் {track} ஏற்கனவே உள்ளது",
"replace_downloaded_tracks": "பதிவிறக்கம் செய்யப்பட்ட அனைத்து பாடல்களையும் மாற்றவும்",
"skip_download_tracks": "பதிவிறக்கம் செய்யப்பட்ட அனைத்து பாடல்களையும் தவிர்க்கவும்",
"do_you_want_to_replace": "ஏற்கனவே உள்ள பாடலை மாற்ற விரும்புகிறீர்களா?",
"replace": "மாற்று",
"skip": "தவிர்",
"select_up_to_count_type": "{count} {type} வரை தேர்ந்தெடுக்கவும்",
"select_genres": "வகைகளைத் தேர்ந்தெடுக்கவும்",
"add_genres": "வகைகளைச் சேர்க்கவும்",
"country": "நாடு",
"number_of_tracks_generate": "உருவாக்க வேண்டிய பாடல்களின் எண்ணிக்கை",
"acousticness": "அகவுஸ்டிக்னெஸ்",
"danceability": "நடனத்தன்மை",
"energy": "ஆற்றல்",
"instrumentalness": "கருவித்தன்மை",
"liveness": "உயிர்ப்புத்தன்மை",
"loudness": "ஒலி அளவு",
"speechiness": "பேச்சுத்தன்மை",
"valence": "உணர்வு",
"popularity": "பிரபலம்",
"key": "இசை குறிப்பு",
"duration": "கால அளவு (வினாடிகள்)",
"tempo": "வேகம் (BPM)",
"mode": "முறை",
"time_signature": "நேர கையொப்பம்",
"short": "குறுகிய",
"medium": "நடுத்தர",
"long": "நீண்ட",
"min": "குறைந்தபட்சம்",
"max": "அதிகபட்சம்",
"target": "இலக்கு",
"moderate": "மிதமான",
"deselect_all": "அனைத்தையும் தேர்வுநீக்கு",
"select_all": "அனைத்தையும் தேர்ந்தெடு",
"are_you_sure": "உறுதியாக இருக்கிறீர்களா?",
"generating_playlist": "உங்கள் தனிப்பயன்பாட்டிற்கான பாடல் பட்டியலை உருவாக்குகிறது...",
"selected_count_tracks": "{count} பாடல்கள் தேர்ந்தெடுக்கப்பட்டன",
"download_warning": "நீங்கள் அனைத்து பாடல்களையும் மொத்தமாக பதிவிறக்கினால், நீங்கள் தெளிவாக இசையைத் திருடுகிறீர்கள் மற்றும் இசையின் படைப்பாற்றல் சமூகத்திற்கு சேதம் விளைவிக்கிறீர்கள். நீங்கள் இதை அறிந்திருக்கிறீர்கள் என்று நம்புகிறேன். எப்போதும், கலைஞரின் கடின உழைப்பை மதித்து ஆதரிக்க முயற்சி செய்யுங்கள்",
"download_ip_ban_warning": "மேலும், அதிகப்படியான பதிவிறக்க கோரிக்கைகள் காரணமாக உங்கள் IP YouTube இல் தடைசெய்யப்படலாம். IP தடை என்பது குறைந்தது 2-3 மாதங்களுக்கு அந்த IP சாதனத்திலிருந்து YouTube ஐப் பயன்படுத்த முடியாது (நீங்கள் உள்நுழைந்திருந்தாலும் கூட). இது ஒருபோதும் நடந்தால் Spotube பொறுப்பேற்காது",
"by_clicking_accept_terms": "'ஏற்றுக்கொள்' என்பதைக் கிளிக் செய்வதன் மூலம் பின்வரும் விதிமுறைகளுக்கு நீங்கள் ஒப்புக்கொள்கிறீர்கள்:",
"download_agreement_1": "நான் இசையைத் திருடுகிறேன் என்பது எனக்குத் தெரியும். நான் கெட்டவன்",
"download_agreement_2": "நான் கலைஞரை முடிந்தவரை ஆதரிப்பேன், அவர்களின் கலைக்கு பணம் செலுத்த எனக்கு பணம் இல்லாததால் மட்டுமே இதைச் செய்கிறேன்",
"download_agreement_3": "என் IP YouTube இல் தடைசெய்யப்படலாம் என்பதை நான் முழுமையாக அறிவேன், மேலும் என் தற்போதைய செயலால் ஏற்படும் எந்த விபத்துகளுக்கும் Spotube அல்லது அதன் உரிமையாளர்கள்/பங்களிப்பாளர்களை பொறுப்பாக்க மாட்டேன்",
"decline": "மறு",
"accept": "ஏற்றுக்கொள்",
"details": "விவரங்கள்",
"youtube": "YouTube",
"channel": "சேனல்",
"likes": "விருப்பங்கள்",
"dislikes": "விருப்பமில்லாதவை",
"views": "பார்வைகள்",
"streamUrl": "ஸ்ட்ரீம் URL",
"stop": "நிறுத்து",
"sort_newest": "புதிதாக சேர்க்கப்பட்டவற்றை வரிசைப்படுத்து",
"sort_oldest": "பழமையானவற்றை வரிசைப்படுத்து",
"sleep_timer": "உறக்க நேரம்",
"mins": "{minutes} நிமிடங்கள்",
"hours": "{hours} மணிநேரங்கள்",
"hour": "{hours} மணிநேரம்",
"custom_hours": "தனிப்பயன் மணிநேரங்கள்",
"logs": "பதிவுகள்",
"developers": "உருவாக்குநர்கள்",
"not_logged_in": "நீங்கள் உள்நுழையவில்லை",
"search_mode": "தேடல் முறை",
"audio_source": "ஒலி மூலம்",
"ok": "சரி",
"failed_to_encrypt": "குறியாக்கம் தோல்வியடைந்தது",
"encryption_failed_warning": "Spotube உங்கள் தரவை பாதுகாப்பாக சேமிக்க குறியாக்கத்தைப் பயன்படுத்துகிறது. ஆனால் அவ்வாறு செய்ய முடியவில்லை. எனவே இது பாதுகாப்பற்ற சேமிப்பகத்திற்கு மாறும்\nநீங்கள் லினக்ஸ் பயன்படுத்துகிறீர்கள் என்றால், எந்த ரகசிய சேவையும் (gnome-keyring, kde-wallet, keepassxc போன்றவை) நிறுவப்பட்டுள்ளதா என்பதை உறுதிப்படுத்தவும்",
"querying_info": "தகவலைக் கேட்கிறது...",
"piped_api_down": "Piped API செயலிழந்துள்ளது",
"piped_down_error_instructions": "Piped நிகழ்வு {pipedInstance} தற்போது செயலிழந்துள்ளது\n\nநிகழ்வை மாற்றவும் அல்லது 'API வகை'யை அதிகாரப்பூர்வ YouTube API க்கு மாற்றவும்\n\nமாற்றத்திற்குப் பிறகு பயன்பாட்டை மறுதொடக்கம் செய்வதை உறுதிப்படுத்தவும்",
"you_are_offline": "நீங்கள் தற்போது ஆஃப்லைனில் உள்ளீர்கள்",
"connection_restored": "உங்கள் இணைய இணைப்பு மீட்டெடுக்கப்பட்டது",
"use_system_title_bar": "கணினி தலைப்புப் பட்டியைப் பயன்படுத்தவும்",
"crunching_results": "முடிவுகளை செயலாக்குகிறது...",
"search_to_get_results": "முடிவுகளைப் பெற தேடவும்",
"use_amoled_mode": "கருமை நிற இருண்ட தீம்",
"pitch_dark_theme": "AMOLED முறை",
"normalize_audio": "ஒலியை சீரமை",
"change_cover": "அட்டையை மாற்று",
"add_cover": "அட்டையைச் சேர்",
"restore_defaults": "இயல்புநிலைகளை மீட்டமை",
"download_music_codec": "இசை கோடெக்கை பதிவிறக்கு",
"streaming_music_codec": "இசை கோடெக்கை ஸ்ட்ரீம் செய்",
"login_with_lastfm": "Last.fm உடன் உள்நுழைக",
"connect": "இணை",
"disconnect_lastfm": "Last.fm இலிருந்து துண்டி",
"disconnect": "துண்டி",
"username": "பயனர்பெயர்",
"password": "கடவுச்சொல்",
"login": "உள்நுழைக",
"login_with_your_lastfm": "உங்கள் Last.fm கணக்குடன் உள்நுழைக",
"scrobble_to_lastfm": "Last.fm க்கு ஸ்க்ரோபிள் செய்",
"go_to_album": "ஆல்பத்திற்குச் செல்",
"discord_rich_presence": "Discord செழுமையான தோற்றம்",
"browse_all": "அனைத்தையும் உலாவு",
"genres": "வகைகள்",
"explore_genres": "வகைகளை ஆராயுங்கள்",
"friends": "நண்பர்கள்",
"no_lyrics_available": "மன்னிக்கவும், இந்தப் பாடலுக்கான பாடல் வரிகளைக் கண்டுபிடிக்க முடியவில்லை",
"start_a_radio": "வானொலியைத் தொடங்கு",
"how_to_start_radio": "வானொலியை எவ்வாறு தொடங்க விரும்புகிறீர்கள்?",
"replace_queue_question": "தற்போதைய வரிசையை மாற்ற விரும்புகிறீர்களா அல்லது அதனுடன் சேர்க்க விரும்புகிறீர்களா?",
"endless_playback": "முடிவற்ற இயக்கம்",
"delete_playlist": "பாடல் பட்டியலை நீக்கு",
"delete_playlist_confirmation": "இந்த பாடல் பட்டியலை நீக்க விரும்புகிறீர்களா?",
"local_tracks": "உள்ளூர் பாடல்கள்",
"local_tab": "உள்ளூர்",
"song_link": "பாடல் இணைப்பு",
"skip_this_nonsense": "இந்த அர்த்தமற்றதைத் தவிர்",
"freedom_of_music": "\"இசையின் சுதந்திரம்\"",
"freedom_of_music_palm": "\"உங்கள் கைகளில் இசையின் சுதந்திரம்\"",
"get_started": "தொடங்குவோம்",
"youtube_source_description": "பரிந்துரைக்கப்படுகிறது மற்றும் சிறப்பாக செயல்படுகிறது.",
"piped_source_description": "சுதந்திரமாக உணர்கிறீர்களா? YouTube போலவே ஆனால் மிகவும் சுதந்திரமானது.",
"jiosaavn_source_description": "தெற்காசியப் பிராந்தியத்திற்கு சிறந்தது.",
"invidious_source_description": "Piped ஐப் போன்றது ஆனால் அதிக கிடைக்கும் தன்மையுடன்.",
"highest_quality": "உயர்ந்த தரம்: {quality}",
"select_audio_source": "ஒலி மூலத்தைத் தேர்ந்தெடுக்கவும்",
"endless_playback_description": "வரிசையின் இறுதியில் புதிய பாடல்களை\nதானாகவே சேர்க்கவும்",
"choose_your_region": "உங்கள் பிராந்தியத்தைத் தேர்ந்தெடுக்கவும்",
"choose_your_region_description": "இது உங்கள் இருப்பிடத்திற்கான சரியான உள்ளடக்கத்தை\nSpotube காட்ட உதவும்.",
"choose_your_language": "உங்கள் மொழியைத் தேர்ந்தெடுக்கவும்",
"help_project_grow": "இந்த திட்டம் வளர உதவுங்கள்",
"help_project_grow_description": "Spotube ஒரு திறந்த மூல திட்டம். திட்டத்திற்கு பங்களிப்பு செய்வதன் மூலம், பிழைகளைப் புகாரளிப்பதன் மூலம் அல்லது புதிய அம்சங்களைப் பரிந்துரைப்பதன் மூலம் இந்தத் திட்டம் வளர உதவலாம்.",
"contribute_on_github": "GitHub இல் பங்களியுங்கள்",
"donate_on_open_collective": "Open Collective இல் நன்கொடை அளியுங்கள்",
"browse_anonymously": "அநாமதேயமாக உலாவுக",
"enable_connect": "இணைப்பை இயக்கு",
"enable_connect_description": "மற்ற சாதனங்களிலிருந்து Spotube ஐக் கட்டுப்படுத்தவும்",
"devices": "சாதனங்கள்",
"select": "தேர்ந்தெடு",
"connect_client_alert": "நீங்கள் {client} ஆல் கட்டுப்படுத்தப்படுகிறீர்கள்",
"this_device": "இந்த சாதனம்",
"remote": "தொலைநிலை",
"stats": "புள்ளிவிவரங்கள்",
"and_n_more": "மற்றும் {count} கூடுதலாக",
"recently_played": "சமீபத்தில் இயக்கியவை",
"browse_more": "மேலும் உலாவு",
"no_title": "தலைப்பு இல்லை",
"not_playing": "இயக்கப்படவில்லை",
"epic_failure": "மோசமான தோல்வி!",
"added_num_tracks_to_queue": "{tracks_length} பாடல்கள் வரிசையில் சேர்க்கப்பட்டன",
"spotube_has_an_update": "Spotube க்கு ஒரு புதுப்பிப்பு உள்ளது",
"download_now": "இப்போது பதிவிறக்கு",
"nightly_version": "Spotube Nightly {nightlyBuildNum} வெளியிடப்பட்டுள்ளது",
"release_version": "Spotube v{version} வெளியிடப்பட்டுள்ளது",
"read_the_latest": "சமீபத்திய ",
"release_notes": "வெளியீட்டு குறிப்புகளைப் படிக்கவும்",
"pick_color_scheme": "வண்ணத் திட்டத்தைத் தேர்ந்தெடுக்கவும்",
"save": "சேமி",
"choose_the_device": "சாதனத்தைத் தேர்ந்தெடுக்கவும்:",
"multiple_device_connected": "பல சாதனங்கள் இணைக்கப்பட்டுள்ளன.\nஇந்த செயல் நடைபெற வேண்டிய சாதனத்தைத் தேர்ந்தெடுக்கவும்",
"nothing_found": "எதுவும் கிடைக்கவில்லை",
"the_box_is_empty": "பெட்டி காலியாக உள்ளது",
"top_artists": "சிறந்த கலைஞர்கள்",
"top_albums": "சிறந்த ஆல்பங்கள்",
"this_week": "இந்த வாரம்",
"this_month": "இந்த மாதம்",
"last_6_months": "கடந்த 6 மாதங்கள்",
"this_year": "இந்த ஆண்டு",
"last_2_years": "கடந்த 2 ஆண்டுகள்",
"all_time": "எல்லா நேரமும்",
"powered_by_provider": "{providerName} ஆல் இயக்கப்படுகிறது",
"email": "மின்னஞ்சல்",
"profile_followers": "பின்தொடர்பவர்கள்",
"birthday": "பிறந்த நாள்",
"subscription": "சந்தா",
"not_born": "பிறக்கவில்லை",
"hacker": "ஹேக்கர்",
"profile": "சுயவிவரம்",
"no_name": "பெயர் இல்லை",
"edit": "திருத்து",
"user_profile": "பயனர் சுயவிவரம்",
"count_plays": "{count} முறை இசைக்கப்பட்டது",
"streaming_fees_hypothetical": "ஸ்ட்ரீமிங் கட்டணங்கள் (கற்பனை)",
"minutes_listened": "காலம் கேட்டது",
"streamed_songs": "ஸ்ட்ரீமிங் செய்யப்பட்ட பாடல்கள்",
"count_streams": "{count} ஸ்ட்ரீம்கள்",
"owned_by_you": "உங்களால் கொண்டது",
"copied_shareurl_to_clipboard": "நகலெடுக்கப்பட்டது {shareUrl} கிளிப்போர்டுக்காக",
"spotify_hipotetical_calculation": "*இது Spotify இன் ஒவ்வொரு ஸ்ட்ரீமிற்கும்\n$0.003 முதல் $0.005 வரை அளவீடு அடிப்படையில் கணக்கிடப்படுகிறது. இது ஒரு கற்பனை\nகணக்கீடு ஆகும், பயனர் எந்த அளவிற்கு கலைஞர்களுக்கு\nஅதோர் பாடலை Spotify மென்பொருளில் கேட்டால் எவ்வளவு பணம் செலுத்தினார்கள் என்பதைக் கண்டுபிடிக்க.",
"count_mins": "{minutes} நிமிடங்கள்",
"summary_minutes": "நிமிடங்கள்",
"summary_listened_to_music": "இசை கேட்டது",
"summary_songs": "பாடல்கள்",
"summary_streamed_overall": "மொத்தமாக ஸ்ட்ரீமிங்",
"summary_owed_to_artists": "கலைஞர்களுக்கு\nஇந்த மாதம் சொந்தமானது",
"summary_artists": "கலைஞர்கள்",
"summary_music_reached_you": "இசை உங்களுக்கு வந்தது",
"summary_full_albums": "முழு ஆல்பங்கள்",
"summary_got_your_love": "உங்கள் அன்பை பெற்றுக்கொண்டேன்",
"summary_playlists": "பாடல் பட்டியல்கள்",
"summary_were_on_repeat": "மீண்டும் மீண்டும் இருந்தன",
"total_money": "மொத்தம் {money}",
"webview_not_found": "வெப்வியூ கிடைக்கவில்லை",
"webview_not_found_description": "உங்கள் சாதனத்தில் எந்தவொரு வெப்வியூ இயக்கத்தை நிறுவவில்லை.\nஇது நிறுவப்பட்டிருந்தால், சுற்றுச்சூழல் பாதையில் PATH உள்ளது என்பதை உறுதிபடுத்தவும்\n\nநிறுவித்த பிறகு, செயலியை மறுதொடக்கம் செய்யவும்",
"unsupported_platform": "அதிர்ஷ்டகாத உருப்படியை ஆதரிக்கவில்லை",
"cache_music": "இசையை கேஷ் செய்",
"open": "திறக்கவும்",
"cache_folder": "கேஷ் அடைவு",
"export": "ஏற்றுமதி",
"clear_cache": "கேஷ் அழிக்கவும்",
"clear_cache_confirmation": "கேஷைப் அழிக்க விரும்புகிறீர்களா?",
"export_cache_files": "கேஷில் உள்ள கோப்புகளை ஏற்றுமதி செய்யவும்",
"found_n_files": "{count} கோப்புகள் கிடைத்தன",
"export_cache_confirmation": "இந்த கோப்புகளை ஏற்றுமதி செய்ய விரும்புகிறீர்களா?",
"exported_n_out_of_m_files": "{filesExported} கோப்புகள் ஏற்றுமதி செய்யப்பட்டன, {files} கோப்புகளில்",
"undo": "செயல்தவிர்",
"download_all": "அனைத்தையும் பதிவிறக்குக",
"add_all_to_playlist": "அனைத்தையும் பாடல் பட்டியலில் சேர்க்கவும்",
"add_all_to_queue": "அனைத்தையும் வரிசைப்படுத்து",
"play_all_next": "அடுத்த உள்ள அனைத்தையும் இயக்கு",
"pause": "நிறுத்து",
"view_all": "அனைத்தையும் காண்க",
"no_tracks_added_yet": "உங்கள் பாடல்களை இன்னும் சேர்க்கவில்லை என்றால் தெரியாதே",
"no_tracks": "இங்கு பாடல்கள் எதுவும் இல்லை",
"no_tracks_listened_yet": "இன்னும் எதையும் கேள்வியில்லை",
"not_following_artists": "நீங்கள் எந்த கலைஞரையும் பின்தொடரவில்லை",
"no_favorite_albums_yet": "நீங்கள் இன்னும் எந்த ஆல்பங்களையும் பிடித்தவையாகச் சேர்க்கவில்லை",
"no_logs_found": "பதிவுகள் எதுவும் கிடைக்கவில்லை",
"youtube_engine": "YouTube இயந்திரம்",
"youtube_engine_not_installed_title": "{engine} நிறுவியதில்லை",
"youtube_engine_not_installed_message": "{engine} உங்கள் கணினியில் நிறுவியதில்லை.",
"youtube_engine_set_path": "PATH மாறியில் கிடைக்கிறதா என்பதை உறுதிப்படுத்தவும் அல்லது\n{engine} செயல் செய்யக்கூடிய முறையை கீழே அமைக்கவும்",
"youtube_engine_unix_issue_message": "macOS/Linux/unix போல் OS இல், .zshrc/.bashrc/.bash_profile போன்றவை அமைப்பில் பாதையை PATH அமைப்பது இயலாது.\nநீங்கள்.shell configuration file இல் பாதையை அமைக்க வேண்டும்",
"download": "பதிவிறக்கு",
"file_not_found": "கோப்பு கிடைக்கவில்லை",
"custom": "தனிப்பயன்",
"add_custom_url": "தனிப்பயன் URL ஐச் சேர்க்கவும்"
}

428
lib/l10n/app_tl.arb Normal file
View File

@ -0,0 +1,428 @@
{
"guest": "Bisita",
"browse": "Mag-browse",
"search": "Maghanap",
"library": "Silid-aklatan",
"lyrics": "Mga Liriko",
"settings": "Mga Setting",
"genre_categories_filter": "I-filter ang mga kategorya o genre...",
"genre": "Genre",
"personalized": "Naka-personalize",
"featured": "Tampok",
"new_releases": "Mga Bagong Paglabas",
"songs": "Mga Kanta",
"playing_track": "Tumutugtog ang {track}",
"queue_clear_alert": "Ito ay magbubura ng kasalukuyang pila. {track_length} na mga track ang tatanggalin\nGusto mo bang magpatuloy?",
"load_more": "Mag-load pa",
"playlists": "Mga Playlist",
"artists": "Mga Artista",
"albums": "Mga Album",
"tracks": "Mga Track",
"downloads": "Mga Download",
"filter_playlists": "I-filter ang iyong mga playlist...",
"liked_tracks": "Mga Nagustuhang Track",
"liked_tracks_description": "Lahat ng mga track na iyong nagustuhan",
"playlist": "Playlist",
"create_a_playlist": "Gumawa ng playlist",
"update_playlist": "I-update ang playlist",
"create": "Lumikha",
"cancel": "Ikansela",
"update": "I-update",
"playlist_name": "Pangalan ng Playlist",
"name_of_playlist": "Pangalan ng playlist",
"description": "Paglalarawan",
"public": "Pampubliko",
"collaborative": "Pakikipagtulungan",
"search_local_tracks": "Maghanap ng mga lokal na track...",
"play": "I-play",
"delete": "Burahin",
"none": "Wala",
"sort_a_z": "Ayusin ayon sa A-Z",
"sort_z_a": "Ayusin ayon sa Z-A",
"sort_artist": "Ayusin ayon sa Artista",
"sort_album": "Ayusin ayon sa Album",
"sort_duration": "Ayusin ayon sa Tagal",
"sort_tracks": "Ayusin ang mga Track",
"currently_downloading": "Kasalukuyang Nagda-download ({tracks_length})",
"cancel_all": "Kanselahin Lahat",
"filter_artist": "I-filter ang mga artista...",
"followers": "{followers} na mga Tagasunod",
"add_artist_to_blacklist": "Idagdag ang artista sa blacklist",
"top_tracks": "Mga Nangungunang Track",
"fans_also_like": "Gusto rin ng mga tagahanga",
"loading": "Naglo-load...",
"artist": "Artista",
"blacklisted": "Naka-blacklist",
"following": "Sinusundan",
"follow": "Sundan",
"artist_url_copied": "Na-copy sa clipboard ang URL ng artista",
"added_to_queue": "Idinagdag ang {tracks} na mga track sa pila",
"filter_albums": "I-filter ang mga album...",
"synced": "Naka-sync",
"plain": "Simpleng",
"shuffle": "I-shuffle",
"search_tracks": "Maghanap ng mga track...",
"released": "Inilabas",
"error": "Error {error}",
"title": "Pamagat",
"time": "Oras",
"more_actions": "Higit pang mga aksyon",
"download_count": "I-download ({count})",
"add_count_to_playlist": "Idagdag ({count}) sa Playlist",
"add_count_to_queue": "Idagdag ({count}) sa Pila",
"play_count_next": "I-play ({count}) kasunod",
"album": "Album",
"copied_to_clipboard": "Na-copy ang {data} sa clipboard",
"add_to_following_playlists": "Idagdag ang {track} sa mga sumusunod na Playlist",
"add": "Idagdag",
"added_track_to_queue": "Idinagdag ang {track} sa pila",
"add_to_queue": "Idagdag sa pila",
"track_will_play_next": "Ang {track} ay tutugtog susunod",
"play_next": "I-play susunod",
"removed_track_from_queue": "Tinanggal ang {track} mula sa pila",
"remove_from_queue": "Alisin mula sa pila",
"remove_from_favorites": "Alisin mula sa mga paborito",
"save_as_favorite": "I-save bilang paborito",
"add_to_playlist": "Idagdag sa playlist",
"remove_from_playlist": "Alisin mula sa playlist",
"add_to_blacklist": "Idagdag sa blacklist",
"remove_from_blacklist": "Alisin mula sa blacklist",
"share": "Ibahagi",
"mini_player": "Mini Player",
"slide_to_seek": "I-slide para mag-seek pasulong o pabalik",
"shuffle_playlist": "I-shuffle ang playlist",
"unshuffle_playlist": "I-unshuffle ang playlist",
"previous_track": "Nakaraang track",
"next_track": "Susunod na track",
"pause_playback": "I-pause ang Playback",
"resume_playback": "Ipagpatuloy ang Playback",
"loop_track": "I-loop ang track",
"no_loop": "Walang loop",
"repeat_playlist": "Ulitin ang playlist",
"queue": "Pila",
"alternative_track_sources": "Alternatibong mga pinagmulan ng track",
"download_track": "I-download ang track",
"tracks_in_queue": "{tracks} na mga track sa pila",
"clear_all": "Burahin lahat",
"show_hide_ui_on_hover": "Ipakita/Itago ang UI sa hover",
"always_on_top": "Palaging nasa ibabaw",
"exit_mini_player": "Lumabas sa Mini player",
"download_location": "Lokasyon ng pag-download",
"local_library": "Lokal na silid-aklatan",
"add_library_location": "Idagdag sa silid-aklatan",
"remove_library_location": "Alisin mula sa silid-aklatan",
"account": "Account",
"login_with_spotify": "Mag-login gamit ang iyong Spotify account",
"connect_with_spotify": "Kumonekta sa Spotify",
"logout": "Mag-logout",
"logout_of_this_account": "Mag-logout sa account na ito",
"language_region": "Wika at Rehiyon",
"language": "Wika",
"system_default": "Default ng Sistema",
"market_place_region": "Rehiyon ng Marketplace",
"recommendation_country": "Bansang Inirerekomenda",
"appearance": "Hitsura",
"layout_mode": "Mode ng Layout",
"override_layout_settings": "I-override ang mga setting ng responsive layout mode",
"adaptive": "Umaangkop",
"compact": "Kompakto",
"extended": "Pinalawig",
"theme": "Tema",
"dark": "Madilim",
"light": "Maliwanag",
"system": "Sistema",
"accent_color": "Kulay ng Accent",
"sync_album_color": "I-sync ang kulay ng album",
"sync_album_color_description": "Ginagamit ang pangunahing kulay ng album art bilang kulay ng accent",
"playback": "Playback",
"audio_quality": "Kalidad ng Audio",
"high": "Mataas",
"low": "Mababa",
"pre_download_play": "Mag-pre-download at i-play",
"pre_download_play_description": "Sa halip na mag-stream ng audio, mag-download ng bytes at i-play sa halip (Inirerekomenda para sa mga gumagamit ng mataas na bandwidth)",
"skip_non_music": "Laktawan ang mga segment na hindi musika (SponsorBlock)",
"blacklist_description": "Mga track at artista na nasa blacklist",
"wait_for_download_to_finish": "Mangyaring maghintay para matapos ang kasalukuyang pag-download",
"desktop": "Desktop",
"close_behavior": "Pag-uugali ng Pagsara",
"close": "Isara",
"minimize_to_tray": "I-minimize sa tray",
"show_tray_icon": "Ipakita ang icon ng System tray",
"about": "Tungkol sa",
"u_love_spotube": "Alam naming gusto mo ang Spotube",
"check_for_updates": "Maghanap ng mga update",
"about_spotube": "Tungkol sa Spotube",
"blacklist": "Blacklist",
"please_sponsor": "Mangyaring Mag-sponsor/Mag-donate",
"spotube_description": "Spotube, isang magaan, cross-platform, libreng-para-sa-lahat na spotify client",
"version": "Bersyon",
"build_number": "Build Number",
"founder": "Nagtatag",
"repository": "Repository",
"bug_issues": "Bug+Mga Isyu",
"made_with": "Ginawa nang may ❤️ sa Bangladesh🇧🇩",
"kingkor_roy_tirtho": "Kingkor Roy Tirtho",
"copyright": "© 2021-{current_year} Kingkor Roy Tirtho",
"license": "Lisensya",
"add_spotify_credentials": "Idagdag ang iyong mga kredensyal sa spotify para makapagsimula",
"credentials_will_not_be_shared_disclaimer": "Huwag mag-alala, ang alinman sa iyong mga kredensyal ay hindi kokolektahin o ibabahagi sa sinuman",
"know_how_to_login": "Hindi mo alam kung paano gawin ito?",
"follow_step_by_step_guide": "Sundin ang Hakbang-hakbang na gabay",
"spotify_cookie": "Spotify {name} Cookie",
"cookie_name_cookie": "{name} Cookie",
"fill_in_all_fields": "Mangyaring punan ang lahat ng field",
"submit": "Isumite",
"exit": "Lumabas",
"previous": "Nakaraan",
"next": "Susunod",
"done": "Tapos na",
"step_1": "Hakbang 1",
"first_go_to": "Una, Pumunta sa",
"login_if_not_logged_in": "at Mag-login/Mag-signup kung hindi ka naka-log in",
"step_2": "Hakbang 2",
"step_2_steps": "1. Kapag naka-log in ka na, pindutin ang F12 o i-right click ang Mouse > Inspect para Buksan ang Browser devtools.\n2. Pagkatapos ay pumunta sa \"Application\" Tab (Chrome, Edge, Brave atbp..) o \"Storage\" Tab (Firefox, Palemoon atbp..)\n3. Pumunta sa \"Cookies\" na seksyon at pagkatapos sa \"https://accounts.spotify.com\" na subseksyon",
"step_3": "Hakbang 3",
"step_3_steps": "Kopyahin ang halaga ng \"sp_dc\" Cookie",
"success_emoji": "Tagumpay🥳",
"success_message": "Ngayon ay matagumpay kang Naka-log in gamit ang iyong Spotify account. Magaling, kaibigan!",
"step_4": "Hakbang 4",
"step_4_steps": "I-paste ang na-kopyang halaga ng \"sp_dc\"",
"something_went_wrong": "May nangyaring mali",
"piped_instance": "Instance ng Piped Server",
"piped_description": "Ang instance ng Piped server na gagamitin para sa pagtutugma ng track",
"piped_warning": "Maaaring hindi gumagana nang mabuti ang ilan sa mga ito. Kaya gamitin sa sarili mong peligro",
"invidious_instance": "Instance ng Invidious Server",
"invidious_description": "Ang instance ng Invidious server na gagamitin para sa pagtutugma ng track",
"invidious_warning": "Maaaring hindi gumagana nang mabuti ang ilan sa mga ito. Kaya gamitin sa sarili mong peligro",
"generate": "Gumawa",
"track_exists": "Ang Track na {track} ay umiiral na",
"replace_downloaded_tracks": "Palitan ang lahat ng na-download na mga track",
"skip_download_tracks": "Laktawan ang pag-download ng lahat ng na-download na mga track",
"do_you_want_to_replace": "Gusto mo bang palitan ang umiiral na track??",
"replace": "Palitan",
"skip": "Laktawan",
"select_up_to_count_type": "Pumili ng hanggang {count} {type}",
"select_genres": "Pumili ng mga Genre",
"add_genres": "Magdagdag ng mga Genre",
"country": "Bansa",
"number_of_tracks_generate": "Bilang ng mga track na gagawin",
"acousticness": "Acoustic-ness",
"danceability": "Kakayahang Sayawin",
"energy": "Enerhiya",
"instrumentalness": "Instrumental-ness",
"liveness": "Liveness",
"loudness": "Lakas",
"speechiness": "Pagsasalita",
"valence": "Valence",
"popularity": "Popularidad",
"key": "Key",
"duration": "Tagal (s)",
"tempo": "Tempo (BPM)",
"mode": "Mode",
"time_signature": "Time Signature",
"short": "Maikli",
"medium": "Katamtaman",
"long": "Mahaba",
"min": "Min",
"max": "Max",
"target": "Target",
"moderate": "Katamtaman",
"deselect_all": "Alisin ang Pagkakapili sa Lahat",
"select_all": "Piliin Lahat",
"are_you_sure": "Sigurado ka ba?",
"generating_playlist": "Gumagawa ng iyong custom na playlist...",
"selected_count_tracks": "Napili ang {count} na mga track",
"download_warning": "Kung nag-download ka ng lahat ng Track sa maramihan, malinaw na nagpa-pirate ka ng Musika at nagsasanhi ng pinsala sa creative society ng Musika. Sana ay alam mo ito. Palaging, subukang igalang at suportahan ang masipag na paggawa ng Artist",
"download_ip_ban_warning": "Sa nga pala, ang iyong IP ay maaaring ma-block sa YouTube dahil sa sobrang mga kahilingan sa pag-download kaysa sa karaniwan. Ang IP block ay nangangahulugang hindi mo magagamit ang YouTube (kahit na naka-log in ka) sa loob ng hindi bababa sa 2-3 buwan mula sa device na may IP na iyon. At hindi pinanghahawakan ng Spotube ang anumang responsibilidad kung mangyayari ito",
"by_clicking_accept_terms": "Sa pamamagitan ng pag-click sa 'tanggapin', sumasang-ayon ka sa mga sumusunod na tuntunin:",
"download_agreement_1": "Alam kong nagpa-pirate ako ng Musika. Masama ako",
"download_agreement_2": "Susuportahan ko ang Artist saan man ako maaari at ginagawa ko lang ito dahil wala akong pera para bumili ng kanilang sining",
"download_agreement_3": "Lubos kong nauunawaan na ang aking IP ay maaaring ma-block sa YouTube at hindi ko pinanghahawakan ang Spotube o ang kanyang mga may-ari/nag-ambag na responsable para sa anumang aksidente na sanhi ng aking kasalukuyang aksyon",
"decline": "Tanggihan",
"accept": "Tanggapin",
"details": "Mga Detalye",
"youtube": "YouTube",
"channel": "Channel",
"likes": "Mga Like",
"dislikes": "Mga Dislike",
"views": "Mga View",
"streamUrl": "Stream URL",
"stop": "Ihinto",
"sort_newest": "Ayusin ayon sa pinakabagong idinagdag",
"sort_oldest": "Ayusin ayon sa pinakalumang idinagdag",
"sleep_timer": "Sleep Timer",
"mins": "{minutes} Minuto",
"hours": "{hours} Oras",
"hour": "{hours} Oras",
"custom_hours": "Custom na Oras",
"logs": "Mga Log",
"developers": "Mga Developer",
"not_logged_in": "Hindi ka naka-log in",
"search_mode": "Mode ng Paghahanap",
"audio_source": "Pinagmulan ng Audio",
"ok": "Ok",
"failed_to_encrypt": "Nabigong i-encrypt",
"encryption_failed_warning": "Gumagamit ng encryption ang Spotube para ligtas na i-store ang iyong data. Ngunit nabigo. Kaya babalik ito sa hindi secure na storage\nKung gumagamit ka ng linux, mangyaring tiyakin na mayroon kang anumang secret-service na naka-install (gnome-keyring, kde-wallet, keepassxc atbp)",
"querying_info": "Kinukuha ang impormasyon...",
"piped_api_down": "Ang Piped API ay hindi gumagana",
"piped_down_error_instructions": "Ang instance ng Piped na {pipedInstance} ay kasalukuyang hindi gumagana\n\nMaaari mong baguhin ang instance o baguhin ang 'Uri ng API' sa opisyal na YouTube API\n\nSiguraduhing i-restart ang app pagkatapos ng pagbabago",
"you_are_offline": "Kasalukuyan kang offline",
"connection_restored": "Naibalik na ang iyong koneksyon sa internet",
"use_system_title_bar": "Gamitin ang title bar ng system",
"crunching_results": "Pinaproseso ang mga resulta...",
"search_to_get_results": "Maghanap para makakuha ng mga resulta",
"use_amoled_mode": "Matingkad na itim na madilim na tema",
"pitch_dark_theme": "AMOLED Mode",
"normalize_audio": "I-normalize ang audio",
"change_cover": "Baguhin ang cover",
"add_cover": "Magdagdag ng cover",
"restore_defaults": "Ibalik ang mga default",
"download_music_codec": "Codec para sa pag-download ng musika",
"streaming_music_codec": "Codec para sa pag-stream ng musika",
"login_with_lastfm": "Mag-login gamit ang Last.fm",
"connect": "Kumonekta",
"disconnect_lastfm": "Idiskonekta ang Last.fm",
"disconnect": "Idiskonekta",
"username": "Username",
"password": "Password",
"login": "Mag-login",
"login_with_your_lastfm": "Mag-login gamit ang iyong Last.fm account",
"scrobble_to_lastfm": "I-scrobble sa Last.fm",
"go_to_album": "Pumunta sa Album",
"discord_rich_presence": "Discord Rich Presence",
"browse_all": "I-browse Lahat",
"genres": "Mga Genre",
"explore_genres": "Tuklasin ang mga Genre",
"friends": "Mga Kaibigan",
"no_lyrics_available": "Paumanhin, hindi mahanap ang lyrics para sa track na ito",
"start_a_radio": "Magsimula ng Radio",
"how_to_start_radio": "Paano mo gustong simulan ang radio?",
"replace_queue_question": "Gusto mo bang palitan ang kasalukuyang pila o idagdag dito?",
"endless_playback": "Walang Hanggang Playback",
"delete_playlist": "Burahin ang Playlist",
"delete_playlist_confirmation": "Sigurado ka bang gusto mong burahin ang playlist na ito?",
"local_tracks": "Mga Lokal na Track",
"local_tab": "Lokal",
"song_link": "Link ng Kanta",
"skip_this_nonsense": "Laktawan ang kalokohan na ito",
"freedom_of_music": "\"Kalayaan ng Musika\"",
"freedom_of_music_palm": "\"Kalayaan ng Musika sa iyong palad\"",
"get_started": "Magsimula na tayo",
"youtube_source_description": "Inirerekomenda at pinakamahusay na gumagana.",
"piped_source_description": "Gusto ng kalayaan? Kapareho ng YouTube ngunit mas malaya.",
"jiosaavn_source_description": "Pinakamahusay para sa rehiyon ng South Asia.",
"invidious_source_description": "Katulad ng Piped ngunit may mas mataas na availability.",
"highest_quality": "Pinakamataas na Kalidad: {quality}",
"select_audio_source": "Pumili ng Pinagmulan ng Audio",
"endless_playback_description": "Awtomatikong magdagdag ng mga bagong kanta\nsa dulo ng pila",
"choose_your_region": "Piliin ang iyong rehiyon",
"choose_your_region_description": "Ito ay tutulong sa Spotube na ipakita sa iyo ang tamang content\npara sa iyong lokasyon.",
"choose_your_language": "Piliin ang iyong wika",
"help_project_grow": "Tulungan ang proyektong ito na lumago",
"help_project_grow_description": "Ang Spotube ay isang open-source na proyekto. Maaari mong tulungan ang proyektong ito na lumago sa pamamagitan ng pag-contribute sa proyekto, pag-ulat ng mga bug, o pagmungkahi ng mga bagong feature.",
"contribute_on_github": "Mag-contribute sa GitHub",
"donate_on_open_collective": "Mag-donate sa Open Collective",
"browse_anonymously": "Mag-browse nang Anonymous",
"enable_connect": "I-enable ang Connect",
"enable_connect_description": "Kontrolin ang Spotube mula sa ibang mga device",
"devices": "Mga Device",
"select": "Pumili",
"connect_client_alert": "Ikaw ay kontrolado ng {client}",
"this_device": "Ang Device na ito",
"remote": "Remote",
"stats": "Mga Stat",
"and_n_more": "at {count} pa",
"recently_played": "Kamakailan Lang na Ni-play",
"browse_more": "Mag-browse pa",
"no_title": "Walang Pamagat",
"not_playing": "Hindi tumutugtog",
"epic_failure": "Epic na pagkabigo!",
"added_num_tracks_to_queue": "Nagdagdag ng {tracks_length} na mga track sa pila",
"spotube_has_an_update": "Ang Spotube ay may update",
"download_now": "I-download Ngayon",
"nightly_version": "Ang Spotube Nightly {nightlyBuildNum} ay inilabas na",
"release_version": "Ang Spotube v{version} ay inilabas na",
"read_the_latest": "Basahin ang pinakabagong ",
"release_notes": "release notes",
"pick_color_scheme": "Pumili ng color scheme",
"save": "I-save",
"choose_the_device": "Piliin ang device:",
"multiple_device_connected": "Mayroong maraming device na nakakonekta.\nPiliin ang device kung saan mo gustong maganap ang aksyon na ito",
"nothing_found": "Walang nahanap",
"the_box_is_empty": "Ang kahon ay walang laman",
"top_artists": "Nangungunang mga Artista",
"top_albums": "Nangungunang mga Album",
"this_week": "Ngayong linggo",
"this_month": "Ngayong buwan",
"last_6_months": "Nakaraang 6 na buwan",
"this_year": "Ngayong taon",
"last_2_years": "Nakaraang 2 taon",
"all_time": "Lahat ng panahon",
"powered_by_provider": "Pinapagana ng {providerName}",
"email": "Email",
"profile_followers": "Mga Tagasunod",
"birthday": "Kaarawan",
"subscription": "Subscription",
"not_born": "Hindi pa ipinanganak",
"hacker": "Hacker",
"profile": "Profile",
"no_name": "Walang Pangalan",
"edit": "I-edit",
"user_profile": "Profile ng User",
"count_plays": "{count} na mga play",
"streaming_fees_hypothetical": "Mga bayarin sa streaming (hypothetical)",
"minutes_listened": "Mga minutong pinapakinggan",
"streamed_songs": "Mga na-stream na kanta",
"count_streams": "{count} na mga stream",
"owned_by_you": "Pag-aari mo",
"copied_shareurl_to_clipboard": "Na-kopya ang {shareUrl} sa clipboard",
"spotify_hipotetical_calculation": "*Ito ay kinalkula batay sa bawat stream\nna bayad ng Spotify na $0.003 hanggang $0.005. Ito ay isang hypothetical\nna pagkalkula para bigyan ang user ng ideya kung magkano\nang kanilang ibabayad sa mga artista kung sila ay nakikinig\nng kanilang kanta sa Spotify.",
"count_mins": "{minutes} minuto",
"summary_minutes": "minuto",
"summary_listened_to_music": "Nakinig sa musika",
"summary_songs": "mga kanta",
"summary_streamed_overall": "Na-stream sa kabuuan",
"summary_owed_to_artists": "Utang sa mga artista\nngayong buwan",
"summary_artists": "artista",
"summary_music_reached_you": "Umabot sa iyo ang musika",
"summary_full_albums": "buong album",
"summary_got_your_love": "Nakuha ang iyong pagmamahal",
"summary_playlists": "mga playlist",
"summary_were_on_repeat": "Pinu-playlst muli",
"total_money": "Kabuuang {money}",
"webview_not_found": "Hindi nahanap ang Webview",
"webview_not_found_description": "Walang webview runtime na naka-install sa iyong device.\nKung naka-install ito, siguraduhing nasa Environment PATH\n\nPagkatapos mag-install, i-restart ang app",
"unsupported_platform": "Hindi suportadong platform",
"cache_music": "I-cache ang musika",
"open": "Buksan",
"cache_folder": "Folder ng cache",
"export": "I-export",
"clear_cache": "Burahin ang cache",
"clear_cache_confirmation": "Gusto mo bang burahin ang cache?",
"export_cache_files": "I-export ang mga Naka-cache na File",
"found_n_files": "Nahanap ang {count} na mga file",
"export_cache_confirmation": "Gusto mo bang i-export ang mga file na ito sa",
"exported_n_out_of_m_files": "Na-export ang {filesExported} mula sa {files} na mga file",
"undo": "I-undo",
"download_all": "I-download lahat",
"add_all_to_playlist": "Idagdag lahat sa playlist",
"add_all_to_queue": "Idagdag lahat sa pila",
"play_all_next": "I-play lahat susunod",
"pause": "Pause",
"view_all": "Tingnan lahat",
"no_tracks_added_yet": "Mukhang wala ka pang idinaragdag na mga track",
"no_tracks": "Mukhang walang mga track dito",
"no_tracks_listened_yet": "Mukhang wala ka pang pinakikinggan",
"not_following_artists": "Hindi ka sumusunod sa anumang mga artista",
"no_favorite_albums_yet": "Mukhang wala ka pang idinagdag na anumang mga album sa iyong mga paborito",
"no_logs_found": "Walang nahanap na mga log",
"youtube_engine": "YouTube Engine",
"youtube_engine_not_installed_title": "Hindi naka-install ang {engine}",
"youtube_engine_not_installed_message": "Hindi naka-install ang {engine} sa iyong sistema.",
"youtube_engine_set_path": "Siguraduhing available ito sa PATH variable o\ni-set ang absolute path sa {engine} executable sa ibaba",
"youtube_engine_unix_issue_message": "Sa macOS/Linux/unix tulad ng OS, ang pag-set ng path sa .zshrc/.bashrc/.bash_profile atbp. ay hindi gagana.\nKailangan mong i-set ang path sa configuration file ng shell",
"download": "I-download",
"file_not_found": "Hindi nahanap ang file",
"custom": "Custom",
"add_custom_url": "Magdagdag ng custom URL"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -12,11 +12,13 @@
/// doannc2212@github => Vietnamese
/// sappho192@github => Korean
/// watchakorn-18k@github => Thai
/// llama3, vishnumur777@github => Tamil
/// Microsoft Copilot, Tutislav@github => Czech
library l10n;
import 'package:shadcn_flutter/shadcn_flutter.dart';
export 'package:spotube/l10n/generated/app_localizations.dart';
class L10n {
static final all = [
@ -41,8 +43,10 @@ class L10n {
const Locale('pl', 'PL'),
const Locale('pt', 'PT'),
const Locale('ru', 'RU'),
const Locale('tl', 'PH'),
const Locale('uk', 'UA'),
const Locale('th', 'TH'),
const Locale('ta', 'IN'),
const Locale('tr', 'TR'),
const Locale('zh', 'CN'),
const Locale('vi', 'VN'),

View File

@ -44,7 +44,6 @@ import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/wm_tools/wm_tools.dart';
import 'package:spotube/utils/migrations/sandbox.dart';
import 'package:spotube/utils/platform.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:timezone/data/latest.dart' as tz;

View File

@ -27,6 +27,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/server/active_sourced_track.dart';
import 'package:spotube/provider/volume_provider.dart';
import 'package:spotube/services/sourced_track/sources/youtube.dart';
import 'package:spotube/utils/platform.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -48,7 +49,7 @@ class PlayerView extends HookConsumerWidget {
ref.watch(audioPlayerProvider.select((s) => s.activeTrack));
final currentTrack = sourcedCurrentTrack ?? currentActiveTrack;
final isLocalTrack = currentTrack is LocalTrack;
final mediaQuery = MediaQuery.of(context);
final mediaQuery = MediaQuery.sizeOf(context);
final shouldHide = useState(true);
@ -101,6 +102,9 @@ class PlayerView extends HookConsumerWidget {
backgroundColor: Colors.transparent,
headers: [
SafeArea(
minimum:
kIsMobile ? const EdgeInsets.only(top: 80) : EdgeInsets.zero,
bottom: false,
child: TitleBar(
surfaceOpacity: 0,
surfaceBlur: 0,

View File

@ -191,7 +191,7 @@ class PlayerActions extends HookConsumerWidget {
sleepTimerNotifier.setSleepTimer(value);
}
},
children: [
items: (context) => [
for (final entry in sleepTimerEntries.entries)
AdaptiveMenuButton(
value: entry.value,

View File

@ -101,11 +101,17 @@ class PlaylistCreateDialog extends HookConsumerWidget {
} else {
await playlistNotifier.create(payload, onError);
}
if (trackIds.isNotEmpty) {
await playlistNotifier.addTracks(trackIds, onError);
}
} finally {
isSubmitting.value = false;
if (context.mounted &&
!ref.read(playlistProvider(playlistId ?? "")).hasError) {
context.router.maybePop();
context.router.maybePop<Playlist>(
await ref.read(playlistProvider(playlistId ?? "").future),
);
}
}
}

View File

@ -133,37 +133,38 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
popup: SelectPopup.builder(
searchPlaceholder: Text(context.l10n.search),
builder: (context, searchQuery) {
final filteredLocale = searchQuery?.isNotEmpty != true
? L10n.all
final hasNotQueried =
searchQuery == null || searchQuery.trim().isEmpty;
final filteredLocale = hasNotQueried
? [
const Locale("system", "system"),
...L10n.all,
]
: L10n.all
.where(
(element) =>
filterLocale(element, searchQuery!),
(element) => filterLocale(
element,
searchQuery.trim(),
),
)
.toList();
return SelectItemBuilder(
childCount: filteredLocale.length + 1,
childCount: filteredLocale.length,
builder: (context, index) {
if (index == 0 &&
searchQuery?.isNotEmpty != true) {
final locale = filteredLocale[index];
if (locale == const Locale("system", "system")) {
return SelectItemButton(
value: const Locale("system", "system"),
value: locale,
child: Text(context.l10n.system_default),
);
}
final indexThen = searchQuery?.isNotEmpty != true
? index
: index - 1;
final locale = filteredLocale[indexThen];
return SelectItemButton(
value: locale,
child: Text(
LanguageLocals.getDisplayLanguage(
locale.languageCode)
.toString(),
locale.languageCode,
).toString(),
),
);
},

View File

@ -96,7 +96,9 @@ class LastFMLoginPage extends HookConsumerWidget {
FormField(
label: Text(context.l10n.username),
key: usernameKey,
validator: const NotEmptyValidator(),
validator: const NotEmptyValidator(
message: "Username is required",
),
child: TextField(
autofillHints: const [
AutofillHints.username,
@ -107,7 +109,9 @@ class LastFMLoginPage extends HookConsumerWidget {
),
FormField(
key: passwordKey,
validator: const NotEmptyValidator(),
validator: const NotEmptyValidator(
message: "Password is required",
),
label: Text(context.l10n.password),
child: TextField(
autofillHints: const [

View File

@ -81,6 +81,7 @@ class LyricsPage extends HookConsumerWidget {
title: tabbar,
height: 58 * context.theme.scaling,
surfaceBlur: 0,
automaticallyImplyLeading: false,
)
: tabbar
],

View File

@ -43,7 +43,9 @@ class RootAppPage extends HookConsumerWidget {
final scaffold = MediaQuery.removeViewInsets(
context: context,
removeBottom: true,
child: const Scaffold(
child: const SafeArea(
top: false,
child: Scaffold(
footers: [
BottomPlayer(),
SpotubeNavigationBar(),
@ -51,6 +53,7 @@ class RootAppPage extends HookConsumerWidget {
floatingFooter: true,
child: Sidebar(child: AutoRouter()),
),
),
);
return scaffold;

View File

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotify/spotify.dart';
@ -11,6 +12,14 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/l10n/l10n.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
final localWithName = L10n.all.map((e) {
final isoCodeName = LanguageLocals.getDisplayLanguage(e.languageCode);
return (
locale: e,
name: "${isoCodeName.name} (${isoCodeName.nativeName})",
);
}).sortedBy((e) => e.name);
class SettingsLanguageRegionSection extends HookConsumerWidget {
const SettingsLanguageRegionSection({super.key});
@ -36,18 +45,8 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
value: const Locale("system", "system"),
child: Text(context.l10n.system_default),
),
for (final locale in L10n.all)
SelectItemButton(
value: locale,
child: Builder(builder: (context) {
final isoCodeName = LanguageLocals.getDisplayLanguage(
locale.languageCode,
);
return Text(
"${isoCodeName.name} (${isoCodeName.nativeName})",
);
}),
),
for (final (:locale, :name) in localWithName)
SelectItemButton(value: locale, child: Text(name)),
],
),
AdaptiveSelectTile<Market>(

View File

@ -1,10 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:drift/drift.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'
hide X509Certificate;
@ -15,6 +16,10 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/database/database.dart';
import 'package:spotube/utils/platform.dart';
import 'package:otp_util/otp_util.dart';
// ignore: implementation_imports
import 'package:otp_util/src/utils/generic_util.dart';
import 'package:spotube/utils/service_utils.dart';
extension ExpirationAuthenticationTableData on AuthenticationTableData {
bool get isExpired => DateTime.now().isAfter(expiration);
@ -30,13 +35,17 @@ extension ExpirationAuthenticationTableData on AuthenticationTableData {
class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
static final Dio dio = () {
final dio = Dio();
(dio.httpClientAdapter as IOHttpClientAdapter)
.createHttpClient = () => HttpClient()
..badCertificateCallback = (X509Certificate cert, String host, int port) {
return host.endsWith("spotify.com") && port == 443;
final dio = Dio()
..httpClientAdapter = Http2Adapter(
ConnectionManager(
idleTimeout: const Duration(seconds: 10),
onClientCreate: (uri, clientSettings) {
clientSettings.onBadCertificate = (X509Certificate cert) {
return uri.host.endsWith("spotify.com");
};
},
),
);
return dio;
}();
@ -100,6 +109,94 @@ class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
.insert(refreshedCredentials, mode: InsertMode.replace);
}
String base32FromBytes(Uint8List e, String secretSauce) {
var t = 0;
var n = 0;
var r = "";
for (int i = 0; i < e.length; i++) {
n = n << 8 | e[i];
t += 8;
while (t >= 5) {
r += secretSauce[n >>> t - 5 & 31];
t -= 5;
}
}
if (t > 0) {
r += secretSauce[n << 5 - t & 31];
}
return r;
}
Uint8List cleanBuffer(String e) {
e = e.replaceAll(" ", "");
final t = List.filled(e.length ~/ 2, 0);
final n = Uint8List.fromList(t);
for (int r = 0; r < e.length; r += 2) {
n[r ~/ 2] = int.parse(e.substring(r, r + 2), radix: 16);
}
return n;
}
Future<String> generateTotp() async {
const secretSauce = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
final secretCipherBytes = const [
12,
56,
76,
33,
88,
44,
88,
33,
78,
78,
11,
66,
22,
22,
55,
69,
54
].mapIndexed((t, e) => e ^ t % 33 + 9).toList();
final secretBytes = cleanBuffer(
utf8
.encode(secretCipherBytes.join(""))
.map((e) => e.toRadixString(16))
.join(),
);
final secret = base32FromBytes(secretBytes, secretSauce);
final res = await dio.get(
"https://open.spotify.com/server-time",
options: Options(
headers: {
"Host": "open.spotify.com",
"User-Agent": ServiceUtils.randomUserAgent(
kIsDesktop ? UserAgentDevice.desktop : UserAgentDevice.mobile,
),
"accept": "*/*",
},
),
);
final serverTimeSeconds = res.data["serverTime"] as int;
final totp = TOTP(
secret: secret,
algorithm: OTPAlgorithm.SHA1,
digits: 6,
interval: 30,
);
return totp.generateOTP(
input: Util.timeFormat(
time: DateTime.fromMillisecondsSinceEpoch(serverTimeSeconds * 1000),
interval: 30,
),
);
}
Future<AuthenticationTableCompanion> credentialsFromCookie(
String cookie,
) async {
@ -108,10 +205,17 @@ class AuthenticationNotifier extends AsyncNotifier<AuthenticationTableData?> {
.split("; ")
.firstWhereOrNull((c) => c.trim().startsWith("sp_dc="))
?.trim();
final totp = await generateTotp();
final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor();
final accessTokenUrl = Uri.parse(
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player"
"&totp=$totp&totpVer=5&ts=$timestamp",
);
final res = await dio.getUri(
Uri.parse(
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player",
),
accessTokenUrl,
options: Options(
headers: {
"Cookie": spDc ?? "",

View File

@ -24,6 +24,9 @@ const supportedAudioTypes = [
"audio/opus",
"audio/wav",
"audio/aac",
"audio/flac",
"audio/x-flac",
"audio/x-wav",
];
const imgMimeToExt = {
@ -68,13 +71,16 @@ final localTracksProvider =
await Directory(location).list(recursive: true).toList();
entities.addAll(
dirEntities
.where(
(e) =>
e is File &&
supportedAudioTypes.contains(lookupMimeType(e.path)),
)
.cast<File>(),
dirEntities.where(
(e) {
final mime = lookupMimeType(e.path) ??
(extension(e.path) == ".opus" ? "audio/opus" : null);
print("${basename(e.path)}: $mime");
return e is File && supportedAudioTypes.contains(mime);
},
).cast<File>(),
);
} catch (e, stack) {
AppLogger.reportError(e, stack);

View File

@ -98,6 +98,23 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
}
});
}
Future<void> addTracks(List<String> trackIds, [ValueChanged? onError]) async {
try {
if (state.value == null) return;
final spotify = ref.read(spotifyProvider);
await spotify.playlists.addTracks(
trackIds.map((id) => "spotify:track:$id").toList(),
state.value!.id!,
);
} catch (e, stack) {
onError?.call(e);
AppLogger.reportError(e, stack);
rethrow;
}
}
}
final playlistProvider =

View File

@ -11,8 +11,6 @@ final homeViewProvider = FutureProvider((ref) async {
authenticationProvider.select((s) => s.asData?.value?.getCookie("sp_t")),
);
if (spTCookie == null) return null;
final spotify = ref.watch(customSpotifyEndpointProvider);
return spotify.getHomeFeed(

View File

@ -5,7 +5,7 @@ import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
final homeSectionViewProvider =
FutureProvider.family<SpotifyHomeFeedSection?, String>(
FutureProvider.family<SpotifyHomeFeedSection, String>(
(ref, sectionUri) async {
final country = ref.watch(
userPreferencesProvider.select((s) => s.market),
@ -14,8 +14,6 @@ final homeSectionViewProvider =
authenticationProvider.select((s) => s.asData?.value?.getCookie("sp_t")),
);
if (spTCookie == null) return null;
final spotify = ref.watch(customSpotifyEndpointProvider);
return spotify.getHomeFeedSection(

View File

@ -117,8 +117,8 @@ class CustomSpotifyEndpoints {
}
Future<SpotifyHomeFeed> getHomeFeed({
required String spTCookie,
required Market country,
String? spTCookie,
}) async {
final headers = {
'app-platform': 'WebPlayer',
@ -137,7 +137,7 @@ class CustomSpotifyEndpoints {
"operationName": "home",
"variables": jsonEncode({
"timeZone": tz.local.name,
"sp_t": spTCookie,
"sp_t": spTCookie ?? "",
"country": country.name,
"facet": null,
"sectionItemsLimit": 10
@ -169,7 +169,7 @@ class CustomSpotifyEndpoints {
Future<SpotifyHomeFeedSection> getHomeFeedSection(
String sectionUri, {
required String spTCookie,
String? spTCookie,
required Market country,
}) async {
final headers = {
@ -189,7 +189,7 @@ class CustomSpotifyEndpoints {
"operationName": "homeSection",
"variables": jsonEncode({
"timeZone": tz.local.name,
"sp_t": spTCookie,
"sp_t": spTCookie ?? "",
"country": country.name,
"uri": sectionUri
}),

View File

@ -1,6 +1,6 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:auto_route/auto_route.dart';
import 'package:dio/dio.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
@ -28,6 +28,11 @@ import 'package:spotube/collections/env.dart';
import 'package:version/version.dart';
enum UserAgentDevice {
desktop,
mobile,
}
abstract class ServiceUtils {
static final _englishMatcherRegex = RegExp(
"^[a-zA-Z0-9\\s!\"#\$%&\\'()*+,-.\\/:;<=>?@\\[\\]^_`{|}~]*\$",
@ -417,4 +422,16 @@ abstract class ServiceUtils {
return null;
}
}
static int randomNumber(int min, int max) {
return min + Random().nextInt(max - min);
}
static String randomUserAgent(UserAgentDevice type) {
if (type == UserAgentDevice.desktop) {
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_${randomNumber(11, 15)}_${randomNumber(4, 9)}) AppleWebKit/${randomNumber(530, 537)}.${randomNumber(30, 37)} (KHTML, like Gecko) Chrome/${randomNumber(80, 105)}.0.${randomNumber(3000, 4500)}.${randomNumber(60, 125)} Safari/${randomNumber(530, 537)}.${randomNumber(30, 36)}";
} else {
return "Mozilla/5.0 (Linux; Android ${randomNumber(8, 13)}) AppleWebKit/${randomNumber(530, 537)}.${randomNumber(30, 36)} (KHTML, like Gecko) Chrome/${randomNumber(101, 116)}.0.${randomNumber(3000, 6000)}.${randomNumber(60, 125)} Mobile Safari/${randomNumber(530, 537)}.${randomNumber(30, 36)}";
}
}
}

View File

@ -166,6 +166,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
base32:
dependency: transitive
description:
name: base32
sha256: ddad4ebfedf93d4500818ed8e61443b734ffe7cf8a45c668c9b34ef6adde02e2
url: "https://pub.dev"
source: hosted
version: "2.1.3"
bonsoir:
dependency: "direct main"
description:
@ -440,7 +448,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: "direct dev"
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
@ -536,6 +544,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.7.0"
dio_http2_adapter:
dependency: "direct main"
description:
name: dio_http2_adapter
sha256: b8bd5d587fd228a461711f8b82f378ccd4bf1fbf7802e7663ca60d7b5ce0e3aa
url: "https://pub.dev"
source: hosted
version: "2.6.0"
dio_web_adapter:
dependency: transitive
description:
@ -1178,6 +1194,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.2"
http2:
dependency: transitive
description:
name: http2
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
http_methods:
dependency: transitive
description:
@ -1539,10 +1563,10 @@ packages:
dependency: "direct main"
description:
name: mime
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "2.0.0"
nm:
dependency: transitive
description:
@ -1639,6 +1663,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.3"
otp_util:
dependency: "direct main"
description:
name: otp_util
sha256: dd8956c6472bacc3ffabe62c03f8a9782d1e5a5a3f2674420970f549d642b1cf
url: "https://pub.dev"
source: hosted
version: "1.0.2"
package_config:
dependency: transitive
description:

View File

@ -3,7 +3,7 @@ description: Open source Spotify client that doesn't require Premium nor uses El
publish_to: "none"
version: 4.0.0+39
version: 4.0.1+40
homepage: https://spotube.krtirtho.dev
repository: https://github.com/KRTirtho/spotube
@ -88,7 +88,7 @@ dependencies:
media_kit: ^1.1.10+1
media_kit_libs_audio: ^1.0.4
metadata_god: ^1.0.0
mime: ^1.0.2
mime: ^2.0.0
open_file: ^3.5.10
package_info_plus: ^6.0.0
palette_generator: ^0.3.3
@ -140,10 +140,11 @@ dependencies:
url: https://github.com/KRTirtho/flutter_new_pipe_extractor.git
http_parser: ^4.1.2
collection: any
otp_util: ^1.0.2
dio_http2_adapter: ^2.6.0
dev_dependencies:
build_runner: ^2.4.13
crypto: ^3.0.3
envied_generator: ^1.0.0
flutter_gen_runner: ^5.4.0
flutter_launcher_icons: ^0.14.2
@ -193,46 +194,6 @@ flutter:
- packages/flutter_undraw/assets/undraw/empty.svg
- packages/flutter_undraw/assets/undraw/no_data.svg
fonts:
- family: GeistSans
fonts:
- asset: packages/shadcn_flutter/fonts/Geist-Black.otf
weight: 800
- asset: packages/shadcn_flutter/fonts/Geist-Bold.otf
weight: 700
- asset: packages/shadcn_flutter/fonts/Geist-Light.otf
weight: 300
- asset: packages/shadcn_flutter/fonts/Geist-Medium.otf
weight: 500
- asset: packages/shadcn_flutter/fonts/Geist-SemiBold.otf
weight: 600
- asset: packages/shadcn_flutter/fonts/Geist-Thin.otf
weight: 100
- asset: packages/shadcn_flutter/fonts/Geist-UltraBlack.otf
weight: 900
- asset: packages/shadcn_flutter/fonts/Geist-UltraLight.otf
weight: 200
- asset: packages/shadcn_flutter/fonts/Geist-Regular.otf
weight: 400
- family: GeistMono
fonts:
- asset: packages/shadcn_flutter/fonts/GeistMono-Black.otf
weight: 800
- asset: packages/shadcn_flutter/fonts/GeistMono-Bold.otf
weight: 700
- asset: packages/shadcn_flutter/fonts/GeistMono-Light.otf
weight: 300
- asset: packages/shadcn_flutter/fonts/GeistMono-Medium.otf
weight: 500
- asset: packages/shadcn_flutter/fonts/GeistMono-Regular.otf
weight: 400
- asset: packages/shadcn_flutter/fonts/GeistMono-SemiBold.otf
weight: 600
- asset: packages/shadcn_flutter/fonts/GeistMono-Thin.otf
weight: 100
- asset: packages/shadcn_flutter/fonts/GeistMono-UltraBlack.otf
weight: 900
- asset: packages/shadcn_flutter/fonts/GeistMono-UltraLight.otf
weight: 200
- family: RadixIcons
fonts:
- asset: packages/shadcn_flutter/icons/RadixIcons.otf