diff --git a/cmd/backends.go b/cmd/backends.go index 43cae08..bf5c7d3 100644 --- a/cmd/backends.go +++ b/cmd/backends.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" "go.uploadedlobster.com/scotty/internal/backends" + "go.uploadedlobster.com/scotty/internal/i18n" ) var backendsCmd = &cobra.Command{ @@ -32,8 +33,8 @@ var backendsCmd = &cobra.Command{ backends := backends.GetBackends() for _, info := range backends { fmt.Printf("%s:\n", info.Name) - fmt.Printf("\texport: %s\n", strings.Join(info.ExportCapabilities, ", ")) - fmt.Printf("\timport: %s\n\n", strings.Join(info.ImportCapabilities, ", ")) + fmt.Println(i18n.Tr("\texport: %s", strings.Join(info.ExportCapabilities, ", "))) + fmt.Println(i18n.Tr("\timport: %s\n", strings.Join(info.ImportCapabilities, ", "))) } }, } diff --git a/cmd/beam.go b/cmd/beam.go index 51dd1c5..83d51db 100644 --- a/cmd/beam.go +++ b/cmd/beam.go @@ -26,7 +26,8 @@ var beamCmd = &cobra.Command{ Long: `Transfers data (listens, loves) between two configured services. The services must be configured and be able to handle export and import of -the data.`, +the data. See "scotty backends" for a list of backends and their supported +features.`, // Run: func(cmd *cobra.Command, args []string) { }, } diff --git a/cmd/root.go b/cmd/root.go index 8b4fdf8..50ada8f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,8 +21,8 @@ import ( "os" "github.com/spf13/cobra" - "github.com/spf13/viper" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/version" ) @@ -32,8 +32,8 @@ var cfgFile string var rootCmd = &cobra.Command{ Use: version.AppName, Short: "Beam data between music listening services", - Long: `Scotty transfers your listens/scrobbles between ListenBrainz and -various other listening and streaming services.`, + Long: `Scotty transfers listens and loves between different listening and streaming +services. Run "scotty backends" for a list of supported service backends.`, Version: version.AppVersion, // Uncomment the following line if your bare application // has an action associated with it: @@ -69,8 +69,6 @@ func init() { func initConfig() { // If a config file is found, read it in. if err := config.InitConfig(cfgFile); err != nil { - fmt.Fprintln(os.Stderr, "Failed reading config:", err) - } else { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + fmt.Fprintln(os.Stderr, i18n.Tr("Failed reading config: %v", err)) } } diff --git a/cmd/service_add.go b/cmd/service_add.go index d995d46..64c905b 100644 --- a/cmd/service_add.go +++ b/cmd/service_add.go @@ -29,12 +29,13 @@ import ( "github.com/spf13/cobra" "go.uploadedlobster.com/scotty/internal/cli" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" ) var serviceAddCmd = &cobra.Command{ Use: "add", Short: "Add a service configuration", - Long: `Add a service configuration.`, + Long: `Interactively add a service to the configuration file.`, Run: func(cmd *cobra.Command, args []string) { // Select backend backend, err := cli.SelectBackend("") @@ -42,12 +43,12 @@ var serviceAddCmd = &cobra.Command{ // Set service name prompt := promptui.Prompt{ - Label: "Service name", + Label: i18n.Tr("Service name"), Default: backend, Validate: func(s string) error { srv, _ := config.GetService(s) if srv != nil { - return errors.New("a service with this name already exists") + return errors.New(i18n.Tr("a service with this name already exists")) } return config.ValidateKey(s) }, @@ -69,7 +70,7 @@ var serviceAddCmd = &cobra.Command{ // Save the service config err = service.Save() cobra.CheckErr(err) - fmt.Printf("Saved service %v using backend %v\n", service.Name, service.Backend) + fmt.Println(i18n.Tr("Saved service %v using backend %v", service.Name, service.Backend)) }, } diff --git a/cmd/service_auth.go b/cmd/service_auth.go index cee8e2f..12da9dd 100644 --- a/cmd/service_auth.go +++ b/cmd/service_auth.go @@ -28,6 +28,7 @@ import ( "go.uploadedlobster.com/scotty/internal/backends" "go.uploadedlobster.com/scotty/internal/cli" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/storage" "golang.org/x/oauth2" @@ -35,12 +36,16 @@ import ( var serviceAuthCmd = &cobra.Command{ Use: "auth", - Short: "Authenticate with a backend", - Long: `For backends requiring authentication this command can be used to authenticate.`, + Short: "Authenticate a service", + Long: `For backends requiring authentication this command can be used to authenticate. + +Authentication is always done per configured service. That means you can have +multiple services using the same backend but different authentication.`, Run: func(cmd *cobra.Command, args []string) { serviceConfig := cli.GetServiceConfigFromFlag(cmd, "service") if serviceConfig == nil { - cobra.CheckErr(errors.New("failed loading service configuration")) + err := errors.New(i18n.Tr("failed loading service configuration")) + cobra.CheckErr(err) } backend, err := backends.ResolveBackend[models.OAuth2Authenticator](serviceConfig) cobra.CheckErr(err) @@ -64,14 +69,14 @@ var serviceAuthCmd = &cobra.Command{ auth.RunOauth2CallbackServer(*redirectURL, authUrl.Param, responseChan) // Open the URL - fmt.Printf("Visit the URL for the auth dialog: %v\n", authUrl.Url) + fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authUrl.Url)) err = browser.OpenURL(authUrl.Url) cobra.CheckErr(err) // Retrieve the code from the authentication callback code := <-responseChan if code.State != authUrl.State { - cobra.CompErrorln("Error: oauth state mismatch") + cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch")) os.Exit(1) } @@ -86,13 +91,13 @@ var serviceAuthCmd = &cobra.Command{ err = db.SetOAuth2Token(serviceConfig.Name, tok) cobra.CheckErr(err) - fmt.Printf("Access token received, you can use %v now.\n\n", serviceConfig.Name) + fmt.Println(i18n.Tr("Access token received, you can use %v now.\n", serviceConfig.Name)) }, } func init() { serviceCmd.AddCommand(serviceAuthCmd) - serviceAuthCmd.Flags().StringP("service", "s", "", "Service configuration (required)") + serviceAuthCmd.Flags().StringP("service", "s", "", "service configuration (required)") serviceAuthCmd.MarkFlagRequired("service") } diff --git a/cmd/service_delete.go b/cmd/service_delete.go index dd7b431..519e9b6 100644 --- a/cmd/service_delete.go +++ b/cmd/service_delete.go @@ -26,29 +26,30 @@ import ( "github.com/spf13/cobra" "go.uploadedlobster.com/scotty/internal/cli" + "go.uploadedlobster.com/scotty/internal/i18n" ) var serviceDeleteCmd = &cobra.Command{ Use: "delete", Short: "Delete existing service configuration", - Long: `Delete an existing service configuration.`, + Long: `Delete an existing service from the configuration file.`, Run: func(cmd *cobra.Command, args []string) { service, err := cli.SelectService() cobra.CheckErr(err) // Prompt for deletion - delete, err := cli.PromptYesNo(fmt.Sprintf("Delete the service configuration \"%v\"?", service)) + delete, err := cli.PromptYesNo(i18n.Tr("Delete the service configuration \"%v\"?", service)) cobra.CheckErr(err) if !delete { - fmt.Println("Aborted") + fmt.Println(i18n.Tr("Aborted")) return } // Delete the service config err = service.Delete() cobra.CheckErr(err) - fmt.Printf("Service \"%v\" deleted\n", service.Name) + fmt.Println(i18n.Tr("Service \"%v\" deleted\n", service.Name)) }, } diff --git a/cmd/service_edit.go b/cmd/service_edit.go index 9f4accf..873fe76 100644 --- a/cmd/service_edit.go +++ b/cmd/service_edit.go @@ -26,12 +26,13 @@ import ( "github.com/spf13/cobra" "go.uploadedlobster.com/scotty/internal/cli" + "go.uploadedlobster.com/scotty/internal/i18n" ) var serviceEditCmd = &cobra.Command{ Use: "edit", Short: "Edit existing service configuration", - Long: `Edit an existing service configuration.`, + Long: `Edit an existing service in the configuration file.`, Run: func(cmd *cobra.Command, args []string) { service, err := cli.SelectService() cobra.CheckErr(err) @@ -48,7 +49,7 @@ var serviceEditCmd = &cobra.Command{ // Save the service config err = service.Save() cobra.CheckErr(err) - fmt.Printf("Updated service %v using backend %v\n", service.Name, service.Backend) + fmt.Println(i18n.Tr("Updated service %v using backend %v\n", service.Name, service.Backend)) }, } diff --git a/cmd/service_list.go b/cmd/service_list.go index 23d64d9..f51ff00 100644 --- a/cmd/service_list.go +++ b/cmd/service_list.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" ) var serviceListCmd = &cobra.Command{ @@ -37,7 +38,7 @@ var serviceListCmd = &cobra.Command{ for _, s := range config.AllServicesAsList() { fmt.Printf("%v\n", s.Name) if verbose { - fmt.Printf("\tbackend: %v\n", s.Backend) + fmt.Println(i18n.Tr("\tbackend: %v", s.Backend)) for k, v := range s.ConfigValues { fmt.Printf("\t%v: %v\n", k, v) } diff --git a/internal/auth/callback.go b/internal/auth/callback.go index 0ad9c9d..6ae0128 100644 --- a/internal/auth/callback.go +++ b/internal/auth/callback.go @@ -19,13 +19,15 @@ import ( "fmt" "net/http" "net/url" + + "go.uploadedlobster.com/scotty/internal/i18n" ) func RunOauth2CallbackServer(redirectURL url.URL, param string, responseChan chan CodeResponse) { http.HandleFunc(redirectURL.Path, func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get(param) state := r.URL.Query().Get("state") - fmt.Fprint(w, "Token received, you can close this window now.") + fmt.Fprint(w, i18n.Tr("Token received, you can close this window now.")) responseChan <- CodeResponse{ Code: code, State: state, diff --git a/internal/backends/backends.go b/internal/backends/backends.go index 8c17d10..8cec9e0 100644 --- a/internal/backends/backends.go +++ b/internal/backends/backends.go @@ -33,6 +33,7 @@ import ( "go.uploadedlobster.com/scotty/internal/backends/spotify" "go.uploadedlobster.com/scotty/internal/backends/subsonic" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) @@ -72,7 +73,7 @@ func ResolveBackend[T interface{}](config *config.ServiceConfig) (T, error) { if implements { result = backend.(T) } else { - err = fmt.Errorf("backend %s does not implement %s", config.Backend, interfaceName) + err = fmt.Errorf(i18n.Tr("backend %s does not implement %s", config.Backend, interfaceName)) } return result, err @@ -81,7 +82,7 @@ func ResolveBackend[T interface{}](config *config.ServiceConfig) (T, error) { func BackendByName(backendName string) (models.Backend, error) { backendType := knownBackends[backendName] if backendType == nil { - return nil, fmt.Errorf("unknown backend \"%s\"", backendName) + return nil, fmt.Errorf(i18n.Tr("unknown backend \"%s\"", backendName)) } return backendType(), nil } diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index 05145fb..802d1ab 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -24,6 +24,7 @@ import ( "go.uploadedlobster.com/scotty/internal/auth" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "golang.org/x/oauth2" ) @@ -39,11 +40,11 @@ func (b *DeezerApiBackend) Name() string { return "deezer" } func (b *DeezerApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "client-id", - Label: "Client ID", + Label: i18n.Tr("Client ID"), Type: models.String, }, { Name: "client-secret", - Label: "Client secret", + Label: i18n.Tr("Client secret"), Type: models.Secret, }} } diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index 8e808f9..05017d3 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -21,6 +21,7 @@ import ( "time" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) @@ -36,15 +37,15 @@ func (b *FunkwhaleApiBackend) Name() string { return "funkwhale" } func (b *FunkwhaleApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "server-url", - Label: "Server URL", + Label: i18n.Tr("Server URL"), Type: models.String, }, { Name: "username", - Label: "User name", + Label: i18n.Tr("User name"), Type: models.String, }, { Name: "token", - Label: "Access token", + Label: i18n.Tr("Access token"), Type: models.Secret, }} } diff --git a/internal/backends/jspf/jspf.go b/internal/backends/jspf/jspf.go index 29ddcdc..944c6e6 100644 --- a/internal/backends/jspf/jspf.go +++ b/internal/backends/jspf/jspf.go @@ -22,6 +22,7 @@ import ( "time" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/pkg/jspf" ) @@ -39,19 +40,19 @@ func (b *JSPFBackend) Name() string { return "jspf" } func (b *JSPFBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "file-path", - Label: "File path", + Label: i18n.Tr("File path"), Type: models.String, }, { Name: "title", - Label: "Playlist title", + Label: i18n.Tr("Playlist title"), Type: models.String, }, { Name: "username", - Label: "User name", + Label: i18n.Tr("User name"), Type: models.String, }, { Name: "identifier", - Label: "Unique playlist identifier", + Label: i18n.Tr("Unique playlist identifier"), Type: models.String, }} } diff --git a/internal/backends/lastfm/lastfm.go b/internal/backends/lastfm/lastfm.go index c823248..c4c2ec3 100644 --- a/internal/backends/lastfm/lastfm.go +++ b/internal/backends/lastfm/lastfm.go @@ -25,6 +25,7 @@ import ( "github.com/shkh/lastfm-go/lastfm" "go.uploadedlobster.com/scotty/internal/auth" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "golang.org/x/oauth2" ) @@ -46,15 +47,15 @@ func (b *LastfmApiBackend) Name() string { return "lastfm" } func (b *LastfmApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "username", - Label: "User name", + Label: i18n.Tr("User name"), Type: models.String, }, { Name: "client-id", - Label: "Client ID", + Label: i18n.Tr("Client ID"), Type: models.String, }, { Name: "client-secret", - Label: "Client secret", + Label: i18n.Tr("Client secret"), Type: models.Secret, }} } diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index 2d3dc95..02b6fb4 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -22,6 +22,7 @@ import ( "time" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/version" ) @@ -37,11 +38,11 @@ func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" } func (b *ListenBrainzApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "username", - Label: "User name", + Label: i18n.Tr("User name"), Type: models.String, }, { Name: "token", - Label: "Access token", + Label: i18n.Tr("Access token"), Type: models.Secret, }} } diff --git a/internal/backends/maloja/maloja.go b/internal/backends/maloja/maloja.go index c47f67a..8057200 100644 --- a/internal/backends/maloja/maloja.go +++ b/internal/backends/maloja/maloja.go @@ -23,6 +23,7 @@ import ( "time" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) @@ -36,15 +37,15 @@ func (b *MalojaApiBackend) Name() string { return "maloja" } func (b *MalojaApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "server-url", - Label: "Server URL", + Label: i18n.Tr("Server URL"), Type: models.String, }, { Name: "token", - Label: "Access token", + Label: i18n.Tr("Access token"), Type: models.Secret, }, { Name: "nofix", - Label: "Disable auto correction of submitted listens", + Label: i18n.Tr("Disable auto correction of submitted listens"), Type: models.Bool, }} } diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index 6964b7e..e09f100 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -23,6 +23,7 @@ import ( "time" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) @@ -39,15 +40,15 @@ func (b *ScrobblerLogBackend) Name() string { return "scrobbler-log" } func (b *ScrobblerLogBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "file-path", - Label: "File path", + Label: i18n.Tr("File path"), Type: models.String, }, { Name: "include-skipped", - Label: "Include skipped listens", + Label: i18n.Tr("Include skipped listens"), Type: models.Bool, }, { Name: "append", - Label: "Append to file", + Label: i18n.Tr("Append to file"), Type: models.Bool, }} } diff --git a/internal/backends/spotify/spotify.go b/internal/backends/spotify/spotify.go index 8a0bc09..9b623cb 100644 --- a/internal/backends/spotify/spotify.go +++ b/internal/backends/spotify/spotify.go @@ -26,6 +26,7 @@ import ( "go.uploadedlobster.com/scotty/internal/auth" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "golang.org/x/oauth2" "golang.org/x/oauth2/spotify" @@ -42,11 +43,11 @@ func (b *SpotifyApiBackend) Name() string { return "spotify" } func (b *SpotifyApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "client-id", - Label: "Client ID", + Label: i18n.Tr("Client ID"), Type: models.String, }, { Name: "client-secret", - Label: "Client secret", + Label: i18n.Tr("Client secret"), Type: models.Secret, }} } diff --git a/internal/backends/subsonic/subsonic.go b/internal/backends/subsonic/subsonic.go index 7defb87..6560319 100644 --- a/internal/backends/subsonic/subsonic.go +++ b/internal/backends/subsonic/subsonic.go @@ -23,6 +23,7 @@ import ( "github.com/delucks/go-subsonic" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/version" ) @@ -37,15 +38,15 @@ func (b *SubsonicApiBackend) Name() string { return "subsonic" } func (b *SubsonicApiBackend) Options() []models.BackendOption { return []models.BackendOption{{ Name: "server-url", - Label: "Server URL", + Label: i18n.Tr("Server URL"), Type: models.String, }, { Name: "username", - Label: "User name", + Label: i18n.Tr("User name"), Type: models.String, }, { Name: "token", - Label: "Access token", + Label: i18n.Tr("Access token"), Type: models.Secret, }} } diff --git a/internal/cli/prompt.go b/internal/cli/prompt.go index 19038e9..9a4ba70 100644 --- a/internal/cli/prompt.go +++ b/internal/cli/prompt.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/manifoldco/promptui" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/models" ) @@ -63,10 +64,12 @@ func PromptBool(opt models.BackendOption) (bool, error) { } func PromptYesNo(label string) (bool, error) { + yes := i18n.Tr("Yes") + no := i18n.Tr("No") sel := promptui.Select{ Label: label, - Items: []string{"Yes", "No"}, + Items: []string{yes, no}, } _, val, err := sel.Run() - return val == "Yes", err + return val == yes, err } diff --git a/internal/cli/services.go b/internal/cli/services.go index d755607..a8bd813 100644 --- a/internal/cli/services.go +++ b/internal/cli/services.go @@ -23,16 +23,17 @@ import ( "github.com/manifoldco/promptui" "go.uploadedlobster.com/scotty/internal/backends" "go.uploadedlobster.com/scotty/internal/config" + "go.uploadedlobster.com/scotty/internal/i18n" ) func SelectService() (config.ServiceConfig, error) { services := config.AllServicesAsList() if len(services) == 0 { - err := errors.New("no existing service configurations") + err := errors.New(i18n.Tr("no existing service configurations")) return config.ServiceConfig{}, err } sel := promptui.Select{ - Label: "Service", + Label: i18n.Tr("Service"), Items: services, Size: 10, } @@ -49,7 +50,7 @@ func SelectBackend(selected string) (string, error) { return b.Name == selected }) sel := promptui.Select{ - Label: "Backend", + Label: i18n.Tr("Backend"), Items: backendList, CursorPos: i, Size: 10, diff --git a/internal/config/config.go b/internal/config/config.go index 6d68d00..f52de99 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,6 +27,7 @@ import ( "github.com/pelletier/go-toml/v2" "github.com/spf13/cobra" "github.com/spf13/viper" + "go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/version" ) @@ -75,7 +76,7 @@ func InitConfig(cfgFile string) error { func WriteConfig(removedKeys ...string) error { file := viper.ConfigFileUsed() if len(file) == 0 { - return errors.New("no configuration file defined, cannot write config") + return errors.New(i18n.Tr("no configuration file defined, cannot write config")) } configMap := viper.AllSettings() @@ -124,7 +125,7 @@ func ValidateKey(key string) error { } else if found { return nil } else { - return fmt.Errorf("key must only consist of A-Za-z0-9_-") + return fmt.Errorf(i18n.Tr("key must only consist of A-Za-z0-9_-")) } } diff --git a/internal/config/services.go b/internal/config/services.go index 8a191f3..2895cb2 100644 --- a/internal/config/services.go +++ b/internal/config/services.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cast" "github.com/spf13/viper" + "go.uploadedlobster.com/scotty/internal/i18n" ) type ServiceConfig struct { @@ -123,5 +124,5 @@ func GetService(name string) (*ServiceConfig, error) { return &service, nil } - return nil, fmt.Errorf("no service configuration \"%v\"", name) + return nil, fmt.Errorf(i18n.Tr("no service configuration \"%v\"", name)) } diff --git a/internal/translations/catalog.go b/internal/translations/catalog.go index 797ce2f..7ccdaa0 100644 --- a/internal/translations/catalog.go +++ b/internal/translations/catalog.go @@ -39,23 +39,71 @@ func init() { } var messageKeyToIndex = map[string]int{ + "\tbackend: %v": 24, + "\texport: %s": 10, + "\timport: %s\n": 11, + "Aborted": 21, + "Access token": 32, + "Access token received, you can use %v now.\n": 19, + "Append to file": 38, + "Backend": 43, + "Client ID": 28, + "Client secret": 29, + "Delete the service configuration \"%v\"?": 20, + "Disable auto correction of submitted listens": 36, "During the import the following errors occurred:": 7, - "Error: %v\n": 8, - "From timestamp: %v (%v)": 4, + "Error: %v\n": 8, + "Error: OAuth state mismatch": 18, + "Failed reading config: %v": 12, + "File path": 33, + "From timestamp: %v (%v)": 4, "Import failed, last reported timestamp was %v (%v)": 5, "Imported %v of %v %s into %v.": 6, + "Include skipped listens": 37, "Latest timestamp: %v (%v)\n": 9, + "No": 40, + "Playlist title": 34, + "Saved service %v using backend %v": 15, + "Server URL": 30, + "Service": 42, + "Service \"%v\" deleted\n": 22, + "Service name": 13, + "Token received, you can close this window now.": 25, "Transferring %s from %s to %s...": 3, + "Unique playlist identifier": 35, + "Updated service %v using backend %v\n": 23, + "User name": 31, + "Visit the URL for authorization: %v": 17, + "Yes": 39, + "a service with this name already exists": 14, + "backend %s does not implement %s": 26, "done": 2, "exporting": 0, + "failed loading service configuration": 16, "importing": 1, + "key must only consist of A-Za-z0-9_-": 45, + "no configuration file defined, cannot write config": 44, + "no existing service configurations": 41, + "no service configuration \"%v\"": 46, + "unknown backend \"%s\"": 27, } -var deIndex = []uint32{ // 11 elements +var deIndex = []uint32{ // 48 elements + // Entry 0 - 1F 0x00000000, 0x0000000b, 0x00000016, 0x0000001d, 0x00000046, 0x00000064, 0x000000a4, 0x000000cf, - 0x00000106, 0x00000119, 0x00000142, -} // Size: 68 bytes + 0x00000106, 0x00000119, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + // Entry 20 - 3F + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, + 0x00000142, 0x00000142, 0x00000142, 0x00000142, +} // Size: 216 bytes const deData string = "" + // Size: 322 bytes "\x02exportiere\x02importiere\x02fertig\x02Übertrage %[1]s von %[2]s nach" + @@ -65,17 +113,45 @@ const deData string = "" + // Size: 322 bytes "\x00\x01\x0a\x0e\x02Fehler: %[1]v\x04\x00\x01\x0a$\x02Neuester Zeitstemp" + "el: %[1]v (%[2]v)" -var enIndex = []uint32{ // 11 elements +var enIndex = []uint32{ // 48 elements + // Entry 0 - 1F 0x00000000, 0x0000000a, 0x00000014, 0x00000019, 0x00000043, 0x00000061, 0x0000009a, 0x000000c4, - 0x000000f5, 0x00000107, 0x0000012c, -} // Size: 68 bytes + 0x000000f5, 0x00000107, 0x0000012c, 0x0000013f, + 0x00000153, 0x00000170, 0x0000017d, 0x000001a5, + 0x000001cd, 0x000001f2, 0x00000219, 0x00000235, + 0x00000268, 0x00000292, 0x0000029a, 0x000002b7, + 0x000002e6, 0x000002fa, 0x00000329, 0x00000350, + 0x00000368, 0x00000372, 0x00000380, 0x0000038b, + // Entry 20 - 3F + 0x00000395, 0x000003a2, 0x000003ac, 0x000003bb, + 0x000003d6, 0x00000403, 0x0000041b, 0x0000042a, + 0x0000042e, 0x00000431, 0x00000454, 0x0000045c, + 0x00000464, 0x00000497, 0x000004bc, 0x000004dd, +} // Size: 216 bytes -const enData string = "" + // Size: 300 bytes +const enData string = "" + // Size: 1245 bytes "\x02exporting\x02importing\x02done\x02Transferring %[1]s from %[2]s to %" + "[3]s...\x02From timestamp: %[1]v (%[2]v)\x02Import failed, last reported" + " timestamp was %[1]v (%[2]v)\x02Imported %[1]v of %[2]v %[3]s into %[4]v" + ".\x02During the import the following errors occurred:\x04\x00\x01\x0a" + - "\x0d\x02Error: %[1]v\x04\x00\x01\x0a \x02Latest timestamp: %[1]v (%[2]v)" + "\x0d\x02Error: %[1]v\x04\x00\x01\x0a \x02Latest timestamp: %[1]v (%[2]v)" + + "\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" + + " this name already exists\x02Saved service %[1]v using backend %[2]v\x02" + + "failed loading service configuration\x02Visit the URL for authorization:" + + " %[1]v\x02Error: OAuth state mismatch\x04\x00\x01\x0a.\x02Access token r" + + "eceived, you can use %[1]v now.\x02Delete the service configuration \x22" + + "%[1]v\x22?\x02Aborted\x04\x00\x01\x0a\x18\x02Service \x22%[1]v\x22 delet" + + "ed\x04\x00\x01\x0a*\x02Updated service %[1]v using backend %[2]v\x04\x01" + + "\x09\x00\x0f\x02backend: %[1]v\x02Token received, you can close this win" + + "dow now.\x02backend %[1]s does not implement %[2]s\x02unknown backend " + + "\x22%[1]s\x22\x02Client ID\x02Client secret\x02Server URL\x02User name" + + "\x02Access token\x02File path\x02Playlist title\x02Unique playlist ident" + + "ifier\x02Disable auto correction of submitted listens\x02Include skipped" + + " listens\x02Append to file\x02Yes\x02No\x02no existing service configura" + + "tions\x02Service\x02Backend\x02no configuration file defined, cannot wri" + + "te config\x02key must only consist of A-Za-z0-9_-\x02no service configur" + + "ation \x22%[1]v\x22" - // Total table size 758 bytes (0KiB); checksum: D85383CE + // Total table size 1999 bytes (1KiB); checksum: FFEA1B2A diff --git a/internal/translations/locales/de/messages.gotext.json b/internal/translations/locales/de/messages.gotext.json index a2cd62c..0c41b77 100644 --- a/internal/translations/locales/de/messages.gotext.json +++ b/internal/translations/locales/de/messages.gotext.json @@ -1,6 +1,82 @@ { "language": "de", "messages": [ + { + "id": "Authenticate a service", + "message": "Authenticate a service", + "translation": "An einem Service anmelden" + }, + { + "id": "For backends requiring authentication this command can be used to authenticate.\n\nAuthentication is always done per configured service. That means you can have\nmultiple services using the same backend but different authentication.", + "message": "For backends requiring authentication this command can be used to authenticate.\n\nAuthentication is always done per configured service. That means you can have\nmultiple services using the same backend but different authentication.", + "translation": "" + }, + { + "id": "failed loading service configuration", + "message": "failed loading service configuration", + "translation": "" + }, + { + "id": "Visit the URL for authorization: {Url}", + "message": "Visit the URL for authorization: {Url}", + "translation": "", + "placeholders": [ + { + "id": "Url", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "authUrl.Url" + } + ] + }, + { + "id": "Error: OAuth state mismatch", + "message": "Error: OAuth state mismatch", + "translation": "" + }, + { + "id": "Access token received, you can use {Name} now.", + "message": "Access token received, you can use {Name} now.", + "translation": "", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "serviceConfig.Name" + } + ] + }, + { + "id": "service configuration (required)", + "message": "service configuration (required)", + "translation": "Servicekonfiguration (notwendig)" + }, + { + "id": "exporting", + "message": "exporting", + "translation": "exportiere", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "importing", + "message": "importing", + "translation": "importiere", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "done", + "message": "done", + "translation": "fertig", + "translatorComment": "Copied from source.", + "fuzzy": true + }, { "id": "Transferring {Entity} from {SourceName} to {TargetName}...", "message": "Transferring {Entity} from {SourceName} to {TargetName}...", @@ -159,27 +235,6 @@ "expr": "result.LastTimestamp.Unix()" } ] - }, - { - "id": "exporting", - "message": "exporting", - "translatorComment": "Copied from source.", - "fuzzy": true, - "translation": "exportiere" - }, - { - "id": "importing", - "message": "importing", - "translatorComment": "Copied from source.", - "fuzzy": true, - "translation": "importiere" - }, - { - "id": "done", - "message": "done", - "translatorComment": "Copied from source.", - "fuzzy": true, - "translation": "fertig" } ] } diff --git a/internal/translations/locales/de/out.gotext.json b/internal/translations/locales/de/out.gotext.json index 321d448..6efac3d 100644 --- a/internal/translations/locales/de/out.gotext.json +++ b/internal/translations/locales/de/out.gotext.json @@ -1,6 +1,295 @@ { "language": "de", "messages": [ + { + "id": "export: {ExportCapabilities__}", + "message": "export: {ExportCapabilities__}", + "translation": "", + "placeholders": [ + { + "id": "ExportCapabilities__", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "strings.Join(info.ExportCapabilities, \", \")" + } + ] + }, + { + "id": "import: {ImportCapabilities__}", + "message": "import: {ImportCapabilities__}", + "translation": "", + "placeholders": [ + { + "id": "ImportCapabilities__", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "strings.Join(info.ImportCapabilities, \", \")" + } + ] + }, + { + "id": "Failed reading config: {Err}", + "message": "Failed reading config: {Err}", + "translation": "", + "placeholders": [ + { + "id": "Err", + "string": "%[1]v", + "type": "error", + "underlyingType": "interface{Error() string}", + "argNum": 1, + "expr": "err" + } + ] + }, + { + "id": "Service name", + "message": "Service name", + "translation": "" + }, + { + "id": "a service with this name already exists", + "message": "a service with this name already exists", + "translation": "" + }, + { + "id": "Saved service {Name} using backend {Backend}", + "message": "Saved service {Name} using backend {Backend}", + "translation": "", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "service.Name" + }, + { + "id": "Backend", + "string": "%[2]v", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "service.Backend" + } + ] + }, + { + "id": "failed loading service configuration", + "message": "failed loading service configuration", + "translation": "" + }, + { + "id": "Visit the URL for authorization: {Url}", + "message": "Visit the URL for authorization: {Url}", + "translation": "", + "placeholders": [ + { + "id": "Url", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "authUrl.Url" + } + ] + }, + { + "id": "Error: OAuth state mismatch", + "message": "Error: OAuth state mismatch", + "translation": "" + }, + { + "id": "Access token received, you can use {Name} now.", + "message": "Access token received, you can use {Name} now.", + "translation": "", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "serviceConfig.Name" + } + ] + }, + { + "id": "Delete the service configuration \"{Service}\"?", + "message": "Delete the service configuration \"{Service}\"?", + "translation": "", + "placeholders": [ + { + "id": "Service", + "string": "%[1]v", + "type": "go.uploadedlobster.com/scotty/internal/config.ServiceConfig", + "underlyingType": "struct{Name string; Backend string; ConfigValues map[string]any}", + "argNum": 1, + "expr": "service" + } + ] + }, + { + "id": "Aborted", + "message": "Aborted", + "translation": "" + }, + { + "id": "Service \"{Name}\" deleted", + "message": "Service \"{Name}\" deleted", + "translation": "", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "service.Name" + } + ] + }, + { + "id": "Updated service {Name} using backend {Backend}", + "message": "Updated service {Name} using backend {Backend}", + "translation": "", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "service.Name" + }, + { + "id": "Backend", + "string": "%[2]v", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "service.Backend" + } + ] + }, + { + "id": "backend: {Backend}", + "message": "backend: {Backend}", + "translation": "", + "placeholders": [ + { + "id": "Backend", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "s.Backend" + } + ] + }, + { + "id": "Token received, you can close this window now.", + "message": "Token received, you can close this window now.", + "translation": "" + }, + { + "id": "backend {Backend} does not implement {InterfaceName}", + "message": "backend {Backend} does not implement {InterfaceName}", + "translation": "", + "placeholders": [ + { + "id": "Backend", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "config.Backend" + }, + { + "id": "InterfaceName", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "interfaceName" + } + ] + }, + { + "id": "unknown backend \"{BackendName}\"", + "message": "unknown backend \"{BackendName}\"", + "translation": "", + "placeholders": [ + { + "id": "BackendName", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "backendName" + } + ] + }, + { + "id": "Client ID", + "message": "Client ID", + "translation": "" + }, + { + "id": "Client secret", + "message": "Client secret", + "translation": "" + }, + { + "id": "Server URL", + "message": "Server URL", + "translation": "" + }, + { + "id": "User name", + "message": "User name", + "translation": "" + }, + { + "id": "Access token", + "message": "Access token", + "translation": "" + }, + { + "id": "File path", + "message": "File path", + "translation": "" + }, + { + "id": "Playlist title", + "message": "Playlist title", + "translation": "" + }, + { + "id": "Unique playlist identifier", + "message": "Unique playlist identifier", + "translation": "" + }, + { + "id": "Disable auto correction of submitted listens", + "message": "Disable auto correction of submitted listens", + "translation": "" + }, + { + "id": "Include skipped listens", + "message": "Include skipped listens", + "translation": "" + }, + { + "id": "Append to file", + "message": "Append to file", + "translation": "" + }, { "id": "exporting", "message": "exporting", @@ -22,6 +311,31 @@ "translatorComment": "Copied from source.", "fuzzy": true }, + { + "id": "Yes", + "message": "Yes", + "translation": "" + }, + { + "id": "No", + "message": "No", + "translation": "" + }, + { + "id": "no existing service configurations", + "message": "no existing service configurations", + "translation": "" + }, + { + "id": "Service", + "message": "Service", + "translation": "" + }, + { + "id": "Backend", + "message": "Backend", + "translation": "" + }, { "id": "Transferring {Entity} from {SourceName} to {TargetName}...", "message": "Transferring {Entity} from {SourceName} to {TargetName}...", @@ -180,6 +494,31 @@ "expr": "result.LastTimestamp.Unix()" } ] + }, + { + "id": "no configuration file defined, cannot write config", + "message": "no configuration file defined, cannot write config", + "translation": "" + }, + { + "id": "key must only consist of A-Za-z0-9_-", + "message": "key must only consist of A-Za-z0-9_-", + "translation": "" + }, + { + "id": "no service configuration \"{Name}\"", + "message": "no service configuration \"{Name}\"", + "translation": "", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "name" + } + ] } ] } \ No newline at end of file diff --git a/internal/translations/locales/en/out.gotext.json b/internal/translations/locales/en/out.gotext.json index 651e329..23d2710 100644 --- a/internal/translations/locales/en/out.gotext.json +++ b/internal/translations/locales/en/out.gotext.json @@ -1,6 +1,353 @@ { "language": "en", "messages": [ + { + "id": "export: {ExportCapabilities__}", + "message": "export: {ExportCapabilities__}", + "translation": "export: {ExportCapabilities__}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "ExportCapabilities__", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "strings.Join(info.ExportCapabilities, \", \")" + } + ], + "fuzzy": true + }, + { + "id": "import: {ImportCapabilities__}", + "message": "import: {ImportCapabilities__}", + "translation": "import: {ImportCapabilities__}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "ImportCapabilities__", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "strings.Join(info.ImportCapabilities, \", \")" + } + ], + "fuzzy": true + }, + { + "id": "Failed reading config: {Err}", + "message": "Failed reading config: {Err}", + "translation": "Failed reading config: {Err}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Err", + "string": "%[1]v", + "type": "error", + "underlyingType": "interface{Error() string}", + "argNum": 1, + "expr": "err" + } + ], + "fuzzy": true + }, + { + "id": "Service name", + "message": "Service name", + "translation": "Service name", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "a service with this name already exists", + "message": "a service with this name already exists", + "translation": "a service with this name already exists", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Saved service {Name} using backend {Backend}", + "message": "Saved service {Name} using backend {Backend}", + "translation": "Saved service {Name} using backend {Backend}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "service.Name" + }, + { + "id": "Backend", + "string": "%[2]v", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "service.Backend" + } + ], + "fuzzy": true + }, + { + "id": "failed loading service configuration", + "message": "failed loading service configuration", + "translation": "failed loading service configuration", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Visit the URL for authorization: {Url}", + "message": "Visit the URL for authorization: {Url}", + "translation": "Visit the URL for authorization: {Url}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Url", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "authUrl.Url" + } + ], + "fuzzy": true + }, + { + "id": "Error: OAuth state mismatch", + "message": "Error: OAuth state mismatch", + "translation": "Error: OAuth state mismatch", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Access token received, you can use {Name} now.", + "message": "Access token received, you can use {Name} now.", + "translation": "Access token received, you can use {Name} now.", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "serviceConfig.Name" + } + ], + "fuzzy": true + }, + { + "id": "Delete the service configuration \"{Service}\"?", + "message": "Delete the service configuration \"{Service}\"?", + "translation": "Delete the service configuration \"{Service}\"?", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Service", + "string": "%[1]v", + "type": "go.uploadedlobster.com/scotty/internal/config.ServiceConfig", + "underlyingType": "struct{Name string; Backend string; ConfigValues map[string]any}", + "argNum": 1, + "expr": "service" + } + ], + "fuzzy": true + }, + { + "id": "Aborted", + "message": "Aborted", + "translation": "Aborted", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Service \"{Name}\" deleted", + "message": "Service \"{Name}\" deleted", + "translation": "Service \"{Name}\" deleted", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "service.Name" + } + ], + "fuzzy": true + }, + { + "id": "Updated service {Name} using backend {Backend}", + "message": "Updated service {Name} using backend {Backend}", + "translation": "Updated service {Name} using backend {Backend}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "service.Name" + }, + { + "id": "Backend", + "string": "%[2]v", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "service.Backend" + } + ], + "fuzzy": true + }, + { + "id": "backend: {Backend}", + "message": "backend: {Backend}", + "translation": "backend: {Backend}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Backend", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "s.Backend" + } + ], + "fuzzy": true + }, + { + "id": "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.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "backend {Backend} does not implement {InterfaceName}", + "message": "backend {Backend} does not implement {InterfaceName}", + "translation": "backend {Backend} does not implement {InterfaceName}", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Backend", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "config.Backend" + }, + { + "id": "InterfaceName", + "string": "%[2]s", + "type": "string", + "underlyingType": "string", + "argNum": 2, + "expr": "interfaceName" + } + ], + "fuzzy": true + }, + { + "id": "unknown backend \"{BackendName}\"", + "message": "unknown backend \"{BackendName}\"", + "translation": "unknown backend \"{BackendName}\"", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "BackendName", + "string": "%[1]s", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "backendName" + } + ], + "fuzzy": true + }, + { + "id": "Client ID", + "message": "Client ID", + "translation": "Client ID", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Client secret", + "message": "Client secret", + "translation": "Client secret", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Server URL", + "message": "Server URL", + "translation": "Server URL", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "User name", + "message": "User name", + "translation": "User name", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Access token", + "message": "Access token", + "translation": "Access token", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "File path", + "message": "File path", + "translation": "File path", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Playlist title", + "message": "Playlist title", + "translation": "Playlist title", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Unique playlist identifier", + "message": "Unique playlist identifier", + "translation": "Unique playlist identifier", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Disable auto correction of submitted listens", + "message": "Disable auto correction of submitted listens", + "translation": "Disable auto correction of submitted listens", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Include skipped listens", + "message": "Include skipped listens", + "translation": "Include skipped listens", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Append to file", + "message": "Append to file", + "translation": "Append to file", + "translatorComment": "Copied from source.", + "fuzzy": true + }, { "id": "exporting", "message": "exporting", @@ -22,6 +369,41 @@ "translatorComment": "Copied from source.", "fuzzy": true }, + { + "id": "Yes", + "message": "Yes", + "translation": "Yes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "No", + "message": "No", + "translation": "No", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "no existing service configurations", + "message": "no existing service configurations", + "translation": "no existing service configurations", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Service", + "message": "Service", + "translation": "Service", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Backend", + "message": "Backend", + "translation": "Backend", + "translatorComment": "Copied from source.", + "fuzzy": true + }, { "id": "Transferring {Entity} from {SourceName} to {TargetName}...", "message": "Transferring {Entity} from {SourceName} to {TargetName}...", @@ -194,6 +576,37 @@ } ], "fuzzy": true + }, + { + "id": "no configuration file defined, cannot write config", + "message": "no configuration file defined, cannot write config", + "translation": "no configuration file defined, cannot write config", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "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_-", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "no service configuration \"{Name}\"", + "message": "no service configuration \"{Name}\"", + "translation": "no service configuration \"{Name}\"", + "translatorComment": "Copied from source.", + "placeholders": [ + { + "id": "Name", + "string": "%[1]v", + "type": "string", + "underlyingType": "string", + "argNum": 1, + "expr": "name" + } + ], + "fuzzy": true } ] } \ No newline at end of file