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:
xockoandClaude Opus 5.5 committed 2026-10-15 00:11:41 +02:00
1 parent 6fd17bc9c5
commit fa67e95fde
29 files changed
+1029 -206

No files matched your search

+273
View File
@@ -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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
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))