Compare commits

...

10 Commits

Author SHA1 Message Date
Kingkor Roy Tirtho
216fdadf25 chore: fix dependency issues 2025-09-08 16:45:55 +06:00
Lorenzo
3599a4340f
docs: Removed Google Play Store link from readme file (#2638)
* Update README.md

* Update README.md

---------

Co-authored-by: Kingkor Roy Tirtho <krtirtho@gmail.com>
2025-09-08 16:34:37 +06:00
Kingkor Roy Tirtho
719229b2b7 chore: add gap between metadata plugin 2025-09-08 16:30:10 +06:00
Kingkor Roy Tirtho
49db82083c chore: add support for language dialects 2025-09-08 16:29:05 +06:00
maboroshin
84f119e482
translation: fix Japanese translations (#2732)
Co-authored-by: Kingkor Roy Tirtho <krtirtho@gmail.com>
2025-09-08 16:15:07 +06:00
SamHacker
aeb8caf059
translation: add Traditional Chinese translation (#2762)
* add: Add the translation file for Chinese Traditional

* i18n: fix wrong translation in Chinese Simpfied

* i18n: Finish the translation in Chinese Traditional file

* i18n: Add Traditional Chinese locale support

* i18n: Add Traditional Chinese language code

---------

Co-authored-by: Kingkor Roy Tirtho <krtirtho@gmail.com>
2025-09-08 16:11:54 +06:00
Kingkor Roy Tirtho
7c4956153a chore: allow for any libjsoncpp 2025-09-08 16:04:23 +06:00
Kingkor Roy Tirtho
58dc80aa09 fix(playback): alternative track sources switch not working 2025-09-08 15:31:29 +06:00
Kingkor Roy Tirtho
4a07945214 fix(track_options): tapping on option doesn't close the menu 2025-09-08 14:27:29 +06:00
Kingkor Roy Tirtho
7b21eca37b fix(playback): play not fetching full playlist if playlist is too long 2025-09-08 14:09:16 +06:00
26 changed files with 2617 additions and 520 deletions

View File

@ -61,10 +61,6 @@ This handy table lists all the methods you can use to install Spotube:
<tr> <tr>
<td>Android</td> <td>Android</td>
<td> <td>
<a href="https://play.google.com/store/apps/details?id=oss.krtirtho.spotube">
<img width="220" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png">
</a>
<br>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk"> <a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk">
<img width="220" alt="APK download" src="https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png"> <img width="220" alt="APK download" src="https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png">
</a> </a>

View File

@ -133,10 +133,14 @@ abstract class LanguageLocals {
// name: "Chichewa", // name: "Chichewa",
// nativeName: "chiCheŵa", // nativeName: "chiCheŵa",
// ), // ),
"zh": const ISOLanguageName( "zh_CN": const ISOLanguageName(
name: "Simplified Chinese", name: "Simplified Chinese",
nativeName: "简体中文", nativeName: "简体中文",
), ),
"zh_TW": const ISOLanguageName(
name: "Traditional Chinese",
nativeName: "繁體中文(台灣)",
),
// "cv": const ISOLanguageName( // "cv": const ISOLanguageName(
// name: "Chuvash", // name: "Chuvash",
// nativeName: "чӑваш чӗлхи", // nativeName: "чӑваш чӗлхи",
@ -747,9 +751,13 @@ abstract class LanguageLocals {
// ) // )
}; };
static ISOLanguageName getDisplayLanguage(key) { static ISOLanguageName getDisplayLanguage(String key, String? countryCode) {
if (isoLangs.containsKey(key)) { if (isoLangs.containsKey(key)) {
return isoLangs[key]!; return isoLangs[key]!;
} else if (countryCode != null &&
countryCode.isNotEmpty &&
isoLangs.containsKey("${key}_$countryCode")) {
return isoLangs["${key}_$countryCode"]!;
} else { } else {
throw Exception("Language key incorrect"); throw Exception("Language key incorrect");
} }

View File

@ -13,6 +13,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/history/history.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/logger/logger.dart';
typedef UseActionCallbacks = ({ typedef UseActionCallbacks = ({
bool isActive, bool isActive,
@ -82,6 +83,9 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) {
allTracks.sublist(initialTracks.length), allTracks.sublist(initialTracks.length),
); );
} }
} catch (e, stack) {
AppLogger.reportError(e, stack);
rethrow;
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
@ -134,6 +138,9 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) {
allTracks.sublist(initialTracks.length), allTracks.sublist(initialTracks.length),
); );
} }
} catch (e, stack) {
AppLogger.reportError(e, stack);
rethrow;
} finally { } finally {
if (context.mounted) { if (context.mounted) {
isLoading.value = false; isLoading.value = false;

View File

@ -18,12 +18,15 @@ class TrackOptions extends HookConsumerWidget {
final bool userPlaylist; final bool userPlaylist;
final String? playlistId; final String? playlistId;
final Widget? icon; final Widget? icon;
final VoidCallback? onTapItem;
const TrackOptions({ const TrackOptions({
super.key, super.key,
required this.track, required this.track,
this.userPlaylist = false, this.userPlaylist = false,
this.playlistId, this.playlistId,
this.icon, this.icon,
this.onTapItem,
}) : assert( }) : assert(
track is SpotubeFullTrackObject || track is SpotubeLocalTrackObject, track is SpotubeFullTrackObject || track is SpotubeLocalTrackObject,
"Track must be a SpotubeFullTrackObject, SpotubeLocalTrackObject", "Track must be a SpotubeFullTrackObject, SpotubeLocalTrackObject",
@ -60,6 +63,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.delete, TrackOptionValue.delete,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.trash), leading: const Icon(SpotubeIcons.trash),
title: Text(context.l10n.delete), title: Text(context.l10n.delete),
@ -73,6 +77,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.album, TrackOptionValue.album,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.album), leading: const Icon(SpotubeIcons.album),
title: Column( title: Column(
@ -96,6 +101,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.addToQueue, TrackOptionValue.addToQueue,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.queueAdd), leading: const Icon(SpotubeIcons.queueAdd),
title: Text(context.l10n.add_to_queue), title: Text(context.l10n.add_to_queue),
@ -108,6 +114,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.playNext, TrackOptionValue.playNext,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.lightning), leading: const Icon(SpotubeIcons.lightning),
title: Text(context.l10n.play_next), title: Text(context.l10n.play_next),
@ -121,6 +128,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.removeFromQueue, TrackOptionValue.removeFromQueue,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
enabled: !isActiveTrack, enabled: !isActiveTrack,
leading: const Icon(SpotubeIcons.queueRemove), leading: const Icon(SpotubeIcons.queueRemove),
@ -135,6 +143,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.favorite, TrackOptionValue.favorite,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: isLiked leading: isLiked
? const Icon( ? const Icon(
@ -157,6 +166,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.startRadio, TrackOptionValue.startRadio,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.radio), leading: const Icon(SpotubeIcons.radio),
title: Text(context.l10n.start_a_radio), title: Text(context.l10n.start_a_radio),
@ -169,6 +179,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.addToPlaylist, TrackOptionValue.addToPlaylist,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.playlistAdd), leading: const Icon(SpotubeIcons.playlistAdd),
title: Text(context.l10n.add_to_playlist), title: Text(context.l10n.add_to_playlist),
@ -183,6 +194,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.removeFromPlaylist, TrackOptionValue.removeFromPlaylist,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.removeFilled), leading: const Icon(SpotubeIcons.removeFilled),
title: Text(context.l10n.remove_from_playlist), title: Text(context.l10n.remove_from_playlist),
@ -196,6 +208,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.download, TrackOptionValue.download,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
enabled: !isInDownloadQueue, enabled: !isInDownloadQueue,
leading: isInDownloadQueue leading: isInDownloadQueue
@ -217,6 +230,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.blacklist, TrackOptionValue.blacklist,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: Icon( leading: Icon(
SpotubeIcons.playlistRemove, SpotubeIcons.playlistRemove,
@ -240,6 +254,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.share, TrackOptionValue.share,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.share), leading: const Icon(SpotubeIcons.share),
title: Text(context.l10n.share), title: Text(context.l10n.share),
@ -253,6 +268,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.songlink, TrackOptionValue.songlink,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: Assets.images.logos.songlinkTransparent.image( leading: Assets.images.logos.songlinkTransparent.image(
width: 22, width: 22,
@ -270,6 +286,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.details, TrackOptionValue.details,
playlistId, playlistId,
); );
onTapItem?.call();
}, },
leading: const Icon(SpotubeIcons.info), leading: const Icon(SpotubeIcons.info),
title: Text(context.l10n.details), title: Text(context.l10n.details),

View File

@ -42,6 +42,9 @@ class TrackOptionsButton extends HookConsumerWidget {
track: track, track: track,
playlistId: playlistId, playlistId: playlistId,
userPlaylist: userPlaylist, userPlaylist: userPlaylist,
onTapItem: () {
closeOverlay(context);
},
), ),
), ),
); );
@ -133,6 +136,9 @@ class TrackOptionsButton extends HookConsumerWidget {
track: track, track: track,
userPlaylist: userPlaylist, userPlaylist: userPlaylist,
playlistId: playlistId, playlistId: playlistId,
onTapItem: () {
closeDrawer(context);
},
), ),
], ],
), ),

View File

@ -22,7 +22,7 @@
"filter_playlists": "あなたの再生リストを絞り込み...", "filter_playlists": "あなたの再生リストを絞り込み...",
"liked_tracks": "いいねした曲", "liked_tracks": "いいねした曲",
"liked_tracks_description": "いいねしたすべての曲", "liked_tracks_description": "いいねしたすべての曲",
"create_playlist": "再生リストの作成", "playlist": "再生リスト",
"create_a_playlist": "再生リストの作成", "create_a_playlist": "再生リストの作成",
"create": "作成", "create": "作成",
"cancel": "キャンセル", "cancel": "キャンセル",
@ -39,8 +39,9 @@
"sort_z_a": "Z-A 順に並び替え", "sort_z_a": "Z-A 順に並び替え",
"sort_artist": "アーティスト順に並び替え", "sort_artist": "アーティスト順に並び替え",
"sort_album": "アルバム順に並び替え", "sort_album": "アルバム順に並び替え",
"sort_duration": "長さ順に並べ替え",
"sort_tracks": "曲の並び替え", "sort_tracks": "曲の並び替え",
"currently_downloading": "いまダウンロード中 ({tracks_length}) 曲", "currently_downloading": "ダウンロード中 ({tracks_length}) 曲",
"cancel_all": "すべてキャンセル", "cancel_all": "すべてキャンセル",
"filter_artist": "アーティストを絞り込み...", "filter_artist": "アーティストを絞り込み...",
"followers": "{followers} フォロワー", "followers": "{followers} フォロワー",
@ -94,6 +95,7 @@
"pause_playback": "再生を停止", "pause_playback": "再生を停止",
"resume_playback": "再生を再開", "resume_playback": "再生を再開",
"loop_track": "曲をループ", "loop_track": "曲をループ",
"no_loop": "ループなし",
"repeat_playlist": "再生リストをリピート", "repeat_playlist": "再生リストをリピート",
"queue": "再生キュー", "queue": "再生キュー",
"alternative_track_sources": "この曲の別の音源を選ぶ", "alternative_track_sources": "この曲の別の音源を選ぶ",
@ -112,8 +114,8 @@
"language_region": "言語 & 地域", "language_region": "言語 & 地域",
"language": "言語", "language": "言語",
"system_default": "システムの既定値", "system_default": "システムの既定値",
"market_place_region": "市場の地域", "market_place_region": "音楽市場の地域",
"recommendation_country": "推薦先の国", "recommendation_country": "おすすめの国",
"appearance": "外観", "appearance": "外観",
"layout_mode": "レイアウトの種類", "layout_mode": "レイアウトの種類",
"override_layout_settings": "レスポンシブなレイアウトの種類の設定を上書きする", "override_layout_settings": "レスポンシブなレイアウトの種類の設定を上書きする",
@ -175,13 +177,18 @@
"step_2": "ステップ 2", "step_2": "ステップ 2",
"step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます", "step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます",
"step_3": "ステップ 3", "step_3": "ステップ 3",
"step_3_steps": "\"sp_dc\" Cookieの値をコピー",
"success_emoji": "成功🥳", "success_emoji": "成功🥳",
"success_message": "アカウントへのログインに成功しました。よくできました!", "success_message": "アカウントへのログインに成功しました。よくできました!",
"step_4": "ステップ 4", "step_4": "ステップ 4",
"step_4_steps": "コピーした\"sp_dc\"の値を貼り付け",
"something_went_wrong": "何か誤りがあります", "something_went_wrong": "何か誤りがあります",
"piped_instance": "Piped サーバーのインスタンス", "piped_instance": "Piped サーバーのインスタンス",
"piped_description": "曲の一致に使う Piped サーバーのインスタンス", "piped_description": "曲の一致に使う Piped サーバーのインスタンス",
"piped_warning": "それらの一部ではうまく動作しないこともあります。自己責任で使用してください", "piped_warning": "それらの一部ではうまく動作しないこともあります。自己責任で使用してください",
"invidious_instance": "Invidiousサーバーインスタンス",
"invidious_description": "曲の一致に使用するInvidiousサーバーインスタンス",
"invidious_warning": "一部はうまく機能しない可能性があります。自己責任で使用してください",
"generate_playlist": "再生リストの生成", "generate_playlist": "再生リストの生成",
"track_exists": "曲 {track} は既に存在します", "track_exists": "曲 {track} は既に存在します",
"replace_downloaded_tracks": "すべてのダウンロード済みの曲を置換", "replace_downloaded_tracks": "すべてのダウンロード済みの曲を置換",
@ -247,102 +254,100 @@
"developers": "開発", "developers": "開発",
"not_logged_in": "ログインしていません", "not_logged_in": "ログインしていません",
"search_mode": "検索モード", "search_mode": "検索モード",
"audio_source": "音声ソース", "audio_source": "音声の提供元",
"ok": "分かりました", "ok": "OK",
"failed_to_encrypt": "暗号化に失敗しました", "failed_to_encrypt": "暗号化に失敗しました",
"encryption_failed_warning": "Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください", "encryption_failed_warning": "SpoTubeはデータを安全に保存するために暗号化を用いますが、暗号化に失敗しました。このため、安全でない保存領域への保存に切り替えます\nOSがLinuxなら、gnome-keyring、kde-wallet、keepassxcなどの管理ツールがインストールされていることを確認してください",
"querying_info": "情報を取得中...", "querying_info": "情報を取得中...",
"piped_api_down": "Piped APIがダウンしています", "piped_api_down": "Piped APIがダウンしています",
"piped_down_error_instructions": "Pipedインスタンス{pipedInstance}は現在ダウンしています\n\nインスタンスを変更するか、'APIタイプ'を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください", "piped_down_error_instructions": "Pipedインスタンス {pipedInstance} は現在ダウンしています\n\nインスタンスを変更するか、「APIの種類」を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください",
"you_are_offline": "現在、オフラインです", "you_are_offline": "現在、オフラインです",
"connection_restored": "インターネット接続が復旧しました", "connection_restored": "インターネット接続が復旧しました",
"use_system_title_bar": "システムタイトルバーを使用する", "use_system_title_bar": "システムのタイトルバーを使う",
"update_playlist": "プレイリストを更新", "update_playlist": "再生リストを更新",
"update": "更新", "update": "更新",
"local_library": "端末内ライブラリ",
"add_library_location": "ライブラリに追加",
"remove_library_location": "ライブラリから削除",
"crunching_results": "結果を処理中...", "crunching_results": "結果を処理中...",
"search_to_get_results": "結果を取得するために検索", "search_to_get_results": "結果を取得するために検索",
"use_amoled_mode": "AMOLEDモードを使用する", "use_amoled_mode": "AMOLEDモードを使用",
"pitch_dark_theme": "ピッチブラックダートテーマ", "pitch_dark_theme": "ピッチブラック ダークテーマ",
"normalize_audio": "オーディオを正規化する", "normalize_audio": "音声を正規化",
"change_cover": "カバーを変更する", "change_cover": "カバーを変更",
"add_cover": "カバーを追加する", "add_cover": "カバーを追加",
"restore_defaults": "デフォルト値に戻す", "restore_defaults": "設定を初期化",
"download_music_codec": "音楽コーデックをダウンロードする", "download_music_codec": "ダウンロード用の音声コーデック",
"streaming_music_codec": "ストリーミング音楽コーデック", "streaming_music_codec": "ストリーミング用の音声コーデック",
"login_with_lastfm": "Last.fmでログインする", "login_with_lastfm": "Last.fmでログイン",
"connect": "接続する", "connect": "接続",
"disconnect_lastfm": "Last.fmから切断する", "disconnect_lastfm": "Last.fmから切断",
"disconnect": "切断する", "disconnect": "切断",
"username": "ユーザー名", "username": "ユーザー名",
"password": "パスワード", "password": "パスワード",
"login": "ログインする", "login": "ログイン",
"login_with_your_lastfm": "あなたのLast.fmアカウントでログインする", "login_with_your_lastfm": "Last.fmアカウントでログイン",
"scrobble_to_lastfm": "Last.fmにスクロブルする", "scrobble_to_lastfm": "Last.fmにスクロブルする",
"go_to_album": "アルバムに移動", "go_to_album": "アルバムに移動",
"discord_rich_presence": "ディスコードリッチプレゼンス", "discord_rich_presence": "Discord リッチプレゼンス",
"browse_all": "すべてを閲覧", "browse_all": "すべてを閲覧",
"genres": "ジャンル", "genres": "ジャンル",
"explore_genres": "ジャンルを探索", "explore_genres": "ジャンルを探索",
"step_3_steps": "\"sp_dc\" Cookieの値をコピー",
"step_4_steps": "コピーした\"sp_dc\"の値を貼り付け",
"friends": "友達", "friends": "友達",
"no_lyrics_available": "申し訳ありませんが、このトラックの歌詞を見つけることができません", "no_lyrics_available": "すみません、この曲の歌詞が見つかりません",
"sort_duration": "時間で並べ替え",
"start_a_radio": "ラジオを開始", "start_a_radio": "ラジオを開始",
"how_to_start_radio": "ラジオをどのように開始しますか?", "how_to_start_radio": "ラジオをどのように開始しますか?",
"replace_queue_question": "現在のキューを置き換えるか、追加しますか?", "replace_queue_question": "現在のキューを置き換えるか、追加しますか?",
"endless_playback": "エンドレス再生", "endless_playback": "エンドレス再生",
"delete_playlist": "プレイリストを削除", "delete_playlist": "再生リストを削除",
"delete_playlist_confirmation": "このプレイリストを削除してもよろしいですか?", "delete_playlist_confirmation": "この再生リストを削除しますか?",
"local_tracks": "ローカルトラック", "local_tracks": "端末内の曲",
"local_tab": "端末内",
"song_link": "曲のリンク", "song_link": "曲のリンク",
"skip_this_nonsense": "この愚かなことをスキップ", "skip_this_nonsense": "こんなことはスキップ",
"freedom_of_music": "“音楽の自由”", "freedom_of_music": "“音楽の自由”",
"freedom_of_music_palm": "“手のひらの中の音楽の自由”", "freedom_of_music_palm": "“音楽の自由を思いのままに”",
"get_started": "さあ始めましょう", "get_started": "さあ始めましょう",
"youtube_source_description": "推奨され、最適に機能します。", "youtube_source_description": "推奨され、最適に機能します。",
"piped_source_description": "自由に感じますか? YouTubeと同じですが、はるかに無料です。", "piped_source_description": "自由を感じるYouTubeと同じだけど、はるかに自由です。",
"jiosaavn_source_description": "南アジア地域向けの最適です。", "jiosaavn_source_description": "南アジア地域では最適です。",
"invidious_source_description": "Pipedに似ていますが、より利用性があります。",
"highest_quality": "最高品質:{quality}", "highest_quality": "最高品質:{quality}",
"select_audio_source": "オーディオソースを選択", "select_audio_source": "音声の提供元を選択",
"endless_playback_description": "新しい曲をキューの最後に自動的に追加", "endless_playback_description": "キューの最後に新しい曲を自動で追加",
"choose_your_region": "地域を選択", "choose_your_region": "地域を選択",
"choose_your_region_description": "これにより、Spotubeがあなたの場所に適したコンテンツを表示できます。", "choose_your_region_description": "Spotubeがあなたの地域に適したコンテンツを表示します。",
"choose_your_language": "言語を選択してください", "choose_your_language": "言語を選択してください",
"help_project_grow": "このプロジェクトの成長を支援する", "help_project_grow": "プロジェクトの成長を支援する",
"help_project_grow_description": "Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグ報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。", "help_project_grow_description": "SpoTubeはオープンソースプロジェクトです。貢献したり、バグ報告したり、新機能を提案することで、プロジェクトの成長に貢献できます。",
"contribute_on_github": "GitHubで貢献する", "contribute_on_github": "GitHubで貢献",
"donate_on_open_collective": "Open Collectiveで寄付する", "donate_on_open_collective": "Open Collectiveで寄付",
"browse_anonymously": "匿名で閲覧する", "browse_anonymously": "匿名で閲覧する",
"enable_connect": "接続を有効にする", "enable_connect": "接続する",
"enable_connect_description": "他のデバイスからSpotubeを制御する", "enable_connect_description": "他の端末からSpotubeを制御する",
"devices": "デバイス", "devices": "機器",
"select": "選択する", "select": "選択",
"connect_client_alert": "{client} によって操作されています", "connect_client_alert": "{client} から操作されています",
"this_device": "このデバイス", "this_device": "この端末",
"remote": "リモート", "remote": "リモート",
"local_library": "ローカルライブラリ",
"add_library_location": "ライブラリに追加",
"remove_library_location": "ライブラリから削除",
"local_tab": "ローカル",
"stats": "統計", "stats": "統計",
"and_n_more": "そして {count} つのアイテム", "and_n_more": "さらに {count} 項目",
"recently_played": "最近再生された", "recently_played": "最近聴いた曲",
"browse_more": "もっと見る", "browse_more": "もっと表示",
"no_title": "タイトルなし", "no_title": "タイトルなし",
"not_playing": "再生中ではありません", "not_playing": "再生なし",
"epic_failure": "壮大な失敗", "epic_failure": "壮大なエラー",
"added_num_tracks_to_queue": "{tracks_length} 曲をキューに追加しました", "added_num_tracks_to_queue": "{tracks_length} 曲をキューに追加しました",
"spotube_has_an_update": "Spotube にアップデートがあります", "spotube_has_an_update": "Spotube の最新版あり",
"download_now": "今すぐダウンロード", "download_now": "今すぐダウンロード",
"nightly_version": "Spotube Nightly {nightlyBuildNum} がリリースされました", "nightly_version": "Spotube Nightly {nightlyBuildNum} がリリースされました",
"release_version": "Spotube v{version} がリリースされました", "release_version": "Spotube v{version} がリリースされました",
"read_the_latest": "最新の ", "read_the_latest": "最新の ",
"release_notes": "リリースノート", "release_notes": "更新情報を読む",
"pick_color_scheme": "カラースキームを選択", "pick_color_scheme": "カラーテーマを選択",
"save": "保存", "save": "保存",
"choose_the_device": "デバイスを選択:", "choose_the_device": "端末を選択:",
"multiple_device_connected": "複数のデバイスが接続されています。\nこのアクションを実行するデバイスを選択してください", "multiple_device_connected": "複数の端末が接続されています。\nこの操作を実行する端末を選択",
"nothing_found": "何も見つかりませんでした", "nothing_found": "何も見つかりませんでした",
"the_box_is_empty": "ボックスは空です", "the_box_is_empty": "ボックスは空です",
"top_artists": "トップアーティスト", "top_artists": "トップアーティスト",
@ -357,7 +362,7 @@
"email": "メール", "email": "メール",
"profile_followers": "フォロワー", "profile_followers": "フォロワー",
"birthday": "誕生日", "birthday": "誕生日",
"subscription": "サブスクリプション", "subscription": "登録",
"not_born": "未出生", "not_born": "未出生",
"hacker": "ハッカー", "hacker": "ハッカー",
"profile": "プロフィール", "profile": "プロフィール",
@ -365,33 +370,29 @@
"edit": "編集", "edit": "編集",
"user_profile": "ユーザープロフィール", "user_profile": "ユーザープロフィール",
"count_plays": "{count} 回再生", "count_plays": "{count} 回再生",
"streaming_fees_hypothetical": "*これは Spotify のストリームあたりの支払い\nが $0.003 から $0.005 であると仮定して計算されています。\nこれは、Spotify でその曲を聴いた場合にアーティストにいくら支払ったかの\n洞察を得るための仮定の計算です。", "streaming_fees_hypothetical": "ストリーミング料金 (概算)",
"count_mins": "{minutes} 分", "minutes_listened": "視聴時間",
"summary_minutes": "分",
"summary_listened_to_music": "音楽を聴いた",
"summary_songs": "曲",
"summary_streamed_overall": "全体のストリーミング",
"summary_owed_to_artists": "今月アーティストに支払うべき額",
"summary_artists": "アーティストの",
"summary_music_reached_you": "音楽があなたに届いた",
"summary_full_albums": "フルアルバム",
"summary_got_your_love": "あなたの愛を受け取った",
"summary_playlists": "プレイリスト",
"summary_were_on_repeat": "リピートしていた",
"total_money": "合計 {money}",
"minutes_listened": "リスニング時間",
"streamed_songs": "ストリーミングされた曲", "streamed_songs": "ストリーミングされた曲",
"count_streams": "{count} 回のストリーム", "count_streams": "{count} 回のストリーム",
"owned_by_you": "あなたが所有", "owned_by_you": "あなたが所有",
"copied_shareurl_to_clipboard": "{shareUrl} をクリップボードにコピーしました", "copied_shareurl_to_clipboard": "{shareUrl} をクリップボードにコピーしました",
"spotify_hipotetical_calculation": "*これは、Spotifyのストリームごとの支払い\nが $0.003 から $0.005 の範囲で計算されています。これは仮想的な\n計算で、Spotify で曲を聴いた場合に、アーティストに\nどれくらい支払ったかをユーザーに示すためのものです。", "spotify_hipotetical_calculation": "*これは、Spotifyのストリームあたり\n$0.003 から $0.005 として計算されています。\n概算であり、Spotify で曲を聴いていたら、アーティストに\nどれくらい支払われたかを示すものです。",
"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が見つかりません", "webview_not_found": "Webviewが見つかりません",
"webview_not_found_description": "デバイスにWebviewランタイムがインストールされていません。\nインストールされている場合は、environment PATHにあることを確認してください\n\nインストール後、アプリを再起動してください", "webview_not_found_description": "端末にWebviewランタイムがインストールされていません。\nインストールされている場合は、環境変数のパスにあるか確認してください\n\nインストール後、アプリを再起動してください",
"unsupported_platform": "サポートされていないプラットフォーム", "unsupported_platform": "未対応のプラットフォーム",
"invidious_instance": "Invidiousサーバーインスタンス",
"invidious_description": "トラックマッチングに使用するInvidiousサーバーインスタンス",
"invidious_warning": "一部はうまく機能しない可能性があります。自己責任で使用してください",
"invidious_source_description": "Pipedに似ていますが、より高い可用性があります。",
"cache_music": "音楽をキャッシュ", "cache_music": "音楽をキャッシュ",
"open": "開く", "open": "開く",
"cache_folder": "キャッシュフォルダー", "cache_folder": "キャッシュフォルダー",
@ -402,22 +403,20 @@
"found_n_files": "{count}ファイルが見つかりました", "found_n_files": "{count}ファイルが見つかりました",
"export_cache_confirmation": "これらのファイルをエクスポートしますか", "export_cache_confirmation": "これらのファイルをエクスポートしますか",
"exported_n_out_of_m_files": "{filesExported} / {files}ファイルがエクスポートされました", "exported_n_out_of_m_files": "{filesExported} / {files}ファイルがエクスポートされました",
"playlist": "プレイリスト",
"no_loop": "ループなし",
"generate": "生成", "generate": "生成",
"undo": "元に戻す", "undo": "元に戻す",
"download_all": "すべてダウンロード", "download_all": "すべてダウンロード",
"add_all_to_playlist": "すべてをプレイリストに追加", "add_all_to_playlist": "すべて再生リストに追加",
"add_all_to_queue": "すべてキューに追加", "add_all_to_queue": "すべてキューに追加",
"play_all_next": "次にすべてを再生", "play_all_next": "すべてを次に再生",
"pause": "一時停止", "pause": "一時停止",
"view_all": "すべてを見る", "view_all": "すべて表示",
"no_tracks_added_yet": "まだ曲を追加していないようです", "no_tracks_added_yet": "まだ曲を追加していないようです",
"no_tracks": "ここには曲がないようです", "no_tracks": "ここには曲がないようです",
"no_tracks_listened_yet": "まだ何も聞いていないようです", "no_tracks_listened_yet": "まだ何も聞いていないようです",
"not_following_artists": "アーティストをフォローしていません", "not_following_artists": "アーティストをフォローしていません",
"no_favorite_albums_yet": "まだお気に入りのアルバムを追加していないようです", "no_favorite_albums_yet": "まだお気に入りのアルバムを追加していないようです",
"no_logs_found": "ログが見つかりませんでした", "no_logs_found": "ログなし",
"youtube_engine": "YouTubeエンジン", "youtube_engine": "YouTubeエンジン",
"youtube_engine_not_installed_title": "{engine}はインストールされていません", "youtube_engine_not_installed_title": "{engine}はインストールされていません",
"youtube_engine_not_installed_message": "{engine}はシステムにインストールされていません。", "youtube_engine_not_installed_message": "{engine}はシステムにインストールされていません。",
@ -425,10 +424,10 @@
"youtube_engine_unix_issue_message": "macOS/Linux/Unix系OSでは、.zshrc/.bashrc/.bash_profileなどでパスを設定しても動作しません。\nシェルの設定ファイルにパスを設定する必要があります", "youtube_engine_unix_issue_message": "macOS/Linux/Unix系OSでは、.zshrc/.bashrc/.bash_profileなどでパスを設定しても動作しません。\nシェルの設定ファイルにパスを設定する必要があります",
"download": "ダウンロード", "download": "ダウンロード",
"file_not_found": "ファイルが見つかりません", "file_not_found": "ファイルが見つかりません",
"custom": "カスタム", "custom": "独自",
"add_custom_url": "カスタムURLを追加", "add_custom_url": "独自にURLを追加",
"edit_port": "ポートを編集", "edit_port": "ポートを編集",
"port_helper_msg": "デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。", "port_helper_msg": "初期設定は-1で、ランダムな番号を示します。ファイアウォールを設定している場合に設定することを推奨します。",
"connect_request": "{client}の接続を許可しますか?", "connect_request": "{client}の接続を許可しますか?",
"connection_request_denied": "接続が拒否されました。ユーザーがアクセスを拒否しました。", "connection_request_denied": "接続が拒否されました。ユーザーがアクセスを拒否しました。",
"hipotetical_calculation": "*これは、オンライン音楽ストリーミングプラットフォームの1ストリームあたりの平均支払い額である$0.003〜$0.005に基づいて計算されています。これは、ユーザーが異なる音楽ストリーミングプラットフォームで曲を聴いた場合に、アーティストにどれだけ支払ったかを把握するための仮説的な計算です。", "hipotetical_calculation": "*これは、オンライン音楽ストリーミングプラットフォームの1ストリームあたりの平均支払い額である$0.003〜$0.005に基づいて計算されています。これは、ユーザーが異なる音楽ストリーミングプラットフォームで曲を聴いた場合に、アーティストにどれだけ支払ったかを把握するための仮説的な計算です。",

View File

@ -179,8 +179,8 @@
"success_message": "你已经成功使用 Spotify 登录。干得漂亮!", "success_message": "你已经成功使用 Spotify 登录。干得漂亮!",
"step_4": "步骤 4", "step_4": "步骤 4",
"something_went_wrong": "某些地方出现了问题", "something_went_wrong": "某些地方出现了问题",
"piped_instance": "管道服务器实例", "piped_instance": "Piped 服务器实例",
"piped_description": "管道服务器实例用于匹配歌曲", "piped_description": "Piped 服务器实例用于匹配歌曲",
"piped_warning": "它们中的一部分可能并不能正常工作。使用时请自行承担风险", "piped_warning": "它们中的一部分可能并不能正常工作。使用时请自行承担风险",
"generate_playlist": "生成歌单", "generate_playlist": "生成歌单",
"track_exists": "歌曲 {track} 已存在", "track_exists": "歌曲 {track} 已存在",

481
lib/l10n/app_zh_TW.arb Normal file
View File

@ -0,0 +1,481 @@
{
"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": "你按過讚的所有歌曲",
"create_playlist": "建立歌單",
"create_a_playlist": "建立一個歌單",
"create": "建立",
"cancel": "取消",
"playlist_name": "歌單名稱",
"name_of_playlist": "歌單的名稱",
"description": "說明",
"public": "公開",
"collaborative": "共享協作",
"search_local_tracks": "搜尋本地歌曲...",
"play": "播放",
"delete": "刪除",
"none": "無",
"sort_a_z": "依字母順序",
"sort_z_a": "依字母倒序",
"sort_artist": "按藝人",
"sort_album": "按專輯",
"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": "此名藝人的分享連結已複製至剪貼簿",
"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": "單曲循環",
"repeat_playlist": "歌單循環",
"queue": "播放清單",
"alternative_track_sources": "其它音源",
"download_track": "下載歌曲",
"tracks_in_queue": "{tracks} 首歌曲在播放清單中",
"clear_all": "清除全部",
"show_hide_ui_on_hover": "游標暫留時顯示 / 隱藏控制列",
"always_on_top": "置頂",
"exit_mini_player": "退出小窗模式",
"download_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": "跳過非音樂片段(跳過贊助商廣告)",
"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": "Kingkor Roy Tirtho",
"copyright": "© 2021-{current_year} Kingkor Roy Tirtho",
"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",
"cookie_name_cookie": "{name} Cookie",
"fill_in_all_fields": "請填入所有欄位",
"submit": "提交",
"exit": "退出",
"previous": "上一步",
"next": "下一步",
"done": "完成",
"step_1": "步驟 1",
"first_go_to": "首先,前往",
"login_if_not_logged_in": "如果尚未登入,請登入或註冊帳戶",
"step_2": "步驟 2",
"step_2_steps": "1. 一旦你已經完成登入, 按 F12 鍵或滑鼠右鍵點選網頁空白區域 > 選擇「檢查」以開啟瀏覽器開發者工具DevTools\n2. 選擇 \"應用程式Application\" 分頁Chrome, Edge, Brave 等基於 Chromium 記憶體或基於 Choxage, nox Firefox 的瀏覽器))\n3. 選擇 \"Cookies\" 欄位然後選擇 \"https://accounts.spotify.com\" 子選單",
"step_3": "步驟 3",
"success_emoji": "成功🥳",
"success_message": "你已經成功使用 Spotify 登入。幹得漂亮!",
"step_4": "步驟 4",
"something_went_wrong": "某些地方出現了問題",
"piped_instance": "Piped 伺服器實例",
"piped_description": "Piped 伺服器實例用於匹配歌曲",
"piped_warning": "它們之中的一部分可能無法正常運作。使用時請自行承擔風險",
"generate_playlist": "產生歌單",
"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": "歌曲長度 (s)",
"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 封鎖,這意味著你的裝置將在長達 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": "記錄檔Log",
"developers": "開發者",
"not_logged_in": "你尚未登入",
"search_mode": "搜尋模式",
"audio_source": "音訊來源",
"ok": "確定",
"failed_to_encrypt": "加密失敗",
"encryption_failed_warning": "Spotube使用加密來安全地儲存您的資料。但是失敗了。因此它將回退到不安全的儲存空間\n如果您使用Linux請確保已安裝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": "使用作業系統的預設視窗標題列",
"update_playlist": "更新播放清單",
"update": "更新",
"crunching_results": "處理結果中...",
"search_to_get_results": "搜尋以取得結果",
"use_amoled_mode": "使用 AMOLED 模式",
"pitch_dark_theme": "漆黑主題",
"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 Rick PresenceDiscord 狀態)",
"browse_all": "瀏覽全部",
"genres": "音樂類型",
"explore_genres": "探索音樂類型",
"step_3_steps": "複製\"sp_dc\" Cookie的值",
"step_4_steps": "貼上複製的\"sp_dc\"值",
"friends": "好友",
"no_lyrics_available": "抱歉,無法找到這首歌的歌詞",
"sort_duration": "依長度排序",
"start_a_radio": "開始收聽電台",
"how_to_start_radio": "您想如何開始收聽電台?",
"replace_queue_question": "您想要取代目前清單還是追加到清單?",
"endless_playback": "無限播放",
"delete_playlist": "刪除播放清單",
"delete_playlist_confirmation": "您確定要刪除此播放清單嗎?",
"local_tracks": "本地音訊",
"song_link": "歌曲連結",
"skip_this_nonsense": "跳過這個無聊內容",
"freedom_of_music": "“音樂的自由”",
"freedom_of_music_palm": "「音樂的自由掌握在您手中」",
"get_started": "我們開始吧",
"youtube_source_description": "建議且效果最佳。",
"piped_source_description": "感覺自由?與 YouTube 一樣,但更自由。",
"jiosaavn_source_description": "最適合南亞地區。",
"highest_quality": "最高音質:{quality}",
"select_audio_source": "選擇音訊來源",
"endless_playback_description": "自動將新歌曲加入清單的結尾",
"choose_your_region": "選擇您的所在地區",
"choose_your_region_description": "這能幫助 Spotube 為您的所在位置顯示正確的內容。",
"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": "遠端",
"local_library": "本地媒體庫",
"add_library_location": "新增至媒體庫",
"remove_library_location": "從媒體庫移除",
"local_tab": "本地",
"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": "*基於 Spotify 每次播放的支付金額\n從 $0.003 到 $0.005 計算。這是一個假設性的\n計算旨在讓用戶了解如果他們在 Spotify 上收聽\n這些歌曲可能會付給作者的金額。",
"count_mins": "{minutes} 分鐘",
"summary_minutes": "分鐘",
"summary_listened_to_music": "聽音樂",
"summary_songs": "歌曲",
"summary_streamed_overall": "整體串流媒體",
"summary_owed_to_artists": "本月欠藝術家的",
"summary_artists": "藝術家的",
"summary_music_reached_you": "音樂接觸到你",
"summary_full_albums": "完整專輯",
"summary_got_your_love": "獲得了你的愛心",
"summary_playlists": "播放清單",
"summary_were_on_repeat": "已經重複播放",
"total_money": "總計 {money}",
"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計算用於給用戶了解他們如果在 Spotify 上\n收聽歌曲會支付給藝術家的金額。",
"webview_not_found": "未找到 Webview 框架",
"webview_not_found_description": "您的裝置中未安裝 Webview Runtime。\n如果已安裝請確保它的位置在系統環境變數PATH中\n\n安裝後重新啟動應用程式",
"unsupported_platform": "不支援的平台",
"invidious_instance": "Invidious 伺服器實例",
"invidious_description": "用於音軌匹配的 Invidious 伺服器實例",
"invidious_warning": "有些可能無法正常運作。請自行承擔風險",
"invidious_source_description": "類似 Piped但可用性更高。",
"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} 個檔案",
"playlist": "播放清單",
"no_loop": "無循環",
"generate": "生成",
"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": "在類 Unix 作業系統(如 macOS/Linux/Unix請在 .zshrc/.bashrc/.bash_profile 等檔案中設定路徑無效。\n您需要在 shell 設定檔中設定路徑",
"download": "下載",
"file_not_found": "找不到檔案",
"custom": "自訂",
"add_custom_url": "新增自訂 URL",
"edit_port": "編輯端口",
"port_helper_msg": "預設值為 -1表示隨機數。如果您已配置防火牆建議設定此項目。",
"connect_request": "允許 {client} 連線嗎?",
"connection_request_denied": "連線被拒絕。請求被使用者拒絕。",
"hipotetical_calculation": "*此為根據線上音樂串流平台平均每次播放 $0.003 至 $0.005 的收益所計算的假設值。此為一個假設性計算,旨在讓使用者了解若他們在不同的音樂串流平台上收聽同一首歌曲,他們將會支付給藝人多少費用。",
"an_error_occurred": "發生錯誤",
"copy_to_clipboard": "複製到剪貼簿",
"view_logs": "檢視日誌",
"retry": "重試",
"no_default_metadata_provider_selected": "您沒有設定預設的中繼資料供應商",
"manage_metadata_providers": "管理中繼資料供應商",
"open_link_in_browser": "要在瀏覽器中開啟連結嗎?",
"do_you_want_to_open_the_following_link": "您想開啟以下連結嗎",
"unsafe_url_warning": "從不受信任的來源開啟連結可能不安全。請務必小心!\n您也可以將連結複製到剪貼簿。",
"copy_link": "複製連結",
"building_your_timeline": "正在根據您的收聽記錄建立您的時間軸...",
"official": "官方",
"author_name": "作者:{author}",
"third_party": "第三方",
"plugin_requires_authentication": "此外掛程式需要驗證",
"update_available": "有可用的更新",
"supports_scrobbling": "支援 Scrobbling",
"plugin_scrobbling_info": "此外掛程式會 Scrobble 您的音樂以產生您的收聽記錄。",
"default_plugin": "預設",
"set_default": "設為預設",
"support": "支援",
"support_plugin_development": "支援外掛程式開發",
"can_access_name_api": "- 可以存取 **{name}** API",
"do_you_want_to_install_this_plugin": "您想安裝此外掛程式嗎?",
"third_party_plugin_warning": "此外掛程式來自第三方儲存庫。請在安裝前確認您信任該來源。",
"author": "作者",
"this_plugin_can_do_following": "此外掛程式可以執行以下操作",
"install": "安裝",
"install_a_metadata_provider": "安裝中繼資料供應商",
"no_tracks_playing": "目前沒有正在播放的曲目",
"synced_lyrics_not_available": "此歌曲沒有同步歌詞。請改用",
"plain_lyrics": "純歌詞",
"tab_instead": "分頁。",
"disclaimer": "免責聲明",
"third_party_plugin_dmca_notice": "Spotube 團隊對任何「第三方」外掛程式不負任何責任(包括法律責任)。\n請自行承擔使用風險。如有任何錯誤/問題,請向該外掛程式的儲存庫回報。\n\n若有任何「第三方」外掛程式違反任何服務/法律實體的服務條款/DMCA請向「第三方」外掛程式作者或託管平台如 GitHub/Codeberg要求採取行動。以上列出的標記為「第三方」外掛程式均為公開/社群維護的外掛程式。我們沒有對其進行審核,因此無法對其採取任何行動。\n\n",
"input_does_not_match_format": "輸入不符合所需格式",
"metadata_provider_plugins": "中繼資料供應商外掛程式",
"paste_plugin_download_url": "貼上下載網址、GitHub/Codeberg 儲存庫網址或 .smplug 檔案的直接連結",
"download_and_install_plugin_from_url": "從網址下載並安裝外掛程式",
"failed_to_add_plugin_error": "新增外掛程式失敗:{error}",
"upload_plugin_from_file": "從檔案上傳外掛程式",
"installed": "已安裝",
"available_plugins": "可用的外掛程式",
"configure_your_own_metadata_plugin": "設定您自己的播放清單/專輯/藝人/動態中繼資料供應商",
"audio_scrobblers": "音訊 Scrobblers",
"scrobbling": "Scrobbling"
}

View File

@ -149,7 +149,8 @@ abstract class AppLocalizations {
Locale('tr'), Locale('tr'),
Locale('uk'), Locale('uk'),
Locale('vi'), Locale('vi'),
Locale('zh') Locale('zh'),
Locale('zh', 'TW')
]; ];
/// No description provided for @guest. /// No description provided for @guest.
@ -2978,6 +2979,18 @@ class _AppLocalizationsDelegate
} }
AppLocalizations lookupAppLocalizations(Locale locale) { AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when language+country codes are specified.
switch (locale.languageCode) {
case 'zh':
{
switch (locale.countryCode) {
case 'TW':
return AppLocalizationsZhTw();
}
break;
}
}
// Lookup logic when only language code is specified. // Lookup logic when only language code is specified.
switch (locale.languageCode) { switch (locale.languageCode) {
case 'ar': case 'ar':

View File

@ -82,13 +82,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get liked_tracks_description => 'いいねしたすべての曲'; String get liked_tracks_description => 'いいねしたすべての曲';
@override @override
String get playlist => 'プレイリスト'; String get playlist => '再生リスト';
@override @override
String get create_a_playlist => '再生リストの作成'; String get create_a_playlist => '再生リストの作成';
@override @override
String get update_playlist => 'プレイリストを更新'; String get update_playlist => '再生リストを更新';
@override @override
String get create => '作成'; String get create => '作成';
@ -139,14 +139,14 @@ class AppLocalizationsJa extends AppLocalizations {
String get sort_album => 'アルバム順に並び替え'; String get sort_album => 'アルバム順に並び替え';
@override @override
String get sort_duration => '時間で並べ替え'; String get sort_duration => '長さ順に並べ替え';
@override @override
String get sort_tracks => '曲の並び替え'; String get sort_tracks => '曲の並び替え';
@override @override
String currently_downloading(Object tracks_length) { String currently_downloading(Object tracks_length) {
return 'いまダウンロード中 ($tracks_length) 曲'; return 'ダウンロード中 ($tracks_length) 曲';
} }
@override @override
@ -368,7 +368,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get download_location => 'ダウンロード先'; String get download_location => 'ダウンロード先';
@override @override
String get local_library => 'ローカルライブラリ'; String get local_library => '端末内ライブラリ';
@override @override
String get add_library_location => 'ライブラリに追加'; String get add_library_location => 'ライブラリに追加';
@ -395,10 +395,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get system_default => 'システムの既定値'; String get system_default => 'システムの既定値';
@override @override
String get market_place_region => '市場の地域'; String get market_place_region => '音楽市場の地域';
@override @override
String get recommendation_country => '推薦先の国'; String get recommendation_country => 'おすすめの国';
@override @override
String get appearance => '外観'; String get appearance => '外観';
@ -588,7 +588,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get invidious_instance => 'Invidiousサーバーインスタンス'; String get invidious_instance => 'Invidiousサーバーインスタンス';
@override @override
String get invidious_description => 'トラックマッチングに使用するInvidiousサーバーインスタンス'; String get invidious_description => '曲の一致に使用するInvidiousサーバーインスタンス';
@override @override
String get invidious_warning => '一部はうまく機能しない可能性があります。自己責任で使用してください'; String get invidious_warning => '一部はうまく機能しない可能性があります。自己責任で使用してください';
@ -804,17 +804,17 @@ class AppLocalizationsJa extends AppLocalizations {
String get search_mode => '検索モード'; String get search_mode => '検索モード';
@override @override
String get audio_source => '音声ソース'; String get audio_source => '音声の提供元';
@override @override
String get ok => '分かりました'; String get ok => 'OK';
@override @override
String get failed_to_encrypt => '暗号化に失敗しました'; String get failed_to_encrypt => '暗号化に失敗しました';
@override @override
String get encryption_failed_warning => String get encryption_failed_warning =>
'Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください'; 'SpoTubeはデータを安全に保存するために暗号化を用いますが、暗号化に失敗しました。このため、安全でない保存領域への保存に切り替えます\nOSがLinuxなら、gnome-keyring、kde-wallet、keepassxcなどの管理ツールがインストールされていることを確認してください';
@override @override
String get querying_info => '情報を取得中...'; String get querying_info => '情報を取得中...';
@ -824,7 +824,7 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String piped_down_error_instructions(Object pipedInstance) { String piped_down_error_instructions(Object pipedInstance) {
return 'Pipedインスタンス$pipedInstanceは現在ダウンしています\n\nインスタンスを変更するか、\'APIタイプ\'を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください'; return 'Pipedインスタンス $pipedInstance は現在ダウンしています\n\nインスタンスを変更するか、「APIの種類」を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください';
} }
@override @override
@ -834,7 +834,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get connection_restored => 'インターネット接続が復旧しました'; String get connection_restored => 'インターネット接続が復旧しました';
@override @override
String get use_system_title_bar => 'システムタイトルバーを使用する'; String get use_system_title_bar => 'システムのタイトルバーを使う';
@override @override
String get crunching_results => '結果を処理中...'; String get crunching_results => '結果を処理中...';
@ -843,40 +843,40 @@ class AppLocalizationsJa extends AppLocalizations {
String get search_to_get_results => '結果を取得するために検索'; String get search_to_get_results => '結果を取得するために検索';
@override @override
String get use_amoled_mode => 'AMOLEDモードを使用する'; String get use_amoled_mode => 'AMOLEDモードを使用';
@override @override
String get pitch_dark_theme => 'ピッチブラックダートテーマ'; String get pitch_dark_theme => 'ピッチブラック ダークテーマ';
@override @override
String get normalize_audio => 'オーディオを正規化する'; String get normalize_audio => '音声を正規化';
@override @override
String get change_cover => 'カバーを変更する'; String get change_cover => 'カバーを変更';
@override @override
String get add_cover => 'カバーを追加する'; String get add_cover => 'カバーを追加';
@override @override
String get restore_defaults => 'デフォルト値に戻す'; String get restore_defaults => '設定を初期化';
@override @override
String get download_music_codec => '音楽コーデックをダウンロードする'; String get download_music_codec => 'ダウンロード用の音声コーデック';
@override @override
String get streaming_music_codec => 'ストリーミング音楽コーデック'; String get streaming_music_codec => 'ストリーミング用の音声コーデック';
@override @override
String get login_with_lastfm => 'Last.fmでログインする'; String get login_with_lastfm => 'Last.fmでログイン';
@override @override
String get connect => '接続する'; String get connect => '接続';
@override @override
String get disconnect_lastfm => 'Last.fmから切断する'; String get disconnect_lastfm => 'Last.fmから切断';
@override @override
String get disconnect => '切断する'; String get disconnect => '切断';
@override @override
String get username => 'ユーザー名'; String get username => 'ユーザー名';
@ -885,10 +885,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get password => 'パスワード'; String get password => 'パスワード';
@override @override
String get login => 'ログインする'; String get login => 'ログイン';
@override @override
String get login_with_your_lastfm => 'あなたのLast.fmアカウントでログインする'; String get login_with_your_lastfm => 'Last.fmアカウントでログイン';
@override @override
String get scrobble_to_lastfm => 'Last.fmにスクロブルする'; String get scrobble_to_lastfm => 'Last.fmにスクロブルする';
@ -897,7 +897,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get go_to_album => 'アルバムに移動'; String get go_to_album => 'アルバムに移動';
@override @override
String get discord_rich_presence => 'ディスコードリッチプレゼンス'; String get discord_rich_presence => 'Discord リッチプレゼンス';
@override @override
String get browse_all => 'すべてを閲覧'; String get browse_all => 'すべてを閲覧';
@ -912,7 +912,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get friends => '友達'; String get friends => '友達';
@override @override
String get no_lyrics_available => '申し訳ありませんが、このトラックの歌詞を見つけることができません'; String get no_lyrics_available => 'すみません、この曲の歌詞が見つかりません';
@override @override
String get start_a_radio => 'ラジオを開始'; String get start_a_radio => 'ラジオを開始';
@ -927,28 +927,28 @@ class AppLocalizationsJa extends AppLocalizations {
String get endless_playback => 'エンドレス再生'; String get endless_playback => 'エンドレス再生';
@override @override
String get delete_playlist => 'プレイリストを削除'; String get delete_playlist => '再生リストを削除';
@override @override
String get delete_playlist_confirmation => 'このプレイリストを削除してもよろしいですか?'; String get delete_playlist_confirmation => 'この再生リストを削除しますか?';
@override @override
String get local_tracks => 'ローカルトラック'; String get local_tracks => '端末内の曲';
@override @override
String get local_tab => 'ローカル'; String get local_tab => '端末内';
@override @override
String get song_link => '曲のリンク'; String get song_link => '曲のリンク';
@override @override
String get skip_this_nonsense => 'の愚かなことをスキップ'; String get skip_this_nonsense => 'んなことはスキップ';
@override @override
String get freedom_of_music => '“音楽の自由”'; String get freedom_of_music => '“音楽の自由”';
@override @override
String get freedom_of_music_palm => '手のひらの中の音楽の自由”'; String get freedom_of_music_palm => '音楽の自由を思いのままに';
@override @override
String get get_started => 'さあ始めましょう'; String get get_started => 'さあ始めましょう';
@ -957,13 +957,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get youtube_source_description => '推奨され、最適に機能します。'; String get youtube_source_description => '推奨され、最適に機能します。';
@override @override
String get piped_source_description => '自由に感じますか? YouTubeと同じですが、はるかに無料です。'; String get piped_source_description => '自由を感じるYouTubeと同じだけど、はるかに自由です。';
@override @override
String get jiosaavn_source_description => '南アジア地域向けの最適です。'; String get jiosaavn_source_description => '南アジア地域では最適です。';
@override @override
String get invidious_source_description => 'Pipedに似ていますが、より高い可用性があります。'; String get invidious_source_description => 'Pipedに似ていますが、より用性があります。';
@override @override
String highest_quality(Object quality) { String highest_quality(Object quality) {
@ -971,56 +971,55 @@ class AppLocalizationsJa extends AppLocalizations {
} }
@override @override
String get select_audio_source => 'オーディオソースを選択'; String get select_audio_source => '音声の提供元を選択';
@override @override
String get endless_playback_description => '新しい曲をキューの最後に自動的に追加'; String get endless_playback_description => 'キューの最後に新しい曲を自動で追加';
@override @override
String get choose_your_region => '地域を選択'; String get choose_your_region => '地域を選択';
@override @override
String get choose_your_region_description => String get choose_your_region_description => 'Spotubeがあなたの地域に適したコンテンツを表示します。';
'これにより、Spotubeがあなたの場所に適したコンテンツを表示できます。';
@override @override
String get choose_your_language => '言語を選択してください'; String get choose_your_language => '言語を選択してください';
@override @override
String get help_project_grow => 'このプロジェクトの成長を支援する'; String get help_project_grow => 'プロジェクトの成長を支援する';
@override @override
String get help_project_grow_description => String get help_project_grow_description =>
'Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグ報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。'; 'SpoTubeはオープンソースプロジェクトです。貢献したり、バグ報告したり、新機能を提案することで、プロジェクトの成長に貢献できます。';
@override @override
String get contribute_on_github => 'GitHubで貢献する'; String get contribute_on_github => 'GitHubで貢献';
@override @override
String get donate_on_open_collective => 'Open Collectiveで寄付する'; String get donate_on_open_collective => 'Open Collectiveで寄付';
@override @override
String get browse_anonymously => '匿名で閲覧する'; String get browse_anonymously => '匿名で閲覧する';
@override @override
String get enable_connect => '接続を有効にする'; String get enable_connect => '接続する';
@override @override
String get enable_connect_description => '他のデバイスからSpotubeを制御する'; String get enable_connect_description => '他の端末からSpotubeを制御する';
@override @override
String get devices => 'デバイス'; String get devices => '機器';
@override @override
String get select => '選択する'; String get select => '選択';
@override @override
String connect_client_alert(Object client) { String connect_client_alert(Object client) {
return '$client によって操作されています'; return '$client から操作されています';
} }
@override @override
String get this_device => 'このデバイス'; String get this_device => 'この端末';
@override @override
String get remote => 'リモート'; String get remote => 'リモート';
@ -1030,23 +1029,23 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String and_n_more(Object count) { String and_n_more(Object count) {
return 'そして $count つのアイテム'; return 'さらに $count 項目';
} }
@override @override
String get recently_played => '最近再生された'; String get recently_played => '最近聴いた曲';
@override @override
String get browse_more => 'もっと見る'; String get browse_more => 'もっと表示';
@override @override
String get no_title => 'タイトルなし'; String get no_title => 'タイトルなし';
@override @override
String get not_playing => '再生中ではありません'; String get not_playing => '再生なし';
@override @override
String get epic_failure => '壮大な失敗'; String get epic_failure => '壮大なエラー';
@override @override
String added_num_tracks_to_queue(Object tracks_length) { String added_num_tracks_to_queue(Object tracks_length) {
@ -1054,7 +1053,7 @@ class AppLocalizationsJa extends AppLocalizations {
} }
@override @override
String get spotube_has_an_update => 'Spotube にアップデートがあります'; String get spotube_has_an_update => 'Spotube の最新版あり';
@override @override
String get download_now => '今すぐダウンロード'; String get download_now => '今すぐダウンロード';
@ -1073,20 +1072,19 @@ class AppLocalizationsJa extends AppLocalizations {
String get read_the_latest => '最新の '; String get read_the_latest => '最新の ';
@override @override
String get release_notes => 'リリースノート'; String get release_notes => '更新情報を読む';
@override @override
String get pick_color_scheme => 'カラースキームを選択'; String get pick_color_scheme => 'カラーテーマを選択';
@override @override
String get save => '保存'; String get save => '保存';
@override @override
String get choose_the_device => 'デバイスを選択:'; String get choose_the_device => '端末を選択:';
@override @override
String get multiple_device_connected => String get multiple_device_connected => '複数の端末が接続されています。\nこの操作を実行する端末を選択';
'複数のデバイスが接続されています。\nこのアクションを実行するデバイスを選択してください';
@override @override
String get nothing_found => '何も見つかりませんでした'; String get nothing_found => '何も見つかりませんでした';
@ -1133,7 +1131,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get birthday => '誕生日'; String get birthday => '誕生日';
@override @override
String get subscription => 'サブスクリプション'; String get subscription => '登録';
@override @override
String get not_born => '未出生'; String get not_born => '未出生';
@ -1159,11 +1157,10 @@ class AppLocalizationsJa extends AppLocalizations {
} }
@override @override
String get streaming_fees_hypothetical => String get streaming_fees_hypothetical => 'ストリーミング料金 (概算)';
'*これは Spotify のストリームあたりの支払い\n\$0.003 から \$0.005 であると仮定して計算されています。\nこれは、Spotify でその曲を聴いた場合にアーティストにいくら支払ったかの\n洞察を得るための仮定の計算です。';
@override @override
String get minutes_listened => 'リスニング時間'; String get minutes_listened => '視聴時間';
@override @override
String get streamed_songs => 'ストリーミングされた曲'; String get streamed_songs => 'ストリーミングされた曲';
@ -1200,32 +1197,32 @@ class AppLocalizationsJa extends AppLocalizations {
String get summary_songs => ''; String get summary_songs => '';
@override @override
String get summary_streamed_overall => '全体のストリーミング'; String get summary_streamed_overall => 'まるごと聴いた';
@override @override
String get summary_owed_to_artists => '今月アーティストに払うべき額'; String get summary_owed_to_artists => '今月アーティストに払う\nべき額';
@override @override
String get summary_artists => 'アーティスト'; String get summary_artists => 'アーティスト';
@override @override
String get summary_music_reached_you => '音楽があなたに届いた'; String get summary_music_reached_you => '音楽が届いた';
@override @override
String get summary_full_albums => 'フルアルバム'; String get summary_full_albums => 'フルアルバム';
@override @override
String get summary_got_your_love => 'あなたの愛を受け取った'; String get summary_got_your_love => 'あなたの愛を受け取った';
@override @override
String get summary_playlists => 'プレイリスト'; String get summary_playlists => '再生リスト';
@override @override
String get summary_were_on_repeat => 'リピートしてい'; String get summary_were_on_repeat => 'をリピートしまし';
@override @override
String total_money(Object money) { String total_money(Object money) {
return '$money'; return '$money';
} }
@override @override
@ -1233,10 +1230,10 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get webview_not_found_description => String get webview_not_found_description =>
'デバイスにWebviewランタイムがインストールされていません。\nインストールされている場合は、environment PATHにあることを確認してください\n\nインストール後、アプリを再起動してください'; '端末にWebviewランタイムがインストールされていません。\nインストールされている場合は、環境変数のパスにあるか確認してください\n\nインストール後、アプリを再起動してください';
@override @override
String get unsupported_platform => 'サポートされていないプラットフォーム'; String get unsupported_platform => '未対応のプラットフォーム';
@override @override
String get cache_music => '音楽をキャッシュ'; String get cache_music => '音楽をキャッシュ';
@ -1276,22 +1273,22 @@ class AppLocalizationsJa extends AppLocalizations {
String get undo => '元に戻す'; String get undo => '元に戻す';
@override @override
String get download_all => 'すべてダウンロード'; String get download_all => 'すべてダウンロード';
@override @override
String get add_all_to_playlist => 'すべてをプレイリストに追加'; String get add_all_to_playlist => 'すべて再生リストに追加';
@override @override
String get add_all_to_queue => 'すべてキューに追加'; String get add_all_to_queue => 'すべてキューに追加';
@override @override
String get play_all_next => '次にすべてを再生'; String get play_all_next => 'すべてを次に再生';
@override @override
String get pause => '一時停止'; String get pause => '一時停止';
@override @override
String get view_all => 'すべてを見る'; String get view_all => 'すべて表示';
@override @override
String get no_tracks_added_yet => 'まだ曲を追加していないようです'; String get no_tracks_added_yet => 'まだ曲を追加していないようです';
@ -1309,7 +1306,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get no_favorite_albums_yet => 'まだお気に入りのアルバムを追加していないようです'; String get no_favorite_albums_yet => 'まだお気に入りのアルバムを追加していないようです';
@override @override
String get no_logs_found => 'ログが見つかりませんでした'; String get no_logs_found => 'ログなし';
@override @override
String get youtube_engine => 'YouTubeエンジン'; String get youtube_engine => 'YouTubeエンジン';
@ -1340,17 +1337,17 @@ class AppLocalizationsJa extends AppLocalizations {
String get file_not_found => 'ファイルが見つかりません'; String get file_not_found => 'ファイルが見つかりません';
@override @override
String get custom => 'カスタム'; String get custom => '独自';
@override @override
String get add_custom_url => 'カスタムURLを追加'; String get add_custom_url => '独自にURLを追加';
@override @override
String get edit_port => 'ポートを編集'; String get edit_port => 'ポートを編集';
@override @override
String get port_helper_msg => String get port_helper_msg =>
'デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。'; '初期設定は-1で、ランダムな番号を示します。ファイアウォールを設定している場合に設定することを推奨します。';
@override @override
String connect_request(Object client) { String connect_request(Object client) {

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
/// watchakorn-18k@github => Thai /// watchakorn-18k@github => Thai
/// llama3, vishnumur777@github => Tamil /// llama3, vishnumur777@github => Tamil
/// Microsoft Copilot, Tutislav@github => Czech /// Microsoft Copilot, Tutislav@github => Czech
/// 510208@github => Traditional Chinese
library l10n; library l10n;
@ -49,6 +50,7 @@ class L10n {
const Locale('ta', 'IN'), const Locale('ta', 'IN'),
const Locale('tr', 'TR'), const Locale('tr', 'TR'),
const Locale('zh', 'CN'), const Locale('zh', 'CN'),
const Locale('zh', 'TW'),
const Locale('vi', 'VN'), const Locale('vi', 'VN'),
const Locale('eu', 'ES'), const Locale('eu', 'ES'),
]; ];

View File

@ -18,6 +18,7 @@ import 'package:spotube/hooks/utils/use_debounce.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/models/playback/track_sources.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart';
import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/server/active_track_sources.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
@ -95,7 +96,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
final controller = useScrollController(); final controller = useScrollController();
final searchRequest = useMemoized(() async { final searchRequest = useMemoized(() async {
if (searchTerm.trim().isEmpty) { if (searchTerm.trim().isEmpty || activeTrackSource == null) {
return <TrackSourceInfo>[]; return <TrackSourceInfo>[];
} }
if (preferences.audioSource == AudioSource.jiosaavn) { if (preferences.audioSource == AudioSource.jiosaavn) {
@ -107,7 +108,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
return siblingType.info; return siblingType.info;
})); }));
final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; final activeSourceInfo = activeTrackSource.info;
return results return results
..removeWhere((element) => element.id == activeSourceInfo.id) ..removeWhere((element) => element.id == activeSourceInfo.id)
@ -122,18 +123,18 @@ class SiblingTracksSheet extends HookConsumerWidget {
resultsYt resultsYt
.map(YoutubeVideoInfo.fromVideo) .map(YoutubeVideoInfo.fromVideo)
.mapIndexed((i, video) async { .mapIndexed((i, video) async {
final siblingType = if (!context.mounted) return null;
await YoutubeSourcedTrack.toSiblingType(i, video, ref); final siblingType =
return siblingType.info; await YoutubeSourcedTrack.toSiblingType(i, video, ref);
}), return siblingType.info;
})
.whereType<Future<TrackSourceInfo>>()
.toList(),
); );
final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; final activeSourceInfo = activeTrackSource.info;
return searchResults return searchResults
..removeWhere((element) => element.id == activeSourceInfo.id) ..removeWhere((element) => element.id == activeSourceInfo.id)
..insert( ..insert(0, activeSourceInfo);
0,
activeSourceInfo,
);
} }
}, [ }, [
searchTerm, searchTerm,
@ -165,8 +166,8 @@ class SiblingTracksSheet extends HookConsumerWidget {
}, [activeTrack, previousActiveTrack]); }, [activeTrack, previousActiveTrack]);
final itemBuilder = useCallback( final itemBuilder = useCallback(
(TrackSourceInfo sourceInfo) { (TrackSourceInfo sourceInfo, AudioSource source) {
final icon = sourceInfoToIconMap[sourceInfo.runtimeType]; final icon = sourceInfoToIconMap[source];
return ButtonTile( return ButtonTile(
style: ButtonVariance.ghost, style: ButtonVariance.ghost,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
@ -197,14 +198,18 @@ class SiblingTracksSheet extends HookConsumerWidget {
enabled: !isFetchingActiveTrack, enabled: !isFetchingActiveTrack,
selected: !isFetchingActiveTrack && selected: !isFetchingActiveTrack &&
sourceInfo.id == activeTrackSource?.info.id, sourceInfo.id == activeTrackSource?.info.id,
onPressed: () { onPressed: () async {
if (!isFetchingActiveTrack && if (!isFetchingActiveTrack &&
sourceInfo.id != activeTrackSource?.info.id) { sourceInfo.id != activeTrackSource?.info.id) {
activeTrackNotifier?.swapWithSibling(sourceInfo); await activeTrackNotifier?.swapWithSibling(sourceInfo);
if (MediaQuery.sizeOf(context).mdAndUp) { await ref.read(audioPlayerProvider.notifier).swapActiveSource();
closeOverlay(context);
} else { if (context.mounted) {
closeDrawer(context); if (MediaQuery.sizeOf(context).mdAndUp) {
closeOverlay(context);
} else {
closeDrawer(context);
}
} }
} }
}, },
@ -301,8 +306,8 @@ class SiblingTracksSheet extends HookConsumerWidget {
controller: controller, controller: controller,
itemCount: siblings.length, itemCount: siblings.length,
separatorBuilder: (context, index) => const Gap(8), separatorBuilder: (context, index) => const Gap(8),
itemBuilder: (context, index) => itemBuilder: (context, index) => itemBuilder(
itemBuilder(siblings[index]), siblings[index], activeTrackSource!.source),
), ),
true => FutureBuilder( true => FutureBuilder(
future: searchRequest, future: searchRequest,
@ -321,8 +326,10 @@ class SiblingTracksSheet extends HookConsumerWidget {
controller: controller, controller: controller,
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
separatorBuilder: (context, index) => const Gap(8), separatorBuilder: (context, index) => const Gap(8),
itemBuilder: (context, index) => itemBuilder: (context, index) => itemBuilder(
itemBuilder(snapshot.data![index]), snapshot.data![index],
preferences.audioSource,
),
); );
}, },
), ),

View File

@ -21,8 +21,10 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
} }
bool filterLocale(Locale locale, String query) { bool filterLocale(Locale locale, String query) {
final language = final language = LanguageLocals.getDisplayLanguage(
LanguageLocals.getDisplayLanguage(locale.languageCode).toString(); locale.languageCode,
locale.countryCode,
).toString();
return language.toLowerCase().contains(query.toLowerCase()); return language.toLowerCase().contains(query.toLowerCase());
} }
@ -124,8 +126,9 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
? Text(context.l10n.system_default) ? Text(context.l10n.system_default)
: Text( : Text(
LanguageLocals.getDisplayLanguage( LanguageLocals.getDisplayLanguage(
value.languageCode) value.languageCode,
.toString(), value.countryCode,
).toString(),
), ),
popup: SelectPopup.builder( popup: SelectPopup.builder(
searchPlaceholder: Text(context.l10n.search), searchPlaceholder: Text(context.l10n.search),
@ -161,6 +164,7 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget {
child: Text( child: Text(
LanguageLocals.getDisplayLanguage( LanguageLocals.getDisplayLanguage(
locale.languageCode, locale.languageCode,
locale.countryCode,
).toString(), ).toString(),
), ),
); );

View File

@ -220,6 +220,9 @@ class SettingsMetadataProviderPage extends HookConsumerWidget {
!pluginReposSnapshot.isLoadingNextPage, !pluginReposSnapshot.isLoadingNextPage,
itemCount: pluginRepos.length, itemCount: pluginRepos.length,
onFetchData: pluginReposNotifier.fetchMore, onFetchData: pluginReposNotifier.fetchMore,
separatorBuilder: (context, index) {
return const Gap(12);
},
loadingBuilder: (context) { loadingBuilder: (context) {
return Skeletonizer( return Skeletonizer(
enabled: true, enabled: true,

View File

@ -13,7 +13,9 @@ import 'package:spotube/l10n/l10n.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
final localWithName = L10n.all.map((e) { final localWithName = L10n.all.map((e) {
final isoCodeName = LanguageLocals.getDisplayLanguage(e.languageCode); print(e);
final isoCodeName =
LanguageLocals.getDisplayLanguage(e.languageCode, e.countryCode);
return ( return (
locale: e, locale: e,
name: "${isoCodeName.name} (${isoCodeName.nativeName})", name: "${isoCodeName.name} (${isoCodeName.nativeName})",

View File

@ -421,6 +421,20 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
); );
} }
Future<void> swapActiveSource() async {
if (state.tracks.isEmpty || state.activeTrack is! SpotubeFullTrackObject) {
return;
}
final currentIndex = state.currentIndex;
final currentTrack = state.activeTrack as SpotubeFullTrackObject;
final swappedMedia = SpotubeMedia(currentTrack);
await audioPlayer.addTrackAt(swappedMedia, currentIndex + 1);
await audioPlayer.skipToNext();
await audioPlayer.removeTrack(currentIndex);
}
Future<void> jumpToTrack(SpotubeTrackObject track) async { Future<void> jumpToTrack(SpotubeTrackObject track) async {
final index = final index =
state.tracks.toList().indexWhere((element) => element.id == track.id); state.tracks.toList().indexWhere((element) => element.id == track.id);

View File

@ -1,8 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart';
import 'package:spotube/services/logger/logger.dart';
abstract class FamilyPaginatedAsyncNotifier<K, A> abstract class FamilyPaginatedAsyncNotifier<K, A>
extends FamilyAsyncNotifier<SpotubePaginationResponseObject<K>, A> extends FamilyAsyncNotifier<SpotubePaginationResponseObject<K>, A>
@ -27,7 +29,8 @@ abstract class FamilyPaginatedAsyncNotifier<K, A>
final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>(); final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
state = AsyncData(newState.copyWith(items: <K>[...oldItems, ...items])); state = AsyncData(newState.copyWith(items: <K>[...oldItems, ...items]));
} catch (e) { } catch (e, stack) {
AppLogger.reportError(e, stack);
state = AsyncData(oldState!); state = AsyncData(oldState!);
} }
} }
@ -38,17 +41,32 @@ abstract class FamilyPaginatedAsyncNotifier<K, A>
bool hasMore = true; bool hasMore = true;
while (hasMore) { while (hasMore) {
await update((state) async { final newState = await fetch(
final newState = await fetch( state.value!.nextOffset!,
state.nextOffset!, max(state.value!.limit, 100),
state.limit, )
); .catchError(
(e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)),
)
.catchError(
(e) => fetch(state.value!.nextOffset!, state.value!.limit),
)
.catchError(
(e) async {
await Future.delayed(const Duration(milliseconds: 500));
return fetch(state.value!.nextOffset!, state.value!.limit);
},
);
hasMore = newState.hasMore; hasMore = newState.hasMore;
final oldItems = state.items.isEmpty ? <K>[] : state.items.cast<K>();
final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>(); final oldItems =
return newState.copyWith(items: <K>[...oldItems, ...items]); state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
}); final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
state = AsyncData(
newState.copyWith(items: [...oldItems, ...items]),
);
} }
return state.value!.items.cast<K>(); return state.value!.items.cast<K>();
@ -78,7 +96,8 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
...newState.items.cast<K>(), ...newState.items.cast<K>(),
]), ]),
); );
} catch (e) { } catch (e, stack) {
AppLogger.reportError(e, stack);
state = AsyncData(oldState!); state = AsyncData(oldState!);
} }
} }
@ -89,18 +108,32 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
bool hasMore = true; bool hasMore = true;
while (hasMore) { while (hasMore) {
await update((state) async { final newState = await fetch(
final newState = await fetch( state.value!.nextOffset!,
state.nextOffset!, max(state.value!.limit, 100),
state.limit, )
); .catchError(
(e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)),
)
.catchError(
(e) => fetch(state.value!.nextOffset!, state.value!.limit),
)
.catchError(
(e) async {
await Future.delayed(const Duration(milliseconds: 500));
return fetch(state.value!.nextOffset!, state.value!.limit);
},
);
hasMore = newState.hasMore; hasMore = newState.hasMore;
return newState.copyWith(items: [
...state.items.cast<K>(), final oldItems =
...newState.items.cast<K>(), state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
]); final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
});
state = AsyncData(
newState.copyWith(items: [...oldItems, ...items]),
);
} }
return state.value!.items.cast<K>(); return state.value!.items.cast<K>();

View File

@ -1,10 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:riverpod/src/async_notifier.dart'; import 'package:riverpod/src/async_notifier.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart';
import 'package:spotube/services/logger/logger.dart';
mixin PaginatedAsyncNotifierMixin<K> mixin PaginatedAsyncNotifierMixin<K>
// ignore: invalid_use_of_internal_member // ignore: invalid_use_of_internal_member
@ -28,7 +30,8 @@ mixin PaginatedAsyncNotifierMixin<K>
final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>(); final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
state = AsyncData(newState.copyWith(items: <K>[...oldItems, ...items])); state = AsyncData(newState.copyWith(items: <K>[...oldItems, ...items]));
} catch (e) { } catch (e, stack) {
AppLogger.reportError(e, stack);
state = AsyncData(oldState!); state = AsyncData(oldState!);
} }
} }
@ -39,17 +42,32 @@ mixin PaginatedAsyncNotifierMixin<K>
bool hasMore = true; bool hasMore = true;
while (hasMore) { while (hasMore) {
await update((state) async { final newState = await fetch(
final newState = await fetch( state.value!.nextOffset!,
state.nextOffset!, max(state.value!.limit, 100),
state.limit, )
); .catchError(
(e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)),
)
.catchError(
(e) => fetch(state.value!.nextOffset!, state.value!.limit),
)
.catchError(
(e) async {
await Future.delayed(const Duration(milliseconds: 500));
return fetch(state.value!.nextOffset!, state.value!.limit);
},
);
hasMore = newState.hasMore; hasMore = newState.hasMore;
final oldItems = state.items.isEmpty ? <K>[] : state.items.cast<K>();
final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>(); final oldItems =
return newState.copyWith(items: <K>[...oldItems, ...items]); state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
}); final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
state = AsyncData(
newState.copyWith(items: [...oldItems, ...items]),
);
} }
return state.value!.items.cast<K>(); return state.value!.items.cast<K>();

View File

@ -216,7 +216,7 @@ class JioSaavnSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: newSiblings, siblings: newSiblings,
sources: source!, sources: source!,
info: info, info: newSourceInfo,
query: query, query: query,
source: AudioSource.jiosaavn, source: AudioSource.jiosaavn,
); );

View File

@ -269,7 +269,7 @@ class PipedSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: newSiblings, siblings: newSiblings,
sources: toSources(manifest), sources: toSources(manifest),
info: info, info: newSourceInfo,
query: query, query: query,
source: source, source: source,
); );

View File

@ -120,6 +120,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
dynamic ref, dynamic ref,
) async { ) async {
assert(ref is WidgetRef || ref is Ref, "Invalid ref type"); assert(ref is WidgetRef || ref is Ref, "Invalid ref type");
List<TrackSource>? sourceMap; List<TrackSource>? sourceMap;
if (index == 0) { if (index == 0) {
final manifest = final manifest =
@ -338,6 +339,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final newSourceInfo = isStepSibling final newSourceInfo = isStepSibling
? sibling ? sibling
: siblings.firstWhere((s) => s.id == sibling.id); : siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, info); ..insert(0, info);
@ -364,7 +366,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
source: source, source: source,
siblings: newSiblings, siblings: newSiblings,
sources: toTrackSources(manifest), sources: toTrackSources(manifest),
info: info, info: newSourceInfo,
query: query, query: query,
); );
} }

View File

@ -23,7 +23,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_discord_rpc flutter_discord_rpc
media_kit_native_event_loop
metadata_god metadata_god
) )

View File

@ -15,7 +15,7 @@ dependencies:
- gir1.2-appindicator3-0.1 | gir1.2-ayatanaappindicator3-0.1 - gir1.2-appindicator3-0.1 | gir1.2-ayatanaappindicator3-0.1
- libsecret-1-0 - libsecret-1-0
- libnotify-bin - libnotify-bin
- libjsoncpp25 - libjsoncpp1 | libjsoncpp25 | libjsoncpp26
- libmpv1 | libmpv2 - libmpv1 | libmpv2
- xdg-user-dirs - xdg-user-dirs
- avahi-daemon - avahi-daemon

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_discord_rpc flutter_discord_rpc
media_kit_native_event_loop
metadata_god metadata_god
smtc_windows smtc_windows
) )