mirror of
https://git.sr.ht/~phw/scotty
synced 2025-06-06 21:08:34 +02:00
Compare commits
No commits in common. "1f48abc2842e30861c5209271626866c1441da65" and "8885e9cebcd90f5b3011af63bbe38d9c470a01b0" have entirely different histories.
1f48abc284
...
8885e9cebc
21 changed files with 435 additions and 340 deletions
15
CHANGES.md
15
CHANGES.md
|
@ -1,16 +1,7 @@
|
||||||
# Scotty Changelog
|
# Scotty Changelog
|
||||||
|
|
||||||
## 0.6.0 - WIP
|
## 0.6.0 - WIP
|
||||||
- Fix program hanging endlessly if import fails (#11)
|
- JSPF: Implemented export as loves and listens
|
||||||
- If import fails still store the last successfully imported timestamp
|
|
||||||
- Show progress bars as aborted on export / import error
|
|
||||||
- JSPF: implemented export as loves and listens
|
|
||||||
- JSPF: write track duration
|
|
||||||
- JSPF: read username and recording MSID
|
|
||||||
- JSPF: add MusicBrainz playlist extension in append mode, if it does not exist
|
|
||||||
in the existing JSPF file
|
|
||||||
- scrobblerlog: fix timezone not being set from config (#6)
|
|
||||||
- scrobblerlog: fix listen export not considering latest timestamp
|
|
||||||
|
|
||||||
|
|
||||||
## 0.5.2 - 2025-05-01
|
## 0.5.2 - 2025-05-01
|
||||||
|
@ -29,9 +20,9 @@
|
||||||
- ListenBrainz: log missing recording MBID on love import
|
- ListenBrainz: log missing recording MBID on love import
|
||||||
- Subsonic: support OpenSubsonic fields for recording MBID and genres (#5)
|
- Subsonic: support OpenSubsonic fields for recording MBID and genres (#5)
|
||||||
- Subsonic: fixed progress for loves export
|
- Subsonic: fixed progress for loves export
|
||||||
- scrobblerlog: add "time-zone" config option (#6)
|
- scrobblerlog: add "time-zone" config option (#6).
|
||||||
- scrobblerlog: fixed progress for listen export
|
- scrobblerlog: fixed progress for listen export
|
||||||
- scrobblerlog: renamed setting `include-skipped` to `ignore-skipped`
|
- scrobblerlog: renamed setting `include-skipped` to `ignore-skipped`.
|
||||||
|
|
||||||
Note: 386 builds for Linux are not available with this release due to an
|
Note: 386 builds for Linux are not available with this release due to an
|
||||||
incompatibility with latest version of gorm.
|
incompatibility with latest version of gorm.
|
||||||
|
|
|
@ -88,13 +88,15 @@ func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan
|
||||||
|
|
||||||
totalDuration := startTime.Sub(oldestTimestamp)
|
totalDuration := startTime.Sub(oldestTimestamp)
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
p := models.Progress{Total: int64(totalDuration.Seconds())}
|
p := models.Progress{Total: int64(totalDuration.Seconds())}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
for {
|
for {
|
||||||
result, err := b.client.UserHistory(offset, perPage)
|
result, err := b.client.UserHistory(offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -153,6 +155,8 @@ func (b *DeezerApiBackend) ExportLoves(oldestTimestamp time.Time, results chan m
|
||||||
offset := math.MaxInt32
|
offset := math.MaxInt32
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
p := models.Progress{Total: int64(perPage)}
|
p := models.Progress{Total: int64(perPage)}
|
||||||
var totalCount int
|
var totalCount int
|
||||||
|
|
||||||
|
@ -160,7 +164,7 @@ out:
|
||||||
for {
|
for {
|
||||||
result, err := b.client.UserTracks(offset, perPage)
|
result, err := b.client.UserTracks(offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||||
|
|
||||||
Scotty is free software: you can redistribute it and/or modify it under the
|
Scotty is free software: you can redistribute it and/or modify it under the
|
||||||
terms of the GNU General Public License as published by the Free Software
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
@ -35,9 +35,8 @@ func (p ListensExportProcessor) ExportBackend() models.Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ListensExportProcessor) Process(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
func (p ListensExportProcessor) Process(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
||||||
defer close(results)
|
|
||||||
defer close(progress)
|
|
||||||
p.Backend.ExportListens(oldestTimestamp, results, progress)
|
p.Backend.ExportListens(oldestTimestamp, results, progress)
|
||||||
|
close(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LovesExportProcessor struct {
|
type LovesExportProcessor struct {
|
||||||
|
@ -49,7 +48,6 @@ func (p LovesExportProcessor) ExportBackend() models.Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p LovesExportProcessor) Process(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
func (p LovesExportProcessor) Process(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
||||||
defer close(results)
|
|
||||||
defer close(progress)
|
|
||||||
p.Backend.ExportLoves(oldestTimestamp, results, progress)
|
p.Backend.ExportLoves(oldestTimestamp, results, progress)
|
||||||
|
close(progress)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,8 @@ func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results c
|
||||||
page := 1
|
page := 1
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
// We need to gather the full list of listens in order to sort them
|
// We need to gather the full list of listens in order to sort them
|
||||||
listens := make(models.ListensList, 0, 2*perPage)
|
listens := make(models.ListensList, 0, 2*perPage)
|
||||||
p := models.Progress{Total: int64(perPage)}
|
p := models.Progress{Total: int64(perPage)}
|
||||||
|
@ -111,6 +113,8 @@ func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results cha
|
||||||
page := 1
|
page := 1
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
// We need to gather the full list of listens in order to sort them
|
// We need to gather the full list of listens in order to sort them
|
||||||
loves := make(models.LovesList, 0, 2*perPage)
|
loves := make(models.LovesList, 0, 2*perPage)
|
||||||
p := models.Progress{Total: int64(perPage)}
|
p := models.Progress{Total: int64(perPage)}
|
||||||
|
@ -119,7 +123,7 @@ out:
|
||||||
for {
|
for {
|
||||||
result, err := b.client.GetFavoriteTracks(page, perPage)
|
result, err := b.client.GetFavoriteTracks(page, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||||
|
|
||||||
This file is part of Scotty.
|
This file is part of Scotty.
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func (p ListensImportProcessor) Process(results chan models.ListensResult, out c
|
||||||
|
|
||||||
func (p ListensImportProcessor) Import(export models.ListensResult, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
func (p ListensImportProcessor) Import(export models.ListensResult, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
||||||
if export.Error != nil {
|
if export.Error != nil {
|
||||||
return result, export.Error
|
return handleError(result, export.Error, progress), export.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
if export.Total > 0 {
|
if export.Total > 0 {
|
||||||
|
@ -51,7 +51,7 @@ func (p ListensImportProcessor) Import(export models.ListensResult, result model
|
||||||
}
|
}
|
||||||
importResult, err := p.Backend.ImportListens(export, result, progress)
|
importResult, err := p.Backend.ImportListens(export, result, progress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return importResult, err
|
return handleError(result, err, progress), err
|
||||||
}
|
}
|
||||||
return importResult, nil
|
return importResult, nil
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func (p LovesImportProcessor) Process(results chan models.LovesResult, out chan
|
||||||
|
|
||||||
func (p LovesImportProcessor) Import(export models.LovesResult, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
func (p LovesImportProcessor) Import(export models.LovesResult, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
||||||
if export.Error != nil {
|
if export.Error != nil {
|
||||||
return result, export.Error
|
return handleError(result, export.Error, progress), export.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
if export.Total > 0 {
|
if export.Total > 0 {
|
||||||
|
@ -80,7 +80,7 @@ func (p LovesImportProcessor) Import(export models.LovesResult, result models.Im
|
||||||
}
|
}
|
||||||
importResult, err := p.Backend.ImportLoves(export, result, progress)
|
importResult, err := p.Backend.ImportLoves(export, result, progress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return importResult, err
|
return handleError(importResult, err, progress), err
|
||||||
}
|
}
|
||||||
return importResult, nil
|
return importResult, nil
|
||||||
}
|
}
|
||||||
|
@ -89,35 +89,35 @@ func process[R models.LovesResult | models.ListensResult, P ImportProcessor[R]](
|
||||||
defer close(out)
|
defer close(out)
|
||||||
defer close(progress)
|
defer close(progress)
|
||||||
result := models.ImportResult{}
|
result := models.ImportResult{}
|
||||||
p := models.Progress{}
|
|
||||||
|
|
||||||
if err := processor.ImportBackend().StartImport(); err != nil {
|
err := processor.ImportBackend().StartImport()
|
||||||
|
if err != nil {
|
||||||
out <- handleError(result, err, progress)
|
out <- handleError(result, err, progress)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for exportResult := range results {
|
for exportResult := range results {
|
||||||
importResult, err := processor.Import(exportResult, result, out, progress)
|
importResult, err := processor.Import(exportResult, result, out, progress)
|
||||||
result.Update(importResult)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processor.ImportBackend().FinishImport()
|
|
||||||
out <- handleError(result, err, progress)
|
out <- handleError(result, err, progress)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
progress <- p.FromImportResult(result)
|
result.Update(importResult)
|
||||||
|
progress <- models.Progress{}.FromImportResult(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := processor.ImportBackend().FinishImport(); err != nil {
|
err = processor.ImportBackend().FinishImport()
|
||||||
|
if err != nil {
|
||||||
out <- handleError(result, err, progress)
|
out <- handleError(result, err, progress)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
progress <- p.FromImportResult(result).Complete()
|
progress <- models.Progress{}.FromImportResult(result).Complete()
|
||||||
out <- result
|
out <- result
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleError(result models.ImportResult, err error, progress chan models.Progress) models.ImportResult {
|
func handleError(result models.ImportResult, err error, progress chan models.Progress) models.ImportResult {
|
||||||
result.Error = err
|
result.Error = err
|
||||||
progress <- models.Progress{}.FromImportResult(result).Abort()
|
progress <- models.Progress{}.FromImportResult(result).Complete()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,11 +77,14 @@ func (b *JSPFBackend) InitConfig(config *config.ServiceConfig) error {
|
||||||
Title: config.GetString("title"),
|
Title: config.GetString("title"),
|
||||||
Creator: config.GetString("username"),
|
Creator: config.GetString("username"),
|
||||||
Identifier: config.GetString("identifier"),
|
Identifier: config.GetString("identifier"),
|
||||||
Date: time.Now(),
|
|
||||||
Tracks: make([]jspf.Track, 0),
|
Tracks: make([]jspf.Track, 0),
|
||||||
|
Extension: jspf.ExtensionMap{
|
||||||
|
jspf.MusicBrainzPlaylistExtensionID: jspf.MusicBrainzPlaylistExtension{
|
||||||
|
LastModifiedAt: time.Now(),
|
||||||
|
Public: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b.addMusicBrainzPlaylistExtension()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +97,11 @@ func (b *JSPFBackend) FinishImport() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *JSPFBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
func (b *JSPFBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
err := b.readJSPF()
|
err := b.readJSPF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -126,9 +131,11 @@ func (b *JSPFBackend) ImportListens(export models.ListensResult, importResult mo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *JSPFBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
func (b *JSPFBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
err := b.readJSPF()
|
err := b.readJSPF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -324,7 +331,6 @@ func (b *JSPFBackend) readJSPF() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.playlist = playlist.Playlist
|
b.playlist = playlist.Playlist
|
||||||
b.addMusicBrainzPlaylistExtension()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,13 +350,3 @@ func (b *JSPFBackend) writeJSPF() error {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
return playlist.Write(file)
|
return playlist.Write(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *JSPFBackend) addMusicBrainzPlaylistExtension() {
|
|
||||||
if b.playlist.Extension == nil {
|
|
||||||
b.playlist.Extension = make(jspf.ExtensionMap, 1)
|
|
||||||
}
|
|
||||||
extension := jspf.MusicBrainzPlaylistExtension{Public: true}
|
|
||||||
b.playlist.Extension.Get(jspf.MusicBrainzPlaylistExtensionID, &extension)
|
|
||||||
extension.LastModifiedAt = time.Now()
|
|
||||||
b.playlist.Extension[jspf.MusicBrainzPlaylistExtensionID] = extension
|
|
||||||
}
|
|
||||||
|
|
|
@ -93,6 +93,8 @@ func (b *LastfmApiBackend) ExportListens(oldestTimestamp time.Time, results chan
|
||||||
minTime := oldestTimestamp
|
minTime := oldestTimestamp
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
// We need to gather the full list of listens in order to sort them
|
// We need to gather the full list of listens in order to sort them
|
||||||
p := models.Progress{Total: int64(page)}
|
p := models.Progress{Total: int64(page)}
|
||||||
|
|
||||||
|
@ -108,7 +110,7 @@ out:
|
||||||
result, err := b.client.User.GetRecentTracks(args)
|
result, err := b.client.User.GetRecentTracks(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +129,7 @@ out:
|
||||||
timestamp, err := strconv.ParseInt(scrobble.Date.Uts, 10, 64)
|
timestamp, err := strconv.ParseInt(scrobble.Date.Uts, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
if timestamp > oldestTimestamp.Unix() {
|
if timestamp > oldestTimestamp.Unix() {
|
||||||
|
@ -256,6 +258,8 @@ func (b *LastfmApiBackend) ExportLoves(oldestTimestamp time.Time, results chan m
|
||||||
page := 1
|
page := 1
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
loves := make(models.LovesList, 0, 2*MaxItemsPerGet)
|
loves := make(models.LovesList, 0, 2*MaxItemsPerGet)
|
||||||
p := models.Progress{Total: int64(perPage)}
|
p := models.Progress{Total: int64(perPage)}
|
||||||
var totalCount int
|
var totalCount int
|
||||||
|
@ -268,7 +272,7 @@ out:
|
||||||
"page": page,
|
"page": page,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -282,7 +286,7 @@ out:
|
||||||
for _, track := range result.Tracks {
|
for _, track := range result.Tracks {
|
||||||
timestamp, err := strconv.ParseInt(track.Date.Uts, 10, 64)
|
timestamp, err := strconv.ParseInt(track.Date.Uts, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,12 +81,14 @@ func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, result
|
||||||
|
|
||||||
totalDuration := startTime.Sub(minTime)
|
totalDuration := startTime.Sub(minTime)
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
p := models.Progress{Total: int64(totalDuration.Seconds())}
|
p := models.Progress{Total: int64(totalDuration.Seconds())}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
result, err := b.client.GetListens(b.username, time.Now(), minTime)
|
result, err := b.client.GetListens(b.username, time.Now(), minTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -193,15 +195,15 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
||||||
|
defer close(results)
|
||||||
exportChan := make(chan models.LovesResult)
|
exportChan := make(chan models.LovesResult)
|
||||||
p := models.Progress{}
|
p := models.Progress{}
|
||||||
|
|
||||||
go b.exportLoves(oldestTimestamp, exportChan)
|
go b.exportLoves(oldestTimestamp, exportChan)
|
||||||
for existingLoves := range exportChan {
|
for existingLoves := range exportChan {
|
||||||
if existingLoves.Error != nil {
|
if existingLoves.Error != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.LovesResult{Error: existingLoves.Error}
|
results <- models.LovesResult{Error: existingLoves.Error}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Total = int64(existingLoves.Total)
|
p.Total = int64(existingLoves.Total)
|
||||||
|
|
|
@ -67,6 +67,8 @@ func (b *MalojaApiBackend) ExportListens(oldestTimestamp time.Time, results chan
|
||||||
page := 0
|
page := 0
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
// We need to gather the full list of listens in order to sort them
|
// We need to gather the full list of listens in order to sort them
|
||||||
listens := make(models.ListensList, 0, 2*perPage)
|
listens := make(models.ListensList, 0, 2*perPage)
|
||||||
p := models.Progress{Total: int64(perPage)}
|
p := models.Progress{Total: int64(perPage)}
|
||||||
|
@ -105,7 +107,6 @@ out:
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
||||||
p := models.Progress{}.FromImportResult(importResult)
|
|
||||||
for _, listen := range export.Items {
|
for _, listen := range export.Items {
|
||||||
scrobble := NewScrobble{
|
scrobble := NewScrobble{
|
||||||
Title: listen.TrackName,
|
Title: listen.TrackName,
|
||||||
|
@ -126,7 +127,7 @@ func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResu
|
||||||
|
|
||||||
importResult.UpdateTimestamp(listen.ListenedAt)
|
importResult.UpdateTimestamp(listen.ListenedAt)
|
||||||
importResult.ImportCount += 1
|
importResult.ImportCount += 1
|
||||||
progress <- p.FromImportResult(importResult)
|
progress <- models.Progress{}.FromImportResult(importResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
return importResult, nil
|
return importResult, nil
|
||||||
|
|
|
@ -132,9 +132,10 @@ func (b *ScrobblerLogBackend) FinishImport() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
||||||
|
defer close(results)
|
||||||
file, err := os.Open(b.filePath)
|
file, err := os.Open(b.filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,12 +101,14 @@ func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results cha
|
||||||
|
|
||||||
totalDuration := startTime.Sub(oldestTimestamp)
|
totalDuration := startTime.Sub(oldestTimestamp)
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
p := models.Progress{Total: int64(totalDuration.Seconds())}
|
p := models.Progress{Total: int64(totalDuration.Seconds())}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
result, err := b.client.RecentlyPlayedAfter(minTime, MaxItemsPerGet)
|
result, err := b.client.RecentlyPlayedAfter(minTime, MaxItemsPerGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -118,7 +120,7 @@ func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results cha
|
||||||
// Set minTime to the newest returned listen
|
// Set minTime to the newest returned listen
|
||||||
after, err := strconv.ParseInt(result.Cursors.After, 10, 64)
|
after, err := strconv.ParseInt(result.Cursors.After, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
} else if after <= minTime.Unix() {
|
} else if after <= minTime.Unix() {
|
||||||
|
@ -161,6 +163,8 @@ func (b *SpotifyApiBackend) ExportLoves(oldestTimestamp time.Time, results chan
|
||||||
offset := math.MaxInt32
|
offset := math.MaxInt32
|
||||||
perPage := MaxItemsPerGet
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
p := models.Progress{Total: int64(perPage)}
|
p := models.Progress{Total: int64(perPage)}
|
||||||
totalCount := 0
|
totalCount := 0
|
||||||
exportCount := 0
|
exportCount := 0
|
||||||
|
@ -169,7 +173,7 @@ out:
|
||||||
for {
|
for {
|
||||||
result, err := b.client.UserTracks(offset, perPage)
|
result, err := b.client.UserTracks(offset, perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- p.Abort()
|
progress <- p.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,9 +73,11 @@ func (b *SpotifyHistoryBackend) InitConfig(config *config.ServiceConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
files, err := filepath.Glob(path.Join(b.dirPath, historyFileGlob))
|
files, err := filepath.Glob(path.Join(b.dirPath, historyFileGlob))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,7 +88,7 @@ func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results
|
||||||
for i, filePath := range files {
|
for i, filePath := range files {
|
||||||
history, err := readHistoryFile(filePath)
|
history, err := readHistoryFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.ListensResult{Error: err}
|
results <- models.ListensResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,16 +64,17 @@ func (b *SubsonicApiBackend) InitConfig(config *config.ServiceConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SubsonicApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
func (b *SubsonicApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
|
||||||
|
defer close(results)
|
||||||
err := b.client.Authenticate(b.password)
|
err := b.client.Authenticate(b.password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
starred, err := b.client.GetStarred2(map[string]string{})
|
starred, err := b.client.GetStarred2(map[string]string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
progress <- models.Progress{}.Abort()
|
progress <- models.Progress{}.Complete()
|
||||||
results <- models.LovesResult{Error: err}
|
results <- models.LovesResult{Error: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||||
|
|
||||||
This file is part of Scotty.
|
This file is part of Scotty.
|
||||||
|
|
||||||
|
@ -28,8 +28,7 @@ import (
|
||||||
"go.uploadedlobster.com/scotty/internal/models"
|
"go.uploadedlobster.com/scotty/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func progressBar(exportProgress chan models.Progress, importProgress chan models.Progress) *mpb.Progress {
|
func progressBar(wg *sync.WaitGroup, exportProgress chan models.Progress, importProgress chan models.Progress) *mpb.Progress {
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
p := mpb.New(
|
p := mpb.New(
|
||||||
mpb.WithWaitGroup(wg),
|
mpb.WithWaitGroup(wg),
|
||||||
mpb.WithOutput(color.Output),
|
mpb.WithOutput(color.Output),
|
||||||
|
@ -59,12 +58,10 @@ func setupProgressBar(p *mpb.Progress, name string) *mpb.Bar {
|
||||||
),
|
),
|
||||||
mpb.AppendDecorators(
|
mpb.AppendDecorators(
|
||||||
decor.OnComplete(
|
decor.OnComplete(
|
||||||
decor.OnAbort(
|
decor.EwmaETA(decor.ET_STYLE_GO, 0, decor.WC{C: decor.DSyncWidth}),
|
||||||
decor.EwmaETA(decor.ET_STYLE_GO, 0, decor.WC{C: decor.DSyncWidth}),
|
|
||||||
i18n.Tr("aborted"),
|
|
||||||
),
|
|
||||||
i18n.Tr("done"),
|
i18n.Tr("done"),
|
||||||
),
|
),
|
||||||
|
// decor.OnComplete(decor.Percentage(decor.WC{W: 5, C: decor.DSyncWidthR}), "done"),
|
||||||
decor.Name(" "),
|
decor.Name(" "),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -75,10 +72,6 @@ func updateProgressBar(bar *mpb.Bar, wg *sync.WaitGroup, progressChan chan model
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
lastIterTime := time.Now()
|
lastIterTime := time.Now()
|
||||||
for progress := range progressChan {
|
for progress := range progressChan {
|
||||||
if progress.Aborted {
|
|
||||||
bar.Abort(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
oldIterTime := lastIterTime
|
oldIterTime := lastIterTime
|
||||||
lastIterTime = time.Now()
|
lastIterTime = time.Now()
|
||||||
bar.EwmaSetCurrent(progress.Elapsed, lastIterTime.Sub(oldIterTime))
|
bar.EwmaSetCurrent(progress.Elapsed, lastIterTime.Sub(oldIterTime))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
|
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||||
|
|
||||||
Scotty is free software: you can redistribute it and/or modify it under the
|
Scotty is free software: you can redistribute it and/or modify it under the
|
||||||
terms of the GNU General Public License as published by the Free Software
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
@ -19,6 +19,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -111,7 +112,8 @@ func (c *TransferCmd[E, I, R]) Transfer(exp backends.ExportProcessor[R], imp bac
|
||||||
// Prepare progress bars
|
// Prepare progress bars
|
||||||
exportProgress := make(chan models.Progress)
|
exportProgress := make(chan models.Progress)
|
||||||
importProgress := make(chan models.Progress)
|
importProgress := make(chan models.Progress)
|
||||||
progress := progressBar(exportProgress, importProgress)
|
var wg sync.WaitGroup
|
||||||
|
progress := progressBar(&wg, exportProgress, importProgress)
|
||||||
|
|
||||||
// Export from source
|
// Export from source
|
||||||
exportChan := make(chan R, 1000)
|
exportChan := make(chan R, 1000)
|
||||||
|
@ -121,20 +123,23 @@ func (c *TransferCmd[E, I, R]) Transfer(exp backends.ExportProcessor[R], imp bac
|
||||||
resultChan := make(chan models.ImportResult)
|
resultChan := make(chan models.ImportResult)
|
||||||
go imp.Process(exportChan, resultChan, importProgress)
|
go imp.Process(exportChan, resultChan, importProgress)
|
||||||
result := <-resultChan
|
result := <-resultChan
|
||||||
progress.Wait()
|
if timestamp.After(result.LastTimestamp) {
|
||||||
|
result.LastTimestamp = timestamp
|
||||||
// Update timestamp
|
|
||||||
err = c.updateTimestamp(&result, timestamp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
fmt.Println(i18n.Tr("Imported %v of %v %s into %v.",
|
progress.Wait()
|
||||||
result.ImportCount, result.TotalCount, c.entity, c.targetName))
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
printTimestamp("Import failed, last reported timestamp was %v (%s)", result.LastTimestamp)
|
printTimestamp("Import failed, last reported timestamp was %v (%s)", result.LastTimestamp)
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
fmt.Println(i18n.Tr("Imported %v of %v %s into %v.",
|
||||||
|
result.ImportCount, result.TotalCount, c.entity, c.targetName))
|
||||||
|
|
||||||
|
// Update timestamp
|
||||||
|
err = c.updateTimestamp(result, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Print errors
|
// Print errors
|
||||||
if len(result.ImportLog) > 0 {
|
if len(result.ImportLog) > 0 {
|
||||||
|
@ -174,7 +179,7 @@ func (c *TransferCmd[E, I, R]) timestamp() (time.Time, error) {
|
||||||
return time.Time{}, errors.New(i18n.Tr("invalid timestamp string \"%v\"", flagValue))
|
return time.Time{}, errors.New(i18n.Tr("invalid timestamp string \"%v\"", flagValue))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TransferCmd[E, I, R]) updateTimestamp(result *models.ImportResult, oldTimestamp time.Time) error {
|
func (c *TransferCmd[E, I, R]) updateTimestamp(result models.ImportResult, oldTimestamp time.Time) error {
|
||||||
if oldTimestamp.After(result.LastTimestamp) {
|
if oldTimestamp.After(result.LastTimestamp) {
|
||||||
result.LastTimestamp = oldTimestamp
|
result.LastTimestamp = oldTimestamp
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,6 @@ type Progress struct {
|
||||||
Total int64
|
Total int64
|
||||||
Elapsed int64
|
Elapsed int64
|
||||||
Completed bool
|
Completed bool
|
||||||
Aborted bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Progress) FromImportResult(result ImportResult) Progress {
|
func (p Progress) FromImportResult(result ImportResult) Progress {
|
||||||
|
@ -227,8 +226,3 @@ func (p Progress) Complete() Progress {
|
||||||
p.Completed = true
|
p.Completed = true
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Progress) Abort() Progress {
|
|
||||||
p.Aborted = true
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
|
@ -42,12 +42,12 @@ var messageKeyToIndex = map[string]int{
|
||||||
"\tbackend: %v": 11,
|
"\tbackend: %v": 11,
|
||||||
"\texport: %s": 0,
|
"\texport: %s": 0,
|
||||||
"\timport: %s\n": 1,
|
"\timport: %s\n": 1,
|
||||||
"%v: %v": 49,
|
"%v: %v": 48,
|
||||||
"Aborted": 8,
|
"Aborted": 8,
|
||||||
"Access token": 19,
|
"Access token": 19,
|
||||||
"Access token received, you can use %v now.\n": 34,
|
"Access token received, you can use %v now.\n": 34,
|
||||||
"Append to file": 21,
|
"Append to file": 21,
|
||||||
"Backend": 43,
|
"Backend": 42,
|
||||||
"Check for duplicate listens on import (slower)": 24,
|
"Check for duplicate listens on import (slower)": 24,
|
||||||
"Client ID": 15,
|
"Client ID": 15,
|
||||||
"Client secret": 16,
|
"Client secret": 16,
|
||||||
|
@ -57,46 +57,45 @@ var messageKeyToIndex = map[string]int{
|
||||||
"Error: OAuth state mismatch": 33,
|
"Error: OAuth state mismatch": 33,
|
||||||
"Failed reading config: %v": 2,
|
"Failed reading config: %v": 2,
|
||||||
"File path": 20,
|
"File path": 20,
|
||||||
"From timestamp: %v (%v)": 45,
|
"From timestamp: %v (%v)": 44,
|
||||||
"Ignore listens in incognito mode": 30,
|
"Ignore listens in incognito mode": 30,
|
||||||
"Ignore skipped listens": 27,
|
"Ignore skipped listens": 27,
|
||||||
"Ignored duplicate listen %v: \"%v\" by %v (%v)": 25,
|
"Ignored duplicate listen %v: \"%v\" by %v (%v)": 25,
|
||||||
"Import failed, last reported timestamp was %v (%s)": 47,
|
"Import failed, last reported timestamp was %v (%s)": 45,
|
||||||
"Import log:": 48,
|
"Import log:": 47,
|
||||||
"Imported %v of %v %s into %v.": 46,
|
"Imported %v of %v %s into %v.": 46,
|
||||||
"Latest timestamp: %v (%v)": 51,
|
"Latest timestamp: %v (%v)": 50,
|
||||||
"Minimum playback duration for skipped tracks (seconds)": 31,
|
"Minimum playback duration for skipped tracks (seconds)": 31,
|
||||||
"No": 40,
|
"No": 39,
|
||||||
"Playlist title": 22,
|
"Playlist title": 22,
|
||||||
"Saved service %v using backend %v": 5,
|
"Saved service %v using backend %v": 5,
|
||||||
"Server URL": 17,
|
"Server URL": 17,
|
||||||
"Service": 42,
|
"Service": 41,
|
||||||
"Service \"%v\" deleted\n": 9,
|
"Service \"%v\" deleted\n": 9,
|
||||||
"Service name": 3,
|
"Service name": 3,
|
||||||
"Specify a time zone for the listen timestamps": 28,
|
"Specify a time zone for the listen timestamps": 28,
|
||||||
"The backend %v requires authentication. Authenticate now?": 6,
|
"The backend %v requires authentication. Authenticate now?": 6,
|
||||||
"Token received, you can close this window now.": 12,
|
"Token received, you can close this window now.": 12,
|
||||||
"Transferring %s from %s to %s…": 44,
|
"Transferring %s from %s to %s…": 43,
|
||||||
"Unique playlist identifier": 23,
|
"Unique playlist identifier": 23,
|
||||||
"Updated service %v using backend %v\n": 10,
|
"Updated service %v using backend %v\n": 10,
|
||||||
"User name": 18,
|
"User name": 18,
|
||||||
"Visit the URL for authorization: %v": 32,
|
"Visit the URL for authorization: %v": 32,
|
||||||
"Yes": 39,
|
"Yes": 38,
|
||||||
"a service with this name already exists": 4,
|
"a service with this name already exists": 4,
|
||||||
"aborted": 37,
|
"backend %s does not implement %s": 13,
|
||||||
"backend %s does not implement %s": 13,
|
"done": 37,
|
||||||
"done": 38,
|
"exporting": 35,
|
||||||
"exporting": 35,
|
"importing": 36,
|
||||||
"importing": 36,
|
"invalid timestamp string \"%v\"": 49,
|
||||||
"invalid timestamp string \"%v\"": 50,
|
"key must only consist of A-Za-z0-9_-": 52,
|
||||||
"key must only consist of A-Za-z0-9_-": 53,
|
"no configuration file defined, cannot write config": 51,
|
||||||
"no configuration file defined, cannot write config": 52,
|
"no existing service configurations": 40,
|
||||||
"no existing service configurations": 41,
|
"no service configuration \"%v\"": 53,
|
||||||
"no service configuration \"%v\"": 54,
|
"unknown backend \"%s\"": 14,
|
||||||
"unknown backend \"%s\"": 14,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var deIndex = []uint32{ // 56 elements
|
var deIndex = []uint32{ // 55 elements
|
||||||
// Entry 0 - 1F
|
// Entry 0 - 1F
|
||||||
0x00000000, 0x00000013, 0x00000027, 0x00000052,
|
0x00000000, 0x00000013, 0x00000027, 0x00000052,
|
||||||
0x0000005e, 0x0000008d, 0x000000bd, 0x00000104,
|
0x0000005e, 0x0000008d, 0x000000bd, 0x00000104,
|
||||||
|
@ -108,14 +107,14 @@ var deIndex = []uint32{ // 56 elements
|
||||||
0x0000037e, 0x000003a4, 0x000003b4, 0x000003da,
|
0x0000037e, 0x000003a4, 0x000003b4, 0x000003da,
|
||||||
// Entry 20 - 3F
|
// Entry 20 - 3F
|
||||||
0x00000418, 0x00000443, 0x0000046d, 0x000004ad,
|
0x00000418, 0x00000443, 0x0000046d, 0x000004ad,
|
||||||
0x000004b8, 0x000004c3, 0x000004cf, 0x000004d6,
|
0x000004b8, 0x000004c3, 0x000004ca, 0x000004cd,
|
||||||
0x000004d9, 0x000004de, 0x00000507, 0x0000050f,
|
0x000004d2, 0x000004fb, 0x00000503, 0x0000050b,
|
||||||
0x00000517, 0x00000540, 0x0000055e, 0x00000589,
|
0x00000534, 0x00000552, 0x0000058f, 0x000005ba,
|
||||||
0x000005c6, 0x000005d1, 0x000005de, 0x00000602,
|
0x000005c5, 0x000005d2, 0x000005f6, 0x00000619,
|
||||||
0x00000625, 0x00000676, 0x000006ad, 0x000006d4,
|
0x0000066a, 0x000006a1, 0x000006c8,
|
||||||
} // Size: 248 bytes
|
} // Size: 244 bytes
|
||||||
|
|
||||||
const deData string = "" + // Size: 1748 bytes
|
const deData string = "" + // Size: 1736 bytes
|
||||||
"\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" +
|
"\x04\x01\x09\x00\x0e\x02Export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02Import:" +
|
||||||
" %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" +
|
" %[1]s\x02Fehler beim Lesen der Konfiguration: %[1]v\x02Servicename\x02e" +
|
||||||
"in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" +
|
"in Service mit diesem Namen existiert bereits\x02Service %[1]v mit dem B" +
|
||||||
|
@ -135,17 +134,17 @@ const deData string = "" + // Size: 1748 bytes
|
||||||
"inimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02Zur Anmeld" +
|
"inimale Wiedergabedauer für übersprungene Titel (Sekunden)\x02Zur Anmeld" +
|
||||||
"ung folgende URL aufrufen: %[1]v\x02Fehler: OAuth-State stimmt nicht übe" +
|
"ung folgende URL aufrufen: %[1]v\x02Fehler: OAuth-State stimmt nicht übe" +
|
||||||
"rein\x04\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwen" +
|
"rein\x04\x00\x01\x0a;\x02Zugriffstoken erhalten, %[1]v kann jetzt verwen" +
|
||||||
"det werden.\x02exportiere\x02importiere\x02abgebrochen\x02fertig\x02Ja" +
|
"det werden.\x02exportiere\x02importiere\x02fertig\x02Ja\x02Nein\x02keine" +
|
||||||
"\x02Nein\x02keine bestehenden Servicekonfigurationen\x02Service\x02Backe" +
|
" bestehenden Servicekonfigurationen\x02Service\x02Backend\x02Übertrage %" +
|
||||||
"nd\x02Übertrage %[1]s von %[2]s nach %[3]s…\x02Ab Zeitstempel: %[1]v (%[" +
|
"[1]s von %[2]s nach %[3]s…\x02Ab Zeitstempel: %[1]v (%[2]v)\x02Import fe" +
|
||||||
"2]v)\x02%[1]v von %[2]v %[3]s in %[4]v importiert.\x02Import fehlgeschla" +
|
"hlgeschlagen, letzter Zeitstempel war %[1]v (%[2]s)\x02%[1]v von %[2]v %" +
|
||||||
"gen, letzter Zeitstempel war %[1]v (%[2]s)\x02Importlog:\x02%[1]v: %[2]v" +
|
"[3]s in %[4]v importiert.\x02Importlog:\x02%[1]v: %[2]v\x02ungültiger Ze" +
|
||||||
"\x02ungültiger Zeitstempel „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)" +
|
"itstempel „%[1]v“\x02Letzter Zeitstempel: %[1]v (%[2]v)\x02keine Konfigu" +
|
||||||
"\x02keine Konfigurationsdatei definiert, Konfiguration kann nicht geschr" +
|
"rationsdatei definiert, Konfiguration kann nicht geschrieben werden\x02S" +
|
||||||
"ieben werden\x02Schlüssel darf nur die Zeichen A-Za-z0-9_- beinhalten" +
|
"chlüssel darf nur die Zeichen A-Za-z0-9_- beinhalten\x02keine Servicekon" +
|
||||||
"\x02keine Servicekonfiguration „%[1]v“"
|
"figuration „%[1]v“"
|
||||||
|
|
||||||
var enIndex = []uint32{ // 56 elements
|
var enIndex = []uint32{ // 55 elements
|
||||||
// Entry 0 - 1F
|
// Entry 0 - 1F
|
||||||
0x00000000, 0x00000013, 0x00000027, 0x00000044,
|
0x00000000, 0x00000013, 0x00000027, 0x00000044,
|
||||||
0x00000051, 0x00000079, 0x000000a1, 0x000000de,
|
0x00000051, 0x00000079, 0x000000a1, 0x000000de,
|
||||||
|
@ -157,14 +156,14 @@ var enIndex = []uint32{ // 56 elements
|
||||||
0x00000307, 0x00000335, 0x00000344, 0x00000365,
|
0x00000307, 0x00000335, 0x00000344, 0x00000365,
|
||||||
// Entry 20 - 3F
|
// Entry 20 - 3F
|
||||||
0x0000039c, 0x000003c3, 0x000003df, 0x00000412,
|
0x0000039c, 0x000003c3, 0x000003df, 0x00000412,
|
||||||
0x0000041c, 0x00000426, 0x0000042e, 0x00000433,
|
0x0000041c, 0x00000426, 0x0000042b, 0x0000042f,
|
||||||
0x00000437, 0x0000043a, 0x0000045d, 0x00000465,
|
0x00000432, 0x00000455, 0x0000045d, 0x00000465,
|
||||||
0x0000046d, 0x00000497, 0x000004b5, 0x000004df,
|
0x0000048f, 0x000004ad, 0x000004e6, 0x00000510,
|
||||||
0x00000518, 0x00000524, 0x00000531, 0x00000552,
|
0x0000051c, 0x00000529, 0x0000054a, 0x0000056a,
|
||||||
0x00000572, 0x000005a5, 0x000005ca, 0x000005eb,
|
0x0000059d, 0x000005c2, 0x000005e3,
|
||||||
} // Size: 248 bytes
|
} // Size: 244 bytes
|
||||||
|
|
||||||
const enData string = "" + // Size: 1515 bytes
|
const enData string = "" + // Size: 1507 bytes
|
||||||
"\x04\x01\x09\x00\x0e\x02export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02import:" +
|
"\x04\x01\x09\x00\x0e\x02export: %[1]s\x04\x01\x09\x01\x0a\x0e\x02import:" +
|
||||||
" %[1]s\x02Failed reading config: %[1]v\x02Service name\x02a service with" +
|
" %[1]s\x02Failed reading config: %[1]v\x02Service name\x02a service with" +
|
||||||
" this name already exists\x02Saved service %[1]v using backend %[2]v\x02" +
|
" this name already exists\x02Saved service %[1]v using backend %[2]v\x02" +
|
||||||
|
@ -182,14 +181,13 @@ const enData string = "" + // Size: 1515 bytes
|
||||||
"mps\x02Directory path\x02Ignore listens in incognito mode\x02Minimum pla" +
|
"mps\x02Directory path\x02Ignore listens in incognito mode\x02Minimum pla" +
|
||||||
"yback duration for skipped tracks (seconds)\x02Visit the URL for authori" +
|
"yback duration for skipped tracks (seconds)\x02Visit the URL for authori" +
|
||||||
"zation: %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access " +
|
"zation: %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access " +
|
||||||
"token received, you can use %[1]v now.\x02exporting\x02importing\x02abor" +
|
"token received, you can use %[1]v now.\x02exporting\x02importing\x02done" +
|
||||||
"ted\x02done\x02Yes\x02No\x02no existing service configurations\x02Servic" +
|
"\x02Yes\x02No\x02no existing service configurations\x02Service\x02Backen" +
|
||||||
"e\x02Backend\x02Transferring %[1]s from %[2]s to %[3]s…\x02From timestam" +
|
"d\x02Transferring %[1]s from %[2]s to %[3]s…\x02From timestamp: %[1]v (%" +
|
||||||
"p: %[1]v (%[2]v)\x02Imported %[1]v of %[2]v %[3]s into %[4]v.\x02Import " +
|
"[2]v)\x02Import failed, last reported timestamp was %[1]v (%[2]s)\x02Imp" +
|
||||||
"failed, last reported timestamp was %[1]v (%[2]s)\x02Import log:\x02%[1]" +
|
"orted %[1]v of %[2]v %[3]s into %[4]v.\x02Import log:\x02%[1]v: %[2]v" +
|
||||||
"v: %[2]v\x02invalid timestamp string \x22%[1]v\x22\x02Latest timestamp: " +
|
"\x02invalid timestamp string \x22%[1]v\x22\x02Latest timestamp: %[1]v (%" +
|
||||||
"%[1]v (%[2]v)\x02no configuration file defined, cannot write config\x02k" +
|
"[2]v)\x02no configuration file defined, cannot write config\x02key must " +
|
||||||
"ey must only consist of A-Za-z0-9_-\x02no service configuration \x22%[1]" +
|
"only consist of A-Za-z0-9_-\x02no service configuration \x22%[1]v\x22"
|
||||||
"v\x22"
|
|
||||||
|
|
||||||
// Total table size 3759 bytes (3KiB); checksum: 7B4CF967
|
// Total table size 3731 bytes (3KiB); checksum: F7951710
|
||||||
|
|
|
@ -368,23 +368,21 @@
|
||||||
"id": "exporting",
|
"id": "exporting",
|
||||||
"message": "exporting",
|
"message": "exporting",
|
||||||
"translatorComment": "Copied from source.",
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true,
|
||||||
"translation": "exportiere"
|
"translation": "exportiere"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "importing",
|
"id": "importing",
|
||||||
"message": "importing",
|
"message": "importing",
|
||||||
"translatorComment": "Copied from source.",
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true,
|
||||||
"translation": "importiere"
|
"translation": "importiere"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "aborted",
|
|
||||||
"message": "aborted",
|
|
||||||
"translation": "abgebrochen"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "done",
|
"id": "done",
|
||||||
"message": "done",
|
"message": "done",
|
||||||
"translatorComment": "Copied from source.",
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true,
|
||||||
"translation": "fertig"
|
"translation": "fertig"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -464,6 +462,27 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"message": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"translation": "Import fehlgeschlagen, letzter Zeitstempel war {Arg_1} ({Arg_2})",
|
||||||
|
"placeholders": [
|
||||||
|
{
|
||||||
|
"id": "Arg_1",
|
||||||
|
"string": "%[1]v",
|
||||||
|
"type": "",
|
||||||
|
"underlyingType": "interface{}",
|
||||||
|
"argNum": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Arg_2",
|
||||||
|
"string": "%[2]s",
|
||||||
|
"type": "",
|
||||||
|
"underlyingType": "string",
|
||||||
|
"argNum": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
||||||
"message": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
"message": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
||||||
|
@ -503,27 +522,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"message": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"translation": "Import fehlgeschlagen, letzter Zeitstempel war {Arg_1} ({Arg_2})",
|
|
||||||
"placeholders": [
|
|
||||||
{
|
|
||||||
"id": "Arg_1",
|
|
||||||
"string": "%[1]v",
|
|
||||||
"type": "",
|
|
||||||
"underlyingType": "interface{}",
|
|
||||||
"argNum": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Arg_2",
|
|
||||||
"string": "%[2]s",
|
|
||||||
"type": "",
|
|
||||||
"underlyingType": "string",
|
|
||||||
"argNum": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "Import log:",
|
"id": "Import log:",
|
||||||
"message": "Import log:",
|
"message": "Import log:",
|
||||||
|
|
|
@ -368,24 +368,22 @@
|
||||||
"id": "exporting",
|
"id": "exporting",
|
||||||
"message": "exporting",
|
"message": "exporting",
|
||||||
"translation": "exportiere",
|
"translation": "exportiere",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "importing",
|
"id": "importing",
|
||||||
"message": "importing",
|
"message": "importing",
|
||||||
"translation": "importiere",
|
"translation": "importiere",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
},
|
"fuzzy": true
|
||||||
{
|
|
||||||
"id": "aborted",
|
|
||||||
"message": "aborted",
|
|
||||||
"translation": "abgebrochen"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "done",
|
"id": "done",
|
||||||
"message": "done",
|
"message": "done",
|
||||||
"translation": "fertig",
|
"translation": "fertig",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Yes",
|
"id": "Yes",
|
||||||
|
@ -464,6 +462,27 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"message": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"translation": "Import fehlgeschlagen, letzter Zeitstempel war {Arg_1} ({Arg_2})",
|
||||||
|
"placeholders": [
|
||||||
|
{
|
||||||
|
"id": "Arg_1",
|
||||||
|
"string": "%[1]v",
|
||||||
|
"type": "",
|
||||||
|
"underlyingType": "interface{}",
|
||||||
|
"argNum": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Arg_2",
|
||||||
|
"string": "%[2]s",
|
||||||
|
"type": "",
|
||||||
|
"underlyingType": "string",
|
||||||
|
"argNum": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
||||||
"message": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
"message": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
||||||
|
@ -503,27 +522,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"message": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"translation": "Import fehlgeschlagen, letzter Zeitstempel war {Arg_1} ({Arg_2})",
|
|
||||||
"placeholders": [
|
|
||||||
{
|
|
||||||
"id": "Arg_1",
|
|
||||||
"string": "%[1]v",
|
|
||||||
"type": "",
|
|
||||||
"underlyingType": "interface{}",
|
|
||||||
"argNum": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Arg_2",
|
|
||||||
"string": "%[2]s",
|
|
||||||
"type": "",
|
|
||||||
"underlyingType": "string",
|
|
||||||
"argNum": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "Import log:",
|
"id": "Import log:",
|
||||||
"message": "Import log:",
|
"message": "Import log:",
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "strings.Join(info.ExportCapabilities, \", \")"
|
"expr": "strings.Join(info.ExportCapabilities, \", \")"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "import: {ImportCapabilities__}",
|
"id": "import: {ImportCapabilities__}",
|
||||||
|
@ -31,7 +32,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "strings.Join(info.ImportCapabilities, \", \")"
|
"expr": "strings.Join(info.ImportCapabilities, \", \")"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Failed reading config: {Err}",
|
"id": "Failed reading config: {Err}",
|
||||||
|
@ -47,19 +49,22 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "err"
|
"expr": "err"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Service name",
|
"id": "Service name",
|
||||||
"message": "Service name",
|
"message": "Service name",
|
||||||
"translation": "Service name",
|
"translation": "Service name",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "a service with this name already exists",
|
"id": "a service with this name already exists",
|
||||||
"message": "a service with this name already exists",
|
"message": "a service with this name already exists",
|
||||||
"translation": "a service with this name already exists",
|
"translation": "a service with this name already exists",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Saved service {Name} using backend {Backend}",
|
"id": "Saved service {Name} using backend {Backend}",
|
||||||
|
@ -83,7 +88,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "service.Backend"
|
"expr": "service.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "The backend {Backend} requires authentication. Authenticate now?",
|
"id": "The backend {Backend} requires authentication. Authenticate now?",
|
||||||
|
@ -99,7 +105,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service.Backend"
|
"expr": "service.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Delete the service configuration \"{Service}\"?",
|
"id": "Delete the service configuration \"{Service}\"?",
|
||||||
|
@ -115,13 +122,15 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service"
|
"expr": "service"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Aborted",
|
"id": "Aborted",
|
||||||
"message": "Aborted",
|
"message": "Aborted",
|
||||||
"translation": "Aborted",
|
"translation": "Aborted",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Service \"{Name}\" deleted",
|
"id": "Service \"{Name}\" deleted",
|
||||||
|
@ -137,7 +146,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service.Name"
|
"expr": "service.Name"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Updated service {Name} using backend {Backend}",
|
"id": "Updated service {Name} using backend {Backend}",
|
||||||
|
@ -161,7 +171,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "service.Backend"
|
"expr": "service.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "backend: {Backend}",
|
"id": "backend: {Backend}",
|
||||||
|
@ -177,13 +188,15 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "s.Backend"
|
"expr": "s.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Token received, you can close this window now.",
|
"id": "Token received, you can close this window now.",
|
||||||
"message": "Token received, you can close this window now.",
|
"message": "Token received, you can close this window now.",
|
||||||
"translation": "Token received, you can close this window now.",
|
"translation": "Token received, you can close this window now.",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "backend {Backend} does not implement {InterfaceName}",
|
"id": "backend {Backend} does not implement {InterfaceName}",
|
||||||
|
@ -207,7 +220,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "interfaceName"
|
"expr": "interfaceName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "unknown backend \"{BackendName}\"",
|
"id": "unknown backend \"{BackendName}\"",
|
||||||
|
@ -223,67 +237,78 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "backendName"
|
"expr": "backendName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Client ID",
|
"id": "Client ID",
|
||||||
"message": "Client ID",
|
"message": "Client ID",
|
||||||
"translation": "Client ID",
|
"translation": "Client ID",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Client secret",
|
"id": "Client secret",
|
||||||
"message": "Client secret",
|
"message": "Client secret",
|
||||||
"translation": "Client secret",
|
"translation": "Client secret",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Server URL",
|
"id": "Server URL",
|
||||||
"message": "Server URL",
|
"message": "Server URL",
|
||||||
"translation": "Server URL",
|
"translation": "Server URL",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "User name",
|
"id": "User name",
|
||||||
"message": "User name",
|
"message": "User name",
|
||||||
"translation": "User name",
|
"translation": "User name",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Access token",
|
"id": "Access token",
|
||||||
"message": "Access token",
|
"message": "Access token",
|
||||||
"translation": "Access token",
|
"translation": "Access token",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "File path",
|
"id": "File path",
|
||||||
"message": "File path",
|
"message": "File path",
|
||||||
"translation": "File path",
|
"translation": "File path",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Append to file",
|
"id": "Append to file",
|
||||||
"message": "Append to file",
|
"message": "Append to file",
|
||||||
"translation": "Append to file",
|
"translation": "Append to file",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Playlist title",
|
"id": "Playlist title",
|
||||||
"message": "Playlist title",
|
"message": "Playlist title",
|
||||||
"translation": "Playlist title",
|
"translation": "Playlist title",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Unique playlist identifier",
|
"id": "Unique playlist identifier",
|
||||||
"message": "Unique playlist identifier",
|
"message": "Unique playlist identifier",
|
||||||
"translation": "Unique playlist identifier",
|
"translation": "Unique playlist identifier",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Check for duplicate listens on import (slower)",
|
"id": "Check for duplicate listens on import (slower)",
|
||||||
"message": "Check for duplicate listens on import (slower)",
|
"message": "Check for duplicate listens on import (slower)",
|
||||||
"translation": "Check for duplicate listens on import (slower)",
|
"translation": "Check for duplicate listens on import (slower)",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})",
|
"id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})",
|
||||||
|
@ -323,43 +348,50 @@
|
||||||
"argNum": 4,
|
"argNum": 4,
|
||||||
"expr": "l.RecordingMBID"
|
"expr": "l.RecordingMBID"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Disable auto correction of submitted listens",
|
"id": "Disable auto correction of submitted listens",
|
||||||
"message": "Disable auto correction of submitted listens",
|
"message": "Disable auto correction of submitted listens",
|
||||||
"translation": "Disable auto correction of submitted listens",
|
"translation": "Disable auto correction of submitted listens",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignore skipped listens",
|
"id": "Ignore skipped listens",
|
||||||
"message": "Ignore skipped listens",
|
"message": "Ignore skipped listens",
|
||||||
"translation": "Ignore skipped listens",
|
"translation": "Ignore skipped listens",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Specify a time zone for the listen timestamps",
|
"id": "Specify a time zone for the listen timestamps",
|
||||||
"message": "Specify a time zone for the listen timestamps",
|
"message": "Specify a time zone for the listen timestamps",
|
||||||
"translation": "Specify a time zone for the listen timestamps",
|
"translation": "Specify a time zone for the listen timestamps",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Directory path",
|
"id": "Directory path",
|
||||||
"message": "Directory path",
|
"message": "Directory path",
|
||||||
"translation": "Directory path",
|
"translation": "Directory path",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignore listens in incognito mode",
|
"id": "Ignore listens in incognito mode",
|
||||||
"message": "Ignore listens in incognito mode",
|
"message": "Ignore listens in incognito mode",
|
||||||
"translation": "Ignore listens in incognito mode",
|
"translation": "Ignore listens in incognito mode",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Minimum playback duration for skipped tracks (seconds)",
|
"id": "Minimum playback duration for skipped tracks (seconds)",
|
||||||
"message": "Minimum playback duration for skipped tracks (seconds)",
|
"message": "Minimum playback duration for skipped tracks (seconds)",
|
||||||
"translation": "Minimum playback duration for skipped tracks (seconds)",
|
"translation": "Minimum playback duration for skipped tracks (seconds)",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Visit the URL for authorization: {URL}",
|
"id": "Visit the URL for authorization: {URL}",
|
||||||
|
@ -375,13 +407,15 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "authURL.URL"
|
"expr": "authURL.URL"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Error: OAuth state mismatch",
|
"id": "Error: OAuth state mismatch",
|
||||||
"message": "Error: OAuth state mismatch",
|
"message": "Error: OAuth state mismatch",
|
||||||
"translation": "Error: OAuth state mismatch",
|
"translation": "Error: OAuth state mismatch",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Access token received, you can use {Name} now.",
|
"id": "Access token received, you can use {Name} now.",
|
||||||
|
@ -397,55 +431,64 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service.Name"
|
"expr": "service.Name"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "exporting",
|
"id": "exporting",
|
||||||
"message": "exporting",
|
"message": "exporting",
|
||||||
"translation": "exporting",
|
"translation": "exporting",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "importing",
|
"id": "importing",
|
||||||
"message": "importing",
|
"message": "importing",
|
||||||
"translation": "importing",
|
"translation": "importing",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "done",
|
"id": "done",
|
||||||
"message": "done",
|
"message": "done",
|
||||||
"translation": "done",
|
"translation": "done",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Yes",
|
"id": "Yes",
|
||||||
"message": "Yes",
|
"message": "Yes",
|
||||||
"translation": "Yes",
|
"translation": "Yes",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "No",
|
"id": "No",
|
||||||
"message": "No",
|
"message": "No",
|
||||||
"translation": "No",
|
"translation": "No",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no existing service configurations",
|
"id": "no existing service configurations",
|
||||||
"message": "no existing service configurations",
|
"message": "no existing service configurations",
|
||||||
"translation": "no existing service configurations",
|
"translation": "no existing service configurations",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Service",
|
"id": "Service",
|
||||||
"message": "Service",
|
"message": "Service",
|
||||||
"translation": "Service",
|
"translation": "Service",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Backend",
|
"id": "Backend",
|
||||||
"message": "Backend",
|
"message": "Backend",
|
||||||
"translation": "Backend",
|
"translation": "Backend",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Transferring {Entity} from {SourceName} to {TargetName}…",
|
"id": "Transferring {Entity} from {SourceName} to {TargetName}…",
|
||||||
|
@ -477,7 +520,8 @@
|
||||||
"argNum": 3,
|
"argNum": 3,
|
||||||
"expr": "c.targetName"
|
"expr": "c.targetName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "From timestamp: {Arg_1} ({Arg_2})",
|
"id": "From timestamp: {Arg_1} ({Arg_2})",
|
||||||
|
@ -499,7 +543,8 @@
|
||||||
"underlyingType": "interface{}",
|
"underlyingType": "interface{}",
|
||||||
"argNum": 2
|
"argNum": 2
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
@ -521,7 +566,8 @@
|
||||||
"underlyingType": "string",
|
"underlyingType": "string",
|
||||||
"argNum": 2
|
"argNum": 2
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
||||||
|
@ -561,13 +607,15 @@
|
||||||
"argNum": 4,
|
"argNum": 4,
|
||||||
"expr": "c.targetName"
|
"expr": "c.targetName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Import log:",
|
"id": "Import log:",
|
||||||
"message": "Import log:",
|
"message": "Import log:",
|
||||||
"translation": "Import log:",
|
"translation": "Import log:",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "{Type}: {Message}",
|
"id": "{Type}: {Message}",
|
||||||
|
@ -591,7 +639,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "entry.Message"
|
"expr": "entry.Message"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "invalid timestamp string \"{FlagValue}\"",
|
"id": "invalid timestamp string \"{FlagValue}\"",
|
||||||
|
@ -607,7 +656,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "flagValue"
|
"expr": "flagValue"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Latest timestamp: {Arg_1} ({Arg_2})",
|
"id": "Latest timestamp: {Arg_1} ({Arg_2})",
|
||||||
|
@ -629,19 +679,22 @@
|
||||||
"underlyingType": "interface{}",
|
"underlyingType": "interface{}",
|
||||||
"argNum": 2
|
"argNum": 2
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no configuration file defined, cannot write config",
|
"id": "no configuration file defined, cannot write config",
|
||||||
"message": "no configuration file defined, cannot write config",
|
"message": "no configuration file defined, cannot write config",
|
||||||
"translation": "no configuration file defined, cannot write config",
|
"translation": "no configuration file defined, cannot write config",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "key must only consist of A-Za-z0-9_-",
|
"id": "key must only consist of A-Za-z0-9_-",
|
||||||
"message": "key must only consist of A-Za-z0-9_-",
|
"message": "key must only consist of A-Za-z0-9_-",
|
||||||
"translation": "key must only consist of A-Za-z0-9_-",
|
"translation": "key must only consist of A-Za-z0-9_-",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no service configuration \"{Name}\"",
|
"id": "no service configuration \"{Name}\"",
|
||||||
|
@ -657,7 +710,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "name"
|
"expr": "name"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "strings.Join(info.ExportCapabilities, \", \")"
|
"expr": "strings.Join(info.ExportCapabilities, \", \")"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "import: {ImportCapabilities__}",
|
"id": "import: {ImportCapabilities__}",
|
||||||
|
@ -31,7 +32,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "strings.Join(info.ImportCapabilities, \", \")"
|
"expr": "strings.Join(info.ImportCapabilities, \", \")"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Failed reading config: {Err}",
|
"id": "Failed reading config: {Err}",
|
||||||
|
@ -47,19 +49,22 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "err"
|
"expr": "err"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Service name",
|
"id": "Service name",
|
||||||
"message": "Service name",
|
"message": "Service name",
|
||||||
"translation": "Service name",
|
"translation": "Service name",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "a service with this name already exists",
|
"id": "a service with this name already exists",
|
||||||
"message": "a service with this name already exists",
|
"message": "a service with this name already exists",
|
||||||
"translation": "a service with this name already exists",
|
"translation": "a service with this name already exists",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Saved service {Name} using backend {Backend}",
|
"id": "Saved service {Name} using backend {Backend}",
|
||||||
|
@ -83,7 +88,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "service.Backend"
|
"expr": "service.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "The backend {Backend} requires authentication. Authenticate now?",
|
"id": "The backend {Backend} requires authentication. Authenticate now?",
|
||||||
|
@ -99,7 +105,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service.Backend"
|
"expr": "service.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Delete the service configuration \"{Service}\"?",
|
"id": "Delete the service configuration \"{Service}\"?",
|
||||||
|
@ -115,13 +122,15 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service"
|
"expr": "service"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Aborted",
|
"id": "Aborted",
|
||||||
"message": "Aborted",
|
"message": "Aborted",
|
||||||
"translation": "Aborted",
|
"translation": "Aborted",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Service \"{Name}\" deleted",
|
"id": "Service \"{Name}\" deleted",
|
||||||
|
@ -137,7 +146,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service.Name"
|
"expr": "service.Name"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Updated service {Name} using backend {Backend}",
|
"id": "Updated service {Name} using backend {Backend}",
|
||||||
|
@ -161,7 +171,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "service.Backend"
|
"expr": "service.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "backend: {Backend}",
|
"id": "backend: {Backend}",
|
||||||
|
@ -177,13 +188,15 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "s.Backend"
|
"expr": "s.Backend"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Token received, you can close this window now.",
|
"id": "Token received, you can close this window now.",
|
||||||
"message": "Token received, you can close this window now.",
|
"message": "Token received, you can close this window now.",
|
||||||
"translation": "Token received, you can close this window now.",
|
"translation": "Token received, you can close this window now.",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "backend {Backend} does not implement {InterfaceName}",
|
"id": "backend {Backend} does not implement {InterfaceName}",
|
||||||
|
@ -207,7 +220,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "interfaceName"
|
"expr": "interfaceName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "unknown backend \"{BackendName}\"",
|
"id": "unknown backend \"{BackendName}\"",
|
||||||
|
@ -223,67 +237,78 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "backendName"
|
"expr": "backendName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Client ID",
|
"id": "Client ID",
|
||||||
"message": "Client ID",
|
"message": "Client ID",
|
||||||
"translation": "Client ID",
|
"translation": "Client ID",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Client secret",
|
"id": "Client secret",
|
||||||
"message": "Client secret",
|
"message": "Client secret",
|
||||||
"translation": "Client secret",
|
"translation": "Client secret",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Server URL",
|
"id": "Server URL",
|
||||||
"message": "Server URL",
|
"message": "Server URL",
|
||||||
"translation": "Server URL",
|
"translation": "Server URL",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "User name",
|
"id": "User name",
|
||||||
"message": "User name",
|
"message": "User name",
|
||||||
"translation": "User name",
|
"translation": "User name",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Access token",
|
"id": "Access token",
|
||||||
"message": "Access token",
|
"message": "Access token",
|
||||||
"translation": "Access token",
|
"translation": "Access token",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "File path",
|
"id": "File path",
|
||||||
"message": "File path",
|
"message": "File path",
|
||||||
"translation": "File path",
|
"translation": "File path",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Append to file",
|
"id": "Append to file",
|
||||||
"message": "Append to file",
|
"message": "Append to file",
|
||||||
"translation": "Append to file",
|
"translation": "Append to file",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Playlist title",
|
"id": "Playlist title",
|
||||||
"message": "Playlist title",
|
"message": "Playlist title",
|
||||||
"translation": "Playlist title",
|
"translation": "Playlist title",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Unique playlist identifier",
|
"id": "Unique playlist identifier",
|
||||||
"message": "Unique playlist identifier",
|
"message": "Unique playlist identifier",
|
||||||
"translation": "Unique playlist identifier",
|
"translation": "Unique playlist identifier",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Check for duplicate listens on import (slower)",
|
"id": "Check for duplicate listens on import (slower)",
|
||||||
"message": "Check for duplicate listens on import (slower)",
|
"message": "Check for duplicate listens on import (slower)",
|
||||||
"translation": "Check for duplicate listens on import (slower)",
|
"translation": "Check for duplicate listens on import (slower)",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})",
|
"id": "Ignored duplicate listen {ListenedAt}: \"{TrackName}\" by {ArtistName} ({RecordingMBID})",
|
||||||
|
@ -323,43 +348,50 @@
|
||||||
"argNum": 4,
|
"argNum": 4,
|
||||||
"expr": "l.RecordingMBID"
|
"expr": "l.RecordingMBID"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Disable auto correction of submitted listens",
|
"id": "Disable auto correction of submitted listens",
|
||||||
"message": "Disable auto correction of submitted listens",
|
"message": "Disable auto correction of submitted listens",
|
||||||
"translation": "Disable auto correction of submitted listens",
|
"translation": "Disable auto correction of submitted listens",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignore skipped listens",
|
"id": "Ignore skipped listens",
|
||||||
"message": "Ignore skipped listens",
|
"message": "Ignore skipped listens",
|
||||||
"translation": "Ignore skipped listens",
|
"translation": "Ignore skipped listens",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Specify a time zone for the listen timestamps",
|
"id": "Specify a time zone for the listen timestamps",
|
||||||
"message": "Specify a time zone for the listen timestamps",
|
"message": "Specify a time zone for the listen timestamps",
|
||||||
"translation": "Specify a time zone for the listen timestamps",
|
"translation": "Specify a time zone for the listen timestamps",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Directory path",
|
"id": "Directory path",
|
||||||
"message": "Directory path",
|
"message": "Directory path",
|
||||||
"translation": "Directory path",
|
"translation": "Directory path",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Ignore listens in incognito mode",
|
"id": "Ignore listens in incognito mode",
|
||||||
"message": "Ignore listens in incognito mode",
|
"message": "Ignore listens in incognito mode",
|
||||||
"translation": "Ignore listens in incognito mode",
|
"translation": "Ignore listens in incognito mode",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Minimum playback duration for skipped tracks (seconds)",
|
"id": "Minimum playback duration for skipped tracks (seconds)",
|
||||||
"message": "Minimum playback duration for skipped tracks (seconds)",
|
"message": "Minimum playback duration for skipped tracks (seconds)",
|
||||||
"translation": "Minimum playback duration for skipped tracks (seconds)",
|
"translation": "Minimum playback duration for skipped tracks (seconds)",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Visit the URL for authorization: {URL}",
|
"id": "Visit the URL for authorization: {URL}",
|
||||||
|
@ -375,13 +407,15 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "authURL.URL"
|
"expr": "authURL.URL"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Error: OAuth state mismatch",
|
"id": "Error: OAuth state mismatch",
|
||||||
"message": "Error: OAuth state mismatch",
|
"message": "Error: OAuth state mismatch",
|
||||||
"translation": "Error: OAuth state mismatch",
|
"translation": "Error: OAuth state mismatch",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Access token received, you can use {Name} now.",
|
"id": "Access token received, you can use {Name} now.",
|
||||||
|
@ -397,24 +431,20 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "service.Name"
|
"expr": "service.Name"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "exporting",
|
"id": "exporting",
|
||||||
"message": "exporting",
|
"message": "exporting",
|
||||||
"translation": "exporting",
|
"translation": "exporting",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "importing",
|
"id": "importing",
|
||||||
"message": "importing",
|
"message": "importing",
|
||||||
"translation": "importing",
|
"translation": "importing",
|
||||||
"translatorComment": "Copied from source."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aborted",
|
|
||||||
"message": "aborted",
|
|
||||||
"translation": "aborted",
|
|
||||||
"translatorComment": "Copied from source.",
|
"translatorComment": "Copied from source.",
|
||||||
"fuzzy": true
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
|
@ -422,37 +452,43 @@
|
||||||
"id": "done",
|
"id": "done",
|
||||||
"message": "done",
|
"message": "done",
|
||||||
"translation": "done",
|
"translation": "done",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Yes",
|
"id": "Yes",
|
||||||
"message": "Yes",
|
"message": "Yes",
|
||||||
"translation": "Yes",
|
"translation": "Yes",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "No",
|
"id": "No",
|
||||||
"message": "No",
|
"message": "No",
|
||||||
"translation": "No",
|
"translation": "No",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no existing service configurations",
|
"id": "no existing service configurations",
|
||||||
"message": "no existing service configurations",
|
"message": "no existing service configurations",
|
||||||
"translation": "no existing service configurations",
|
"translation": "no existing service configurations",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Service",
|
"id": "Service",
|
||||||
"message": "Service",
|
"message": "Service",
|
||||||
"translation": "Service",
|
"translation": "Service",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Backend",
|
"id": "Backend",
|
||||||
"message": "Backend",
|
"message": "Backend",
|
||||||
"translation": "Backend",
|
"translation": "Backend",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Transferring {Entity} from {SourceName} to {TargetName}…",
|
"id": "Transferring {Entity} from {SourceName} to {TargetName}…",
|
||||||
|
@ -484,7 +520,8 @@
|
||||||
"argNum": 3,
|
"argNum": 3,
|
||||||
"expr": "c.targetName"
|
"expr": "c.targetName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "From timestamp: {Arg_1} ({Arg_2})",
|
"id": "From timestamp: {Arg_1} ({Arg_2})",
|
||||||
|
@ -506,7 +543,31 @@
|
||||||
"underlyingType": "interface{}",
|
"underlyingType": "interface{}",
|
||||||
"argNum": 2
|
"argNum": 2
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"message": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"translation": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
||||||
|
"translatorComment": "Copied from source.",
|
||||||
|
"placeholders": [
|
||||||
|
{
|
||||||
|
"id": "Arg_1",
|
||||||
|
"string": "%[1]v",
|
||||||
|
"type": "",
|
||||||
|
"underlyingType": "interface{}",
|
||||||
|
"argNum": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Arg_2",
|
||||||
|
"string": "%[2]s",
|
||||||
|
"type": "",
|
||||||
|
"underlyingType": "string",
|
||||||
|
"argNum": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
"id": "Imported {ImportCount} of {TotalCount} {Entity} into {TargetName}.",
|
||||||
|
@ -546,35 +607,15 @@
|
||||||
"argNum": 4,
|
"argNum": 4,
|
||||||
"expr": "c.targetName"
|
"expr": "c.targetName"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
},
|
"fuzzy": true
|
||||||
{
|
|
||||||
"id": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"message": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"translation": "Import failed, last reported timestamp was {Arg_1} ({Arg_2})",
|
|
||||||
"translatorComment": "Copied from source.",
|
|
||||||
"placeholders": [
|
|
||||||
{
|
|
||||||
"id": "Arg_1",
|
|
||||||
"string": "%[1]v",
|
|
||||||
"type": "",
|
|
||||||
"underlyingType": "interface{}",
|
|
||||||
"argNum": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Arg_2",
|
|
||||||
"string": "%[2]s",
|
|
||||||
"type": "",
|
|
||||||
"underlyingType": "string",
|
|
||||||
"argNum": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Import log:",
|
"id": "Import log:",
|
||||||
"message": "Import log:",
|
"message": "Import log:",
|
||||||
"translation": "Import log:",
|
"translation": "Import log:",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "{Type}: {Message}",
|
"id": "{Type}: {Message}",
|
||||||
|
@ -598,7 +639,8 @@
|
||||||
"argNum": 2,
|
"argNum": 2,
|
||||||
"expr": "entry.Message"
|
"expr": "entry.Message"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "invalid timestamp string \"{FlagValue}\"",
|
"id": "invalid timestamp string \"{FlagValue}\"",
|
||||||
|
@ -614,7 +656,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "flagValue"
|
"expr": "flagValue"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Latest timestamp: {Arg_1} ({Arg_2})",
|
"id": "Latest timestamp: {Arg_1} ({Arg_2})",
|
||||||
|
@ -636,19 +679,22 @@
|
||||||
"underlyingType": "interface{}",
|
"underlyingType": "interface{}",
|
||||||
"argNum": 2
|
"argNum": 2
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no configuration file defined, cannot write config",
|
"id": "no configuration file defined, cannot write config",
|
||||||
"message": "no configuration file defined, cannot write config",
|
"message": "no configuration file defined, cannot write config",
|
||||||
"translation": "no configuration file defined, cannot write config",
|
"translation": "no configuration file defined, cannot write config",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "key must only consist of A-Za-z0-9_-",
|
"id": "key must only consist of A-Za-z0-9_-",
|
||||||
"message": "key must only consist of A-Za-z0-9_-",
|
"message": "key must only consist of A-Za-z0-9_-",
|
||||||
"translation": "key must only consist of A-Za-z0-9_-",
|
"translation": "key must only consist of A-Za-z0-9_-",
|
||||||
"translatorComment": "Copied from source."
|
"translatorComment": "Copied from source.",
|
||||||
|
"fuzzy": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no service configuration \"{Name}\"",
|
"id": "no service configuration \"{Name}\"",
|
||||||
|
@ -664,7 +710,8 @@
|
||||||
"argNum": 1,
|
"argNum": 1,
|
||||||
"expr": "name"
|
"expr": "name"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"fuzzy": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue