Mark user interface strings

For now exclude command help, as cobra itself is not localizable yet.
This commit is contained in:
Philipp Wolfer 2023-12-09 16:43:14 +01:00
parent 511b71b909
commit d6ca8d33f7
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
27 changed files with 1005 additions and 95 deletions

View file

@ -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, ", ")))
}
},
}

View file

@ -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) { },
}

View file

@ -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))
}
}

View file

@ -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))
},
}

View file

@ -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")
}

View file

@ -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))
},
}

View file

@ -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))
},
}

View file

@ -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)
}

View file

@ -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,

View file

@ -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
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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,
}}
}

View file

@ -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
}

View file

@ -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,

View file

@ -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_-"))
}
}

View file

@ -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))
}

View file

@ -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

View file

@ -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"
}
]
}

View file

@ -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"
}
]
}
]
}

View file

@ -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
}
]
}