diff --git a/res/translations/en.json b/res/translations/en.json index 06ab870..4ec790c 100644 --- a/res/translations/en.json +++ b/res/translations/en.json @@ -3,12 +3,14 @@ "Add to playlist": "Add to playlist", "Add to queue": "Add to queue", "Album": "Album", + "album": "album", "Album count": "Album count", "Album filters": "Album filters", "Album gain": "Album gain", "Album info not available": "Album info not available", "Album peak": "Album peak", "Albums": "Albums", + "albums": "albums", "All": "All", "All Tracks": "All Tracks", "Alt. URL": "Alt. URL", @@ -16,9 +18,11 @@ "Artist": "Artist", "Artists": "Artists", "Artist biography not available.": "Artist biography not available.", + "Audio device": "Audio device", "Audio Drama": "Audio Drama", "Audiobook": "Audiobook", "Authentication failed": "Authentication failed", + "Auto": "Auto", "Autoselect device": "Autoselect device", "Bit rate": "Bit rate", "BPM": "BPM", @@ -46,6 +50,7 @@ "Disable server transcoding": "Disable server transcoding", "Disc number": "Disc number", "Discography": "Discography", + "discs": "discs", "DJ-Mix": "DJ-Mix", "Download": "Download", "Download completed": "Download completed", @@ -58,6 +63,7 @@ "Enter": "Enter", "EP": "EP", "Equalizer": "Equalizer", + "Exclusive mode": "Exclusive mode", "Favorites": "Favorites", "Field Recording": "Field Recording", "File path": "File path", @@ -66,6 +72,7 @@ "General": "General", "Genre": "Genre", "Genres": "Genres", + "Github page": "Github page", "Home Page": "Home Page", "hr": "hr", "hrs": "hrs", @@ -75,10 +82,12 @@ "Is not favorite": "Is not favorite", "Last played": "Last played", "Live": "Live", + "Locally": "Locally", "Login to Server": "Login to Server", "Lyrics": "Lyrics", "Lyrics not available": "Lyrics not available", "min": "min", + "minutes of track have been played": "minutes of track have been played", "Mixtape": "Mixtape", "My Server": "My Server", "Name": "Name", @@ -89,24 +98,38 @@ "(none)": "(none)", "none selected": "none selected", "OK": "OK", - "(optional)": "(optional)", + "optional": "optional", + "or when": "or when", "Password": "Password", + "Paused": "Paused", + "percent of track is played": "percent of track is played", "Play": "Play", + "Play Artist Radio": "Play Artist Radio", "Play count": "Play count", + "Play Discography": "Play Discography", "Play next": "Play next", "Play Queue": "Play Queue", "Play random": "Play random", + "Playback": "Playback", + "Playing": "Playing", "Playlist": "Playlist", "Playlists": "Playlists", "playlist by": "playlist by", + "Plays": "Plays", + "Prevent clipping": "Prevent clipping", "Private": "Private", "Public": "Public", + "Rating": "Rating", + "reissued": "reissued", "Related": "Related", "Remix": "Remix", "Remove from playlist": "Remove from playlist", + "ReplayGain mode": "ReplayGain mode", + "ReplayGain preamp": "ReplayGain preamp", "Restart required": "Restart required", "Save play queue on exit": "Save play queue on exit", "Saved at": "Saved at", + "Scrobble when": "Scrobble when", "Search Everywhere": "Search Everywhere", "Search page": "Search page", "Search playlists or new playlist name": "Search playlists or new playlist name", @@ -123,16 +146,21 @@ "Show notification on track change": "Show notification on track change", "Show year in album grid cards": "Show year in album grid cards", "Shuffle": "Shuffle", + "Shuffle albums": "Shuffle albums", + "Shuffle tracks": "Shuffle tracks", "Similar artists": "Similar artists", "Single": "Single", "Skip duplicate tracks": "Skip duplicate tracks", "Soundtrack": "Soundtrack", "Spoken Word": "Spoken Word", "Startup page": "Startup page", + "Stopped": "Stopped", + "Support the project": "Support the project", "Testing connection": "Testing connection", "Theme": "Theme", "Title": "Title", "Top Tracks": "Top Tracks", + "Total time": "Total time", "Track": "Track", "track": "track", "Track count": "Track count", @@ -141,9 +169,11 @@ "Track number": "Track number", "Track peak": "Track peak", "tracks": "tracks", + "UI Scaling": "UI Scaling", "URL": "URL", "Use legacy authentication": "Use legacy authentication", "Username": "Username", + "version": "version", "wrong URL": "wrong URL", "wrong username/password": "wrong username/password", "Year": "Year", diff --git a/ui/browsing/albumpage.go b/ui/browsing/albumpage.go index c243e26..01fcc69 100644 --- a/ui/browsing/albumpage.go +++ b/ui/browsing/albumpage.go @@ -212,7 +212,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader { SizeName: theme.SizeNameHeadingText, } a.releaseTypeLabel = widget.NewRichText( - &widget.TextSegment{Text: "Album", Style: util.BoldRichTextStyle}, + &widget.TextSegment{Text: lang.L("Album"), Style: util.BoldRichTextStyle}, &widget.TextSegment{Text: " by", Style: widget.RichTextStyle{Inline: true}}, ) a.artistLabel = widgets.NewMultiHyperlink() @@ -361,16 +361,16 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string { var discs string if len(a.Tracks) > 0 { if discCount := a.Tracks[len(a.Tracks)-1].DiscNumber; discCount > 1 { - discs = fmt.Sprintf("%d discs · ", discCount) + discs = fmt.Sprintf("%d %s · ", discCount, lang.L("discs")) } } - tracks := "tracks" + tracks := lang.L("tracks") if a.TrackCount == 1 { - tracks = "track" + tracks = lang.L("track") } yearStr := strconv.Itoa(a.Year) if a.ReissueYear > a.Year { - yearStr += fmt.Sprintf(" (reissued %d)", a.ReissueYear) + yearStr += fmt.Sprintf(" (%s %d)", lang.L("reissued"), a.ReissueYear) } return fmt.Sprintf("%s · %d %s · %s%s", yearStr, a.TrackCount, tracks, discs, util.SecondsToTimeString(float64(a.Duration))) } diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 5e3cfe6..307a996 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -4,6 +4,7 @@ import ( "slices" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" @@ -27,7 +28,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr * return NewGridViewPage(adapter, pool, mp, im) } -func (a *albumsPageAdapter) Title() string { return "Albums" } +func (a *albumsPageAdapter) Title() string { return lang.L("Albums") } func (a *albumsPageAdapter) Filter() mediaprovider.AlbumFilter { if a.filter == nil { diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index d13ee17..1d409c6 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -329,10 +329,10 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader { } } a.favoriteBtn = widgets.NewFavoriteButton(func() { go a.toggleFavorited() }) - a.playBtn = widget.NewButtonWithIcon("Play Discography", theme.MediaPlayIcon(), func() { + a.playBtn = widget.NewButtonWithIcon(lang.L("Play Discography"), theme.MediaPlayIcon(), func() { go a.artistPage.pm.PlayArtistDiscography(a.artistID, false /*shuffle*/) }) - a.playRadioBtn = widget.NewButtonWithIcon("Play Artist Radio", myTheme.ShuffleIcon, func() { + a.playRadioBtn = widget.NewButtonWithIcon(lang.L("Play Artist Radio"), myTheme.ShuffleIcon, func() { // must not pass playArtistRadio func directly to NewButton // because the artistPage bound to this header can change when reused a.artistPage.playArtistRadio() @@ -342,11 +342,11 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader { a.menuBtn = widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil) a.menuBtn.OnTapped = func() { if pop == nil { - shuffleTracks := fyne.NewMenuItem("Shuffle tracks", func() { + shuffleTracks := fyne.NewMenuItem(lang.L("Shuffle tracks"), func() { go a.artistPage.pm.PlayArtistDiscography(a.artistID, true /*shuffle*/) }) shuffleTracks.Icon = myTheme.TracksIcon - shuffleAlbums := fyne.NewMenuItem("Shuffle albums", func() { + shuffleAlbums := fyne.NewMenuItem(lang.L("Shuffle albums"), func() { go a.artistPage.pm.ShuffleArtistAlbums(a.artistID) }) shuffleAlbums.Icon = myTheme.AlbumIcon @@ -416,7 +416,7 @@ func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) { } if len(a.similarArtists.Objects) == 0 { - a.similarArtists.Add(widget.NewLabel("Similar Artists:")) + a.similarArtists.Add(widget.NewLabel(lang.L("Similar artists") + ":")) } for _, obj := range a.similarArtists.Objects { obj.Hide() diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 3599cd2..50ed0dd 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -365,9 +365,9 @@ func (a *FavoritesPage) onShowFavoriteArtists() { func buildArtistGridViewModel(artists []*mediaprovider.Artist) []widgets.GridViewItemModel { model := make([]widgets.GridViewItemModel, 0) for _, ar := range artists { - albums := "albums" + albums := lang.L("albums") if ar.AlbumCount == 1 { - albums = "album" + albums = lang.L("album") } model = append(model, widgets.GridViewItemModel{ ID: ar.ID, diff --git a/ui/browsing/gridviewpage.go b/ui/browsing/gridviewpage.go index 99a31c2..0c6f3f8 100644 --- a/ui/browsing/gridviewpage.go +++ b/ui/browsing/gridviewpage.go @@ -9,6 +9,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -149,7 +150,7 @@ func (g *GridViewPage[M, F]) createTitleAndSort() { func (g *GridViewPage[M, F]) createSearchAndFilter() { g.searcher = widgets.NewSearchEntry() - g.searcher.PlaceHolder = "Search page" + g.searcher.PlaceHolder = lang.L("Search page") g.searcher.Text = g.searchText g.searcher.OnSearched = g.OnSearched g.filterBtn = g.adapter.FilterButton() diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 576a6b9..252ff09 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -182,7 +182,7 @@ func NewNowPlayingPage( a.pm.LoadItems(items, backend.InsertNext, false) } a.lyricsViewer = widgets.NewLyricsViewer() - a.statusLabel = widget.NewLabel("Stopped") + a.statusLabel = widget.NewLabel(lang.L("Stopped")) a.Reload() return a @@ -538,12 +538,13 @@ func (a *NowPlayingPage) formatStatusLine() { curPlayer := a.pm.CurrentPlayer() playerStats := curPlayer.GetStatus() lastStatus := a.statusLabel.Text - state := "Stopped" + stopped := lang.L("Stopped") + state := stopped switch playerStats.State { case player.Paused: - state = "Paused" + state = lang.L("Paused") case player.Playing: - state = "Playing" + state = lang.L("Playing") } dur := 0.0 @@ -552,7 +553,7 @@ func (a *NowPlayingPage) formatStatusLine() { } statusSuffix := "" trackNum := 0 - if state != "Stopped" { + if state != stopped { trackNum = a.pm.NowPlayingIndex() + 1 statusSuffix = fmt.Sprintf(" %s/%s", util.SecondsToMMSS(playerStats.TimePos), @@ -562,14 +563,14 @@ func (a *NowPlayingPage) formatStatusLine() { len(a.queue), statusSuffix) mediaInfo := "" - if state != "Stopped" { + if state != stopped { mediaInfo = a.formatMediaInfoStr(curPlayer) } if mediaInfo != "" { mediaInfo = " · " + mediaInfo } - a.statusLabel.Text = fmt.Sprintf("%s%s | Total time: %s", status, mediaInfo, util.SecondsToTimeString(a.totalTime)) + a.statusLabel.Text = fmt.Sprintf("%s%s | %s: %s", status, mediaInfo, lang.L("Total time"), util.SecondsToTimeString(a.totalTime)) if lastStatus != a.statusLabel.Text { a.statusLabel.Refresh() } diff --git a/ui/dialogs/aboutdialog.go b/ui/dialogs/aboutdialog.go index d79b393..c0fc433 100644 --- a/ui/dialogs/aboutdialog.go +++ b/ui/dialogs/aboutdialog.go @@ -9,6 +9,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -37,7 +38,7 @@ func NewAboutDialog(version string) *AboutDialog { widget.NewSeparator(), container.NewHBox( layout.NewSpacer(), - widget.NewButton("Close", func() { + widget.NewButton(lang.L("Close"), func() { if a.OnDismiss != nil { a.OnDismiss() } @@ -59,7 +60,7 @@ func (a *AboutDialog) buildMainTabContainer(version string) *fyne.Container { title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true title.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameSubHeadingText title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter - versionLbl := newCenterAlignLabel(fmt.Sprintf("version %s", version)) + versionLbl := newCenterAlignLabel(fmt.Sprintf("%s %s", lang.L("version"), version)) copyright := newCenterAlignLabel(res.Copyright) license := widget.NewRichTextWithText("GNU General Public License version 3 (GPL v3)") ts := license.Segments[0].(*widget.TextSegment) @@ -69,9 +70,9 @@ func (a *AboutDialog) buildMainTabContainer(version string) *fyne.Container { kofiUrl, _ := url.Parse(res.KofiURL) githubKofi := container.NewCenter( container.New(layout.NewCustomPaddedHBoxLayout(-10), - widget.NewHyperlink("Github page", ghUrl), + widget.NewHyperlink(lang.L("Github page"), ghUrl), widget.NewLabel("·"), - widget.NewHyperlink("Support the project", kofiUrl)), + widget.NewHyperlink(lang.L("Support the project"), kofiUrl)), ) return container.New(&layout.CustomPaddedLayout{TopPadding: 10, BottomPadding: 10}, diff --git a/ui/dialogs/addeditserverdialog.go b/ui/dialogs/addeditserverdialog.go index b48b5f2..4f0a0d5 100644 --- a/ui/dialogs/addeditserverdialog.go +++ b/ui/dialogs/addeditserverdialog.go @@ -1,6 +1,8 @@ package dialogs import ( + "fmt" + "github.com/dweymouth/supersonic/backend" "fyne.io/fyne/v2" @@ -68,7 +70,7 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen userField := widget.NewEntryWithData(binding.BindString(&a.Username)) userField.OnSubmitted = func(_ string) { focusHandler(a.passField) } altHostField := widget.NewEntryWithData(binding.BindString(&a.AltHost)) - altHostField.SetPlaceHolder(lang.L("(optional)") + " https://my-external-domain.net/music") + altHostField.SetPlaceHolder(fmt.Sprintf("(%s)", lang.L("optional")) + " https://my-external-domain.net/music") altHostField.OnSubmitted = func(_ string) { focusHandler(userField) } hostField := widget.NewEntryWithData(binding.BindString(&a.Host)) hostField.SetPlaceHolder("http://localhost:4533") diff --git a/ui/dialogs/searchdialog.go b/ui/dialogs/searchdialog.go index 849c660..117cac1 100644 --- a/ui/dialogs/searchdialog.go +++ b/ui/dialogs/searchdialog.go @@ -8,6 +8,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/lang" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" @@ -270,11 +271,11 @@ func (s *searchResult) Update(result *mediaprovider.SearchResult) { s.imageLoader.Load(result.CoverID) s.title.SetText(result.Name) - maybePluralize := func(s string, size int) string { + maybePluralize := func(key string, size int) string { if size != 1 { - return s + "s" + return lang.L(key + "s") } - return s + return lang.L(key) } var secondaryText string switch result.Type { diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index 88d3880..6624fa6 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -160,16 +160,18 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container. systemTrayEnable.Checked = s.config.Application.EnableSystemTray // save play queue settings - saveToServer := widget.NewRadioGroup([]string{"Locally", "To server"}, func(choice string) { - s.config.Application.SaveQueueToServer = choice == "To server" + locally := lang.L("Locally") + toServer := lang.L("To server") + saveToServer := widget.NewRadioGroup([]string{locally, toServer}, func(choice string) { + s.config.Application.SaveQueueToServer = choice == toServer }) saveToServer.Horizontal = true if !s.config.Application.SavePlayQueue { saveToServer.Disable() } - saveToServer.Selected = "Locally" + saveToServer.Selected = locally if s.config.Application.SaveQueueToServer { - saveToServer.Selected = "To server" + saveToServer.Selected = toServer } saveQueue := widget.NewCheck(lang.L("Save play queue on exit"), func(save bool) { s.config.Application.SavePlayQueue = save @@ -233,7 +235,7 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container. if lastScrobbleText == "" { lastScrobbleText = "4" // default scrobble minutes } - durationEnabled := widget.NewCheck("or when", func(checked bool) { + durationEnabled := widget.NewCheck(lang.L("or when"), func(checked bool) { if !checked { s.config.Scrobbling.ThresholdTimeSeconds = -1 lastScrobbleText = durationEntry.Text @@ -292,14 +294,14 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container. widget.NewRichText(&widget.TextSegment{Text: "Scrobbling", Style: util.BoldRichTextStyle}), scrobbleEnabled, container.NewHBox( - widget.NewLabel("Scrobble when"), + widget.NewLabel(lang.L("Scrobble when")), percentEntry, - widget.NewLabel("percent of track is played"), + widget.NewLabel(lang.L("percent of track is played")), ), container.NewHBox( durationEnabled, durationEntry, - widget.NewLabel("minutes of track have been played"), + widget.NewLabel(lang.L("minutes of track have been played")), ), )) } @@ -324,7 +326,7 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo } } - replayGainSelect := widget.NewSelect([]string{"None", "Album", "Track", "Auto"}, nil) + replayGainSelect := widget.NewSelect([]string{lang.L("None"), lang.L("Album"), lang.L("Track"), lang.L("Auto")}, nil) replayGainSelect.OnChanged = func(_ string) { switch replayGainSelect.SelectedIndex() { case 0: @@ -377,7 +379,7 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo }) preventClipping.Checked = s.config.ReplayGain.PreventClipping - audioExclusive := widget.NewCheck("Audio exclusive mode", func(checked bool) { + audioExclusive := widget.NewCheck(lang.L("Exclusive mode"), func(checked bool) { s.config.LocalPlayback.AudioExclusive = checked s.onAudioExclusiveSettingsChanged() }) @@ -393,20 +395,20 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo preampGain.Disable() } - return container.NewTabItem("Playback", container.NewVBox( + return container.NewTabItem(lang.L("Playback"), container.NewVBox( disableTranscode, container.New(&layout.CustomPaddedLayout{TopPadding: 5}, container.New(layout.NewFormLayout(), - widget.NewLabel("Audio device"), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect), + widget.NewLabel(lang.L("Audio device")), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect), layout.NewSpacer(), audioExclusive, )), s.newSectionSeparator(), widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}), container.New(layout.NewFormLayout(), - widget.NewLabel("ReplayGain mode"), container.NewGridWithColumns(2, replayGainSelect), - widget.NewLabel("ReplayGain preamp"), container.NewHBox(preampGain, widget.NewLabel("dB")), - widget.NewLabel("Prevent clipping"), preventClipping, + widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect), + widget.NewLabel(lang.L("ReplayGain preamp")), container.NewHBox(preampGain, widget.NewLabel("dB")), + widget.NewLabel(lang.L("Prevent clipping")), preventClipping, ), )) } @@ -487,7 +489,7 @@ func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.Ta return container.NewTabItem("Experimental", container.NewVBox( warningLabel, s.newSectionSeparator(), - widget.NewRichText(&widget.TextSegment{Text: "UI Scaling", Style: util.BoldRichTextStyle}), + widget.NewRichText(&widget.TextSegment{Text: lang.L("UI Scaling"), Style: util.BoldRichTextStyle}), uiScaleRadio, s.newSectionSeparator(), widget.NewRichText(&widget.TextSegment{Text: "Application Font", Style: util.BoldRichTextStyle}), diff --git a/ui/widgets/tracklistrow.go b/ui/widgets/tracklistrow.go index 1334af3..9e9920a 100644 --- a/ui/widgets/tracklistrow.go +++ b/ui/widgets/tracklistrow.go @@ -63,9 +63,9 @@ var ( rating := lang.L("Rating") plays := lang.L("Plays") comment := lang.L("Comment") - bitrate := lang.L("Bitrate") + bitrate := lang.L("Bit rate") size := lang.L("Size") - filepath := lang.L("File Path") + filepath := lang.L("File path") CompactTracklistRowColumns = []TracklistColumn{ {Name: ColumnNum, Col: ListColumn{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},