Private
Public Access
Minamp 1.5: Galician interface
- Interface in Galician by default regardless of the phone's language, with English selectable under Axustes -> Idioma; switching rebuilds the screens - All text in tools/strings.py (key -> English, Galician), generating res/values/strings.xml (gl) and res/values-en/strings.xml; layouts now use string resources - util/Tr.java: translations for the network and Plex layers, which can't read Android resources (falls back to English in desktop tests) - net/RefusedException: distinguish "server answered but refused" from connection failures - The connection log stays in English for troubleshooting Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6fd17bc9c5
commit
fa67e95fde
29 files changed
+1029
-206
No files matched your search
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.minamp"
|
||||
android:versionCode="5"
|
||||
android:versionName="1.4"
|
||||
android:versionCode="6"
|
||||
android:versionName="1.5"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
A small Plexamp-style music player for Plex, built for the Samsung Galaxy Mini (GT-S5570:
|
||||
Android 2.2/2.3, 240×320 ldpi screen, 600 MHz ARMv6, 384 MB RAM). Runs on any Android 2.2+ phone.
|
||||
|
||||
## Language
|
||||
|
||||
The interface is in **Galician** by default, whatever language the phone is set to. English is
|
||||
available under *Axustes → Idioma*. All text lives in `tools/strings.py` (key → English, Galician),
|
||||
which generates `res/values/strings.xml` (Galician) and `res/values-en/strings.xml`; the connection
|
||||
log stays in English since it's meant for troubleshooting.
|
||||
|
||||
## Install
|
||||
|
||||
1. On the phone: **Settings → Applications → Unknown sources** (tick it).
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:orientation="horizontal">
|
||||
<TextView android:id="@+id/tab_home" style="@style/Tab" android:text="HOME" />
|
||||
<TextView android:id="@+id/tab_artists" style="@style/Tab" android:text="ARTISTS" />
|
||||
<TextView android:id="@+id/tab_albums" style="@style/Tab" android:text="ALBUMS" />
|
||||
<TextView android:id="@+id/tab_playlists" style="@style/Tab" android:text="PLAYLISTS" />
|
||||
<TextView android:id="@+id/tab_home" style="@style/Tab" android:text="@string/tab_home" />
|
||||
<TextView android:id="@+id/tab_artists" style="@style/Tab" android:text="@string/tab_artists" />
|
||||
<TextView android:id="@+id/tab_albums" style="@style/Tab" android:text="@string/tab_albums" />
|
||||
<TextView android:id="@+id/tab_playlists" style="@style/Tab" android:text="@string/tab_playlists" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
android:layout_width="0dp" android:layout_height="wrap_content"
|
||||
android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:text="PLAYING FROM" android:textSize="9sp" android:textStyle="bold"
|
||||
android:text="@string/playing_from" android:textSize="9sp" android:textStyle="bold"
|
||||
android:textColor="@color/text2" />
|
||||
<TextView android:id="@+id/np_from" style="@style/Sub"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
@@ -133,13 +133,13 @@
|
||||
<TextView android:id="@+id/np_sleep_text"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textColor="#B3FFFFFF" android:paddingLeft="5dp"
|
||||
android:text="Sleep" />
|
||||
android:text="@string/sleep" />
|
||||
</LinearLayout>
|
||||
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
|
||||
<TextView android:id="@+id/np_artist_link"
|
||||
android:layout_width="wrap_content" android:layout_height="match_parent"
|
||||
android:gravity="center_vertical" android:background="@drawable/bg_pressable"
|
||||
android:paddingLeft="8dp" android:paddingRight="8dp"
|
||||
android:textSize="11sp" android:textColor="#B3FFFFFF" android:text="Album ›" />
|
||||
android:textSize="11sp" android:textColor="#B3FFFFFF" android:text="@string/album_link" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -13,7 +13,7 @@
|
||||
<ImageButton android:id="@+id/btn_back" style="@style/IconButton" />
|
||||
<EditText android:id="@+id/search_box" style="@style/Edit"
|
||||
android:layout_width="0dp" android:layout_weight="1"
|
||||
android:hint="Artists, albums, tracks"
|
||||
android:hint="@string/search_hint"
|
||||
android:imeOptions="actionSearch"
|
||||
android:inputType="text" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -19,23 +19,23 @@
|
||||
android:layout_marginTop="12dp" android:src="@drawable/ic_launcher"
|
||||
android:scaleType="fitCenter" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:text="Minamp" android:textSize="26sp" android:textStyle="bold"
|
||||
android:text="@string/app_name" android:textSize="26sp" android:textStyle="bold"
|
||||
android:paddingTop="8dp" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:gravity="center" android:textColor="@color/text2" android:textSize="13sp"
|
||||
android:paddingTop="4dp" android:paddingBottom="22dp"
|
||||
android:text="Your Plex music, anywhere,\non a very small phone." />
|
||||
android:text="@string/tagline" />
|
||||
<Button android:id="@+id/btn_signin" style="@style/ButtonAccent"
|
||||
android:layout_width="match_parent" android:text="Sign in with Plex" />
|
||||
android:layout_width="match_parent" android:text="@string/sign_in" />
|
||||
<Button android:id="@+id/btn_manual" style="@style/ButtonOutline"
|
||||
android:layout_width="match_parent" android:layout_marginTop="10dp"
|
||||
android:text="Connect to a server address" />
|
||||
android:text="@string/connect_address" />
|
||||
<TextView android:id="@+id/btn_setup_log"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:layout_marginTop="18dp" android:padding="8dp"
|
||||
android:background="@drawable/bg_pressable"
|
||||
android:textColor="@color/text2" android:textSize="12sp"
|
||||
android:text="Connection log" />
|
||||
android:text="@string/connection_log" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- PIN -->
|
||||
@@ -46,13 +46,13 @@
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:gravity="center" android:textColor="@color/text2" android:textSize="13sp"
|
||||
android:paddingTop="12dp"
|
||||
android:text="On a computer or a newer phone, open" />
|
||||
android:text="@string/pin_open" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:text="plex.tv/link" android:textSize="22sp" android:textStyle="bold"
|
||||
android:textColor="@color/accent" android:paddingTop="4dp" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textColor="@color/text2" android:textSize="13sp" android:paddingTop="10dp"
|
||||
android:text="and enter this code:" />
|
||||
android:text="@string/pin_enter" />
|
||||
<TextView android:id="@+id/pin_code"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="40sp" android:textStyle="bold" android:typeface="monospace"
|
||||
@@ -64,14 +64,14 @@
|
||||
<TextView android:id="@+id/pin_status"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textColor="@color/text2" android:textSize="12sp" android:paddingLeft="8dp"
|
||||
android:text="Waiting for you to link…" />
|
||||
android:text="@string/pin_waiting" />
|
||||
</LinearLayout>
|
||||
<Button android:id="@+id/btn_pin_check" style="@style/ButtonAccent"
|
||||
android:layout_width="match_parent" android:layout_marginTop="20dp"
|
||||
android:text="I've entered it \u2014 check now" />
|
||||
android:text="@string/pin_check_now" />
|
||||
<Button android:id="@+id/btn_pin_cancel" style="@style/ButtonOutline"
|
||||
android:layout_width="match_parent" android:layout_marginTop="10dp"
|
||||
android:text="Cancel" />
|
||||
android:text="@string/cancel" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- manual -->
|
||||
@@ -79,21 +79,21 @@
|
||||
android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:visibility="gone">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:text="Server address" android:textSize="18sp" android:textStyle="bold" />
|
||||
android:text="@string/server_address" android:textSize="18sp" android:textStyle="bold" />
|
||||
<TextView style="@style/Sub" android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:singleLine="false" android:paddingTop="4dp" android:paddingBottom="10dp"
|
||||
android:text="e.g. 192.168.1.20 or https://plex.example.com. Signing in with Plex is easier and finds remote addresses for you." />
|
||||
android:text="@string/server_address_help" />
|
||||
<EditText android:id="@+id/edit_url" style="@style/Edit"
|
||||
android:hint="Address" android:inputType="textUri" />
|
||||
android:hint="@string/address_hint" android:inputType="textUri" />
|
||||
<EditText android:id="@+id/edit_token" style="@style/Edit"
|
||||
android:layout_marginTop="8dp" android:hint="X-Plex-Token (optional on your LAN)"
|
||||
android:layout_marginTop="8dp" android:hint="@string/token_hint"
|
||||
android:inputType="textVisiblePassword" />
|
||||
<Button android:id="@+id/btn_manual_go" style="@style/ButtonAccent"
|
||||
android:layout_width="match_parent" android:layout_marginTop="14dp"
|
||||
android:text="Connect" />
|
||||
android:text="@string/connect" />
|
||||
<Button android:id="@+id/btn_manual_back" style="@style/ButtonOutline"
|
||||
android:layout_width="match_parent" android:layout_marginTop="10dp"
|
||||
android:text="Back" />
|
||||
android:text="@string/back" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- choose server / library -->
|
||||
@@ -111,7 +111,7 @@
|
||||
android:orientation="vertical" />
|
||||
<Button android:id="@+id/btn_choose_back" style="@style/ButtonOutline"
|
||||
android:layout_width="match_parent" android:layout_marginTop="14dp"
|
||||
android:text="Back" />
|
||||
android:text="@string/back" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- busy -->
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
android:paddingTop="10dp">
|
||||
<Button android:id="@+id/detail_play" style="@style/ButtonAccent"
|
||||
android:layout_width="0dp" android:layout_weight="1"
|
||||
android:layout_marginRight="5dp" android:text="Play" />
|
||||
android:layout_marginRight="5dp" android:text="@string/play" />
|
||||
<Button android:id="@+id/detail_shuffle" style="@style/ButtonOutline"
|
||||
android:layout_width="0dp" android:layout_weight="1"
|
||||
android:layout_marginLeft="5dp" android:text="Shuffle" />
|
||||
android:layout_marginLeft="5dp" android:text="@string/shuffle" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -16,5 +16,5 @@
|
||||
android:paddingTop="8dp" />
|
||||
<Button android:id="@+id/status_retry" style="@style/ButtonOutline"
|
||||
android:layout_width="wrap_content" android:layout_marginTop="12dp"
|
||||
android:text="Try again" android:visibility="gone" />
|
||||
android:text="@string/try_again" android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,209 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- generated by tools/strings.py: edit that file instead -->
|
||||
<resources>
|
||||
<string name="add_to_queue">Add to queue</string>
|
||||
<string name="added_to_queue">Added to queue</string>
|
||||
<string name="adding_x">Adding %1$s…</string>
|
||||
<string name="address_hint">Address</string>
|
||||
<string name="album">Album</string>
|
||||
<string name="album_link">Album ›</string>
|
||||
<string name="app_name">Minamp</string>
|
||||
<string name="applies_next_track">Applies from the next track</string>
|
||||
<string name="artist">Artist</string>
|
||||
<string name="artist_radio">Artist radio</string>
|
||||
<string name="artist_radio_shuffle">Artist radio (shuffle)</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="buffering">Buffering…</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="cant_list_servers">Couldn\'t list servers: %1$s</string>
|
||||
<string name="cant_load_tap">Couldn\'t load: %1$s (tap to retry)</string>
|
||||
<string name="cant_reach_plextv">Couldn\'t reach plex.tv: %1$s</string>
|
||||
<string name="cant_save">Couldn\'t save: %1$s</string>
|
||||
<string name="cant_save_rating">Couldn\'t save rating: %1$s</string>
|
||||
<string name="choose_library">Choose a music library</string>
|
||||
<string name="choose_server">Choose a server</string>
|
||||
<string name="clear_everything">Clear everything</string>
|
||||
<string name="clear_upcoming">Clear upcoming tracks</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="connect">Connect</string>
|
||||
<string name="connect_address">Connect to a server address</string>
|
||||
<string name="connected_x">Connected: %1$s</string>
|
||||
<string name="connecting">Connecting…</string>
|
||||
<string name="connecting_to">Connecting to %1$s…\nTrying home network, then remote, then relay.</string>
|
||||
<string name="connection_log">Connection log</string>
|
||||
<string name="contacting_plextv">Contacting plex.tv…\n(the first secure connection takes a few seconds on this phone)</string>
|
||||
<string name="decade_radio_x">%1$ds radio</string>
|
||||
<string name="decade_x">%1$ds</string>
|
||||
<string name="download_first_toast">Download before playing: %1$s</string>
|
||||
<string name="downloading_pct">Downloading %1$d%%</string>
|
||||
<string name="end_of_track">End of track</string>
|
||||
<string name="enter_address">Enter your server\'s address</string>
|
||||
<string name="error_last_time">Minamp hit an error last time</string>
|
||||
<string name="finding_decades">Finding decades…</string>
|
||||
<string name="finding_servers">Finding your servers…</string>
|
||||
<string name="go_to_album">Go to album</string>
|
||||
<string name="go_to_artist">Go to artist</string>
|
||||
<string name="h_min">%1$d h %2$d min</string>
|
||||
<string name="https_home_off">Using plain HTTP at home (faster)</string>
|
||||
<string name="https_home_on">Using HTTPS at home (each new connection is slower on this phone)</string>
|
||||
<string name="language_title">Language</string>
|
||||
<string name="library_radio">Library radio</string>
|
||||
<string name="library_radio_sub">Shuffle everything</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="loading_albums">Loading albums…</string>
|
||||
<string name="loading_artists">Loading artists…</string>
|
||||
<string name="loading_libraries">Loading libraries…</string>
|
||||
<string name="loading_playlists">Loading playlists…</string>
|
||||
<string name="log_empty">Nothing logged yet.</string>
|
||||
<string name="love">Add to favourites</string>
|
||||
<string name="loved">Added to favourites</string>
|
||||
<string name="loved_radio">Favourites radio</string>
|
||||
<string name="loved_radio_sub">Your top rated</string>
|
||||
<string name="min">%1$d min</string>
|
||||
<string name="music">Music</string>
|
||||
<string name="n_albums">%1$d albums</string>
|
||||
<string name="n_albums_one">%1$d album</string>
|
||||
<string name="n_tracks">%1$d tracks</string>
|
||||
<string name="n_tracks_one">%1$d track</string>
|
||||
<string name="no_albums">No albums</string>
|
||||
<string name="no_albums_library">No albums in this library</string>
|
||||
<string name="no_artists_library">No artists in this library</string>
|
||||
<string name="no_music_libraries">%1$s has no music libraries</string>
|
||||
<string name="no_playlists">No music playlists yet</string>
|
||||
<string name="no_sd">No SD card available</string>
|
||||
<string name="no_servers">No Plex Media Servers on this account</string>
|
||||
<string name="no_tracks">No tracks</string>
|
||||
<string name="no_years">No release years in this library</string>
|
||||
<string name="not_connected_yet">not connected yet</string>
|
||||
<string name="nothing_found_for">Nothing found for %1$s</string>
|
||||
<string name="nothing_found_q">Nothing found for “%1$s”</string>
|
||||
<string name="nothing_here">Nothing here</string>
|
||||
<string name="nothing_here_yet">Nothing here yet</string>
|
||||
<string name="nothing_playing">Nothing playing</string>
|
||||
<string name="nothing_to_play">Nothing to play</string>
|
||||
<string name="now_playing">Now playing</string>
|
||||
<string name="off">Off</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="on">On</string>
|
||||
<string name="paused_dot">Paused · </string>
|
||||
<string name="pick_something">Pick something from your library</string>
|
||||
<string name="pin_check_failed">Check %1$d failed: %2$s — retrying</string>
|
||||
<string name="pin_check_now">I\'ve entered it — check now</string>
|
||||
<string name="pin_enter">and enter this code:</string>
|
||||
<string name="pin_expired">Code expired — press Cancel and sign in again</string>
|
||||
<string name="pin_linked">Linked!</string>
|
||||
<string name="pin_not_yet">Not linked yet · checked %1$d× · %2$d min left</string>
|
||||
<string name="pin_open">On a computer or a newer phone, open</string>
|
||||
<string name="pin_waiting">Waiting for you to link…</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="play_all">Play all</string>
|
||||
<string name="play_next">Play next</string>
|
||||
<string name="playback_error">Playback error (%1$d/%2$d)</string>
|
||||
<string name="playing_from">PLAYING FROM</string>
|
||||
<string name="playing_next">Playing next</string>
|
||||
<string name="playing_x">Playing %1$s</string>
|
||||
<string name="playlist">Playlist</string>
|
||||
<string name="plex_server">Plex server</string>
|
||||
<string name="quality_away_title">Quality away / mobile</string>
|
||||
<string name="quality_home_title">Quality at home</string>
|
||||
<string name="quality_original">Original</string>
|
||||
<string name="queue_empty">The queue is empty</string>
|
||||
<string name="random_album">Random album</string>
|
||||
<string name="random_album_sub">Feeling lucky</string>
|
||||
<string name="recently_added">Recently added</string>
|
||||
<string name="recently_played">Recently played</string>
|
||||
<string name="reconnecting">Reconnecting…</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="remove_from_queue">Remove from queue</string>
|
||||
<string name="repeat_off">Repeat off</string>
|
||||
<string name="repeat_queue">Repeat queue</string>
|
||||
<string name="repeat_track">Repeat track</string>
|
||||
<string name="save_to_sd">Save to SD card</string>
|
||||
<string name="saved_log">Saved as Minamp-log.txt on the SD card</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="search_hint">Artists, albums, tracks</string>
|
||||
<string name="search_in">Search %1$s</string>
|
||||
<string name="searching">Searching…</string>
|
||||
<string name="secure"> (secure)</string>
|
||||
<string name="see_all">SEE ALL ›</string>
|
||||
<string name="server_address">Server address</string>
|
||||
<string name="server_address_help">e.g. 192.168.1.20 or https://plex.example.com. Signing in with Plex is easier and finds remote addresses for you.</string>
|
||||
<string name="server_home_plus">home + </string>
|
||||
<string name="server_local_only">local only</string>
|
||||
<string name="server_remote">remote</string>
|
||||
<string name="server_shared">Shared with you</string>
|
||||
<string name="server_wants_token">The server wants a token (or sign in with Plex instead)</string>
|
||||
<string name="server_yours">Yours</string>
|
||||
<string name="set_connection">Connection: %1$s</string>
|
||||
<string name="set_download_first">Download tracks before playing: %1$s</string>
|
||||
<string name="set_language">Language: %1$s</string>
|
||||
<string name="set_library">Library: %1$s</string>
|
||||
<string name="set_log">Connection log & streaming test</string>
|
||||
<string name="set_quality_away">Quality away / mobile data: %1$s</string>
|
||||
<string name="set_quality_home">Quality at home: %1$s</string>
|
||||
<string name="set_secure_home">Encrypt at home too (slower): %1$s</string>
|
||||
<string name="set_server">Server: %1$s</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="shuffle">Shuffle</string>
|
||||
<string name="shuffle_off">Shuffle off</string>
|
||||
<string name="shuffle_on">Shuffle on</string>
|
||||
<string name="shuffling_x">Shuffling %1$s</string>
|
||||
<string name="sign_in">Sign in with Plex</string>
|
||||
<string name="sign_out">Sign out</string>
|
||||
<string name="sign_out_confirm">Sign out and forget this server?</string>
|
||||
<string name="signed_in_as">Signed in as %1$s</string>
|
||||
<string name="signed_in_finding">Signed in. Finding your servers…</string>
|
||||
<string name="sleep">Sleep</string>
|
||||
<string name="sleep_15">15 minutes</string>
|
||||
<string name="sleep_30">30 minutes</string>
|
||||
<string name="sleep_45">45 minutes</string>
|
||||
<string name="sleep_60">1 hour</string>
|
||||
<string name="sleep_90">1½ hours</string>
|
||||
<string name="sleep_eot">End of this track</string>
|
||||
<string name="sleep_off">Off</string>
|
||||
<string name="sleep_paused">Sleep timer: paused</string>
|
||||
<string name="sleep_timer">Sleep timer</string>
|
||||
<string name="sort_added">Recently added</string>
|
||||
<string name="sort_albums">Sort albums</string>
|
||||
<string name="sort_albums_by">Sort albums by</string>
|
||||
<string name="sort_artist">Artist</string>
|
||||
<string name="sort_played">Most played</string>
|
||||
<string name="sort_title">Title</string>
|
||||
<string name="sort_year">Year (newest)</string>
|
||||
<string name="starting_x">Starting %1$s…</string>
|
||||
<string name="stream_interrupted">Stream interrupted</string>
|
||||
<string name="surprise_me">Surprise me</string>
|
||||
<string name="tab_albums">ALBUMS</string>
|
||||
<string name="tab_artists">ARTISTS</string>
|
||||
<string name="tab_home">HOME</string>
|
||||
<string name="tab_playlists">PLAYLISTS</string>
|
||||
<string name="tagline">Your Plex music, anywhere,\non a very small phone.</string>
|
||||
<string name="test_streaming">Test streaming</string>
|
||||
<string name="testing">Testing… (about 30 s)</string>
|
||||
<string name="time_travel">Time travel</string>
|
||||
<string name="time_travel_sub">Pick a decade</string>
|
||||
<string name="time_travel_title">Time travel radio</string>
|
||||
<string name="token_hint">X-Plex-Token (optional on your LAN)</string>
|
||||
<string name="tr_artist">Artist</string>
|
||||
<string name="tr_cant_reach">Can\'t reach %1$s</string>
|
||||
<string name="tr_cert_dates">Certificate expired or not yet valid (check the phone\'s date)</string>
|
||||
<string name="tr_cert_mismatch">Certificate does not match %1$s</string>
|
||||
<string name="tr_cert_untrusted">Untrusted certificate chain for %1$s</string>
|
||||
<string name="tr_file_503">Plex couldn\'t open this file (503). Is the drive or folder with your music available to the server?</string>
|
||||
<string name="tr_kind_direct">Direct</string>
|
||||
<string name="tr_kind_home">Home network</string>
|
||||
<string name="tr_kind_relay">Plex Relay</string>
|
||||
<string name="tr_kind_remote">Remote</string>
|
||||
<string name="tr_n_tracks">%1$d tracks</string>
|
||||
<string name="tr_n_tracks_one">%1$d track</string>
|
||||
<string name="tr_no_media">Track has no playable media</string>
|
||||
<string name="tr_not_authorized">Not authorized (401) on %1$s</string>
|
||||
<string name="tr_refused">Server refused the stream (%1$d)</string>
|
||||
<string name="tr_server">server</string>
|
||||
<string name="try_again">Try again</string>
|
||||
<string name="unencrypted"> (unencrypted)</string>
|
||||
<string name="unlove">Remove from favourites</string>
|
||||
<string name="unloved">Removed from favourites</string>
|
||||
<string name="up_next">Up next</string>
|
||||
<string name="you">you</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,209 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- generated by tools/strings.py: edit that file instead -->
|
||||
<resources>
|
||||
<string name="add_to_queue">Engadir á cola</string>
|
||||
<string name="added_to_queue">Engadido á cola</string>
|
||||
<string name="adding_x">Engadindo %1$s…</string>
|
||||
<string name="address_hint">Enderezo</string>
|
||||
<string name="album">Álbum</string>
|
||||
<string name="album_link">Álbum ›</string>
|
||||
<string name="app_name">Minamp</string>
|
||||
<string name="applies_next_track">Aplícase desde a seguinte pista</string>
|
||||
<string name="artist">Artista</string>
|
||||
<string name="artist_radio">Radio do artista</string>
|
||||
<string name="artist_radio_shuffle">Radio do artista (aleatorio)</string>
|
||||
<string name="back">Volver</string>
|
||||
<string name="buffering">Cargando…</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="cant_list_servers">Non se puideron listar os servidores: %1$s</string>
|
||||
<string name="cant_load_tap">Non se puido cargar: %1$s (toca para tentar de novo)</string>
|
||||
<string name="cant_reach_plextv">Non se puido contactar con plex.tv: %1$s</string>
|
||||
<string name="cant_save">Non se puido gardar: %1$s</string>
|
||||
<string name="cant_save_rating">Non se puido gardar a valoración: %1$s</string>
|
||||
<string name="choose_library">Escolle unha biblioteca de música</string>
|
||||
<string name="choose_server">Escolle un servidor</string>
|
||||
<string name="clear_everything">Borralo todo</string>
|
||||
<string name="clear_upcoming">Borrar as pistas seguintes</string>
|
||||
<string name="close">Pechar</string>
|
||||
<string name="connect">Conectar</string>
|
||||
<string name="connect_address">Conectar a un enderezo de servidor</string>
|
||||
<string name="connected_x">Conectado: %1$s</string>
|
||||
<string name="connecting">Conectando…</string>
|
||||
<string name="connecting_to">Conectando con %1$s…\nProbando a rede da casa, despois a remota e despois o relay.</string>
|
||||
<string name="connection_log">Rexistro de conexión</string>
|
||||
<string name="contacting_plextv">Contactando con plex.tv…\n(a primeira conexión segura tarda uns segundos neste teléfono)</string>
|
||||
<string name="decade_radio_x">Radio da década de %1$d</string>
|
||||
<string name="decade_x">Década de %1$d</string>
|
||||
<string name="download_first_toast">Descargar antes de reproducir: %1$s</string>
|
||||
<string name="downloading_pct">Descargando %1$d%%</string>
|
||||
<string name="end_of_track">Fin da pista</string>
|
||||
<string name="enter_address">Escribe o enderezo do teu servidor</string>
|
||||
<string name="error_last_time">Minamp tivo un erro a última vez</string>
|
||||
<string name="finding_decades">Buscando décadas…</string>
|
||||
<string name="finding_servers">Buscando os teus servidores…</string>
|
||||
<string name="go_to_album">Ir ao álbum</string>
|
||||
<string name="go_to_artist">Ir ao artista</string>
|
||||
<string name="h_min">%1$d h %2$d min</string>
|
||||
<string name="https_home_off">Usando HTTP simple na casa (máis rápido)</string>
|
||||
<string name="https_home_on">Usando HTTPS na casa (cada conexión nova é máis lenta neste teléfono)</string>
|
||||
<string name="language_title">Idioma</string>
|
||||
<string name="library_radio">Radio da biblioteca</string>
|
||||
<string name="library_radio_sub">Todo ao chou</string>
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="loading_albums">Cargando álbums…</string>
|
||||
<string name="loading_artists">Cargando artistas…</string>
|
||||
<string name="loading_libraries">Cargando bibliotecas…</string>
|
||||
<string name="loading_playlists">Cargando listas…</string>
|
||||
<string name="log_empty">Aínda non hai nada no rexistro.</string>
|
||||
<string name="love">Engadir a favoritas</string>
|
||||
<string name="loved">Engadida a favoritas</string>
|
||||
<string name="loved_radio">Radio de favoritas</string>
|
||||
<string name="loved_radio_sub">As mellor valoradas</string>
|
||||
<string name="min">%1$d min</string>
|
||||
<string name="music">Música</string>
|
||||
<string name="n_albums">%1$d álbums</string>
|
||||
<string name="n_albums_one">%1$d álbum</string>
|
||||
<string name="n_tracks">%1$d pistas</string>
|
||||
<string name="n_tracks_one">%1$d pista</string>
|
||||
<string name="no_albums">Non hai álbums</string>
|
||||
<string name="no_albums_library">Esta biblioteca non ten álbums</string>
|
||||
<string name="no_artists_library">Esta biblioteca non ten artistas</string>
|
||||
<string name="no_music_libraries">%1$s non ten bibliotecas de música</string>
|
||||
<string name="no_playlists">Aínda non hai listas de música</string>
|
||||
<string name="no_sd">Non hai tarxeta SD dispoñible</string>
|
||||
<string name="no_servers">Esta conta non ten ningún Plex Media Server</string>
|
||||
<string name="no_tracks">Non hai pistas</string>
|
||||
<string name="no_years">Esta biblioteca non ten anos de publicación</string>
|
||||
<string name="not_connected_yet">aínda sen conectar</string>
|
||||
<string name="nothing_found_for">Non se atopou nada para %1$s</string>
|
||||
<string name="nothing_found_q">Non se atopou nada para “%1$s”</string>
|
||||
<string name="nothing_here">Non hai nada aquí</string>
|
||||
<string name="nothing_here_yet">Aínda non hai nada</string>
|
||||
<string name="nothing_playing">Non se está a reproducir nada</string>
|
||||
<string name="nothing_to_play">Non hai nada que reproducir</string>
|
||||
<string name="now_playing">En reprodución</string>
|
||||
<string name="off">Desactivado</string>
|
||||
<string name="ok">Aceptar</string>
|
||||
<string name="on">Activado</string>
|
||||
<string name="paused_dot">En pausa · </string>
|
||||
<string name="pick_something">Escolle algo da túa biblioteca</string>
|
||||
<string name="pin_check_failed">Fallou a comprobación %1$d: %2$s; tentando de novo</string>
|
||||
<string name="pin_check_now">Xa o escribín: comprobar agora</string>
|
||||
<string name="pin_enter">e escribe este código:</string>
|
||||
<string name="pin_expired">O código caducou: preme Cancelar e inicia sesión de novo</string>
|
||||
<string name="pin_linked">Vinculado!</string>
|
||||
<string name="pin_not_yet">Aínda sen vincular · comprobado %1$d× · quedan %2$d min</string>
|
||||
<string name="pin_open">Nun ordenador ou nun teléfono máis novo, abre</string>
|
||||
<string name="pin_waiting">Agardando a que vincules a conta…</string>
|
||||
<string name="play">Reproducir</string>
|
||||
<string name="play_all">Reproducir todo</string>
|
||||
<string name="play_next">Reproducir a continuación</string>
|
||||
<string name="playback_error">Erro de reprodución (%1$d/%2$d)</string>
|
||||
<string name="playing_from">REPRODUCINDO DESDE</string>
|
||||
<string name="playing_next">Soará a continuación</string>
|
||||
<string name="playing_x">Reproducindo %1$s</string>
|
||||
<string name="playlist">Lista de reprodución</string>
|
||||
<string name="plex_server">Servidor Plex</string>
|
||||
<string name="quality_away_title">Calidade fóra / datos móbiles</string>
|
||||
<string name="quality_home_title">Calidade na casa</string>
|
||||
<string name="quality_original">Orixinal</string>
|
||||
<string name="queue_empty">A cola está baleira</string>
|
||||
<string name="random_album">Álbum ao chou</string>
|
||||
<string name="random_album_sub">Proba sorte</string>
|
||||
<string name="recently_added">Engadidos recentemente</string>
|
||||
<string name="recently_played">Escoitados recentemente</string>
|
||||
<string name="reconnecting">Reconectando…</string>
|
||||
<string name="refresh">Actualizar</string>
|
||||
<string name="remove_from_queue">Quitar da cola</string>
|
||||
<string name="repeat_off">Sen repetición</string>
|
||||
<string name="repeat_queue">Repetir a cola</string>
|
||||
<string name="repeat_track">Repetir a pista</string>
|
||||
<string name="save_to_sd">Gardar na tarxeta SD</string>
|
||||
<string name="saved_log">Gardado como Minamp-log.txt na tarxeta SD</string>
|
||||
<string name="search">Buscar</string>
|
||||
<string name="search_hint">Artistas, álbums, cancións</string>
|
||||
<string name="search_in">Buscar en %1$s</string>
|
||||
<string name="searching">Buscando…</string>
|
||||
<string name="secure"> (cifrada)</string>
|
||||
<string name="see_all">VER TODO ›</string>
|
||||
<string name="server_address">Enderezo do servidor</string>
|
||||
<string name="server_address_help">p. ex. 192.168.1.20 ou https://plex.exemplo.com. Iniciar sesión con Plex é máis doado e atopa por ti os enderezos remotos.</string>
|
||||
<string name="server_home_plus">casa + </string>
|
||||
<string name="server_local_only">só local</string>
|
||||
<string name="server_remote">remoto</string>
|
||||
<string name="server_shared">Compartido contigo</string>
|
||||
<string name="server_wants_token">O servidor pide un token (ou inicia sesión con Plex)</string>
|
||||
<string name="server_yours">Teu</string>
|
||||
<string name="set_connection">Conexión: %1$s</string>
|
||||
<string name="set_download_first">Descargar as pistas antes de reproducilas: %1$s</string>
|
||||
<string name="set_language">Idioma: %1$s</string>
|
||||
<string name="set_library">Biblioteca: %1$s</string>
|
||||
<string name="set_log">Rexistro de conexión e proba de reprodución</string>
|
||||
<string name="set_quality_away">Calidade fóra / datos móbiles: %1$s</string>
|
||||
<string name="set_quality_home">Calidade na casa: %1$s</string>
|
||||
<string name="set_secure_home">Cifrar tamén na casa (máis lento): %1$s</string>
|
||||
<string name="set_server">Servidor: %1$s</string>
|
||||
<string name="settings">Axustes</string>
|
||||
<string name="shuffle">Aleatorio</string>
|
||||
<string name="shuffle_off">Aleatorio desactivado</string>
|
||||
<string name="shuffle_on">Aleatorio activado</string>
|
||||
<string name="shuffling_x">Reproducindo ao chou %1$s</string>
|
||||
<string name="sign_in">Iniciar sesión con Plex</string>
|
||||
<string name="sign_out">Pechar sesión</string>
|
||||
<string name="sign_out_confirm">Pechar sesión e esquecer este servidor?</string>
|
||||
<string name="signed_in_as">Sesión iniciada como %1$s</string>
|
||||
<string name="signed_in_finding">Sesión iniciada. Buscando os teus servidores…</string>
|
||||
<string name="sleep">Temporizador</string>
|
||||
<string name="sleep_15">15 minutos</string>
|
||||
<string name="sleep_30">30 minutos</string>
|
||||
<string name="sleep_45">45 minutos</string>
|
||||
<string name="sleep_60">1 hora</string>
|
||||
<string name="sleep_90">1½ horas</string>
|
||||
<string name="sleep_eot">Ao rematar esta pista</string>
|
||||
<string name="sleep_off">Desactivado</string>
|
||||
<string name="sleep_paused">Temporizador de apagado: en pausa</string>
|
||||
<string name="sleep_timer">Temporizador de apagado</string>
|
||||
<string name="sort_added">Engadidos recentemente</string>
|
||||
<string name="sort_albums">Ordenar álbums</string>
|
||||
<string name="sort_albums_by">Ordenar álbums por</string>
|
||||
<string name="sort_artist">Artista</string>
|
||||
<string name="sort_played">Máis escoitados</string>
|
||||
<string name="sort_title">Título</string>
|
||||
<string name="sort_year">Ano (máis recentes)</string>
|
||||
<string name="starting_x">Iniciando %1$s…</string>
|
||||
<string name="stream_interrupted">Interrompeuse a transmisión</string>
|
||||
<string name="surprise_me">Sorpréndeme</string>
|
||||
<string name="tab_albums">ÁLBUMS</string>
|
||||
<string name="tab_artists">ARTISTAS</string>
|
||||
<string name="tab_home">INICIO</string>
|
||||
<string name="tab_playlists">LISTAS</string>
|
||||
<string name="tagline">A túa música de Plex, onde queiras,\nnun teléfono moi pequeno.</string>
|
||||
<string name="test_streaming">Probar reprodución</string>
|
||||
<string name="testing">Probando… (uns 30 s)</string>
|
||||
<string name="time_travel">Viaxe no tempo</string>
|
||||
<string name="time_travel_sub">Escolle unha década</string>
|
||||
<string name="time_travel_title">Radio viaxe no tempo</string>
|
||||
<string name="token_hint">X-Plex-Token (opcional na rede da casa)</string>
|
||||
<string name="tr_artist">Artista</string>
|
||||
<string name="tr_cant_reach">Non se pode conectar con %1$s</string>
|
||||
<string name="tr_cert_dates">Certificado caducado ou aínda non válido (comproba a data do teléfono)</string>
|
||||
<string name="tr_cert_mismatch">O certificado non corresponde a %1$s</string>
|
||||
<string name="tr_cert_untrusted">Cadea de certificados non fiable para %1$s</string>
|
||||
<string name="tr_file_503">Plex non puido abrir este ficheiro (503). Está dispoñible para o servidor o disco ou cartafol coa túa música?</string>
|
||||
<string name="tr_kind_direct">Directa</string>
|
||||
<string name="tr_kind_home">Rede da casa</string>
|
||||
<string name="tr_kind_relay">Plex Relay</string>
|
||||
<string name="tr_kind_remote">Remota</string>
|
||||
<string name="tr_n_tracks">%1$d pistas</string>
|
||||
<string name="tr_n_tracks_one">%1$d pista</string>
|
||||
<string name="tr_no_media">A pista non ten ningún ficheiro reproducible</string>
|
||||
<string name="tr_not_authorized">Sen autorización (401) en %1$s</string>
|
||||
<string name="tr_refused">O servidor rexeitou a transmisión (%1$d)</string>
|
||||
<string name="tr_server">servidor</string>
|
||||
<string name="try_again">Tentar de novo</string>
|
||||
<string name="unencrypted"> (sen cifrar)</string>
|
||||
<string name="unlove">Quitar de favoritas</string>
|
||||
<string name="unloved">Quitada de favoritas</string>
|
||||
<string name="up_next">A continuación</string>
|
||||
<string name="you">ti</string>
|
||||
</resources>
|
||||
@@ -49,6 +49,13 @@ public class App extends Application {
|
||||
handler = new Handler();
|
||||
prefs = getSharedPreferences("minamp", MODE_PRIVATE);
|
||||
installCrashLogger();
|
||||
applyLanguage(this);
|
||||
com.minamp.util.Tr.source = new com.minamp.util.Tr.Source() {
|
||||
public String get(String key) {
|
||||
int id = getResources().getIdentifier("tr_" + key, "string", getPackageName());
|
||||
return id == 0 ? null : getString(id);
|
||||
}
|
||||
};
|
||||
|
||||
// Android 2.2's HttpURLConnection pool is buggy; we use our own client, but be safe.
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
@@ -91,6 +98,38 @@ public class App extends Application {
|
||||
}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ language
|
||||
|
||||
public static final String[] LANGUAGES = {"gl", "en"};
|
||||
public static final String[] LANGUAGE_NAMES = {"Galego", "English"};
|
||||
|
||||
/** Galician unless the user picked English, whatever language the phone itself uses. */
|
||||
public String language() { return prefs.getString("lang", "gl"); }
|
||||
|
||||
public static String languageName(String code) {
|
||||
for (int i = 0; i < LANGUAGES.length; i++) if (LANGUAGES[i].equals(code)) return LANGUAGE_NAMES[i];
|
||||
return code;
|
||||
}
|
||||
|
||||
/** Forces the app's language on a context's resources (call before inflating any layout). */
|
||||
public static void applyLanguage(android.content.Context c) {
|
||||
App app = instance;
|
||||
if (app == null) return;
|
||||
java.util.Locale loc = new java.util.Locale(app.language());
|
||||
java.util.Locale.setDefault(loc);
|
||||
android.content.res.Resources r = c.getResources();
|
||||
android.content.res.Configuration cfg = r.getConfiguration();
|
||||
if (loc.equals(cfg.locale)) return;
|
||||
cfg.locale = loc;
|
||||
r.updateConfiguration(cfg, r.getDisplayMetrics());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(android.content.res.Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
applyLanguage(this); // the system resets the locale on config changes
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ crash reports
|
||||
|
||||
private static final String CRASH_FILE = "crash.txt";
|
||||
|
||||
@@ -64,6 +64,8 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
});
|
||||
}
|
||||
|
||||
protected String str(int id, Object... args) { return args.length == 0 ? getString(id) : getString(id, args); }
|
||||
|
||||
protected void toast(String s) { Toast.makeText(this, s, s != null && s.length() > 40 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show(); }
|
||||
|
||||
protected static String msg(Exception e) { return PlayerService.message(e); }
|
||||
@@ -72,8 +74,10 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
|
||||
@Override
|
||||
protected void onCreate(android.os.Bundle b) {
|
||||
super.onCreate(b);
|
||||
app = App.get();
|
||||
App.applyLanguage(this);
|
||||
App.applyLanguage(getApplicationContext());
|
||||
super.onCreate(b);
|
||||
}
|
||||
|
||||
/** If the last run hit an error, show it so it can be reported (also saved as Minamp-error.txt on the SD card). */
|
||||
@@ -89,9 +93,9 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
tv.setPadding(p, p, p, p);
|
||||
sv.addView(tv);
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Minamp hit an error last time")
|
||||
.setTitle(R.string.error_last_time)
|
||||
.setView(sv)
|
||||
.setPositiveButton("OK", null)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -254,7 +258,7 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
}
|
||||
|
||||
protected void playList(List<Item> tracks, int start, boolean shuffle, String title) {
|
||||
if (tracks.isEmpty()) { toast("Nothing to play"); return; }
|
||||
if (tracks.isEmpty()) { toast(str(R.string.nothing_to_play)); return; }
|
||||
PlayQueue.get().set(tracks, start, shuffle, title);
|
||||
PlayerService.playCurrent(this, 0);
|
||||
}
|
||||
@@ -263,14 +267,14 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
protected void playContainer(final Item it, final boolean shuffle, final int how) {
|
||||
final Library lib = lib();
|
||||
if (lib == null) return;
|
||||
toast(how == 0 ? (shuffle ? "Shuffling " : "Playing ") + it.title : "Adding " + it.title + "…");
|
||||
toast(how == 0 ? str(shuffle ? R.string.shuffling_x : R.string.playing_x, it.title) : str(R.string.adding_x, it.title));
|
||||
run(new Task<List<Item>>() {
|
||||
public List<Item> work() throws Exception { return lib.tracksOf(it); }
|
||||
public void done(List<Item> tracks) {
|
||||
if (tracks.isEmpty()) { toast("No tracks"); return; }
|
||||
if (tracks.isEmpty()) { toast(str(R.string.no_tracks)); return; }
|
||||
boolean wasEmpty = PlayQueue.get().size() == 0;
|
||||
if (how == 1) { PlayQueue.get().playNext(tracks); toast("Playing next"); }
|
||||
else if (how == 2) { PlayQueue.get().append(tracks); toast("Added to queue"); }
|
||||
if (how == 1) { PlayQueue.get().playNext(tracks); toast(str(R.string.playing_next)); }
|
||||
else if (how == 2) { PlayQueue.get().append(tracks); toast(str(R.string.added_to_queue)); }
|
||||
else { playList(tracks, shuffle ? -1 : 0, shuffle, it.title); return; }
|
||||
if (wasEmpty) PlayerService.playCurrent(BaseActivity.this, 0);
|
||||
}
|
||||
@@ -279,11 +283,11 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
}
|
||||
|
||||
protected void runRadio(final String title, final Task<List<Item>> fetch) {
|
||||
toast("Starting " + title + "…");
|
||||
toast(str(R.string.starting_x, title));
|
||||
run(new Task<List<Item>>() {
|
||||
public List<Item> work() throws Exception { return fetch.work(); }
|
||||
public void done(List<Item> tracks) {
|
||||
if (tracks.isEmpty()) { toast("Nothing found for " + title); return; }
|
||||
if (tracks.isEmpty()) { toast(str(R.string.nothing_found_for, title)); return; }
|
||||
playList(tracks, 0, false, title);
|
||||
}
|
||||
public void fail(Exception e) { toast(msg(e)); }
|
||||
@@ -295,17 +299,17 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
final List<String> labels = new ArrayList<String>();
|
||||
final List<Integer> actions = new ArrayList<Integer>();
|
||||
if (it.type == Item.TRACK) {
|
||||
labels.add("Play next"); actions.add(1);
|
||||
labels.add("Add to queue"); actions.add(2);
|
||||
if (it.albumKey.length() > 0) { labels.add("Go to album"); actions.add(5); }
|
||||
if (it.artistKey.length() > 0) { labels.add("Go to artist"); actions.add(6); }
|
||||
labels.add(it.loved() ? "Unlove" : "Love"); actions.add(7);
|
||||
labels.add(str(R.string.play_next)); actions.add(1);
|
||||
labels.add(str(R.string.add_to_queue)); actions.add(2);
|
||||
if (it.albumKey.length() > 0) { labels.add(str(R.string.go_to_album)); actions.add(5); }
|
||||
if (it.artistKey.length() > 0) { labels.add(str(R.string.go_to_artist)); actions.add(6); }
|
||||
labels.add(str(it.loved() ? R.string.unlove : R.string.love)); actions.add(7);
|
||||
} else if (it.isContainer()) {
|
||||
labels.add("Play"); actions.add(0);
|
||||
labels.add(it.type == Item.ARTIST ? "Artist radio (shuffle)" : "Shuffle"); actions.add(3);
|
||||
labels.add("Play next"); actions.add(1);
|
||||
labels.add("Add to queue"); actions.add(2);
|
||||
if (it.type == Item.ALBUM && it.artistKey.length() > 0) { labels.add("Go to artist"); actions.add(6); }
|
||||
labels.add(str(R.string.play)); actions.add(0);
|
||||
labels.add(str(it.type == Item.ARTIST ? R.string.artist_radio_shuffle : R.string.shuffle)); actions.add(3);
|
||||
labels.add(str(R.string.play_next)); actions.add(1);
|
||||
labels.add(str(R.string.add_to_queue)); actions.add(2);
|
||||
if (it.type == Item.ALBUM && it.artistKey.length() > 0) { labels.add(str(R.string.go_to_artist)); actions.add(6); }
|
||||
} else return;
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(it.title)
|
||||
@@ -327,7 +331,7 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
boolean wasEmpty = PlayQueue.get().size() == 0;
|
||||
if (action == 1) PlayQueue.get().playNext(one); else PlayQueue.get().append(one);
|
||||
if (wasEmpty) PlayerService.playCurrent(this, 0);
|
||||
toast(action == 1 ? "Playing next" : "Added to queue");
|
||||
toast(str(action == 1 ? R.string.playing_next : R.string.added_to_queue));
|
||||
} else {
|
||||
playContainer(it, false, action);
|
||||
}
|
||||
@@ -357,11 +361,11 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
if (after != null) after.run();
|
||||
run(new Task<Object>() {
|
||||
public Object work() throws Exception { lib.rate(it, love ? 10 : -1); return null; }
|
||||
public void done(Object o) { toast(love ? "Loved" : "Removed from loved"); }
|
||||
public void done(Object o) { toast(str(love ? R.string.loved : R.string.unloved)); }
|
||||
public void fail(Exception e) {
|
||||
it.userRating = old;
|
||||
if (after != null) after.run();
|
||||
toast("Couldn't save rating: " + msg(e));
|
||||
toast(str(R.string.cant_save_rating, msg(e)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -370,9 +374,9 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, 1, 0, "Now playing");
|
||||
menu.add(0, 2, 0, "Search");
|
||||
menu.add(0, 3, 0, "Settings");
|
||||
menu.add(0, 1, 0, R.string.now_playing);
|
||||
menu.add(0, 2, 0, R.string.search);
|
||||
menu.add(0, 3, 0, R.string.settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -388,53 +392,54 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
|
||||
protected void showSettings() {
|
||||
Server s = app.server;
|
||||
String conn = s == null || s.active() == null ? "not connected yet" : Server.kindLabel(s.active().kind)
|
||||
+ (s.active().uri.startsWith("https") ? " (secure)" : " (unencrypted)");
|
||||
String conn = s == null || s.active() == null ? str(R.string.not_connected_yet) : Server.kindLabel(s.active().kind)
|
||||
+ str(s.active().uri.startsWith("https") ? R.string.secure : R.string.unencrypted);
|
||||
final String[] rows = {
|
||||
"Quality at home: " + qualityLabel(app.qualityHome()),
|
||||
"Quality away / mobile data: " + qualityLabel(app.qualityAway()),
|
||||
"Download tracks before playing: " + (app.downloadFirst() ? "On" : "Off"),
|
||||
"Encrypt at home too (slower): " + (Server.preferSecureAtHome ? "On" : "Off"),
|
||||
"Library: " + app.sectionTitle(),
|
||||
"Server: " + (s == null ? "-" : s.name),
|
||||
"Connection: " + conn,
|
||||
"Connection log & streaming test",
|
||||
"Sign out",
|
||||
str(R.string.set_quality_home, qualityLabel(app.qualityHome())),
|
||||
str(R.string.set_quality_away, qualityLabel(app.qualityAway())),
|
||||
str(R.string.set_download_first, str(app.downloadFirst() ? R.string.on : R.string.off)),
|
||||
str(R.string.set_secure_home, str(Server.preferSecureAtHome ? R.string.on : R.string.off)),
|
||||
str(R.string.set_library, app.sectionTitle()),
|
||||
str(R.string.set_server, s == null ? "-" : s.name),
|
||||
str(R.string.set_connection, conn),
|
||||
str(R.string.set_log),
|
||||
str(R.string.set_language, App.languageName(app.language())),
|
||||
str(R.string.sign_out),
|
||||
};
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Settings")
|
||||
.setTitle(R.string.settings)
|
||||
.setItems(rows, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
switch (which) {
|
||||
case 0: pickQuality("qHome", app.qualityHome(), "Quality at home"); break;
|
||||
case 1: pickQuality("qAway", app.qualityAway(), "Quality away / mobile"); break;
|
||||
case 0: pickQuality("qHome", app.qualityHome(), str(R.string.quality_home_title)); break;
|
||||
case 1: pickQuality("qAway", app.qualityAway(), str(R.string.quality_away_title)); break;
|
||||
case 2:
|
||||
app.prefs.edit().putBoolean("downloadFirst", !app.downloadFirst()).commit();
|
||||
toast("Download before playing: " + (app.downloadFirst() ? "on" : "off"));
|
||||
toast(str(R.string.download_first_toast, str(app.downloadFirst() ? R.string.on : R.string.off)));
|
||||
break;
|
||||
case 3:
|
||||
Server.preferSecureAtHome = !Server.preferSecureAtHome;
|
||||
app.prefs.edit().putBoolean("secureHome", Server.preferSecureAtHome).commit();
|
||||
if (app.server != null) app.server.invalidate();
|
||||
toast(Server.preferSecureAtHome ? "Using HTTPS at home (each new connection is slower on this phone)"
|
||||
: "Using plain HTTP at home (faster)");
|
||||
toast(str(Server.preferSecureAtHome ? R.string.https_home_on : R.string.https_home_off));
|
||||
break;
|
||||
case 4: startActivity(new Intent(BaseActivity.this, SetupActivity.class).putExtra(SetupActivity.EXTRA_MODE, SetupActivity.MODE_LIBRARY)); break;
|
||||
case 5: startActivity(new Intent(BaseActivity.this, SetupActivity.class).putExtra(SetupActivity.EXTRA_MODE, SetupActivity.MODE_SERVER)); break;
|
||||
case 6:
|
||||
if (app.server != null) {
|
||||
app.server.invalidate();
|
||||
toast("Reconnecting…");
|
||||
toast(str(R.string.reconnecting));
|
||||
final Server sv = app.server;
|
||||
run(new Task<Server.Conn>() {
|
||||
public Server.Conn work() throws Exception { return sv.pick(); }
|
||||
public void done(Server.Conn c) { toast("Connected: " + Server.kindLabel(c.kind)); onConnectionChanged(); }
|
||||
public void done(Server.Conn c) { toast(str(R.string.connected_x, Server.kindLabel(c.kind))); onConnectionChanged(); }
|
||||
public void fail(Exception e) { toast(msg(e)); }
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 7: showDiagnostics(); break;
|
||||
case 8: confirmSignOut(); break;
|
||||
case 8: pickLanguage(); break;
|
||||
case 9: confirmSignOut(); break;
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
@@ -454,17 +459,17 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
int p = Ui.dp(this, 6);
|
||||
tv.setPadding(p, p, p, p);
|
||||
String log = com.minamp.net.DiagLog.dump();
|
||||
tv.setText(log.length() > 0 ? log : "Nothing logged yet.");
|
||||
tv.setText(log.length() > 0 ? log : str(R.string.log_empty));
|
||||
sv.addView(tv);
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(this)
|
||||
.setTitle("Connection log")
|
||||
.setTitle(R.string.connection_log)
|
||||
.setView(sv)
|
||||
.setNegativeButton("Save to SD card", new DialogInterface.OnClickListener() {
|
||||
.setNegativeButton(R.string.save_to_sd, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) { saveDiagnostics(); }
|
||||
})
|
||||
.setPositiveButton("Close", null);
|
||||
.setPositiveButton(R.string.close, null);
|
||||
if (app.library != null) {
|
||||
b.setNeutralButton("Test streaming", new DialogInterface.OnClickListener() {
|
||||
b.setNeutralButton(R.string.test_streaming, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) { testStreaming(); }
|
||||
});
|
||||
}
|
||||
@@ -476,7 +481,7 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
private void saveDiagnostics() {
|
||||
try {
|
||||
if (!android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState())) {
|
||||
toast("No SD card available");
|
||||
toast(str(R.string.no_sd));
|
||||
return;
|
||||
}
|
||||
java.io.File f = new java.io.File(android.os.Environment.getExternalStorageDirectory(), "Minamp-log.txt");
|
||||
@@ -485,9 +490,9 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
+ ", Android " + android.os.Build.VERSION.RELEASE + "\n";
|
||||
o.write((head + com.minamp.net.DiagLog.dump()).getBytes("UTF-8"));
|
||||
o.close();
|
||||
toast("Saved as Minamp-log.txt on the SD card");
|
||||
toast(str(R.string.saved_log));
|
||||
} catch (Exception e) {
|
||||
toast("Couldn't save: " + msg(e));
|
||||
toast(str(R.string.cant_save, msg(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +500,7 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
private void testStreaming() {
|
||||
final Library lib = lib();
|
||||
final Item current = PlayQueue.get().current();
|
||||
toast("Testing\u2026 (about 30 s)");
|
||||
toast(str(R.string.testing));
|
||||
run(new Task<Object>() {
|
||||
public Object work() throws Exception {
|
||||
com.minamp.net.DiagLog.log("=== streaming test ===");
|
||||
@@ -559,29 +564,56 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
});
|
||||
}
|
||||
|
||||
private static String qualityLabel(int kbps) {
|
||||
private String qualityLabel(int kbps) {
|
||||
if (kbps <= 0) return str(R.string.quality_original);
|
||||
for (int i = 0; i < App.QUALITIES.length; i++) if (App.QUALITIES[i] == kbps) return App.QUALITY_LABELS[i];
|
||||
return kbps + " kbps";
|
||||
}
|
||||
|
||||
private String[] qualityLabels() {
|
||||
String[] l = App.QUALITY_LABELS.clone();
|
||||
l[0] = str(R.string.quality_original);
|
||||
return l;
|
||||
}
|
||||
|
||||
private void pickLanguage() {
|
||||
int sel = 0;
|
||||
for (int i = 0; i < App.LANGUAGES.length; i++) if (App.LANGUAGES[i].equals(app.language())) sel = i;
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.language_title)
|
||||
.setSingleChoiceItems(App.LANGUAGE_NAMES, sel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
d.dismiss();
|
||||
if (App.LANGUAGES[which].equals(app.language())) return;
|
||||
app.prefs.edit().putString("lang", App.LANGUAGES[which]).commit();
|
||||
App.applyLanguage(getApplicationContext());
|
||||
App.applyLanguage(BaseActivity.this);
|
||||
// rebuild the screens in the new language
|
||||
startActivity(new Intent(BaseActivity.this, HomeActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).putExtra("relaunch", true));
|
||||
finish();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void pickQuality(final String key, int current, String title) {
|
||||
int sel = 0;
|
||||
for (int i = 0; i < App.QUALITIES.length; i++) if (App.QUALITIES[i] == current) sel = i;
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(title)
|
||||
.setSingleChoiceItems(App.QUALITY_LABELS, sel, new DialogInterface.OnClickListener() {
|
||||
.setSingleChoiceItems(qualityLabels(), sel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
app.prefs.edit().putInt(key, App.QUALITIES[which]).commit();
|
||||
d.dismiss();
|
||||
toast("Applies from the next track");
|
||||
toast(str(R.string.applies_next_track));
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void confirmSignOut() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage("Sign out and forget this server?")
|
||||
.setPositiveButton("Sign out", new DialogInterface.OnClickListener() {
|
||||
.setMessage(R.string.sign_out_confirm)
|
||||
.setPositiveButton(R.string.sign_out, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
PlayerService.send(BaseActivity.this, PlayerService.ACTION_STOP);
|
||||
PlayQueue.get().clear();
|
||||
@@ -590,7 +622,7 @@ public abstract class BaseActivity extends Activity implements PlayerService.Lis
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class BrowseActivity extends BaseActivity {
|
||||
|
||||
Button play = (Button) header.findViewById(R.id.detail_play);
|
||||
Button shuffle = (Button) header.findViewById(R.id.detail_shuffle);
|
||||
if (item.type == Item.ARTIST) { play.setText("Play all"); shuffle.setText("Artist radio"); }
|
||||
if (item.type == Item.ARTIST) { play.setText(R.string.play_all); shuffle.setText(R.string.artist_radio); }
|
||||
play.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { playAll(false); } });
|
||||
shuffle.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { playAll(true); } });
|
||||
|
||||
@@ -73,9 +73,9 @@ public class BrowseActivity extends BaseActivity {
|
||||
|
||||
private String kindLabel() {
|
||||
switch (item.type) {
|
||||
case Item.ARTIST: return "Artist";
|
||||
case Item.ALBUM: return "Album";
|
||||
case Item.PLAYLIST: return "Playlist";
|
||||
case Item.ARTIST: return str(R.string.artist);
|
||||
case Item.ALBUM: return str(R.string.album);
|
||||
case Item.PLAYLIST: return str(R.string.playlist);
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -95,13 +95,13 @@ public class BrowseActivity extends BaseActivity {
|
||||
private void setMeta(List<Item> children) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (item.type == Item.ARTIST) {
|
||||
sb.append(children.size()).append(children.size() == 1 ? " album" : " albums");
|
||||
sb.append(str(children.size() == 1 ? R.string.n_albums_one : R.string.n_albums, children.size()));
|
||||
} else {
|
||||
long total = 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < children.size(); i++) if (children.get(i).type == Item.TRACK) { n++; total += children.get(i).duration; }
|
||||
if (item.year > 0) sb.append(item.year).append(" · ");
|
||||
sb.append(n).append(n == 1 ? " track" : " tracks");
|
||||
sb.append(str(n == 1 ? R.string.n_tracks_one : R.string.n_tracks, n));
|
||||
if (total > 0) sb.append(" · ").append(Ui.longDuration(total));
|
||||
}
|
||||
((TextView) header.findViewById(R.id.detail_meta)).setText(sb.toString());
|
||||
@@ -110,7 +110,7 @@ public class BrowseActivity extends BaseActivity {
|
||||
private void load() {
|
||||
final Library lib = lib();
|
||||
final Item target = item;
|
||||
showStatus(true, "Loading…", null);
|
||||
showStatus(true, str(R.string.loading), null);
|
||||
run(new Task<Object[]>() {
|
||||
public Object[] work() throws Exception {
|
||||
Item full = null;
|
||||
@@ -137,7 +137,7 @@ public class BrowseActivity extends BaseActivity {
|
||||
adapter.setAlbumArtist(item.artist);
|
||||
adapter.set(children, -1);
|
||||
setMeta(children);
|
||||
if (children.isEmpty()) showStatus(false, "Nothing here", null);
|
||||
if (children.isEmpty()) showStatus(false, str(R.string.nothing_here), null);
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
showStatus(false, msg(e), new Runnable() { public void run() { load(); } });
|
||||
|
||||
@@ -29,7 +29,7 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
private static final int TAB_HOME = 0, TAB_ARTISTS = 1, TAB_ALBUMS = 2, TAB_PLAYLISTS = 3;
|
||||
private static final String[] ALBUM_SORTS = {"titleSort", "artist.titleSort", "addedAt:desc", "year:desc", "viewCount:desc"};
|
||||
private static final String[] ALBUM_SORT_LABELS = {"Title", "Artist", "Recently added", "Year (newest)", "Most played"};
|
||||
private static final int[] ALBUM_SORT_LABELS = {R.string.sort_title, R.string.sort_artist, R.string.sort_added, R.string.sort_year, R.string.sort_played};
|
||||
|
||||
private TextView[] tabs;
|
||||
private View tabLine;
|
||||
@@ -90,6 +90,14 @@ public class HomeActivity extends BaseActivity {
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
if (intent.getBooleanExtra("relaunch", false)) {
|
||||
// language changed: rebuild from scratch so every inflated text is re-read
|
||||
finish();
|
||||
overridePendingTransition(0, 0);
|
||||
startActivity(new Intent(this, HomeActivity.class));
|
||||
overridePendingTransition(0, 0);
|
||||
return;
|
||||
}
|
||||
// library or server may have changed in setup
|
||||
if (!app.isConfigured()) { startActivity(new Intent(this, SetupActivity.class)); finish(); return; }
|
||||
setupTopBar(app.sectionTitle(), connectionLine(), false);
|
||||
@@ -154,33 +162,33 @@ public class HomeActivity extends BaseActivity {
|
||||
int pad = Ui.dp(this, 8);
|
||||
|
||||
LinearLayout row1 = tileRow();
|
||||
row1.addView(tile(inf, Glyph.RADIO, "Library radio", "Shuffle everything", new View.OnClickListener() {
|
||||
row1.addView(tile(inf, Glyph.RADIO, str(R.string.library_radio), str(R.string.library_radio_sub), new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
final Library lib = lib();
|
||||
runRadio("Library radio", new Task<List<Item>>() { public List<Item> work() throws Exception { return lib.libraryRadio(); } });
|
||||
runRadio(str(R.string.library_radio), new Task<List<Item>>() { public List<Item> work() throws Exception { return lib.libraryRadio(); } });
|
||||
}
|
||||
}));
|
||||
row1.addView(spacer());
|
||||
row1.addView(tile(inf, Glyph.HEART, "Loved radio", "Your top rated", new View.OnClickListener() {
|
||||
row1.addView(tile(inf, Glyph.HEART, str(R.string.loved_radio), str(R.string.loved_radio_sub), new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
final Library lib = lib();
|
||||
runRadio("Loved radio", new Task<List<Item>>() { public List<Item> work() throws Exception { return lib.topRatedRadio(); } });
|
||||
runRadio(str(R.string.loved_radio), new Task<List<Item>>() { public List<Item> work() throws Exception { return lib.topRatedRadio(); } });
|
||||
}
|
||||
}));
|
||||
homeContent.addView(row1);
|
||||
|
||||
LinearLayout row2 = tileRow();
|
||||
row2.addView(tile(inf, Glyph.CLOCK, "Time travel", "Pick a decade", new View.OnClickListener() {
|
||||
row2.addView(tile(inf, Glyph.CLOCK, str(R.string.time_travel), str(R.string.time_travel_sub), new View.OnClickListener() {
|
||||
public void onClick(View v) { timeTravel(); }
|
||||
}));
|
||||
row2.addView(spacer());
|
||||
row2.addView(tile(inf, Glyph.SHUFFLE, "Random album", "Feeling lucky", new View.OnClickListener() {
|
||||
row2.addView(tile(inf, Glyph.SHUFFLE, str(R.string.random_album), str(R.string.random_album_sub), new View.OnClickListener() {
|
||||
public void onClick(View v) { randomAlbum(); }
|
||||
}));
|
||||
homeContent.addView(row2);
|
||||
|
||||
addShelf(inf, "Recently added", lib().recentlyAddedPath(20), ALBUM_SORTS[2]);
|
||||
addShelf(inf, "Recently played", lib().recentlyPlayedPath(20), null);
|
||||
addShelf(inf, str(R.string.recently_added), lib().recentlyAddedPath(20), ALBUM_SORTS[2]);
|
||||
addShelf(inf, str(R.string.recently_played), lib().recentlyPlayedPath(20), null);
|
||||
homeContent.setPadding(0, pad / 2, 0, pad);
|
||||
}
|
||||
|
||||
@@ -216,7 +224,7 @@ public class HomeActivity extends BaseActivity {
|
||||
head.addView(h, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
if (seeAllSort != null) {
|
||||
TextView all = (TextView) inf.inflate(R.layout.row_header, null);
|
||||
all.setText("SEE ALL ›");
|
||||
all.setText(R.string.see_all);
|
||||
all.setTextColor(getResources().getColor(R.color.text2));
|
||||
all.setPadding(Ui.dp(this, 10), Ui.dp(this, 12), Ui.dp(this, 10), Ui.dp(this, 4));
|
||||
all.setBackgroundResource(R.drawable.bg_pressable);
|
||||
@@ -239,7 +247,7 @@ public class HomeActivity extends BaseActivity {
|
||||
strip.setPadding(Ui.dp(this, 6), 0, Ui.dp(this, 6), 0);
|
||||
hs.addView(strip);
|
||||
final TextView loading = new TextView(this);
|
||||
loading.setText("Loading…");
|
||||
loading.setText(R.string.loading);
|
||||
loading.setTextColor(getResources().getColor(R.color.text2));
|
||||
loading.setTextSize(12);
|
||||
loading.setPadding(Ui.dp(this, 6), Ui.dp(this, 30), 0, Ui.dp(this, 30));
|
||||
@@ -251,7 +259,7 @@ public class HomeActivity extends BaseActivity {
|
||||
public List<Item> work() throws Exception { return lib.server.items(path); }
|
||||
public void done(List<Item> items) {
|
||||
strip.removeAllViews();
|
||||
if (items.isEmpty()) { loading.setText("Nothing here yet"); strip.addView(loading); return; }
|
||||
if (items.isEmpty()) { loading.setText(R.string.nothing_here_yet); strip.addView(loading); return; }
|
||||
LayoutInflater li = getLayoutInflater();
|
||||
int art = Ui.dp(HomeActivity.this, 88);
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
@@ -266,7 +274,7 @@ public class HomeActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
loading.setText("Couldn't load: " + msg(e) + " (tap to retry)");
|
||||
loading.setText(str(R.string.cant_load_tap, msg(e)));
|
||||
loading.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) { loaded[TAB_HOME] = false; buildHome(); loaded[TAB_HOME] = true; }
|
||||
});
|
||||
@@ -276,20 +284,20 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
private void timeTravel() {
|
||||
final Library lib = lib();
|
||||
toast("Finding decades…");
|
||||
toast(str(R.string.finding_decades));
|
||||
run(new Task<List<Integer>>() {
|
||||
public List<Integer> work() throws Exception { return lib.decades(); }
|
||||
public void done(final List<Integer> decades) {
|
||||
if (decades.isEmpty()) { toast("No release years in this library"); return; }
|
||||
if (decades.isEmpty()) { toast(str(R.string.no_years)); return; }
|
||||
String[] labels = new String[decades.size() + 1];
|
||||
labels[0] = "Surprise me";
|
||||
for (int i = 0; i < decades.size(); i++) labels[i + 1] = decades.get(i) + "s";
|
||||
new AlertDialog.Builder(HomeActivity.this).setTitle("Time travel radio")
|
||||
labels[0] = str(R.string.surprise_me);
|
||||
for (int i = 0; i < decades.size(); i++) labels[i + 1] = str(R.string.decade_x, decades.get(i));
|
||||
new AlertDialog.Builder(HomeActivity.this).setTitle(R.string.time_travel_title)
|
||||
.setItems(labels, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
final int dec = which == 0 ? decades.get(new Random().nextInt(decades.size())).intValue()
|
||||
: decades.get(which - 1).intValue();
|
||||
runRadio(dec + "s radio", new Task<List<Item>>() {
|
||||
runRadio(str(R.string.decade_radio_x, dec), new Task<List<Item>>() {
|
||||
public List<Item> work() throws Exception { return lib.timeTravelRadio(dec); }
|
||||
});
|
||||
}
|
||||
@@ -304,7 +312,7 @@ public class HomeActivity extends BaseActivity {
|
||||
run(new Task<List<Item>>() {
|
||||
public List<Item> work() throws Exception { return lib.server.items(lib.albumsPath("random", 0, 1)); }
|
||||
public void done(List<Item> l) {
|
||||
if (l.isEmpty()) { toast("No albums"); return; }
|
||||
if (l.isEmpty()) { toast(str(R.string.no_albums)); return; }
|
||||
Item a = l.get(0);
|
||||
playContainer(a, false, 0);
|
||||
open(a, null, 0, null);
|
||||
@@ -322,7 +330,7 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
private void loadArtists(final int from) {
|
||||
final Library lib = lib();
|
||||
if (from == 0) showStatus(true, "Loading artists…", null);
|
||||
if (from == 0) showStatus(true, str(R.string.loading_artists), null);
|
||||
run(new Task<Object[]>() {
|
||||
public Object[] work() throws Exception {
|
||||
Json.Obj mc = lib.server.get(lib.artistsPath(from, 100));
|
||||
@@ -334,7 +342,7 @@ public class HomeActivity extends BaseActivity {
|
||||
List<Item> l = (List<Item>) r[0];
|
||||
int total = ((Integer) r[1]).intValue();
|
||||
if (from == 0) artists.set(l, total); else artists.append(l, total);
|
||||
if (from == 0 && l.isEmpty() && tab == TAB_ARTISTS) showStatus(false, "No artists in this library", null);
|
||||
if (from == 0 && l.isEmpty() && tab == TAB_ARTISTS) showStatus(false, str(R.string.no_artists_library), null);
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
artists.loadFailed();
|
||||
@@ -347,7 +355,7 @@ public class HomeActivity extends BaseActivity {
|
||||
private void loadAlbums(final int from) {
|
||||
final Library lib = lib();
|
||||
final String sort = ALBUM_SORTS[Math.min(app.prefs.getInt("albumSort", 0), ALBUM_SORTS.length - 1)];
|
||||
if (from == 0) showStatus(true, "Loading albums…", null);
|
||||
if (from == 0) showStatus(true, str(R.string.loading_albums), null);
|
||||
run(new Task<Object[]>() {
|
||||
public Object[] work() throws Exception {
|
||||
Json.Obj mc = lib.server.get(lib.albumsPath(sort, from, 100));
|
||||
@@ -359,7 +367,7 @@ public class HomeActivity extends BaseActivity {
|
||||
List<Item> l = (List<Item>) r[0];
|
||||
int total = ((Integer) r[1]).intValue();
|
||||
if (from == 0) { albums.set(l, total); albumsList.setSelection(0); } else albums.append(l, total);
|
||||
if (from == 0 && l.isEmpty() && tab == TAB_ALBUMS) showStatus(false, "No albums in this library", null);
|
||||
if (from == 0 && l.isEmpty() && tab == TAB_ALBUMS) showStatus(false, str(R.string.no_albums_library), null);
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
albums.loadFailed();
|
||||
@@ -371,7 +379,7 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
private void loadPlaylists() {
|
||||
final Library lib = lib();
|
||||
showStatus(true, "Loading playlists…", null);
|
||||
showStatus(true, str(R.string.loading_playlists), null);
|
||||
run(new Task<List<Item>>() {
|
||||
public List<Item> work() throws Exception { return lib.server.items(lib.playlistsPath()); }
|
||||
public void done(List<Item> l) {
|
||||
@@ -379,7 +387,7 @@ public class HomeActivity extends BaseActivity {
|
||||
List<Item> audio = new ArrayList<Item>();
|
||||
for (int i = 0; i < l.size(); i++) if (l.get(i).type == Item.PLAYLIST) audio.add(l.get(i));
|
||||
playlists.set(audio, -1);
|
||||
if (audio.isEmpty() && tab == TAB_PLAYLISTS) showStatus(false, "No music playlists yet", null);
|
||||
if (audio.isEmpty() && tab == TAB_PLAYLISTS) showStatus(false, str(R.string.no_playlists), null);
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
if (tab == TAB_PLAYLISTS) showStatus(false, msg(e), new Runnable() { public void run() { loadPlaylists(); } });
|
||||
@@ -392,8 +400,8 @@ public class HomeActivity extends BaseActivity {
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
menu.add(0, 10, 0, "Sort albums");
|
||||
menu.add(0, 11, 0, "Refresh");
|
||||
menu.add(0, 10, 0, R.string.sort_albums);
|
||||
menu.add(0, 11, 0, R.string.refresh);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -407,8 +415,10 @@ public class HomeActivity extends BaseActivity {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == 10) {
|
||||
new AlertDialog.Builder(this).setTitle("Sort albums by")
|
||||
.setSingleChoiceItems(ALBUM_SORT_LABELS, app.prefs.getInt("albumSort", 0), new DialogInterface.OnClickListener() {
|
||||
String[] sortLabels = new String[ALBUM_SORT_LABELS.length];
|
||||
for (int i = 0; i < sortLabels.length; i++) sortLabels[i] = str(ALBUM_SORT_LABELS[i]);
|
||||
new AlertDialog.Builder(this).setTitle(R.string.sort_albums_by)
|
||||
.setSingleChoiceItems(sortLabels, app.prefs.getInt("albumSort", 0), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
d.dismiss();
|
||||
app.prefs.edit().putInt("albumSort", which).commit();
|
||||
|
||||
@@ -87,7 +87,7 @@ public class NowPlayingActivity extends BaseActivity {
|
||||
public void onClick(View v) {
|
||||
PlayQueue.get().cycleRepeat();
|
||||
int r = PlayQueue.get().repeat();
|
||||
toast(r == PlayQueue.REPEAT_ALL ? "Repeat queue" : r == PlayQueue.REPEAT_ONE ? "Repeat track" : "Repeat off");
|
||||
toast(str(r == PlayQueue.REPEAT_ALL ? R.string.repeat_queue : r == PlayQueue.REPEAT_ONE ? R.string.repeat_track : R.string.repeat_off));
|
||||
}
|
||||
});
|
||||
loveGlyph = Ui.glyph(this, Glyph.HEART_OUTLINE, white, 20); loveGlyph.scale(1f);
|
||||
@@ -153,8 +153,8 @@ public class NowPlayingActivity extends BaseActivity {
|
||||
Item it = PlayQueue.get().current();
|
||||
PlayQueue q = PlayQueue.get();
|
||||
if (it == null) {
|
||||
title.setText("Nothing playing");
|
||||
sub.setText("Pick something from your library");
|
||||
title.setText(R.string.nothing_playing);
|
||||
sub.setText(R.string.pick_something);
|
||||
from.setText("");
|
||||
art.setImageResource(R.drawable.art_placeholder);
|
||||
shownThumb = null;
|
||||
@@ -170,7 +170,7 @@ public class NowPlayingActivity extends BaseActivity {
|
||||
loveGlyph.setType(it.loved() ? Glyph.HEART : Glyph.HEART_OUTLINE);
|
||||
loveGlyph.setColor(it.loved() ? accent : white);
|
||||
albumLink.setVisibility(it.albumKey.length() > 0 ? View.VISIBLE : View.INVISIBLE);
|
||||
albumLink.setText("Album ›");
|
||||
albumLink.setText(R.string.album_link);
|
||||
|
||||
if (!it.thumb.equals(shownThumb)) {
|
||||
shownThumb = it.thumb;
|
||||
@@ -203,9 +203,9 @@ public class NowPlayingActivity extends BaseActivity {
|
||||
|
||||
private void updateSleep() {
|
||||
long at = PlayerService.sleepAt();
|
||||
if (PlayerService.sleepEndOfTrack()) sleepText.setText("End of track");
|
||||
if (PlayerService.sleepEndOfTrack()) sleepText.setText(R.string.end_of_track);
|
||||
else if (at > 0) sleepText.setText(Ui.time(at - System.currentTimeMillis()));
|
||||
else sleepText.setText("Sleep");
|
||||
else sleepText.setText(R.string.sleep);
|
||||
}
|
||||
|
||||
private void setBackdrop(int color) {
|
||||
@@ -216,8 +216,9 @@ public class NowPlayingActivity extends BaseActivity {
|
||||
|
||||
private void sleepDialog() {
|
||||
final int[] mins = {0, 15, 30, 45, 60, 90, -1};
|
||||
String[] labels = {"Off", "15 minutes", "30 minutes", "45 minutes", "1 hour", "1½ hours", "End of this track"};
|
||||
new AlertDialog.Builder(this).setTitle("Sleep timer")
|
||||
String[] labels = {str(R.string.sleep_off), str(R.string.sleep_15), str(R.string.sleep_30), str(R.string.sleep_45),
|
||||
str(R.string.sleep_60), str(R.string.sleep_90), str(R.string.sleep_eot)};
|
||||
new AlertDialog.Builder(this).setTitle(R.string.sleep_timer)
|
||||
.setItems(labels, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
startService(new Intent(NowPlayingActivity.this, PlayerService.class)
|
||||
|
||||
@@ -146,6 +146,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
App.applyLanguage(this);
|
||||
instance = this;
|
||||
audio = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||||
audio.registerMediaButtonEventReceiver(new ComponentName(this, MediaButtonReceiver.class));
|
||||
@@ -261,7 +262,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
offsetMs = 0;
|
||||
lastPos = posMs;
|
||||
playWhenReady = play;
|
||||
setState(BUFFERING, "Connecting…");
|
||||
setState(BUFFERING, getString(R.string.connecting));
|
||||
goForeground();
|
||||
if (play) requestFocus();
|
||||
final boolean download = forceDownload || app.downloadFirst();
|
||||
@@ -297,8 +298,13 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
} catch (final Throwable e) {
|
||||
if (!(e instanceof Exception)) App.logError("player", e);
|
||||
final String m = com.minamp.net.Threads.describe(e);
|
||||
final int code = e instanceof com.minamp.net.RefusedException ? ((com.minamp.net.RefusedException) e).code : 0;
|
||||
post(new Runnable() {
|
||||
public void run() { if (g == gen) trackFailed(m); }
|
||||
public void run() {
|
||||
if (g != gen) return;
|
||||
if (code > 0) { refusal = m; refusalCode = code; }
|
||||
trackFailed(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -319,6 +325,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
File f = new File(base, "play-" + (g % 2) + ".tmp");
|
||||
Http.Response r = null;
|
||||
String why = null;
|
||||
int lastCode = 0;
|
||||
for (int i = 0; i < urls.size(); i++) {
|
||||
r = Http.get(urls.get(i), App.get().server != null ? App.get().server.mediaHeaders() : Plex.headers(null), 30000);
|
||||
if (r.code == 200) {
|
||||
@@ -328,12 +335,13 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
}
|
||||
break;
|
||||
}
|
||||
why = "Server refused the stream (" + r.code + ")";
|
||||
why = com.minamp.util.Tr.t("refused", "Server refused the stream (%1$d)", r.code);
|
||||
String body = new String(r.readAll(), "UTF-8");
|
||||
if (body.trim().length() > 0) why += ": " + Plex.snippet(body);
|
||||
lastCode = r.code;
|
||||
r = null;
|
||||
}
|
||||
if (r == null) throw new IOException(why);
|
||||
if (r == null) throw new com.minamp.net.RefusedException(lastCode, why);
|
||||
InputStream in = r.body;
|
||||
FileOutputStream out = new FileOutputStream(f);
|
||||
try {
|
||||
@@ -349,7 +357,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
lastUi = got;
|
||||
final int pct = (int) Math.min(99, got * 100 / total);
|
||||
final int fg = g;
|
||||
post(new Runnable() { public void run() { if (fg == gen) setState(BUFFERING, "Downloading " + pct + "%"); } });
|
||||
post(new Runnable() { public void run() { if (fg == gen) setState(BUFFERING, getString(R.string.downloading_pct, pct)); } });
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -400,7 +408,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
mp.setDataSource(proxy.register(routeId, urls, ext, transcoding,
|
||||
App.get().server != null ? App.get().server.mediaHeaders() : null));
|
||||
}
|
||||
setState(BUFFERING, "Buffering…");
|
||||
setState(BUFFERING, getString(R.string.buffering));
|
||||
mp.prepareAsync();
|
||||
} catch (Exception e) {
|
||||
trackFailed(message(e));
|
||||
@@ -433,7 +441,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
Item it = track;
|
||||
// A stream that ends well before the track does means the network dropped.
|
||||
if (it != null && it.duration > 0 && pos > 0 && pos < it.duration - 15000 && !fromFile) {
|
||||
trackFailed("Stream interrupted");
|
||||
trackFailed(getString(R.string.stream_interrupted));
|
||||
return;
|
||||
}
|
||||
if (it != null) {
|
||||
@@ -443,7 +451,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
if (sleepEndOfTrack) {
|
||||
sleepEndOfTrack = false;
|
||||
PlayQueue.get().advance(false);
|
||||
stopAll("Sleep timer");
|
||||
stopAll(getString(R.string.sleep_timer));
|
||||
return;
|
||||
}
|
||||
if (PlayQueue.get().advance(false)) {
|
||||
@@ -457,15 +465,14 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
|
||||
public boolean onError(MediaPlayer p, int what, int extra) {
|
||||
if (p != mp) return true;
|
||||
trackFailed("Playback error (" + what + "/" + extra + ")");
|
||||
trackFailed(getString(R.string.playback_error, what, extra));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void trackFailed(String msg) {
|
||||
long pos = Math.max(currentPosition(), lastPos);
|
||||
if (refusal != null) msg = refusal; // the server's own reason beats "Playback error (1/-1004)"
|
||||
boolean permanent = refusalCode >= 400 && refusalCode < 500 && refusalCode != 408 && refusalCode != 429
|
||||
|| (msg != null && msg.startsWith("Server refused the stream (4"));
|
||||
boolean permanent = refusalCode >= 400 && refusalCode < 500 && refusalCode != 408 && refusalCode != 429;
|
||||
refusal = null;
|
||||
refusalCode = 0;
|
||||
retries = permanent ? 99 : retries + 1;
|
||||
@@ -593,7 +600,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
if (sleepAt > 0 && now >= sleepAt) {
|
||||
sleepAt = 0;
|
||||
pause();
|
||||
toast("Sleep timer: paused");
|
||||
toast(getString(R.string.sleep_paused));
|
||||
}
|
||||
if (state == PLAYING && track != null) {
|
||||
long pos = currentPosition();
|
||||
@@ -700,7 +707,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
|
||||
Intent open = new Intent(this, NowPlayingActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0, open, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
String line = (state == PAUSED ? "Paused · " : "") + it.artist;
|
||||
String line = (state == PAUSED ? getString(R.string.paused_dot) : "") + it.artist;
|
||||
n.setLatestEventInfo(this, it.title, line, pi);
|
||||
if (state == PAUSED || state == IDLE) {
|
||||
// let Android reclaim us while paused, but keep the notification as a shortcut back
|
||||
|
||||
@@ -18,11 +18,11 @@ public class QueueActivity extends BaseActivity {
|
||||
protected void onCreate(Bundle b) {
|
||||
super.onCreate(b);
|
||||
setContentView(R.layout.activity_queue);
|
||||
setupTopBar("Up next", "", true);
|
||||
setupTopBar(str(R.string.up_next), "", true);
|
||||
barAction(R.id.btn_action, Glyph.SHUFFLE, new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
PlayQueue.get().setShuffle(!PlayQueue.get().shuffle());
|
||||
toast(PlayQueue.get().shuffle() ? "Shuffle on" : "Shuffle off");
|
||||
toast(str(PlayQueue.get().shuffle() ? R.string.shuffle_on : R.string.shuffle_off));
|
||||
}
|
||||
});
|
||||
barAction(R.id.btn_action2, Glyph.MORE, new View.OnClickListener() {
|
||||
@@ -41,8 +41,8 @@ public class QueueActivity extends BaseActivity {
|
||||
public boolean onItemLongClick(AdapterView<?> p, View v, final int pos, long id) {
|
||||
final Item it = PlayQueue.get().at(pos);
|
||||
final boolean isCurrent = pos == PlayQueue.get().index();
|
||||
String[] opts = isCurrent ? new String[] {"Go to album", "Go to artist"}
|
||||
: new String[] {"Play next", "Remove from queue", "Go to album", "Go to artist"};
|
||||
String[] opts = isCurrent ? new String[] {str(R.string.go_to_album), str(R.string.go_to_artist)}
|
||||
: new String[] {str(R.string.play_next), str(R.string.remove_from_queue), str(R.string.go_to_album), str(R.string.go_to_artist)};
|
||||
new AlertDialog.Builder(QueueActivity.this).setTitle(it.title)
|
||||
.setItems(opts, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
@@ -73,14 +73,14 @@ public class QueueActivity extends BaseActivity {
|
||||
PlayQueue q = PlayQueue.get();
|
||||
adapter.set(q.items(), -1);
|
||||
adapter.setHighlight(q.index());
|
||||
setBarSub(q.size() + (q.size() == 1 ? " track" : " tracks") + (q.title().length() > 0 ? " · " + q.title() : ""));
|
||||
if (q.size() == 0) showStatus(false, "The queue is empty", null); else hideStatus();
|
||||
setBarSub(str(q.size() == 1 ? R.string.n_tracks_one : R.string.n_tracks, q.size()) + (q.title().length() > 0 ? " \u00b7 " + q.title() : ""));
|
||||
if (q.size() == 0) showStatus(false, str(R.string.queue_empty), null); else hideStatus();
|
||||
if (scroll && q.index() > 0) list.setSelection(Math.max(0, q.index() - 1));
|
||||
}
|
||||
|
||||
private void clearDialog() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setItems(new String[] {"Clear upcoming tracks", "Clear everything"}, new DialogInterface.OnClickListener() {
|
||||
.setItems(new String[] {str(R.string.clear_upcoming), str(R.string.clear_everything)}, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
PlayQueue q = PlayQueue.get();
|
||||
if (w == 0) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SearchActivity extends BaseActivity {
|
||||
public void onItemClick(AdapterView<?> p, View v, int pos, long id) {
|
||||
Item it = adapter.getItem(pos);
|
||||
hideKeyboard();
|
||||
open(it, null, 0, "Search");
|
||||
open(it, null, 0, str(R.string.search));
|
||||
}
|
||||
});
|
||||
list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@@ -75,7 +75,7 @@ public class SearchActivity extends BaseActivity {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
showStatus(false, "Search " + app.sectionTitle(), null);
|
||||
showStatus(false, str(R.string.search_in, app.sectionTitle()), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,19 +93,19 @@ public class SearchActivity extends BaseActivity {
|
||||
|
||||
private void search(boolean force) {
|
||||
final String q = box.getText().toString().trim();
|
||||
if (q.length() < 2) { adapter.set(new java.util.ArrayList<Item>(), -1); showStatus(false, "Search " + app.sectionTitle(), null); return; }
|
||||
if (q.length() < 2) { adapter.set(new java.util.ArrayList<Item>(), -1); showStatus(false, str(R.string.search_in, app.sectionTitle()), null); return; }
|
||||
if (!force && q.equals(lastQuery)) return;
|
||||
lastQuery = q;
|
||||
final int my = ++seq;
|
||||
final Library lib = lib();
|
||||
showStatus(true, "Searching…", null);
|
||||
showStatus(true, str(R.string.searching), null);
|
||||
run(new Task<List<Item>>() {
|
||||
public List<Item> work() throws Exception { return lib.search(q); }
|
||||
public void done(List<Item> r) {
|
||||
if (my != seq) return;
|
||||
adapter.set(r, -1);
|
||||
list.setSelection(0);
|
||||
if (r.isEmpty()) showStatus(false, "Nothing found for “" + q + "”", null); else hideStatus();
|
||||
if (r.isEmpty()) showStatus(false, str(R.string.nothing_found_q, q), null); else hideStatus();
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
if (my != seq) return;
|
||||
|
||||
@@ -103,7 +103,7 @@ public class SetupActivity extends BaseActivity {
|
||||
|
||||
private void startPin() {
|
||||
clearPendingPin();
|
||||
showBusy("Contacting plex.tv\u2026\n(the first secure connection takes a few seconds on this phone)");
|
||||
showBusy(str(R.string.contacting_plextv));
|
||||
final int my = ++pinSeq;
|
||||
run(new Task<Plex.Pin>() {
|
||||
public Plex.Pin work() throws Exception { return Plex.createPin(); }
|
||||
@@ -117,7 +117,7 @@ public class SetupActivity extends BaseActivity {
|
||||
public void fail(Exception e) {
|
||||
if (my != pinSeq) return;
|
||||
show(welcome);
|
||||
toast("Couldn't reach plex.tv: " + msg(e));
|
||||
toast(str(R.string.cant_reach_plextv, msg(e)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public class SetupActivity extends BaseActivity {
|
||||
StringBuilder spaced = new StringBuilder();
|
||||
for (int i = 0; i < code.length(); i++) { if (i > 0) spaced.append(' '); spaced.append(code.charAt(i)); }
|
||||
pinCode.setText(spaced.toString());
|
||||
pinStatus.setText("Waiting for you to link\u2026");
|
||||
pinStatus.setText(R.string.pin_waiting);
|
||||
show(pinPage);
|
||||
int my = ++pinSeq;
|
||||
checkPinNow(my);
|
||||
@@ -170,7 +170,7 @@ public class SetupActivity extends BaseActivity {
|
||||
if (my != pinSeq || destroyed || pinCheckRunning) return;
|
||||
long age = System.currentTimeMillis() - pinStarted;
|
||||
if (age > 14 * 60 * 1000) {
|
||||
pinStatus.setText("Code expired \u2014 press Cancel and sign in again");
|
||||
pinStatus.setText(R.string.pin_expired);
|
||||
clearPendingPin();
|
||||
return;
|
||||
}
|
||||
@@ -183,11 +183,11 @@ public class SetupActivity extends BaseActivity {
|
||||
pinChecks++;
|
||||
if (token == null) {
|
||||
long left = (14 * 60 * 1000 - (System.currentTimeMillis() - pinStarted)) / 60000;
|
||||
pinStatus.setText("Not linked yet \u00b7 checked " + pinChecks + "\u00d7 \u00b7 " + left + " min left");
|
||||
pinStatus.setText(str(R.string.pin_not_yet, pinChecks, (int) left));
|
||||
schedulePinCheck(my);
|
||||
return;
|
||||
}
|
||||
pinStatus.setText("Linked!");
|
||||
pinStatus.setText(R.string.pin_linked);
|
||||
pinSeq++;
|
||||
clearPendingPin();
|
||||
signedIn(token);
|
||||
@@ -196,14 +196,14 @@ public class SetupActivity extends BaseActivity {
|
||||
pinCheckRunning = false;
|
||||
if (my != pinSeq) return;
|
||||
pinChecks++;
|
||||
pinStatus.setText("Check " + pinChecks + " failed: " + msg(e) + " \u2014 retrying");
|
||||
pinStatus.setText(str(R.string.pin_check_failed, pinChecks, msg(e)));
|
||||
schedulePinCheck(my);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void signedIn(final String account) {
|
||||
showBusy("Signed in. Finding your servers…");
|
||||
showBusy(str(R.string.signed_in_finding));
|
||||
app.prefs.edit().putString("account", account).commit();
|
||||
run(new Task<String>() {
|
||||
public String work() throws Exception { return Plex.username(account); }
|
||||
@@ -213,18 +213,18 @@ public class SetupActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void loadServers(final String account) {
|
||||
showBusy("Finding your servers…");
|
||||
showBusy(str(R.string.finding_servers));
|
||||
run(new Task<List<Server>>() {
|
||||
public List<Server> work() throws Exception { return Plex.servers(account); }
|
||||
public void done(final List<Server> servers) {
|
||||
if (servers.isEmpty()) {
|
||||
show(welcome);
|
||||
toast("No Plex Media Servers on this account");
|
||||
toast(str(R.string.no_servers));
|
||||
return;
|
||||
}
|
||||
if (servers.size() == 1) { pickServer(servers.get(0), null); return; }
|
||||
chooseTitle.setText("Choose a server");
|
||||
chooseSub.setText("Signed in as " + app.prefs.getString("username", "you"));
|
||||
chooseTitle.setText(R.string.choose_server);
|
||||
chooseSub.setText(str(R.string.signed_in_as, app.prefs.getString("username", str(R.string.you))));
|
||||
chooseList.removeAllViews();
|
||||
for (int i = 0; i < servers.size(); i++) {
|
||||
final Server s = servers.get(i);
|
||||
@@ -233,7 +233,7 @@ public class SetupActivity extends BaseActivity {
|
||||
int kind = s.connections.get(k).kind;
|
||||
if (kind == Server.LOCAL || kind == Server.LOCAL_HTTP) local++; else remote++;
|
||||
}
|
||||
String sub = (s.owned ? "Yours" : "Shared with you") + " · " + (local > 0 ? "home + " : "") + (remote > 0 ? "remote" : "local only");
|
||||
String sub = str(s.owned ? R.string.server_yours : R.string.server_shared) + " \u00b7 " + (local > 0 ? str(R.string.server_home_plus) : "") + str(remote > 0 ? R.string.server_remote : R.string.server_local_only);
|
||||
addChoice(s.name, sub, new View.OnClickListener() {
|
||||
public void onClick(View v) { pickServer(s, new Runnable() { public void run() { done(servers); } }); }
|
||||
});
|
||||
@@ -243,7 +243,7 @@ public class SetupActivity extends BaseActivity {
|
||||
}
|
||||
public void fail(Exception e) {
|
||||
show(welcome);
|
||||
toast("Couldn't list servers: " + msg(e));
|
||||
toast(str(R.string.cant_list_servers, msg(e)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -257,7 +257,7 @@ public class SetupActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void pickServer(final Server s, final Runnable back) {
|
||||
showBusy("Connecting to " + s.name + "…\nTrying home network, then remote, then relay.");
|
||||
showBusy(str(R.string.connecting_to, s.name));
|
||||
run(new Task<Server.Conn>() {
|
||||
public Server.Conn work() throws Exception { return s.pick(); }
|
||||
public void done(Server.Conn c) {
|
||||
@@ -275,12 +275,12 @@ public class SetupActivity extends BaseActivity {
|
||||
private void connectManual() {
|
||||
String url = ((EditText) findViewById(R.id.edit_url)).getText().toString().trim();
|
||||
String token = ((EditText) findViewById(R.id.edit_token)).getText().toString().trim();
|
||||
if (url.length() == 0) { toast("Enter your server's address"); return; }
|
||||
if (url.length() == 0) { toast(str(R.string.enter_address)); return; }
|
||||
final Server s = new Server();
|
||||
s.name = "Plex server";
|
||||
s.name = str(R.string.plex_server);
|
||||
s.token = token;
|
||||
s.add(Server.normalizeManual(url), Server.MANUAL);
|
||||
showBusy("Connecting…");
|
||||
showBusy(str(R.string.connecting));
|
||||
run(new Task<String>() {
|
||||
public String work() throws Exception {
|
||||
s.pick();
|
||||
@@ -294,7 +294,7 @@ public class SetupActivity extends BaseActivity {
|
||||
public void fail(Exception e) {
|
||||
show(manual);
|
||||
boolean auth = e instanceof Plex.PlexException && ((Plex.PlexException) e).code == 401;
|
||||
toast(auth ? "The server wants a token (or sign in with Plex instead)" : msg(e));
|
||||
toast(auth ? str(R.string.server_wants_token) : msg(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -302,22 +302,22 @@ public class SetupActivity extends BaseActivity {
|
||||
// ------------------------------------------------------------------ libraries
|
||||
|
||||
private void loadSections(final Server s, final Runnable back) {
|
||||
showBusy("Loading libraries…");
|
||||
showBusy(str(R.string.loading_libraries));
|
||||
run(new Task<List<Server.Section>>() {
|
||||
public List<Server.Section> work() throws Exception { return s.musicSections(); }
|
||||
public void done(List<Server.Section> secs) {
|
||||
if (secs.isEmpty()) {
|
||||
if (back != null) back.run(); else show(welcome);
|
||||
toast(s.name + " has no music libraries");
|
||||
toast(str(R.string.no_music_libraries, s.name));
|
||||
return;
|
||||
}
|
||||
if (secs.size() == 1) { finishWith(s, secs.get(0)); return; }
|
||||
chooseTitle.setText("Choose a music library");
|
||||
chooseTitle.setText(R.string.choose_library);
|
||||
chooseSub.setText(s.name);
|
||||
chooseList.removeAllViews();
|
||||
for (int i = 0; i < secs.size(); i++) {
|
||||
final Server.Section sec = secs.get(i);
|
||||
addChoice(sec.title, "Music", new View.OnClickListener() {
|
||||
addChoice(sec.title, str(R.string.music), new View.OnClickListener() {
|
||||
public void onClick(View v) { finishWith(s, sec); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ public final class Ui {
|
||||
|
||||
public static String longDuration(long ms) {
|
||||
long min = (ms + 30000) / 60000;
|
||||
if (min < 60) return min + " min";
|
||||
return (min / 60) + " h " + (min % 60) + " min";
|
||||
App app = App.get();
|
||||
if (min < 60) return app.getString(R.string.min, (int) min);
|
||||
return app.getString(R.string.h_min, (int) (min / 60), (int) (min % 60));
|
||||
}
|
||||
|
||||
/** Average colour of a bitmap, nudged towards something vivid but dark enough for white text. */
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.minamp.util.Tr;
|
||||
import com.minamp.bc.asn1.ASN1Encoding;
|
||||
import com.minamp.bc.asn1.ASN1ObjectIdentifier;
|
||||
import com.minamp.bc.asn1.ASN1OctetString;
|
||||
@@ -69,7 +70,7 @@ public final class CertVerifier {
|
||||
synchronized (verifiedChains) { known = verifiedChains.contains(chainKey); }
|
||||
if (known) {
|
||||
Certificate leaf = Certificate.getInstance(chainDer.get(0));
|
||||
if (!hostMatches(leaf, host)) throw new IOException("Certificate does not match " + host);
|
||||
if (!hostMatches(leaf, host)) throw new IOException(Tr.t("cert_mismatch", "Certificate does not match %1$s", host));
|
||||
if (new Date().getTime() > 1704067200000L) checkDates(leaf, new Date());
|
||||
return;
|
||||
}
|
||||
@@ -83,7 +84,7 @@ public final class CertVerifier {
|
||||
|
||||
Certificate leaf = chain.get(0);
|
||||
if (!hostMatches(leaf, host)) {
|
||||
throw new IOException("Certificate does not match " + host);
|
||||
throw new IOException(Tr.t("cert_mismatch", "Certificate does not match %1$s", host));
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
@@ -115,7 +116,7 @@ public final class CertVerifier {
|
||||
if (!isCa(cand)) continue;
|
||||
if (signatureValid(current, cand)) { next = cand; break; }
|
||||
}
|
||||
if (next == null) throw new IOException("Untrusted certificate chain for " + host);
|
||||
if (next == null) throw new IOException(Tr.t("cert_untrusted", "Untrusted certificate chain for %1$s", host));
|
||||
current = next;
|
||||
}
|
||||
throw new IOException("Certificate chain too long");
|
||||
@@ -125,7 +126,7 @@ public final class CertVerifier {
|
||||
Date from = c.getStartDate().getDate();
|
||||
Date to = c.getEndDate().getDate();
|
||||
if (now.before(from) || now.after(to)) {
|
||||
throw new IOException("Certificate expired or not yet valid (check the phone's date)");
|
||||
throw new IOException(Tr.t("cert_dates", "Certificate expired or not yet valid (check the phone's date)"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.minamp.net;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** The server answered, but refused the request (HTTP status in {@link #code}). */
|
||||
public class RefusedException extends IOException {
|
||||
public final int code;
|
||||
|
||||
public RefusedException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -197,10 +197,6 @@ public final class StreamProxy implements Runnable {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private static final class RefusedException extends IOException {
|
||||
final int code;
|
||||
RefusedException(int code, String msg) { super(msg); this.code = code; }
|
||||
}
|
||||
|
||||
/** Tries the route's URLs in order; returns the accepted response or throws with the server's words. */
|
||||
private Http.Response openUpstream(Route route, String method, String range, int first, int last) throws IOException {
|
||||
@@ -223,9 +219,10 @@ public final class StreamProxy implements Runnable {
|
||||
try { body = "HEAD".equals(method) ? "" : new String(up.readAll(), "UTF-8"); } catch (IOException ignored) {}
|
||||
up.close();
|
||||
code = up.code;
|
||||
refusal = "Server refused the stream (" + up.code + ")" + (body.trim().length() > 0 ? ": " + com.minamp.plex.Plex.snippet(body) : "");
|
||||
refusal = com.minamp.util.Tr.t("refused", "Server refused the stream (%1$d)", up.code)
|
||||
+ (body.trim().length() > 0 ? ": " + com.minamp.plex.Plex.snippet(body) : "");
|
||||
if (up.code == 503 && route.urls.get(i).indexOf("/library/parts/") >= 0) {
|
||||
refusal = "Plex couldn't open this file (503). Is the drive or folder with your music available to the server?";
|
||||
refusal = com.minamp.util.Tr.t("file_503", "Plex couldn't open this file (503). Is the drive or folder with your music available to the server?");
|
||||
}
|
||||
DiagLog.log("stream variant " + i + " refused: " + refusal);
|
||||
}
|
||||
|
||||
@@ -117,8 +117,9 @@ public final class Item {
|
||||
switch (type) {
|
||||
case TRACK: return artist + (album.length() > 0 ? " · " + album : "");
|
||||
case ALBUM: return artist + (year > 0 ? " · " + year : "");
|
||||
case PLAYLIST: return leafCount + (leafCount == 1 ? " track" : " tracks");
|
||||
case ARTIST: return "Artist";
|
||||
case PLAYLIST: return leafCount == 1 ? com.minamp.util.Tr.t("n_tracks_one", "%1$d track", leafCount)
|
||||
: com.minamp.util.Tr.t("n_tracks", "%1$d tracks", leafCount);
|
||||
case ARTIST: return com.minamp.util.Tr.t("artist", "Artist");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public final class Library {
|
||||
t.bitrate = full.bitrate; t.partSize = full.partSize;
|
||||
if (t.duration == 0) t.duration = full.duration;
|
||||
}
|
||||
if (t.partKey.length() == 0) throw new IOException("Track has no playable media");
|
||||
if (t.partKey.length() == 0) throw new IOException(com.minamp.util.Tr.t("no_media", "Track has no playable media"));
|
||||
}
|
||||
|
||||
public static void shuffle(List<Item> l) {
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class Plex {
|
||||
static Json.Obj fetchJson(String method, String url, String token, int timeout) throws IOException {
|
||||
Http.Response r = Http.request(method, url, headers(token), "POST".equals(method) ? new byte[0] : null, timeout);
|
||||
String body = r.readString();
|
||||
if (r.code == 401) throw new PlexException(401, "Not authorized (401) on " + describePath(url));
|
||||
if (r.code == 401) throw new PlexException(401, com.minamp.util.Tr.t("not_authorized", "Not authorized (401) on %1$s", describePath(url)));
|
||||
if (r.code < 200 || r.code >= 300) {
|
||||
String snippet = snippet(body);
|
||||
com.minamp.net.DiagLog.log(" server said: " + snippet);
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.minamp.net.Http;
|
||||
import com.minamp.util.Tr;
|
||||
import com.minamp.util.Json;
|
||||
|
||||
/**
|
||||
@@ -104,10 +105,10 @@ public final class Server {
|
||||
|
||||
public static String kindLabel(int kind) {
|
||||
switch (kind) {
|
||||
case LOCAL: case LOCAL_HTTP: return "Home network";
|
||||
case MANUAL: return "Direct";
|
||||
case REMOTE: case REMOTE_HTTP: return "Remote";
|
||||
case RELAY: return "Plex Relay";
|
||||
case LOCAL: case LOCAL_HTTP: return Tr.t("kind_home", "Home network");
|
||||
case MANUAL: return Tr.t("kind_direct", "Direct");
|
||||
case REMOTE: case REMOTE_HTTP: return Tr.t("kind_remote", "Remote");
|
||||
case RELAY: return Tr.t("kind_relay", "Plex Relay");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -240,7 +241,8 @@ public final class Server {
|
||||
if (err == null) return choose(c);
|
||||
if (firstError == null) firstError = kindLabel(c.kind) + ": " + err;
|
||||
}
|
||||
throw new IOException("Can't reach " + (name.length() > 0 ? name : "server") + (firstError != null ? "\n" + firstError : ""));
|
||||
throw new IOException(Tr.t("cant_reach", "Can't reach %1$s", name.length() > 0 ? name : Tr.t("server", "server"))
|
||||
+ (firstError != null ? "\n" + firstError : ""));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ requests
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.minamp.util;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Translations for the pure-Java layers (network, Plex), which can't see Android resources.
|
||||
* The app installs a {@link Source} that reads string resources named "tr_" + key; without one
|
||||
* (desktop tests) the English text passed in is used.
|
||||
*/
|
||||
public final class Tr {
|
||||
public interface Source { String get(String key); }
|
||||
public static volatile Source source;
|
||||
|
||||
private Tr() {}
|
||||
|
||||
public static String t(String key, String english, Object... args) {
|
||||
String f = null;
|
||||
Source s = source;
|
||||
if (s != null) {
|
||||
try { f = s.get(key); } catch (Exception ignored) {}
|
||||
}
|
||||
if (f == null) f = english;
|
||||
return args.length == 0 ? f : String.format(Locale.US, f, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
"""Single source of truth for UI text: key -> (English, Galician).
|
||||
|
||||
Generates res/values/strings.xml (Galician, the default) and res/values-en/strings.xml (English).
|
||||
Keys starting with tr_ are looked up by name from the pure-Java layers (com.minamp.util.Tr).
|
||||
Run: python3 tools/strings.py
|
||||
"""
|
||||
import os
|
||||
|
||||
S = {
|
||||
"app_name": ("Minamp", "Minamp"),
|
||||
|
||||
# tabs / headers
|
||||
"tab_home": ("HOME", "INICIO"),
|
||||
"tab_artists": ("ARTISTS", "ARTISTAS"),
|
||||
"tab_albums": ("ALBUMS", "ÁLBUMS"),
|
||||
"tab_playlists": ("PLAYLISTS", "LISTAS"),
|
||||
"playing_from": ("PLAYING FROM", "REPRODUCINDO DESDE"),
|
||||
"see_all": ("SEE ALL ›", "VER TODO ›"),
|
||||
"album_link": ("Album ›", "Álbum ›"),
|
||||
|
||||
# setup
|
||||
"tagline": ("Your Plex music, anywhere,\non a very small phone.", "A túa música de Plex, onde queiras,\nnun teléfono moi pequeno."),
|
||||
"sign_in": ("Sign in with Plex", "Iniciar sesión con Plex"),
|
||||
"connect_address": ("Connect to a server address", "Conectar a un enderezo de servidor"),
|
||||
"connection_log": ("Connection log", "Rexistro de conexión"),
|
||||
"pin_open": ("On a computer or a newer phone, open", "Nun ordenador ou nun teléfono máis novo, abre"),
|
||||
"pin_enter": ("and enter this code:", "e escribe este código:"),
|
||||
"pin_waiting": ("Waiting for you to link…", "Agardando a que vincules a conta…"),
|
||||
"pin_check_now": ("I've entered it — check now", "Xa o escribín: comprobar agora"),
|
||||
"cancel": ("Cancel", "Cancelar"),
|
||||
"server_address": ("Server address", "Enderezo do servidor"),
|
||||
"server_address_help": ("e.g. 192.168.1.20 or https://plex.example.com. Signing in with Plex is easier and finds remote addresses for you.",
|
||||
"p. ex. 192.168.1.20 ou https://plex.exemplo.com. Iniciar sesión con Plex é máis doado e atopa por ti os enderezos remotos."),
|
||||
"address_hint": ("Address", "Enderezo"),
|
||||
"token_hint": ("X-Plex-Token (optional on your LAN)", "X-Plex-Token (opcional na rede da casa)"),
|
||||
"connect": ("Connect", "Conectar"),
|
||||
"back": ("Back", "Volver"),
|
||||
"contacting_plextv": ("Contacting plex.tv…\n(the first secure connection takes a few seconds on this phone)",
|
||||
"Contactando con plex.tv…\n(a primeira conexión segura tarda uns segundos neste teléfono)"),
|
||||
"cant_reach_plextv": ("Couldn't reach plex.tv: %1$s", "Non se puido contactar con plex.tv: %1$s"),
|
||||
"pin_expired": ("Code expired — press Cancel and sign in again", "O código caducou: preme Cancelar e inicia sesión de novo"),
|
||||
"pin_not_yet": ("Not linked yet · checked %1$d× · %2$d min left", "Aínda sen vincular · comprobado %1$d× · quedan %2$d min"),
|
||||
"pin_linked": ("Linked!", "Vinculado!"),
|
||||
"pin_check_failed": ("Check %1$d failed: %2$s — retrying", "Fallou a comprobación %1$d: %2$s; tentando de novo"),
|
||||
"signed_in_finding": ("Signed in. Finding your servers…", "Sesión iniciada. Buscando os teus servidores…"),
|
||||
"finding_servers": ("Finding your servers…", "Buscando os teus servidores…"),
|
||||
"no_servers": ("No Plex Media Servers on this account", "Esta conta non ten ningún Plex Media Server"),
|
||||
"choose_server": ("Choose a server", "Escolle un servidor"),
|
||||
"signed_in_as": ("Signed in as %1$s", "Sesión iniciada como %1$s"),
|
||||
"you": ("you", "ti"),
|
||||
"server_yours": ("Yours", "Teu"),
|
||||
"server_shared": ("Shared with you", "Compartido contigo"),
|
||||
"server_home_plus": ("home + ", "casa + "),
|
||||
"server_remote": ("remote", "remoto"),
|
||||
"server_local_only": ("local only", "só local"),
|
||||
"cant_list_servers": ("Couldn't list servers: %1$s", "Non se puideron listar os servidores: %1$s"),
|
||||
"connecting_to": ("Connecting to %1$s…\nTrying home network, then remote, then relay.",
|
||||
"Conectando con %1$s…\nProbando a rede da casa, despois a remota e despois o relay."),
|
||||
"enter_address": ("Enter your server's address", "Escribe o enderezo do teu servidor"),
|
||||
"plex_server": ("Plex server", "Servidor Plex"),
|
||||
"connecting": ("Connecting…", "Conectando…"),
|
||||
"server_wants_token": ("The server wants a token (or sign in with Plex instead)", "O servidor pide un token (ou inicia sesión con Plex)"),
|
||||
"loading_libraries": ("Loading libraries…", "Cargando bibliotecas…"),
|
||||
"no_music_libraries": ("%1$s has no music libraries", "%1$s non ten bibliotecas de música"),
|
||||
"choose_library": ("Choose a music library", "Escolle unha biblioteca de música"),
|
||||
"music": ("Music", "Música"),
|
||||
|
||||
# common
|
||||
"ok": ("OK", "Aceptar"),
|
||||
"close": ("Close", "Pechar"),
|
||||
"loading": ("Loading…", "Cargando…"),
|
||||
"try_again": ("Try again", "Tentar de novo"),
|
||||
"nothing_here": ("Nothing here", "Non hai nada aquí"),
|
||||
"nothing_here_yet": ("Nothing here yet", "Aínda non hai nada"),
|
||||
"cant_load_tap": ("Couldn't load: %1$s (tap to retry)", "Non se puido cargar: %1$s (toca para tentar de novo)"),
|
||||
"on": ("On", "Activado"),
|
||||
"off": ("Off", "Desactivado"),
|
||||
"error_last_time": ("Minamp hit an error last time", "Minamp tivo un erro a última vez"),
|
||||
"min": ("%1$d min", "%1$d min"),
|
||||
"h_min": ("%1$d h %2$d min", "%1$d h %2$d min"),
|
||||
|
||||
# items / counts
|
||||
"artist": ("Artist", "Artista"),
|
||||
"album": ("Album", "Álbum"),
|
||||
"playlist": ("Playlist", "Lista de reprodución"),
|
||||
"n_albums_one": ("%1$d album", "%1$d álbum"),
|
||||
"n_albums": ("%1$d albums", "%1$d álbums"),
|
||||
"n_tracks_one": ("%1$d track", "%1$d pista"),
|
||||
"n_tracks": ("%1$d tracks", "%1$d pistas"),
|
||||
|
||||
# actions
|
||||
"play": ("Play", "Reproducir"),
|
||||
"play_all": ("Play all", "Reproducir todo"),
|
||||
"shuffle": ("Shuffle", "Aleatorio"),
|
||||
"artist_radio": ("Artist radio", "Radio do artista"),
|
||||
"artist_radio_shuffle": ("Artist radio (shuffle)", "Radio do artista (aleatorio)"),
|
||||
"play_next": ("Play next", "Reproducir a continuación"),
|
||||
"add_to_queue": ("Add to queue", "Engadir á cola"),
|
||||
"go_to_album": ("Go to album", "Ir ao álbum"),
|
||||
"go_to_artist": ("Go to artist", "Ir ao artista"),
|
||||
"love": ("Add to favourites", "Engadir a favoritas"),
|
||||
"unlove": ("Remove from favourites", "Quitar de favoritas"),
|
||||
"loved": ("Added to favourites", "Engadida a favoritas"),
|
||||
"unloved": ("Removed from favourites", "Quitada de favoritas"),
|
||||
"cant_save_rating": ("Couldn't save rating: %1$s", "Non se puido gardar a valoración: %1$s"),
|
||||
"nothing_to_play": ("Nothing to play", "Non hai nada que reproducir"),
|
||||
"shuffling_x": ("Shuffling %1$s", "Reproducindo ao chou %1$s"),
|
||||
"playing_x": ("Playing %1$s", "Reproducindo %1$s"),
|
||||
"adding_x": ("Adding %1$s…", "Engadindo %1$s…"),
|
||||
"no_tracks": ("No tracks", "Non hai pistas"),
|
||||
"playing_next": ("Playing next", "Soará a continuación"),
|
||||
"added_to_queue": ("Added to queue", "Engadido á cola"),
|
||||
"starting_x": ("Starting %1$s…", "Iniciando %1$s…"),
|
||||
"nothing_found_for": ("Nothing found for %1$s", "Non se atopou nada para %1$s"),
|
||||
|
||||
# menu / settings
|
||||
"now_playing": ("Now playing", "En reprodución"),
|
||||
"search": ("Search", "Buscar"),
|
||||
"settings": ("Settings", "Axustes"),
|
||||
"not_connected_yet": ("not connected yet", "aínda sen conectar"),
|
||||
"secure": (" (secure)", " (cifrada)"),
|
||||
"unencrypted": (" (unencrypted)", " (sen cifrar)"),
|
||||
"set_quality_home": ("Quality at home: %1$s", "Calidade na casa: %1$s"),
|
||||
"set_quality_away": ("Quality away / mobile data: %1$s", "Calidade fóra / datos móbiles: %1$s"),
|
||||
"set_download_first": ("Download tracks before playing: %1$s", "Descargar as pistas antes de reproducilas: %1$s"),
|
||||
"set_secure_home": ("Encrypt at home too (slower): %1$s", "Cifrar tamén na casa (máis lento): %1$s"),
|
||||
"set_library": ("Library: %1$s", "Biblioteca: %1$s"),
|
||||
"set_server": ("Server: %1$s", "Servidor: %1$s"),
|
||||
"set_connection": ("Connection: %1$s", "Conexión: %1$s"),
|
||||
"set_log": ("Connection log & streaming test", "Rexistro de conexión e proba de reprodución"),
|
||||
"set_language": ("Language: %1$s", "Idioma: %1$s"),
|
||||
"sign_out": ("Sign out", "Pechar sesión"),
|
||||
"quality_home_title": ("Quality at home", "Calidade na casa"),
|
||||
"quality_away_title": ("Quality away / mobile", "Calidade fóra / datos móbiles"),
|
||||
"quality_original": ("Original", "Orixinal"),
|
||||
"download_first_toast": ("Download before playing: %1$s", "Descargar antes de reproducir: %1$s"),
|
||||
"https_home_on": ("Using HTTPS at home (each new connection is slower on this phone)",
|
||||
"Usando HTTPS na casa (cada conexión nova é máis lenta neste teléfono)"),
|
||||
"https_home_off": ("Using plain HTTP at home (faster)", "Usando HTTP simple na casa (máis rápido)"),
|
||||
"reconnecting": ("Reconnecting…", "Reconectando…"),
|
||||
"connected_x": ("Connected: %1$s", "Conectado: %1$s"),
|
||||
"applies_next_track": ("Applies from the next track", "Aplícase desde a seguinte pista"),
|
||||
"sign_out_confirm": ("Sign out and forget this server?", "Pechar sesión e esquecer este servidor?"),
|
||||
"language_title": ("Language", "Idioma"),
|
||||
|
||||
# diagnostics
|
||||
"log_empty": ("Nothing logged yet.", "Aínda non hai nada no rexistro."),
|
||||
"save_to_sd": ("Save to SD card", "Gardar na tarxeta SD"),
|
||||
"test_streaming": ("Test streaming", "Probar reprodución"),
|
||||
"no_sd": ("No SD card available", "Non hai tarxeta SD dispoñible"),
|
||||
"saved_log": ("Saved as Minamp-log.txt on the SD card", "Gardado como Minamp-log.txt na tarxeta SD"),
|
||||
"cant_save": ("Couldn't save: %1$s", "Non se puido gardar: %1$s"),
|
||||
"testing": ("Testing… (about 30 s)", "Probando… (uns 30 s)"),
|
||||
|
||||
# home
|
||||
"library_radio": ("Library radio", "Radio da biblioteca"),
|
||||
"library_radio_sub": ("Shuffle everything", "Todo ao chou"),
|
||||
"loved_radio": ("Favourites radio", "Radio de favoritas"),
|
||||
"loved_radio_sub": ("Your top rated", "As mellor valoradas"),
|
||||
"time_travel": ("Time travel", "Viaxe no tempo"),
|
||||
"time_travel_sub": ("Pick a decade", "Escolle unha década"),
|
||||
"random_album": ("Random album", "Álbum ao chou"),
|
||||
"random_album_sub": ("Feeling lucky", "Proba sorte"),
|
||||
"recently_added": ("Recently added", "Engadidos recentemente"),
|
||||
"recently_played": ("Recently played", "Escoitados recentemente"),
|
||||
"finding_decades": ("Finding decades…", "Buscando décadas…"),
|
||||
"no_years": ("No release years in this library", "Esta biblioteca non ten anos de publicación"),
|
||||
"surprise_me": ("Surprise me", "Sorpréndeme"),
|
||||
"decade_x": ("%1$ds", "Década de %1$d"),
|
||||
"time_travel_title": ("Time travel radio", "Radio viaxe no tempo"),
|
||||
"decade_radio_x": ("%1$ds radio", "Radio da década de %1$d"),
|
||||
"no_albums": ("No albums", "Non hai álbums"),
|
||||
"loading_artists": ("Loading artists…", "Cargando artistas…"),
|
||||
"no_artists_library": ("No artists in this library", "Esta biblioteca non ten artistas"),
|
||||
"loading_albums": ("Loading albums…", "Cargando álbums…"),
|
||||
"no_albums_library": ("No albums in this library", "Esta biblioteca non ten álbums"),
|
||||
"loading_playlists": ("Loading playlists…", "Cargando listas…"),
|
||||
"no_playlists": ("No music playlists yet", "Aínda non hai listas de música"),
|
||||
"sort_albums": ("Sort albums", "Ordenar álbums"),
|
||||
"refresh": ("Refresh", "Actualizar"),
|
||||
"sort_albums_by": ("Sort albums by", "Ordenar álbums por"),
|
||||
"sort_title": ("Title", "Título"),
|
||||
"sort_artist": ("Artist", "Artista"),
|
||||
"sort_added": ("Recently added", "Engadidos recentemente"),
|
||||
"sort_year": ("Year (newest)", "Ano (máis recentes)"),
|
||||
"sort_played": ("Most played", "Máis escoitados"),
|
||||
|
||||
# now playing
|
||||
"repeat_queue": ("Repeat queue", "Repetir a cola"),
|
||||
"repeat_track": ("Repeat track", "Repetir a pista"),
|
||||
"repeat_off": ("Repeat off", "Sen repetición"),
|
||||
"nothing_playing": ("Nothing playing", "Non se está a reproducir nada"),
|
||||
"pick_something": ("Pick something from your library", "Escolle algo da túa biblioteca"),
|
||||
"end_of_track": ("End of track", "Fin da pista"),
|
||||
"sleep": ("Sleep", "Temporizador"),
|
||||
"sleep_timer": ("Sleep timer", "Temporizador de apagado"),
|
||||
"sleep_off": ("Off", "Desactivado"),
|
||||
"sleep_15": ("15 minutes", "15 minutos"),
|
||||
"sleep_30": ("30 minutes", "30 minutos"),
|
||||
"sleep_45": ("45 minutes", "45 minutos"),
|
||||
"sleep_60": ("1 hour", "1 hora"),
|
||||
"sleep_90": ("1½ hours", "1½ horas"),
|
||||
"sleep_eot": ("End of this track", "Ao rematar esta pista"),
|
||||
"sleep_paused": ("Sleep timer: paused", "Temporizador de apagado: en pausa"),
|
||||
|
||||
# playback service
|
||||
"buffering": ("Buffering…", "Cargando…"),
|
||||
"downloading_pct": ("Downloading %1$d%%", "Descargando %1$d%%"),
|
||||
"stream_interrupted": ("Stream interrupted", "Interrompeuse a transmisión"),
|
||||
"playback_error": ("Playback error (%1$d/%2$d)", "Erro de reprodución (%1$d/%2$d)"),
|
||||
"paused_dot": ("Paused · ", "En pausa · "),
|
||||
|
||||
# queue
|
||||
"up_next": ("Up next", "A continuación"),
|
||||
"shuffle_on": ("Shuffle on", "Aleatorio activado"),
|
||||
"shuffle_off": ("Shuffle off", "Aleatorio desactivado"),
|
||||
"remove_from_queue": ("Remove from queue", "Quitar da cola"),
|
||||
"queue_empty": ("The queue is empty", "A cola está baleira"),
|
||||
"clear_upcoming": ("Clear upcoming tracks", "Borrar as pistas seguintes"),
|
||||
"clear_everything": ("Clear everything", "Borralo todo"),
|
||||
|
||||
# search
|
||||
"search_hint": ("Artists, albums, tracks", "Artistas, álbums, cancións"),
|
||||
"search_in": ("Search %1$s", "Buscar en %1$s"),
|
||||
"searching": ("Searching…", "Buscando…"),
|
||||
"nothing_found_q": ("Nothing found for “%1$s”", "Non se atopou nada para “%1$s”"),
|
||||
|
||||
# pure-Java layers (looked up by name through com.minamp.util.Tr)
|
||||
"tr_kind_home": ("Home network", "Rede da casa"),
|
||||
"tr_kind_direct": ("Direct", "Directa"),
|
||||
"tr_kind_remote": ("Remote", "Remota"),
|
||||
"tr_kind_relay": ("Plex Relay", "Plex Relay"),
|
||||
"tr_cant_reach": ("Can't reach %1$s", "Non se pode conectar con %1$s"),
|
||||
"tr_server": ("server", "servidor"),
|
||||
"tr_not_authorized": ("Not authorized (401) on %1$s", "Sen autorización (401) en %1$s"),
|
||||
"tr_refused": ("Server refused the stream (%1$d)", "O servidor rexeitou a transmisión (%1$d)"),
|
||||
"tr_file_503": ("Plex couldn't open this file (503). Is the drive or folder with your music available to the server?",
|
||||
"Plex non puido abrir este ficheiro (503). Está dispoñible para o servidor o disco ou cartafol coa túa música?"),
|
||||
"tr_cert_mismatch": ("Certificate does not match %1$s", "O certificado non corresponde a %1$s"),
|
||||
"tr_cert_untrusted": ("Untrusted certificate chain for %1$s", "Cadea de certificados non fiable para %1$s"),
|
||||
"tr_cert_dates": ("Certificate expired or not yet valid (check the phone's date)",
|
||||
"Certificado caducado ou aínda non válido (comproba a data do teléfono)"),
|
||||
"tr_no_media": ("Track has no playable media", "A pista non ten ningún ficheiro reproducible"),
|
||||
"tr_artist": ("Artist", "Artista"),
|
||||
"tr_n_tracks_one": ("%1$d track", "%1$d pista"),
|
||||
"tr_n_tracks": ("%1$d tracks", "%1$d pistas"),
|
||||
}
|
||||
|
||||
|
||||
def esc(s):
|
||||
s = s.replace("\\", "\\\\").replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
s = s.replace("'", "\\'").replace('"', '\\"').replace("\n", "\\n")
|
||||
if s.startswith("@") or s.startswith("?"):
|
||||
s = "\\" + s
|
||||
return s
|
||||
|
||||
|
||||
def write(path, idx):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write('<?xml version="1.0" encoding="utf-8"?>\n<!-- generated by tools/strings.py: edit that file instead -->\n<resources>\n')
|
||||
for k in sorted(S):
|
||||
v = S[k][idx]
|
||||
fmt = ' formatted="false"' if ("%" in v and "%1$" not in v and "%%" not in v) else ""
|
||||
f.write(' <string name="%s"%s>%s</string>\n' % (k, fmt, esc(v)))
|
||||
f.write("</resources>\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "res")
|
||||
write(os.path.join(root, "values", "strings.xml"), 1) # Galician: default
|
||||
write(os.path.join(root, "values-en", "strings.xml"), 0) # English
|
||||
print("%d strings" % len(S))
|
||||
Reference in new issue
Block a user