From 860a4cec1687d2d2b06b24bd1ea8b41f0fa81325 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Fri, 15 Apr 2022 12:11:00 +0900 Subject: [PATCH] Localization support (#99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * basic i18n poc * translate home, filters, tabs support dot notation in backend for namespaces * i18n context menu, artist filters, list controls also nothings here fix backend not caching fallback * i18n queue, artist view, search/results * i18n settings and server view * Added translation using Weblate (Norwegian Bokmål) * Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (6 of 6 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/nb_NO/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/ * fix url escaping * added some mostly naive text overflow fixes rewrote filter context menu as a slide in because the old one apparently can't handle dynamic width * Added translation using Weblate (French) * Translated using Weblate (French) Currently translated at 17.4% (11 of 63 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * Translated using Weblate (French) Currently translated at 19.0% (12 of 63 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * Translated using Weblate (French) Currently translated at 40.0% (26 of 65 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * add weblate and some pretty badges to readme * fix link * Translated using Weblate (French) Currently translated at 50.7% (33 of 65 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * Translated using Weblate (English) Currently translated at 100.0% (65 of 65 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/en/ * Translated using Weblate (French) Currently translated at 90.7% (59 of 65 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * i18n now playing context type fix overscroll on new filter menu fix getting default namespace from the i18n backend * Translated using Weblate (French) Currently translated at 96.9% (63 of 65 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * Translated using Weblate (French) Currently translated at 100.0% (66 of 66 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/fr/ * Translated using Weblate (Japanese) (#98) Currently translated at 7.5% (5 of 66 strings) Translation: Subtracks/subtracks Translate-URL: https://hosted.weblate.org/projects/subtracks/subtracks/ja/ Co-authored-by: Austin Riedhammer * little note to remind me why that's there * update licenses Co-authored-by: Allan Nordhøy Co-authored-by: Hosted Weblate Co-authored-by: Clyhtsuriva --- README.md | 9 ++ .../app/src/main/assets/custom/i18n/en.json | 151 ++++++++++++++++++ .../app/src/main/assets/custom/i18n/fr.json | 151 ++++++++++++++++++ .../app/src/main/assets/custom/i18n/ja.json | 15 ++ .../src/main/assets/custom/i18n/nb_NO.json | 1 + android/app/src/main/assets/licenses.html | 129 +++++++++++++++ android/app/src/main/assets/licenses/npm.txt | 129 +++++++++++++++ .../main/java/com/subtracks/MainActivity.java | 3 + app/App.tsx | 6 +- app/components/Button.tsx | 10 +- app/components/ContextMenu.tsx | 29 +++- app/components/FilterButton.tsx | 72 ++++++--- app/components/ListPlayerControls.tsx | 12 +- app/components/NothingHere.tsx | 9 +- app/components/withSuspense.tsx | 32 ++++ app/i18n.ts | 64 ++++++++ app/navigation/BottomTabBar.tsx | 5 +- app/navigation/BottomTabNavigator.tsx | 22 +-- app/navigation/LibraryTopTabNavigator.tsx | 17 +- app/navigation/RootNavigator.tsx | 50 +++--- app/screens/ArtistView.tsx | 98 +++++++----- app/screens/Home.tsx | 19 +-- app/screens/LibraryAlbums.tsx | 58 ++++--- app/screens/LibraryArtists.tsx | 45 +++--- app/screens/NowPlayingView.tsx | 33 ++-- app/screens/Search.tsx | 94 ++++++----- app/screens/SearchResultsView.tsx | 11 +- app/screens/ServerView.tsx | 42 ++--- app/screens/Settings.tsx | 93 ++++++----- app/screens/SongListView.tsx | 6 +- app/screens/WebViewScreen.tsx | 12 +- app/state/settings.ts | 15 +- index.js | 15 ++ package.json | 3 + yarn.lock | 42 ++++- 35 files changed, 1186 insertions(+), 316 deletions(-) create mode 100644 android/app/src/main/assets/custom/i18n/en.json create mode 100644 android/app/src/main/assets/custom/i18n/fr.json create mode 100644 android/app/src/main/assets/custom/i18n/ja.json create mode 100644 android/app/src/main/assets/custom/i18n/nb_NO.json create mode 100644 app/components/withSuspense.tsx create mode 100644 app/i18n.ts diff --git a/README.md b/README.md index 949f7a1..7757a22 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ # Subtracks is an Android open source music streaming app for [Subsonic-API-compatible](http://www.subsonic.org/pages/api.jsp) servers ([Subsonic](http://www.subsonic.org/pages/index.jsp), [Navidrome](https://www.navidrome.org/), [Airsonic](https://airsonic.github.io/), and more). It's designed to give you clean and convenient access to your music in the style of modern media players. +[![Translation status](https://hosted.weblate.org/widgets/subtracks/-/subtracks/svg-badge.svg)](https://hosted.weblate.org/engage/subtracks/) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/austinried/subtracks/build-release-debugsign/main) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/austinried/subtracks?label=github) ![F-Droid](https://img.shields.io/f-droid/v/com.subtracks) + # Screenshots

home @@ -45,3 +47,10 @@ Subtracks is an Android open source music streaming app for [Subsonic-API-compat # Building See [Building from source](BUILDING.md). + +# Translations +Want to see Subtracks in your language? Visit the project on [Weblate](https://hosted.weblate.org/engage/subtracks/) to help! + + +Translation status + diff --git a/android/app/src/main/assets/custom/i18n/en.json b/android/app/src/main/assets/custom/i18n/en.json new file mode 100644 index 0000000..74f5f08 --- /dev/null +++ b/android/app/src/main/assets/custom/i18n/en.json @@ -0,0 +1,151 @@ +{ + "resources": { + "song": { + "name": "Song", + "name_plural": "Songs", + "lists": { + "artistTopSongs": "Top Songs" + } + }, + "album": { + "name": "Album", + "name_plural": "Albums", + "lists": { + "sort": "Sort Albums", + "random": "Random", + "newest": "Recently Added", + "frequent": "Frequently Played", + "recent": "Recently Played", + "starred": "Starred", + "alphabeticalByName": "By Name", + "alphabeticalByArtist": "By Artist", + "byYear": "By Year", + "byGenre": "By Genre" + }, + "actions": { + "play": "Play Album", + "view": "View Album" + } + }, + "artist": { + "name": "Artist", + "name_plural": "Artists", + "lists": { + "sort": "Sort Artists", + "random": "Random", + "starred": "Starred", + "alphabeticalByName": "By Name" + }, + "actions": { + "view": "View Artist" + } + }, + "playlist": { + "name": "Playlist", + "name_plural": "Playlists", + "actions": { + "play": "Play Playlist" + } + }, + "queue": { + "name": "Queue", + "name_plural": "Queues" + } + }, + "context": { + "actions": { + "star": "Star", + "unstar": "Unstar" + } + }, + "navigation": { + "tabs": { + "home": "Home", + "library": "Library", + "search": "Search", + "settings": "Settings" + } + }, + "messages": { + "nothingHere": "Nothing here…" + }, + "search": { + "inputPlaceholder": "Search", + "headerTitle": "Search: {{query}}", + "moreResults": "More…", + "nowPlayingContext": "Search Results" + }, + "settings": { + "servers": { + "name": "Servers", + "fields": { + "address": "Address", + "username": "Username", + "password": "Password" + }, + "options": { + "forcePlaintextPassword": { + "title": "Force plaintext password", + "descriptionOn": "Send password in plaintext (legacy, make sure your connection is secure!)", + "descriptionOff": "Send password as token + salt" + } + }, + "actions": { + "add": "Add Server", + "testConnection": "Test Connection", + "delete": "Delete", + "save": "Save" + }, + "messages": { + "connectionOk": "Connection to {{address}} OK!", + "connectionFailed": "Connection to {{address}} failed, check settings or server" + } + }, + "network": { + "name": "Network", + "values": { + "kbps": "{{value}}kbps", + "unlimitedKbps": "Unlimited", + "seconds": "{{value}} seconds" + }, + "options": { + "maxBitrateWifi": { + "title": "Maximum bitrate (Wi-Fi)" + }, + "maxBitrateMobile": { + "title": "Maximum bitrate (mobile)" + }, + "minBuffer": { + "title": "Minimum buffer time" + }, + "maxBuffer": { + "title": "Maximum buffer time" + } + } + }, + "music": { + "name": "Music", + "options": { + "scrobble": { + "title": "Scrobble plays", + "descriptionOn": "Scrobble play history", + "descriptionOff": "Don't scrobble play history" + } + } + }, + "reset": { + "name": "Reset", + "actions": { + "clearImageCache": "Clear Image Cache" + } + }, + "about": { + "name": "About", + "version": "version {{version}}", + "actions": { + "projectHomepage": "Project Homepage", + "licenses": "Licenses" + } + } + } +} diff --git a/android/app/src/main/assets/custom/i18n/fr.json b/android/app/src/main/assets/custom/i18n/fr.json new file mode 100644 index 0000000..4588807 --- /dev/null +++ b/android/app/src/main/assets/custom/i18n/fr.json @@ -0,0 +1,151 @@ +{ + "resources": { + "album": { + "name": "Album", + "name_plural": "Albums", + "lists": { + "random": "Aléatoire", + "newest": "Récemment Ajouté", + "frequent": "Fréquemment Joué", + "recent": "Récemment Joué", + "alphabeticalByName": "Par Nom", + "byYear": "Par Année", + "alphabeticalByArtist": "Par Artiste", + "byGenre": "Par Genre", + "starred": "Favoris", + "sort": "Trier les albums" + }, + "actions": { + "play": "Jouer l'album", + "view": "Voir l'album" + } + }, + "song": { + "name": "Chanson", + "name_plural": "Chansons", + "lists": { + "artistTopSongs": "Meilleures Chansons" + } + }, + "artist": { + "name": "Artiste", + "name_plural": "Artistes", + "lists": { + "random": "Aléatoire", + "starred": "Favoris", + "alphabeticalByName": "Par Nom", + "sort": "Trier les artistes" + }, + "actions": { + "view": "Voir l'artiste" + } + }, + "playlist": { + "actions": { + "play": "Lire la playlist" + }, + "name": "Playlist", + "name_plural": "Playlists" + }, + "queue": { + "name": "File d'attente", + "name_plural": "Files d'attente" + } + }, + "settings": { + "network": { + "values": { + "seconds": "{{value}} secondes", + "unlimitedKbps": "Illimité", + "kbps": "{{value}}kbit/s" + }, + "options": { + "maxBitrateWifi": { + "title": "Débit binaire maximum (Wi-Fi)" + }, + "maxBitrateMobile": { + "title": "Débit binaire maximum (mobile)" + }, + "maxBuffer": { + "title": "Temps maximum en mémoire tampon" + }, + "minBuffer": { + "title": "Temps minimum en mémoire tampon" + } + }, + "name": "Réseau" + }, + "servers": { + "fields": { + "username": "Nom d'utilisateur", + "address": "Adresse", + "password": "Mot de passe" + }, + "actions": { + "testConnection": "Tester la connexion", + "add": "Ajouter un serveur", + "delete": "Supprimer", + "save": "Sauvegarder" + }, + "name": "Serveurs", + "options": { + "forcePlaintextPassword": { + "title": "Forcer le mot de passe en texte clair", + "descriptionOn": "Envoyer le mot de passe en test clair (héritage, assurez-vous que la connexion est sécurisée !)", + "descriptionOff": "Envoyer le mot de passe sous forme de jeton + salage" + } + }, + "messages": { + "connectionOk": "Connexion à {{address}} OK !", + "connectionFailed": "Échec de la connexion à {{address}}, vérifiez les paramètres ou le serveur" + } + }, + "music": { + "name": "Musique", + "options": { + "scrobble": { + "descriptionOff": "Ne pas scrobbler l'historique de lecture", + "descriptionOn": "Scrobbler l'historique de lecture", + "title": "Scrobbler la lecture" + } + } + }, + "about": { + "version": "version {{version}}", + "name": "À propos", + "actions": { + "licenses": "Licenses", + "projectHomepage": "Page d'accueil du projet" + } + }, + "reset": { + "actions": { + "clearImageCache": "Vider le cache d'images" + }, + "name": "Réinitialiser" + } + }, + "navigation": { + "tabs": { + "library": "Bibliothèque", + "home": "Accueil", + "search": "Recherche", + "settings": "Paramètres" + } + }, + "search": { + "headerTitle": "Recherche : {{query}}", + "inputPlaceholder": "Recherche", + "moreResults": "Plus…", + "nowPlayingContext": "Résultats de recherche" + }, + "context": { + "actions": { + "star": "Mettre en favoris", + "unstar": "Enlever des favoris" + } + }, + "messages": { + "nothingHere": "Rien ici…" + } +} diff --git a/android/app/src/main/assets/custom/i18n/ja.json b/android/app/src/main/assets/custom/i18n/ja.json new file mode 100644 index 0000000..b894777 --- /dev/null +++ b/android/app/src/main/assets/custom/i18n/ja.json @@ -0,0 +1,15 @@ +{ + "resources": { + "album": { + "lists": { + "random": "ランダムアルバム", + "frequent": "よく聴くアルバム", + "recent": "最近再生した", + "starred": "星付きアルバム" + } + }, + "song": { + "name": "歌" + } + } +} diff --git a/android/app/src/main/assets/custom/i18n/nb_NO.json b/android/app/src/main/assets/custom/i18n/nb_NO.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/android/app/src/main/assets/custom/i18n/nb_NO.json @@ -0,0 +1 @@ +{} diff --git a/android/app/src/main/assets/licenses.html b/android/app/src/main/assets/licenses.html index 10a3be8..96563a8 100644 --- a/android/app/src/main/assets/licenses.html +++ b/android/app/src/main/assets/licenses.html @@ -27165,6 +27165,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----- +The following software may be included in this product: html-escaper. A copy of the source code may be downloaded from https://github.com/WebReflection/html-escaper.git. This software contains the following license and notice below: + +Copyright (C) 2017-present by Andrea Giammarchi - @WebReflection + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +----- + The following software may be included in this product: http-errors. A copy of the source code may be downloaded from https://github.com/jshttp/http-errors.git. This software contains the following license and notice below: The MIT License (MIT) @@ -27192,6 +27216,32 @@ THE SOFTWARE. ----- +The following software may be included in this product: i18next. A copy of the source code may be downloaded from https://github.com/i18next/i18next.git. This software contains the following license and notice below: + +The MIT License (MIT) + +Copyright (c) 2022 i18next + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: ieee754. A copy of the source code may be downloaded from git://github.com/feross/ieee754.git. This software contains the following license and notice below: Copyright 2008 Fair Oaks Labs, Inc. @@ -29386,6 +29436,32 @@ SOFTWARE. ----- +The following software may be included in this product: react-i18next. A copy of the source code may be downloaded from https://github.com/i18next/react-i18next.git. This software contains the following license and notice below: + +The MIT License (MIT) + +Copyright (c) 2022 i18next + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: react-native-blob-util. A copy of the source code may be downloaded from https://github.com/RonRadtke/react-native-blob-util. This software contains the following license and notice below: MIT License @@ -29542,6 +29618,32 @@ SOFTWARE. ----- +The following software may be included in this product: react-native-localize. A copy of the source code may be downloaded from https://github.com/zoontek/react-native-localize.git. This software contains the following license and notice below: + +MIT License + +Copyright (c) 2017-present, Mathieu Acthernoene + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: react-native-popup-menu. A copy of the source code may be downloaded from git+ssh://git@github.com:instea/react-native-popup-menu.git. This software contains the following license and notice below: ISC License @@ -31559,6 +31661,33 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ----- +The following software may be included in this product: void-elements. A copy of the source code may be downloaded from https://github.com/pugjs/void-elements.git. This software contains the following license and notice below: + +(The MIT License) + +Copyright (c) 2014 hemanth + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +----- + The following software may be included in this product: walker. A copy of the source code may be downloaded from https://github.com/daaku/nodejs-walker. This software contains the following license and notice below: Copyright 2013 Naitik Shah diff --git a/android/app/src/main/assets/licenses/npm.txt b/android/app/src/main/assets/licenses/npm.txt index 7465c4b..7373ba8 100644 --- a/android/app/src/main/assets/licenses/npm.txt +++ b/android/app/src/main/assets/licenses/npm.txt @@ -3863,6 +3863,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----- +The following software may be included in this product: html-escaper. A copy of the source code may be downloaded from https://github.com/WebReflection/html-escaper.git. This software contains the following license and notice below: + +Copyright (C) 2017-present by Andrea Giammarchi - @WebReflection + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +----- + The following software may be included in this product: http-errors. A copy of the source code may be downloaded from https://github.com/jshttp/http-errors.git. This software contains the following license and notice below: The MIT License (MIT) @@ -3890,6 +3914,32 @@ THE SOFTWARE. ----- +The following software may be included in this product: i18next. A copy of the source code may be downloaded from https://github.com/i18next/i18next.git. This software contains the following license and notice below: + +The MIT License (MIT) + +Copyright (c) 2022 i18next + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: ieee754. A copy of the source code may be downloaded from git://github.com/feross/ieee754.git. This software contains the following license and notice below: Copyright 2008 Fair Oaks Labs, Inc. @@ -6084,6 +6134,32 @@ SOFTWARE. ----- +The following software may be included in this product: react-i18next. A copy of the source code may be downloaded from https://github.com/i18next/react-i18next.git. This software contains the following license and notice below: + +The MIT License (MIT) + +Copyright (c) 2022 i18next + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: react-native-blob-util. A copy of the source code may be downloaded from https://github.com/RonRadtke/react-native-blob-util. This software contains the following license and notice below: MIT License @@ -6240,6 +6316,32 @@ SOFTWARE. ----- +The following software may be included in this product: react-native-localize. A copy of the source code may be downloaded from https://github.com/zoontek/react-native-localize.git. This software contains the following license and notice below: + +MIT License + +Copyright (c) 2017-present, Mathieu Acthernoene + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +----- + The following software may be included in this product: react-native-popup-menu. A copy of the source code may be downloaded from git+ssh://git@github.com:instea/react-native-popup-menu.git. This software contains the following license and notice below: ISC License @@ -8257,6 +8359,33 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ----- +The following software may be included in this product: void-elements. A copy of the source code may be downloaded from https://github.com/pugjs/void-elements.git. This software contains the following license and notice below: + +(The MIT License) + +Copyright (c) 2014 hemanth + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +----- + The following software may be included in this product: walker. A copy of the source code may be downloaded from https://github.com/daaku/nodejs-walker. This software contains the following license and notice below: Copyright 2013 Naitik Shah diff --git a/android/app/src/main/java/com/subtracks/MainActivity.java b/android/app/src/main/java/com/subtracks/MainActivity.java index 8b2893c..838a88b 100644 --- a/android/app/src/main/java/com/subtracks/MainActivity.java +++ b/android/app/src/main/java/com/subtracks/MainActivity.java @@ -14,6 +14,9 @@ public class MainActivity extends ReactActivity { return "subtracks"; } + // required by react-native-screens + // "This change is required to avoid crashes related to View state being not persisted consistently across Activity restarts." + // https://reactnavigation.org/docs/getting-started @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(null); diff --git a/app/App.tsx b/app/App.tsx index bfe7ab4..5865b0d 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -2,12 +2,12 @@ import RootNavigator from '@app/navigation/RootNavigator' import SplashPage from '@app/screens/SplashPage' import colors from '@app/styles/colors' import React from 'react' -import { StatusBar, View, StyleSheet } from 'react-native' -import ProgressHook from './components/ProgressHook' -import { useStore } from './state/store' +import { StatusBar, StyleSheet, View } from 'react-native' import { MenuProvider } from 'react-native-popup-menu' import { QueryClientProvider } from 'react-query' +import ProgressHook from './components/ProgressHook' import queryClient from './queryClient' +import { useStore } from './state/store' const Debug = () => { const currentTrackTitle = useStore(store => store.currentTrack?.title) diff --git a/app/components/Button.tsx b/app/components/Button.tsx index 7ed49af..b98422c 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -16,7 +16,13 @@ const Button: React.FC<{ onPress={onPress} disabled={disabled} style={[styles.container, buttonStyle !== undefined ? styles[buttonStyle] : {}, style]}> - {title ? {title} : children} + {title ? ( + + {title} + + ) : ( + children + )} ) } @@ -26,6 +32,7 @@ const styles = StyleSheet.create({ backgroundColor: colors.accent, paddingHorizontal: 10, minHeight: 42, + maxHeight: 42, justifyContent: 'center', borderRadius: 1000, }, @@ -43,6 +50,7 @@ const styles = StyleSheet.create({ fontFamily: font.bold, color: colors.text.primary, paddingHorizontal: 14, + textAlign: 'center', }, }) diff --git a/app/components/ContextMenu.tsx b/app/components/ContextMenu.tsx index 9d0649a..b8ff6a4 100644 --- a/app/components/ContextMenu.tsx +++ b/app/components/ContextMenu.tsx @@ -6,12 +6,14 @@ import font from '@app/styles/font' import { NavigationProp, useNavigation } from '@react-navigation/native' import { ReactComponentLike } from 'prop-types' import React from 'react' +import { useTranslation } from 'react-i18next' import { ScrollView, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native' import { Menu, MenuOption, MenuOptions, MenuTrigger, renderers } from 'react-native-popup-menu' import IconFA from 'react-native-vector-icons/FontAwesome' import IconFA5 from 'react-native-vector-icons/FontAwesome5' import CoverArt from './CoverArt' import { Star } from './Star' +import { withSuspenseMemo } from './withSuspense' const { SlideInMenu } = renderers @@ -106,7 +108,9 @@ const ContextMenuIconTextOption = React.memo( return ( {Icon} - {text} + + {text} + ) }, @@ -154,27 +158,30 @@ const MenuHeader = React.memo<{ )) -const OptionStar = React.memo<{ +const OptionStar = withSuspenseMemo<{ id: string type: StarrableItemType additionalText?: string }>(({ id, type, additionalText: text }) => { const { query, toggle } = useStar(id, type) + const { t } = useTranslation('context.actions') return ( } - text={(query.data ? 'Unstar' : 'Star') + (text ? ` ${text}` : '')} + text={(query.data ? t('unstar') : t('star')) + (text ? ` ${text}` : '')} onSelect={() => toggle.mutate()} /> ) }) -const OptionViewArtist = React.memo<{ +const OptionViewArtist = withSuspenseMemo<{ navigation: NavigationProp artist?: string artistId?: string }>(({ navigation, artist, artistId }) => { + const { t } = useTranslation('resources.artist.actions') + if (!artist || !artistId) { return <> } @@ -184,17 +191,19 @@ const OptionViewArtist = React.memo<{ IconComponent={IconFA} name="microphone" size={26} - text="View Artist" + text={t('view')} onSelect={() => navigation.navigate('artist', { id: artistId, title: artist })} /> ) }) -const OptionViewAlbum = React.memo<{ +const OptionViewAlbum = withSuspenseMemo<{ navigation: NavigationProp album?: string albumId?: string }>(({ navigation, album, albumId }) => { + const { t } = useTranslation('resources.album.actions') + if (!album || !albumId) { return <> } @@ -204,7 +213,7 @@ const OptionViewAlbum = React.memo<{ IconComponent={IconFA5} name="compact-disc" size={26} - text="View Album" + text={t('view')} onSelect={() => navigation.navigate('album', { id: albumId, title: album })} /> ) @@ -318,6 +327,8 @@ const styles = StyleSheet.create({ }, optionsWrapper: { // marginBottom: 10, + paddingHorizontal: 20, + // backgroundColor: 'purple', }, menuHeader: { paddingTop: 14, @@ -348,9 +359,11 @@ const styles = StyleSheet.create({ }, option: { paddingVertical: 8, - paddingHorizontal: 20, + // paddingHorizontal: 100, flexDirection: 'row', alignItems: 'center', + // backgroundColor: 'blue', + overflow: 'hidden', }, icon: { marginRight: 10, diff --git a/app/components/FilterButton.tsx b/app/components/FilterButton.tsx index 17f4c56..3871684 100644 --- a/app/components/FilterButton.tsx +++ b/app/components/FilterButton.tsx @@ -1,10 +1,13 @@ import colors from '@app/styles/colors' import font from '@app/styles/font' import React from 'react' -import { Text, StyleSheet } from 'react-native' -import { MenuOption, Menu, MenuTrigger, MenuOptions } from 'react-native-popup-menu' +import { Text, StyleSheet, View } from 'react-native' +import { MenuOption, Menu, MenuTrigger, MenuOptions, renderers } from 'react-native-popup-menu' import PressableOpacity from './PressableOpacity' import Icon from 'react-native-vector-icons/MaterialCommunityIcons' +import { ScrollView } from 'react-native-gesture-handler' + +const { SlideInMenu } = renderers export type OptionData = { value: string @@ -17,12 +20,14 @@ const Option = React.memo<{ selected?: boolean }>(({ text, value, selected }) => ( - {text} {selected ? ( - + ) : ( - + )} + + {text} + )) @@ -30,9 +35,10 @@ const FilterButton = React.memo<{ value?: string data: OptionData[] onSelect?: (selection: string) => void -}>(({ value, data, onSelect }) => { + title: string +}>(({ value, data, onSelect, title }) => { return ( -

+ - + - {data.map(o => ( - ) @@ -71,28 +84,45 @@ const styles = StyleSheet.create({ alignItems: 'center', backgroundColor: colors.accent, }, - filterIcon: { - // top: 4, + optionsScroll: { + maxHeight: 260, }, optionsWrapper: { - maxWidth: 145, + overflow: 'hidden', }, optionsContainer: { - backgroundColor: colors.gradient.high, - maxWidth: 145, + backgroundColor: 'rgba(45, 45, 45, 0.95)', + }, + header: { + paddingHorizontal: 20, + // paddingVertical: 10, + marginTop: 16, + marginBottom: 6, + }, + headerText: { + fontFamily: font.bold, + fontSize: 20, + color: colors.text.primary, }, option: { - flexDirection: 'row', - paddingHorizontal: 12, paddingVertical: 8, - justifyContent: 'center', + paddingHorizontal: 20, + flexDirection: 'row', alignItems: 'center', + justifyContent: 'flex-start', }, optionText: { - color: colors.text.primary, fontFamily: font.semiBold, fontSize: 16, - flex: 1, + color: colors.text.primary, + }, + icon: { + marginRight: 14, + width: 32, + height: 32, + justifyContent: 'center', + alignItems: 'center', + // backgroundColor: 'red', }, }) diff --git a/app/components/ListPlayerControls.tsx b/app/components/ListPlayerControls.tsx index 466bf81..7bf6ad2 100644 --- a/app/components/ListPlayerControls.tsx +++ b/app/components/ListPlayerControls.tsx @@ -2,19 +2,22 @@ import Button from '@app/components/Button' import { Song } from '@app/models/library' import colors from '@app/styles/colors' import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' import Icon from 'react-native-vector-icons/Ionicons' import IconMat from 'react-native-vector-icons/MaterialIcons' +import { withSuspenseMemo } from './withSuspense' -const ListPlayerControls = React.memo<{ +const ListPlayerControls = withSuspenseMemo<{ songs: Song[] - typeName: string + listType: 'album' | 'playlist' style?: StyleProp play: () => void shuffle: () => void disabled?: boolean -}>(({ typeName, style, play, shuffle, disabled }) => { +}>(({ listType, style, play, shuffle, disabled }) => { const [downloaded, setDownloaded] = useState(false) + const { t } = useTranslation('resources') return ( @@ -31,7 +34,7 @@ const ListPlayerControls = React.memo<{ -