Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .mockery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ packages:
JobLocale:
config:
dir: services/jobs/locales/sdkmocks
github.com/Smartling/api-sdk-go/api/job/string:
interfaces:
JobString:
config:
dir: services/jobs/strings/sdkmocks
github.com/Smartling/api-sdk-go/api/glossary:
interfaces:
Glossary:
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ test_integration:
go test ./tests/cmd/mt/detect/...
go test ./tests/cmd/mt/translate/...
go test ./tests/cmd/jobs/locales/...
go test ./tests/cmd/jobs/strings/...
65 changes: 65 additions & 0 deletions cmd/jobs/strings/add/cmd_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package add

import (
"fmt"
"os"

stringscmd "github.com/Smartling/smartling-cli/cmd/jobs/strings"
"github.com/Smartling/smartling-cli/output"
"github.com/Smartling/smartling-cli/services/helpers/rlog"

"github.com/spf13/cobra"
)

const (
hashcodeFlag = "hashcode"
targetLocaleFlag = "target-locale"
moveEnabledFlag = "move-enabled"
)

// NewJobStringsAddCmd returns new command to job string add
func NewJobStringsAddCmd(initializer stringscmd.SrvInitializer) *cobra.Command {
var (
hashcodes []string
targetLocales []string
moveEnabled bool
)
addCmd := &cobra.Command{
Use: "add <translationJobUid|translationJobName>",
Short: "Add strings to a translation job.",
Long: `Assign strings (by hashcode) to an existing translation job, identified by UID or name.`,
Args: cobra.ExactArgs(1),
Example: `
# Add two strings to a job

smartling-cli jobs strings add aabbccdd1122 --hashcode h1 --hashcode h2

# Add a string for specific locales, moving it if it already belongs to another job

smartling-cli jobs strings add "Website Q1 2026" --hashcode h1 --target-locale fr-FR --move-enabled
`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

params, err := resolveParams(args[0], hashcodes, targetLocales, moveEnabled)
if err != nil {
return fmt.Errorf("failed to resolve add params: %w", err)
}
format, err := cmd.Flags().GetString("output")
if err != nil {
return err
}
return run(ctx, initializer, params, output.Params{Format: format})
},
}

addCmd.Flags().StringArrayVar(&hashcodes, hashcodeFlag, nil, "String hashcode to add (repeatable, required).")
addCmd.Flags().StringArrayVar(&targetLocales, targetLocaleFlag, nil, "Target locale to add the strings to (repeatable; default all job locales).")
addCmd.Flags().BoolVar(&moveEnabled, moveEnabledFlag, false, "Move the string into this job if it already belongs to another job for a locale.")
Comment thread
az-smartling marked this conversation as resolved.
if err := addCmd.MarkFlagRequired(hashcodeFlag); err != nil {
rlog.Errorf("failed to mark --%s required: %s", hashcodeFlag, err)
os.Exit(1)
}

return addCmd
}
29 changes: 29 additions & 0 deletions cmd/jobs/strings/add/resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package add

import (
rootcmd "github.com/Smartling/smartling-cli/cmd"
clierror "github.com/Smartling/smartling-cli/services/helpers/cli_error"
"github.com/Smartling/smartling-cli/services/helpers/rlog"
srv "github.com/Smartling/smartling-cli/services/jobs/strings"
)

func resolveParams(jobUIDOrName string, hashcodes, targetLocales []string, moveEnabled bool) (srv.AddParams, error) {
rlog.Debugf("resolving add params")

cnf, err := rootcmd.Config()
if err != nil {
return srv.AddParams{}, clierror.UIError{
Operation: "config",
Err: err,
Description: "failed to read config",
}
}

return srv.AddParams{
ProjectID: cnf.ProjectID,
JobUIDOrName: jobUIDOrName,
Hashcodes: hashcodes,
TargetLocaleIDs: targetLocales,
MoveEnabled: moveEnabled,
}, nil
}
56 changes: 56 additions & 0 deletions cmd/jobs/strings/add/resolve_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package add

import (
"os"
"path/filepath"
"testing"

rootcmd "github.com/Smartling/smartling-cli/cmd"
)

func TestMain(m *testing.M) {
rootcmd.ConfigureLogger()
os.Exit(m.Run())
}

func writeConfig(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "smartling.yml")
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
return path
}

func Test_resolveParams(t *testing.T) {
// Config() always requires these.
t.Setenv("SMARTLING_USER_ID", "test-user")
t.Setenv("SMARTLING_SECRET", "test-secret")

root := rootcmd.NewRootCmd()
cfgPath := writeConfig(t, "project_id: config-project-id\n")
if err := root.PersistentFlags().Set("config", cfgPath); err != nil {
t.Fatalf("set config flag: %v", err)
}
t.Cleanup(func() { _ = root.PersistentFlags().Set("config", "") })

params, err := resolveParams("aabbccdd1122", []string{"h1", "h2"}, []string{"fr-FR"}, true)
if err != nil {
t.Fatalf("resolveParams() error = %v", err)
}
if params.ProjectID != "config-project-id" {
t.Errorf("ProjectID = %q, want config-project-id", params.ProjectID)
}
if params.JobUIDOrName != "aabbccdd1122" {
t.Errorf("JobUIDOrName = %q, want aabbccdd1122", params.JobUIDOrName)
}
if len(params.Hashcodes) != 2 || params.Hashcodes[0] != "h1" {
t.Errorf("Hashcodes = %v, want [h1 h2]", params.Hashcodes)
}
if len(params.TargetLocaleIDs) != 1 || params.TargetLocaleIDs[0] != "fr-FR" {
t.Errorf("TargetLocaleIDs = %v, want [fr-FR]", params.TargetLocaleIDs)
}
if !params.MoveEnabled {
t.Error("MoveEnabled = false, want true")
}
}
47 changes: 47 additions & 0 deletions cmd/jobs/strings/add/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package add

import (
"context"
"errors"
"fmt"

stringscmd "github.com/Smartling/smartling-cli/cmd/jobs/strings"
"github.com/Smartling/smartling-cli/output"
"github.com/Smartling/smartling-cli/output/static"
clierror "github.com/Smartling/smartling-cli/services/helpers/cli_error"
"github.com/Smartling/smartling-cli/services/helpers/rlog"
srv "github.com/Smartling/smartling-cli/services/jobs/strings"

jobapi "github.com/Smartling/api-sdk-go/api/job"
)

func run(ctx context.Context,
initializer stringscmd.SrvInitializer,
params srv.AddParams,
outputParams output.Params,
) error {
rlog.Debugf("running jobs strings add with params: %v", params)
stringsSrv, err := initializer.InitJobStringsSrv(ctx)
if err != nil {
return clierror.UIError{
Operation: "init",
Err: err,
Description: "unable to initialize Job Strings service",
}
}

addOutput, err := stringsSrv.RunAdd(ctx, params)
if err != nil {
if errors.Is(err, jobapi.ErrNotFound) {
return clierror.UIError{
Operation: "find job",
Err: err,
Description: fmt.Sprintf("no job found for %q", params.JobUIDOrName),
}
}
return err
}

static.GetOutputFormat[srv.MutateOutput](outputParams.Format).FormatAndRender(addOutput)
return nil
}
33 changes: 33 additions & 0 deletions cmd/jobs/strings/cmd_job_strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package jobstrings

import (
"github.com/spf13/cobra"
)

// NewJobStringsCmd returns new job strings command
func NewJobStringsCmd() *cobra.Command {
jobStringsCmd := &cobra.Command{
Use: "strings",
Short: "Manage strings on a translation job.",
Long: `Add, remove, or list the strings on an existing translation job.

Strings are identified by hashcode. Use these commands to assign strings to a
job, detach them, or inspect which strings a job currently contains.`,
Example: `
# Add strings to a job

smartling-cli jobs strings add <translationJobUid|translationJobName> --hashcode <hashcode>

# Remove strings from a job

smartling-cli jobs strings remove <translationJobUid|translationJobName> --hashcode <hashcode>

# List a job's strings

smartling-cli jobs strings list <translationJobUid|translationJobName>

`,
}

return jobStringsCmd
}
59 changes: 59 additions & 0 deletions cmd/jobs/strings/list/cmd_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package list

import (
"fmt"

stringscmd "github.com/Smartling/smartling-cli/cmd/jobs/strings"
"github.com/Smartling/smartling-cli/output"

"github.com/spf13/cobra"
)

const (
targetLocaleFlag = "target-locale"
limitFlag = "limit"
offsetFlag = "offset"
)

// NewJobStringsListCmd returns new command to job string list
func NewJobStringsListCmd(initializer stringscmd.SrvInitializer) *cobra.Command {
var (
targetLocale string
limit uint32
offset uint32
)
listCmd := &cobra.Command{
Use: "list <translationJobUid|translationJobName>",
Short: "List the strings on a translation job.",
Long: `List the strings (by hashcode and target locale) assigned to a translation job, identified by UID or name.`,
Args: cobra.ExactArgs(1),
Example: `
# List a job's strings

smartling-cli jobs strings list aabbccdd1122

# List strings for one locale as a table

smartling-cli jobs strings list "Website Q1 2026" --target-locale fr-FR --output table
`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

params, err := resolveParams(args[0], targetLocale, limit, offset)
if err != nil {
return fmt.Errorf("failed to resolve list params: %w", err)
}
format, err := cmd.Flags().GetString("output")
if err != nil {
return err
}
return run(ctx, initializer, params, output.Params{Format: format})
},
}

listCmd.Flags().StringVar(&targetLocale, targetLocaleFlag, "", "Filter strings by target locale.")
listCmd.Flags().Uint32Var(&limit, limitFlag, 0, "Maximum number of strings to return.")
listCmd.Flags().Uint32Var(&offset, offsetFlag, 0, "Number of strings to skip.")

return listCmd
}
29 changes: 29 additions & 0 deletions cmd/jobs/strings/list/resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package list

import (
rootcmd "github.com/Smartling/smartling-cli/cmd"
clierror "github.com/Smartling/smartling-cli/services/helpers/cli_error"
"github.com/Smartling/smartling-cli/services/helpers/rlog"
srv "github.com/Smartling/smartling-cli/services/jobs/strings"
)

func resolveParams(jobUIDOrName, targetLocale string, limit, offset uint32) (srv.ListParams, error) {
rlog.Debugf("resolving list params")

cnf, err := rootcmd.Config()
if err != nil {
return srv.ListParams{}, clierror.UIError{
Operation: "config",
Err: err,
Description: "failed to read config",
}
}

return srv.ListParams{
ProjectID: cnf.ProjectID,
JobUIDOrName: jobUIDOrName,
TargetLocaleID: targetLocale,
Limit: limit,
Offset: offset,
}, nil
}
53 changes: 53 additions & 0 deletions cmd/jobs/strings/list/resolve_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package list

import (
"os"
"path/filepath"
"testing"

rootcmd "github.com/Smartling/smartling-cli/cmd"
)

func TestMain(m *testing.M) {
rootcmd.ConfigureLogger()
os.Exit(m.Run())
}

func writeConfig(t *testing.T, content string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "smartling.yml")
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
return path
}

func Test_resolveParams(t *testing.T) {
// Config() always requires these.
t.Setenv("SMARTLING_USER_ID", "test-user")
t.Setenv("SMARTLING_SECRET", "test-secret")

root := rootcmd.NewRootCmd()
cfgPath := writeConfig(t, "project_id: config-project-id\n")
if err := root.PersistentFlags().Set("config", cfgPath); err != nil {
t.Fatalf("set config flag: %v", err)
}
t.Cleanup(func() { _ = root.PersistentFlags().Set("config", "") })

params, err := resolveParams("aabbccdd1122", "fr-FR", 10, 20)
if err != nil {
t.Fatalf("resolveParams() error = %v", err)
}
if params.ProjectID != "config-project-id" {
t.Errorf("ProjectID = %q, want config-project-id", params.ProjectID)
}
if params.JobUIDOrName != "aabbccdd1122" {
t.Errorf("JobUIDOrName = %q, want aabbccdd1122", params.JobUIDOrName)
}
if params.TargetLocaleID != "fr-FR" {
t.Errorf("TargetLocaleID = %q, want fr-FR", params.TargetLocaleID)
}
if params.Limit != 10 || params.Offset != 20 {
t.Errorf("Limit/Offset = %d/%d, want 10/20", params.Limit, params.Offset)
}
}
Loading