Compare commits

...

6 Commits

Author SHA1 Message Date
yanggqi
871ee730cd
fix(ui): update Chinese simplified translation (#4403)
* Update zh-Hans.json

Updated Chinese translation

* Update resources/i18n/zh-Hans.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update resources/i18n/zh-Hans.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update resources/i18n/zh-Hans.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update resources/i18n/zh-Hans.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update zh-Hans.json

* Update resources/i18n/zh-Hans.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update resources/i18n/zh-Hans.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-31 12:18:06 -04:00
Deluan
c2657e0adb chore: add make stop target to terminate development servers
Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-30 17:49:41 -04:00
Deluan
aff9c7120b feat(ui): add Genre column as optional field in playlist table view
Added genre as a toggleable column in the playlist songs table. The Genre column
displays genre information for each song in playlists and is available through
the column toggle menu but disabled by default.

Implements feature request from GitHub discussion #4400.

Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-29 20:54:04 -04:00
Deluan
94d2696c84 feat(subsonic): populate Folder field with user's accessible library IDs
Added functionality to populate the Folder field in GetUser and GetUsers API responses
with the library IDs that the user has access to. This allows Subsonic API clients
to understand which music folders (libraries) a user can access for proper
content filtering and UI presentation.

Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-29 18:00:33 -04:00
Michael Brückner
949bff993e
fix(ui): update Deutsch, Galego, Italiano translations (#4394) 2025-07-29 12:06:29 -04:00
Muhammed Šehić
b2ee5b5156
feat(ui): add new Bosnian translation (#4399)
Update translations for Bosnian language
2025-07-29 12:06:09 -04:00
9 changed files with 791 additions and 16 deletions

View File

@ -32,6 +32,14 @@ server: check_go_env buildjs ##@Development Start the backend in development mod
@ND_ENABLEINSIGHTSCOLLECTOR="false" go tool reflex -d none -c reflex.conf
.PHONY: server
stop: ##@Development Stop development servers (UI and backend)
@echo "Stopping development servers..."
@-pkill -f "vite"
@-pkill -f "go tool reflex.*reflex.conf"
@-pkill -f "go run.*netgo"
@echo "Development servers stopped."
.PHONY: stop
watch: ##@Development Start Go tests in watch mode (re-run when code changes)
go tool ginkgo watch -tags=netgo -notify ./...
.PHONY: watch

628
resources/i18n/bs.json Normal file
View File

@ -0,0 +1,628 @@
{
"languageName": "Bosanski",
"resources": {
"song": {
"name": "Pjesma |||| Pjesme",
"fields": {
"albumArtist": "Izvođač albuma",
"duration": "Trajanje",
"trackNumber": "Pjesma #",
"playCount": "Reprodukcija",
"title": "Naslov",
"artist": "Izvođač",
"album": "Album",
"path": "Putanja datoteke",
"genre": "Žanr",
"compilation": "Kompilacija",
"year": "Godina",
"size": "Veličina datoteke",
"updatedAt": "Dodano",
"bitRate": "Brzina prijenosa",
"discSubtitle": "Podnaslov CD-a",
"starred": "Favorit",
"comment": "Komentar",
"rating": "Ocjena",
"quality": "Kvaliteta",
"bpm": "BPM",
"playDate": "Posljednja reprodukcija",
"channels": "Kanali",
"createdAt": "Dodano",
"grouping": "Grupisanje",
"mood": "Raspoloženje",
"participants": "Dodatni učesnici",
"tags": "Dodatne oznake",
"mappedTags": "Mapirane oznake",
"rawTags": "Sirovi podaci oznaka",
"bitDepth": "Dubina bita",
"sampleRate": "Uzorkovanje",
"missing": "Nedostaje",
"libraryName": "Biblioteka"
},
"actions": {
"addToQueue": "Reprodukcija kasnije",
"playNow": "Reprodukcija sada",
"addToPlaylist": "Dodaj u playlistu",
"shuffleAll": "Nasumična reprodukcija",
"download": "Preuzmi",
"playNext": "Reprodukcija sljedeće",
"info": "Više informacija",
"showInPlaylist": "Prikaži u playlisti"
}
},
"album": {
"name": "Album |||| Albumi",
"fields": {
"albumArtist": "Izvođač albuma",
"artist": "Izvođač",
"duration": "Trajanje",
"songCount": "Broj pjesama",
"playCount": "Reprodukcija",
"name": "Naziv",
"genre": "Žanr",
"compilation": "Kompilacija",
"year": "Godina",
"updatedAt": "Ažurirano",
"comment": "Komentar",
"rating": "Ocjena",
"createdAt": "Dodano",
"size": "Veličina",
"originalDate": "Originalni datum",
"releaseDate": "Datum izdanja",
"releases": "Izdanje |||| Izdanja",
"released": "Objavljeno",
"recordLabel": "Izdavač",
"catalogNum": "Kataloški broj",
"releaseType": "Tip",
"grouping": "Grupisanje",
"media": "Medij",
"mood": "Raspoloženje",
"date": "Datum snimanja",
"missing": "Nedostaje",
"libraryName": "Biblioteka"
},
"actions": {
"playAll": "Reprodukcija",
"playNext": "Reprodukcija sljedeće",
"addToQueue": "Dodaj u red",
"shuffle": "Nasumična reprodukcija",
"addToPlaylist": "Dodaj u playlistu",
"download": "Preuzmi",
"info": "Više informacija",
"share": "Podijeli"
},
"lists": {
"all": "Sve",
"random": "Nasumično",
"recentlyAdded": "Nedavno dodano",
"recentlyPlayed": "Nedavno reproducirano",
"mostPlayed": "Najviše reproducirano",
"starred": "Favoriti",
"topRated": "Najbolje ocijenjeno"
}
},
"artist": {
"name": "Izvođač |||| Izvođači",
"fields": {
"name": "Naziv",
"albumCount": "Broj albuma",
"songCount": "Broj pjesama",
"playCount": "Reprodukcija",
"rating": "Ocjena",
"genre": "Žanr",
"size": "Veličina",
"role": "Uloga",
"missing": "Nedostaje"
},
"roles": {
"albumartist": "Izvođač albuma |||| Izvođači albuma",
"artist": "Izvođač |||| Izvođači",
"composer": "Kompozitor |||| Kompozitori",
"conductor": "Dirigent |||| Dirigenti",
"lyricist": "Tekstopisac |||| Tekstopisci",
"arranger": "Aranžer |||| Aranžeri",
"producer": "Producent |||| Producenti",
"director": "Direktor |||| Direktori",
"engineer": "Inženjer |||| Inženjeri",
"mixer": "Mikser |||| Mikseri",
"remixer": "Remikser |||| Remikseri",
"djmixer": "DJ Mikser |||| DJ Mikseri",
"performer": "Izvođač |||| Izvođači",
"maincredit": "Izvođač albuma ili izvođač |||| Izvođači albuma ili izvođači"
},
"actions": {
"shuffle": "Nasumična reprodukcija",
"radio": "Radio",
"topSongs": "Najpopularnije pjesme"
}
},
"user": {
"name": "Korisnik |||| Korisnici",
"fields": {
"userName": "Korisničko ime",
"isAdmin": "Je admin",
"lastLoginAt": "Posljednja prijava",
"updatedAt": "Ažurirano",
"name": "Ime",
"password": "Lozinka",
"createdAt": "Kreirano",
"changePassword": "Promijeni lozinku?",
"currentPassword": "Trenutna lozinka",
"newPassword": "Nova lozinka",
"token": "Token",
"lastAccessAt": "Posljednji pristup",
"libraries": "Biblioteke"
},
"helperTexts": {
"name": "Promjena će biti aktivna nakon sljedeće prijave",
"libraries": "Odaberi specifične biblioteke za ovog korisnika ili ostavi prazno za standardne biblioteke"
},
"notifications": {
"created": "Korisnik kreiran",
"updated": "Korisnik ažuriran",
"deleted": "Korisnik obrisan"
},
"message": {
"listenBrainzToken": "Unesite svoj ListenBrainz korisnički token",
"clickHereForToken": "Kliknite ovdje za dobijanje tokena",
"selectAllLibraries": "Odaberi sve biblioteke",
"adminAutoLibraries": "Administratori automatski imaju pristup svim bibliotekama"
},
"validation": {
"librariesRequired": "Ne-administratori moraju imati barem jednu odabranu biblioteku"
}
},
"player": {
"name": "Player |||| Playeri",
"fields": {
"name": "Naziv",
"transcodingId": "ID transkodiranja",
"maxBitRate": "Maks. brzina prijenosa",
"client": "Klijent",
"userName": "Korisničko ime",
"lastSeen": "Posljednji put viđen",
"reportRealPath": "Prikaži stvarnu putanju",
"scrobbleEnabled": "Slanje podataka o reprodukciji (scrobbling)"
}
},
"transcoding": {
"name": "Transkodiranje |||| Transkodiranja",
"fields": {
"name": "Naziv",
"targetFormat": "Ciljani format",
"defaultBitRate": "Zadana brzina prijenosa",
"command": "Komanda"
}
},
"playlist": {
"name": "Playlista |||| Playliste",
"fields": {
"name": "Naziv",
"duration": "Trajanje",
"ownerName": "Vlasnik",
"public": "Javna",
"updatedAt": "Ažurirano",
"createdAt": "Kreirano",
"songCount": "Broj pjesama",
"comment": "Komentar",
"sync": "Auto-uvoz",
"path": "Uvezi iz"
},
"actions": {
"selectPlaylist": "Odaberi playlistu:",
"addNewPlaylist": "Kreiraj \"%{name}\"",
"export": "Izvezi",
"makePublic": "Učini javnom",
"makePrivate": "Učini privatnom",
"saveQueue": "Sačuvaj red čekanja u playlistu",
"searchOrCreate": "Pretraži playlistu ili kreiraj novu...",
"pressEnterToCreate": "Pritisni Enter za kreiranje nove playliste",
"removeFromSelection": "Ukloni iz odabira"
},
"message": {
"duplicate_song": "Dodaj duplikate",
"song_exist": "Neke pjesme su već u playlisti. Želiš li ih ipak dodati ili preskočiti?",
"noPlaylistsFound": "Nije pronađena nijedna playlista",
"noPlaylists": "Nema playlisti"
}
},
"radio": {
"name": "Radio |||| Radiji",
"fields": {
"name": "Naziv",
"streamUrl": "Stream URL",
"homePageUrl": "URL početne stranice",
"updatedAt": "Ažurirano",
"createdAt": "Dodano"
},
"actions": {
"playNow": "Reprodukcija sada"
}
},
"share": {
"name": "Dijeljenje |||| Dijeljenja",
"fields": {
"username": "Podijeljeno od strane",
"url": "URL",
"description": "Opis",
"contents": "Sadržaj",
"expiresAt": "Vrijedi do",
"lastVisitedAt": "Posljednja posjeta",
"visitCount": "Posjete",
"format": "Format",
"maxBitRate": "Maks. brzina prijenosa",
"updatedAt": "Ažurirano",
"createdAt": "Kreirano",
"downloadable": "Dozvoli preuzimanje?"
}
},
"missing": {
"name": "Nedostajuća datoteka |||| Nedostajuće datoteke",
"fields": {
"path": "Putanja",
"size": "Veličina",
"updatedAt": "Nedostaje od",
"libraryName": "Biblioteka"
},
"actions": {
"remove": "Ukloni",
"remove_all": "ukloni sve"
},
"notifications": {
"removed": "Nedostajuća(e) datoteka(e) uklonjena(e)"
},
"empty": "nema nedostajućih datoteka"
},
"library": {
"name": "Biblioteka |||| Biblioteke",
"fields": {
"name": "Naziv",
"path": "Putanja",
"remotePath": "Udaljena putanja",
"lastScanAt": "Posljednje skeniranje",
"songCount": "Pjesme",
"albumCount": "Albumi",
"artistCount": "Izvođači",
"totalSongs": "Pjesme",
"totalAlbums": "Albumi",
"totalArtists": "Izvođači",
"totalFolders": "Folderi",
"totalFiles": "Datoteke",
"totalMissingFiles": "Nedostajuće datoteke",
"totalSize": "Veličina",
"totalDuration": "Trajanje",
"defaultNewUsers": "Standardno za nove korisnike",
"createdAt": "Kreirano",
"updatedAt": "Ažurirano"
},
"sections": {
"basic": "Osnovne informacije",
"statistics": "Statistika"
},
"actions": {
"scan": "Skeniraj biblioteku",
"manageUsers": "Upravljaj pristupima",
"viewDetails": "Pogledaj detalje"
},
"notifications": {
"created": "Biblioteka uspješno kreirana",
"updated": "Biblioteka uspješno ažurirana",
"deleted": "Biblioteka uspješno obrisana",
"scanStarted": "Skeniranje biblioteke započeto",
"scanCompleted": "Skeniranje biblioteke završeno"
},
"validation": {
"nameRequired": "Naziv biblioteke je obavezan",
"pathRequired": "Putanja biblioteke je obavezna",
"pathNotDirectory": "Putanja biblioteke mora biti folder",
"pathNotFound": "Putanja biblioteke nije pronađena",
"pathNotAccessible": "Putanja biblioteke nije dostupna",
"pathInvalid": "Putanja biblioteke nije validna"
},
"messages": {
"deleteConfirm": "Da li zaista želiš obrisati ovu biblioteku? Pristup i podaci će biti uklonjeni.",
"scanInProgress": "Skeniranje biblioteke u toku...",
"noLibrariesAssigned": "Nema dodijeljenih biblioteka"
}
}
},
"ra": {
"auth": {
"welcome1": "Hvala što ste instalirali Navidrome!",
"welcome2": "Prvo kreirajte admin korisnika",
"confirmPassword": "Potvrdi lozinku",
"buttonCreateAdmin": "Kreiraj admina",
"auth_check_error": "Prijavite se da biste nastavili",
"user_menu": "Profil",
"username": "Korisničko ime",
"password": "Lozinka",
"sign_in": "Prijava",
"sign_in_error": "Greška pri prijavi",
"logout": "Odjava",
"insightsCollectionNote": "Navidrome prikuplja anonimne statistike \nda podrži razvoj projekta. \nKliknite [ovdje] za više informacija ili da isključite \"Insights\""
},
"validation": {
"invalidChars": "Koristite samo slova i brojeve",
"passwordDoesNotMatch": "Lozinke se ne podudaraju",
"required": "Obavezno",
"minLength": "Mora imati najmanje %{min} znakova",
"maxLength": "Mora imati najviše %{max} znakova",
"minValue": "Mora biti najmanje %{min}",
"maxValue": "Mora biti %{max} ili manje",
"number": "Mora biti broj",
"email": "Mora biti validna e-mail adresa",
"oneOf": "Mora biti jedan od: %{options}",
"regex": "Mora odgovarati regularnom izrazu: %{pattern}",
"unique": "Mora biti jedinstveno",
"url": "Mora biti validan URL"
},
"action": {
"add_filter": "Dodaj filter",
"add": "Dodaj",
"back": "Nazad",
"bulk_actions": "1 odabrana stavka |||| %{smart_count} odabrane stavke",
"cancel": "Otkaži",
"clear_input_value": "Obriši unos",
"clone": "Kloniraj",
"confirm": "Potvrdi",
"create": "Kreiraj",
"delete": "Obriši",
"edit": "Uredi",
"export": "Izvezi",
"list": "Lista",
"refresh": "Osvježi",
"remove_filter": "Ukloni filter",
"remove": "Ukloni",
"save": "Sačuvaj",
"search": "Pretraži",
"show": "Prikaži",
"sort": "Sortiraj",
"undo": "Poništi",
"expand": "Proširi",
"close": "Zatvori",
"open_menu": "Otvori meni",
"close_menu": "Zatvori meni",
"unselect": "Poništi odabir",
"skip": "Preskoči",
"bulk_actions_mobile": "1 |||| %{smart_count}",
"share": "Podijeli",
"download": "Preuzmi"
},
"boolean": {
"true": "Da",
"false": "Ne"
},
"page": {
"create": "Kreiraj %{name}",
"dashboard": "Kontrolna tabla",
"edit": "%{name} #%{id}",
"error": "Nešto je pošlo po zlu",
"list": "%{name}",
"loading": "Učitavanje",
"not_found": "Nije pronađeno",
"show": "%{name} #%{id}",
"empty": "Još nema %{name}.",
"invite": "Želiš li dodati jednu?"
},
"input": {
"file": {
"upload_several": "Povuci datoteke ovdje za prijenos ili klikni za odabir.",
"upload_single": "Povuci datoteku ovdje za prijenos ili klikni za odabir."
},
"image": {
"upload_several": "Povuci slike ovdje za prijenos ili klikni za odabir.",
"upload_single": "Povuci sliku ovdje za prijenos ili klikni za odabir."
},
"references": {
"all_missing": "Povezane reference nisu pronađene.",
"many_missing": "Neke povezane reference više nisu dostupne.",
"single_missing": "Povezana referenca više nije dostupna."
},
"password": {
"toggle_visible": "Sakrij lozinku",
"toggle_hidden": "Prikaži lozinku"
}
},
"message": {
"about": "O aplikaciji",
"are_you_sure": "Jesi li siguran?",
"bulk_delete_content": "Da li zaista želiš obrisati \"%{name}\"? |||| Da li zaista želiš obrisati %{smart_count} stavki?",
"bulk_delete_title": "Obriši %{name} |||| Obriši %{smart_count} %{name} stavki",
"delete_content": "Da li zaista želiš obrisati ovaj sadržaj?",
"delete_title": "Obriši %{name} #%{id}",
"details": "Detalji",
"error": "Došlo je do greške i zahtjev nije mogao biti završen.",
"invalid_form": "Formular nije validan. Provjeri unose.",
"loading": "Stranica se učitava",
"no": "Ne",
"not_found": "Stranica nije pronađena.",
"yes": "Da",
"unsaved_changes": "Neke promjene nisu sačuvane. Želiš li ih ignorisati?"
},
"navigation": {
"no_results": "Nema rezultata",
"no_more_results": "Stranica %{page} nema sadržaja.",
"page_out_of_boundaries": "Stranica %{page} je izvan opsega",
"page_out_from_end": "Posljednja stranica",
"page_out_from_begin": "Prva stranica",
"page_range_info": "%{offsetBegin}-%{offsetEnd} od %{total}",
"page_rows_per_page": "Redova po stranici:",
"next": "Sljedeća",
"prev": "Prethodna",
"skip_nav": "Preskoči na sadržaj"
},
"notification": {
"updated": "Stavka ažurirana |||| %{smart_count} stavki ažurirano",
"created": "Stavka kreirana",
"deleted": "Stavka obrisana |||| %{smart_count} stavki obrisano",
"bad_item": "Neispravna stavka",
"item_doesnt_exist": "Stavka ne postoji",
"http_error": "Greška u komunikaciji sa serverom",
"data_provider_error": "Greška u dataProvider-u. Provjeri konzolu za detalje.",
"i18n_error": "Prijevod za odabrani jezik nije dostupan",
"canceled": "Akcija otkazana",
"logged_out": "Sesija je istekla. Ponovo se prijavi.",
"new_version": "Nova verzija dostupna! Osveži stranicu."
},
"toggleFieldsMenu": {
"columnsToDisplay": "Odaberi kolone",
"layout": "Izgled",
"grid": "Mreža",
"table": "Tabela"
}
},
"message": {
"note": "NAPOMENA",
"transcodingDisabled": "Izmjena postavki transkodiranja preko web sučelja je onemogućena iz sigurnosnih razloga. Ako želiš promijeniti opcije transkodiranja (urediti ili dodati), ponovo pokreni server sa konfiguracijskom opcijom %{config}.",
"transcodingEnabled": "Navidrome trenutno radi sa %{config}, što omogućava izvršavanje sistemskih komandi kroz postavke transkodiranja preko web sučelja. Preporučujemo da ovo onemogućiš iz sigurnosnih razloga i koristiš samo prilikom konfiguracije transkodiranja.",
"songsAddedToPlaylist": "1 pjesma dodana u playlistu |||| %{smart_count} pjesme dodane u playlistu",
"noPlaylistsAvailable": "Nema dostupnih playlisti",
"delete_user_title": "Obriši korisnika '%{name}'",
"delete_user_content": "Da li zaista želiš obrisati ovog korisnika i sve njegove podatke (uključujući playliste i postavke)?",
"notifications_blocked": "Blokirali ste obavijesti za ovu stranicu u postavkama preglednika",
"notifications_not_available": "Ovaj preglednik ne podržava desktop obavijesti",
"lastfmLinkSuccess": "Last.fm veza uspostavljena i scrobbling omogućen",
"lastfmLinkFailure": "Last.fm veza nije uspjela",
"lastfmUnlinkSuccess": "Last.fm veza uklonjena i scrobbling onemogućen",
"lastfmUnlinkFailure": "Last.fm veza nije uklonjena",
"openIn": {
"lastfm": "Prikaži na Last.fm",
"musicbrainz": "Prikaži na MusicBrainz"
},
"lastfmLink": "Pročitaj više",
"listenBrainzLinkSuccess": "ListenBrainz veza uspostavljena i scrobbling omogućen kao korisnik: %{user}",
"listenBrainzLinkFailure": "ListenBrainz veza nije uspjela: %{error}",
"listenBrainzUnlinkSuccess": "ListenBrainz veza uklonjena i scrobbling onemogućen",
"listenBrainzUnlinkFailure": "ListenBrainz veza nije uklonjena",
"downloadOriginalFormat": "Preuzmi u originalnom formatu",
"shareOriginalFormat": "Podijeli u originalnom formatu",
"shareDialogTitle": "Podijeli %{resource} '%{name}'",
"shareBatchDialogTitle": "Podijeli 1 %{resource} |||| Podijeli %{smart_count} %{resource}",
"shareSuccess": "URL kopiran u međuspremnik: %{url}",
"shareFailure": "Greška pri kopiranju URL-a %{url} u međuspremnik",
"downloadDialogTitle": "Preuzmi %{resource} '%{name}' (%{size})",
"shareCopyToClipboard": "Kopiraj u međuspremnik: Ctrl+C, Enter",
"remove_missing_title": "Ukloni nedostajuće datoteke",
"remove_missing_content": "Da li zaista želiš ukloniti odabrane nedostajuće datoteke iz baze podataka? Sve reference na datoteke (broj reprodukcija, ocjene) bit će trajno obrisane.",
"remove_all_missing_title": "Ukloni sve nedostajuće datoteke",
"remove_all_missing_content": "Da li zaista želiš ukloniti sve nedostajuće datoteke iz baze podataka? Sve reference na datoteke (broj reprodukcija, ocjene) bit će trajno obrisane.",
"noSimilarSongsFound": "Nema sličnih pjesama",
"noTopSongsFound": "Nema popularnih pjesama"
},
"menu": {
"library": "Biblioteka",
"settings": "Postavke",
"version": "Verzija",
"theme": "Tema",
"personal": {
"name": "Lično",
"options": {
"theme": "Tema",
"language": "Jezik",
"defaultView": "Zadani pregled",
"desktop_notifications": "Desktop obavijesti",
"lastfmScrobbling": "Last.fm scrobbling",
"listenBrainzScrobbling": "ListenBrainz scrobbling",
"replaygain": "ReplayGain mod",
"preAmp": "ReplayGain pojačanje (dB)",
"gain": {
"none": "Isključeno",
"album": "Koristi album gain",
"track": "Koristi pjesmu gain"
},
"lastfmNotConfigured": "Last.fm API ključ nije konfiguriran"
}
},
"albumList": "Albumi",
"about": "O aplikaciji",
"playlists": "Playliste",
"sharedPlaylists": "Dijeljene playliste",
"librarySelector": {
"allLibraries": "Sve biblioteke (%{count})",
"multipleLibraries": "%{selected} od %{total} biblioteka",
"selectLibraries": "Odaberi biblioteke",
"none": "Nijedna"
}
},
"player": {
"playListsText": "Reprodukcija reda čekanja",
"openText": "Otvori",
"closeText": "Zatvori",
"notContentText": "Nema muzike",
"clickToPlayText": "Klikni za reprodukciju",
"clickToPauseText": "Klikni za pauzu",
"nextTrackText": "Sljedeća pjesma",
"previousTrackText": "Prethodna pjesma",
"reloadText": "Ponovo učitaj",
"volumeText": "Glasnoća",
"toggleLyricText": "Prikaži/sakrij tekst",
"toggleMiniModeText": "Minimiziraj",
"destroyText": "Uništi",
"downloadText": "Preuzmi",
"removeAudioListsText": "Ukloni audio liste",
"clickToDeleteText": "Klikni za brisanje %{name}",
"emptyLyricText": "Nema teksta",
"playModeText": {
"order": "Redom",
"orderLoop": "Ponavljaj",
"singleLoop": "Ponavljaj jednu",
"shufflePlay": "Nasumična reprodukcija"
}
},
"about": {
"links": {
"homepage": "Početna stranica",
"source": "Izvorni kod",
"featureRequests": "Zahtjevi za funkcijama",
"lastInsightsCollection": "Posljednje prikupljanje \"Insights\"",
"insights": {
"disabled": "Isključeno",
"waiting": "Čekanje"
}
},
"tabs": {
"about": "O aplikaciji",
"config": "Konfiguracija"
},
"config": {
"configName": "Postavka",
"environmentVariable": "Varijabla okruženja",
"currentValue": "Vrijednost",
"configurationFile": "Konfiguracijska datoteka",
"exportToml": "Izvezi konfiguraciju (TOML)",
"exportSuccess": "Konfiguracija kopirana u međuspremnik u TOML formatu",
"exportFailed": "Greška pri kopiranju konfiguracije",
"devFlagsHeader": "Dev postavke (mogu se promijeniti)",
"devFlagsComment": "Eksperimentalne postavke koje mogu biti uklonjene ili promijenjene u budućnosti"
}
},
"activity": {
"title": "Aktivnost",
"totalScanned": "Ukupno skeniranih foldera",
"quickScan": "Brzo skeniranje",
"fullScan": "Potpuno skeniranje",
"serverUptime": "Vrijeme rada servera",
"serverDown": "ISKLJUČEN",
"scanType": "Tip",
"status": "Greška pri skeniranju",
"elapsedTime": "Proteklo vrijeme"
},
"help": {
"title": "Navidrome prečice",
"hotkeys": {
"show_help": "Prikaži ovu pomoć",
"toggle_menu": "Uključi/isključi bočnu traku",
"toggle_play": "Reprodukcija / Pauza",
"prev_song": "Prethodna pjesma",
"next_song": "Sljedeća pjesma",
"vol_up": "Glasnije",
"vol_down": "Tiše",
"toggle_love": "Dodaj u favorite",
"current_song": "Prikaži trenutnu pjesmu"
}
},
"nowPlaying": {
"title": "Trenutna reprodukcija",
"empty": "Nema reprodukcije",
"minutesAgo": "Prije %{smart_count} minute |||| Prije %{smart_count} minuta"
}
}

View File

@ -273,7 +273,7 @@
"empty": "keine fehlenden Dateien"
},
"library": {
"name": "Bibliothek ||| Bibliotheken",
"name": "Bibliothek |||| Bibliotheken",
"fields": {
"name": "Name",
"path": "Pfad",

View File

@ -63,7 +63,7 @@
"size": "Tamaño",
"originalDate": "Orixinal",
"releaseDate": "Publicado",
"releases": "Publicación ||| Publicacións",
"releases": "Publicación |||| Publicacións",
"released": "Publicado",
"recordLabel": "Editorial",
"catalogNum": "Número de catálogo",

View File

@ -232,7 +232,7 @@
"add_filter": "Aggiungi un filtro",
"add": "Aggiungi",
"back": "Indietro",
"bulk_actions": "Un elemento selezionato ||| %{smart_count} elementi selezionati",
"bulk_actions": "Un elemento selezionato |||| %{smart_count} elementi selezionati",
"cancel": "Annulla",
"clear_input_value": "Cancella",
"clone": "Duplica",

View File

@ -13,12 +13,14 @@
"album": "专辑",
"path": "文件路径",
"genre": "流派",
"libraryName": "媒体库",
"compilation": "合辑",
"year": "发行年份",
"size": "文件大小",
"updatedAt": "更新于",
"bitRate": "比特率",
"bitDepth": "比特深度",
"sampleRate": "采样率",
"channels": "声道",
"discSubtitle": "字幕",
"starred": "收藏",
@ -33,12 +35,14 @@
"participants": "其他参与人员",
"tags": "附加标签",
"mappedTags": "映射标签",
"rawTags": "原始标签"
"rawTags": "原始标签",
"missing": "缺失"
},
"actions": {
"addToQueue": "加入播放列表",
"playNow": "立即播放",
"addToPlaylist": "加入歌单",
"showInPlaylist": "定位到播放列表",
"shuffleAll": "全部随机播放",
"download": "下载",
"playNext": "下一首播放",
@ -56,6 +60,7 @@
"size": "文件大小",
"name": "名称",
"genre": "流派",
"libraryName": "媒体库",
"compilation": "合辑",
"year": "发行年份",
"date": "录制日期",
@ -72,7 +77,8 @@
"releaseType": "发行类型",
"grouping": "分组",
"media": "媒体类型",
"mood": "情绪"
"mood": "情绪",
"missing": "缺失"
},
"actions": {
"playAll": "立即播放",
@ -104,7 +110,8 @@
"playCount": "播放次数",
"rating": "评分",
"genre": "流派",
"role": "参与角色"
"role": "参与角色",
"missing": "缺失"
},
"roles": {
"albumartist": "专辑歌手",
@ -119,7 +126,13 @@
"mixer": "混音师",
"remixer": "重混师",
"djmixer": "DJ混音师",
"performer": "演奏家"
"performer": "演奏家",
"maincredit": "主要艺术家"
},
"actions": {
"topSongs": "热门歌曲",
"shuffle": "随机播放",
"radio": "电台"
}
},
"user": {
@ -136,19 +149,26 @@
"changePassword": "修改密码?",
"currentPassword": "当前密码",
"newPassword": "新密码",
"token": "令牌"
"token": "令牌",
"libraries": "媒体库"
},
"helperTexts": {
"name": "名称的更改将在下次登录时生效"
"name": "名称的更改将在下次登录时生效",
"libraries": "为该用户选择指定媒体库,留空则使用默认媒体库"
},
"notifications": {
"created": "用户已创建",
"updated": "用户已更新",
"deleted": "用户已删除"
},
"validation": {
"librariesRequired": "普通用户必须至少选择一个媒体库"
},
"message": {
"listenBrainzToken": "输入您的 ListenBrainz 用户令牌",
"clickHereForToken": "点击这里来获得你的 ListenBrainz 令牌"
"clickHereForToken": "点击这里来获得你的 ListenBrainz 令牌",
"selectAllLibraries": "选择全部媒体库",
"adminAutoLibraries": "管理员默认可访问所有媒体库"
}
},
"player": {
@ -191,12 +211,18 @@
"selectPlaylist": "选择歌单",
"addNewPlaylist": "新建 %{name}",
"export": "导出",
"saveQueue": "保存为歌单",
"makePublic": "设为公开",
"makePrivate": "设为私有"
"makePrivate": "设为私有",
"searchOrCreate": "搜索歌单,或输入名称新建…",
"pressEnterToCreate": "按 Enter 键新建歌单",
"removeFromSelection": "移除选中项"
},
"message": {
"duplicate_song": "添加重复的歌曲",
"song_exist": "部分选定的歌曲已存在歌单中,继续添加或是跳过它们?"
"song_exist": "部分选定的歌曲已存在歌单中,继续添加或是跳过它们?",
"noPlaylistsFound": "未找到歌单",
"noPlaylists": "暂无可用歌单"
}
},
"radio": {
@ -237,14 +263,68 @@
"fields": {
"path": "路径",
"size": "文件大小",
"libraryName": "媒体库",
"updatedAt": "丢失于"
},
"actions": {
"remove": "移除"
"remove": "移除",
"remove_all": "移除所有"
},
"notifications": {
"removed": "丢失文件已移除"
}
},
"library": {
"name": "媒体库",
"fields": {
"name": "名称",
"path": "路径",
"remotePath": "远程路径",
"lastScanAt": "上次扫描",
"songCount": "歌曲",
"albumCount": "专辑",
"artistCount": "艺术家",
"totalSongs": "歌曲",
"totalAlbums": "专辑",
"totalArtists": "艺术家",
"totalFolders": "目录",
"totalFiles": "文件",
"totalMissingFiles": "缺失的文件",
"totalSize": "总大小",
"totalDuration": "时长",
"defaultNewUsers": "新用户默认",
"createdAt": "创建于",
"updatedAt": "更新于"
},
"sections": {
"basic": "基本信息",
"statistics": "统计数据"
},
"actions": {
"scan": "扫描媒体库",
"manageUsers": "管理用户权限",
"viewDetails": "查看详情"
},
"notifications": {
"created": "媒体库已创建",
"updated": "媒体库已更新",
"deleted": "媒体库已删除",
"scanStarted": "开始扫描媒体库",
"scanCompleted": "媒体库扫描已完成"
},
"validation": {
"nameRequired": "媒体库名称不能为空!",
"pathRequired": "媒体库路径不能为空!",
"pathNotDirectory": "媒体库路径必须为目录!",
"pathNotFound": "媒体库路径不存在!",
"pathNotAccessible": "媒体库路径无法访问!",
"pathInvalid": "媒体库路径无效!"
},
"messages": {
"deleteConfirm": "您确定要删除此媒体库吗?此操作将删除所有关联数据及用户访问权限!",
"scanInProgress": "正在扫描...",
"noLibrariesAssigned": "该用户未分配任何媒体库!"
}
}
},
"ra": {
@ -397,11 +477,15 @@
"transcodingDisabled": "出于安全原因,从 Web 界面更改转码配置的功能已被禁用。要更改(编辑或新增)转码选项,请在启用 %{config} 选项的情况下重新启动服务器。",
"transcodingEnabled": "Navidrome 当前与 %{config} 一起使用,可以通过配置转码选项来执行任意命令,建议仅在配置转码选项时启用此功能。",
"songsAddedToPlaylist": "已添加 %{smart_count} 首歌到歌单",
"noSimilarSongsFound": "未找到相似歌曲",
"noTopSongsFound": "未找到热门歌曲",
"noPlaylistsAvailable": "没有有效的歌单",
"delete_user_title": "删除用户 %{name}",
"delete_user_content": "您确定要删除该用户及其相关数据(包括歌单和用户配置)吗?",
"remove_missing_title": "移除丢失文件",
"remove_missing_content": "您确定要将选中的丢失文件从数据库中永久移除吗?此操作将删除所有相关信息,包括播放次数和评分。",
"remove_all_missing_title": "删除所有丢失文件",
"remove_all_missing_content": "您确定要从数据库中删除所有丢失文件吗?这将永久删除对它们的所有引用,包括它们的播放次数和评分。",
"notifications_blocked": "您已在浏览器的设置中屏蔽了此网站的通知",
"notifications_not_available": "此浏览器不支持桌面通知",
"lastfmLinkSuccess": "Last.fm 已关联并启用喜好记录",
@ -428,6 +512,12 @@
},
"menu": {
"library": "曲库",
"librarySelector": {
"allLibraries": "全部媒体库 (%{count})",
"multipleLibraries": "已选 %{selected} 共 %{total} 媒体库",
"selectLibraries": "选择媒体库",
"none": "无"
},
"settings": "设置",
"version": "版本",
"theme": "主题",
@ -490,6 +580,21 @@
"disabled": "禁用",
"waiting": "等待"
}
},
"tabs": {
"about": "关于",
"config": "配置"
},
"config": {
"configName": "配置名称",
"environmentVariable": "环境变量",
"currentValue": "当前值",
"configurationFile": "配置文件",
"exportToml": "导出配置TOML",
"exportSuccess": "配置以 TOML 格式导出到剪贴板",
"exportFailed": "复制配置失败",
"devFlagsHeader": "开发标志(可能会更改/删除)",
"devFlagsComment": "这些是实验性设置,可能会在未来版本中删除"
}
},
"activity": {
@ -498,7 +603,15 @@
"quickScan": "快速扫描",
"fullScan": "完全扫描",
"serverUptime": "服务器已运行",
"serverDown": "服务器已离线"
"serverDown": "服务器已离线",
"scanType": "扫描类型",
"status": "扫描状态",
"elapsedTime": "用时"
},
"nowPlaying": {
"title": "正在播放",
"empty": "无播放内容",
"minutesAgo": "%{smart_count} 分钟前"
},
"help": {
"title": "Navidrome 快捷键",
@ -514,4 +627,4 @@
"toggle_love": "添加/移除星标"
}
}
}
}

View File

@ -7,6 +7,7 @@ import (
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/slice"
)
// buildUserResponse creates a User response object from a User model
@ -19,6 +20,7 @@ func buildUserResponse(user model.User) responses.User {
ScrobblingEnabled: true,
DownloadRole: conf.Server.EnableDownloads,
ShareRole: conf.Server.EnableSharing,
Folder: slice.Map(user.Libraries, func(lib model.Library) int32 { return int32(lib.ID) }),
}
if conf.Server.Jukebox.Enabled {
@ -28,7 +30,6 @@ func buildUserResponse(user model.User) responses.User {
return userResponse
}
// TODO This is a placeholder. The real one has to read this info from a config file or the database
func (api *Router) GetUser(r *http.Request) (*responses.Subsonic, error) {
loggedUser, ok := request.UserFrom(r.Context())
if !ok {

View File

@ -36,6 +36,12 @@ var _ = Describe("Users", func() {
conf.Server.EnableSharing = true
conf.Server.Jukebox.Enabled = false
// Set up user with libraries
testUser.Libraries = model.Libraries{
{ID: 10, Name: "Music"},
{ID: 20, Name: "Podcasts"},
}
// Create request with user in context
req := httptest.NewRequest("GET", "/rest/getUser", nil)
ctx := request.WithUser(context.Background(), testUser)
@ -57,6 +63,7 @@ var _ = Describe("Users", func() {
Expect(userResponse.User.ScrobblingEnabled).To(BeTrue())
Expect(userResponse.User.DownloadRole).To(BeTrue())
Expect(userResponse.User.ShareRole).To(BeTrue())
Expect(userResponse.User.Folder).To(ContainElements(int32(10), int32(20)))
// Verify GetUsers response structure
Expect(usersResponse.Status).To(Equal(responses.StatusOK))
@ -75,6 +82,7 @@ var _ = Describe("Users", func() {
Expect(singleUser.DownloadRole).To(Equal(userFromList.DownloadRole))
Expect(singleUser.ShareRole).To(Equal(userFromList.ShareRole))
Expect(singleUser.JukeboxRole).To(Equal(userFromList.JukeboxRole))
Expect(singleUser.Folder).To(Equal(userFromList.Folder))
})
})
@ -93,4 +101,19 @@ var _ = Describe("Users", func() {
Entry("jukebox enabled, admin-only, regular user", true, true, false, false),
Entry("jukebox enabled, admin-only, admin user", true, true, true, true),
)
Describe("Folder list population", func() {
It("should populate Folder field with user's accessible library IDs", func() {
testUser.Libraries = model.Libraries{
{ID: 1, Name: "Music"},
{ID: 2, Name: "Podcasts"},
{ID: 5, Name: "Audiobooks"},
}
response := buildUserResponse(testUser)
Expect(response.Folder).To(HaveLen(3))
Expect(response.Folder).To(ContainElements(int32(1), int32(2), int32(5)))
})
})
})

View File

@ -169,6 +169,7 @@ const PlaylistSongs = ({ playlistId, readOnly, actions, ...props }) => {
quality: isDesktop && <QualityInfo source="quality" sortable={false} />,
channels: isDesktop && <NumberField source="channels" />,
bpm: isDesktop && <NumberField source="bpm" />,
genre: <TextField source="genre" />,
rating: config.enableStarRating && (
<RatingField
source="rating"
@ -190,6 +191,7 @@ const PlaylistSongs = ({ playlistId, readOnly, actions, ...props }) => {
'playCount',
'playDate',
'albumArtist',
'genre',
'rating',
],
})