Skip to content

Commit 8f9c658

Browse files
authored
Merge pull request #1 from fdddf/optimize/dedup-cli-and-fix-race
Dedup CLI commands and fix server race + debug noise
2 parents 8387ab8 + b7db281 commit 8f9c658

8 files changed

Lines changed: 326 additions & 660 deletions

File tree

cmd/baidu.go

Lines changed: 4 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package cmd
22

33
import (
4-
"context"
5-
"fmt"
64
"time"
75

8-
"github.com/fdddf/xcstrings-translator/internal/model"
96
"github.com/fdddf/xcstrings-translator/internal/translator"
107
"github.com/spf13/cobra"
118
"github.com/spf13/viper"
@@ -34,153 +31,10 @@ func init() {
3431
}
3532

3633
func runBaiduTranslate(cmd *cobra.Command, args []string) error {
37-
// Get configuration values with fallbacks
38-
inputFile := viper.GetString("global.input_file")
39-
if cmd.Flags().Changed("input") {
40-
inputFile, _ = cmd.Flags().GetString("input")
41-
}
34+
g := resolveGlobalOptions(cmd)
35+
appID := stringFlag(cmd, "app-id", "baidu.app_id")
36+
appSecret := stringFlag(cmd, "app-secret", "baidu.app_secret")
4237

43-
outputFile := viper.GetString("global.output_file")
44-
if cmd.Flags().Changed("output") {
45-
outputFile, _ = cmd.Flags().GetString("output")
46-
}
47-
48-
sourceLang := viper.GetString("global.source_language")
49-
if cmd.Flags().Changed("source-language") {
50-
sourceLang, _ = cmd.Flags().GetString("source-language")
51-
}
52-
53-
targetLangs := viper.GetStringSlice("global.target_languages")
54-
if cmd.Flags().Changed("target-languages") {
55-
targetLangs, _ = cmd.Flags().GetStringSlice("target-languages")
56-
}
57-
58-
concurrency := viper.GetInt("global.concurrency")
59-
if cmd.Flags().Changed("concurrency") {
60-
concurrency, _ = cmd.Flags().GetInt("concurrency")
61-
}
62-
63-
verbose := viper.GetBool("global.verbose")
64-
if cmd.Flags().Changed("verbose") {
65-
verbose, _ = cmd.Flags().GetBool("verbose")
66-
}
67-
68-
// Get Baidu specific config
69-
appID := viper.GetString("baidu.app_id")
70-
if cmd.Flags().Changed("app-id") {
71-
appID, _ = cmd.Flags().GetString("app-id")
72-
}
73-
74-
appSecret := viper.GetString("baidu.app_secret")
75-
if cmd.Flags().Changed("app-secret") {
76-
appSecret, _ = cmd.Flags().GetString("app-secret")
77-
}
78-
79-
if verbose {
80-
fmt.Printf("Starting Baidu Translate with:\n")
81-
fmt.Printf(" Input file: %s\n", inputFile)
82-
fmt.Printf(" Output file: %s\n", outputFile)
83-
fmt.Printf(" Source language: %s\n", sourceLang)
84-
fmt.Printf(" Target languages: %v\n", targetLangs)
85-
fmt.Printf(" Concurrency: %d\n", concurrency)
86-
fmt.Printf(" AppID: %s\n", appID)
87-
}
88-
89-
// Load xcstrings file
90-
if verbose {
91-
fmt.Println("Loading xcstrings file...")
92-
}
93-
xcstrings, err := model.LoadXCStrings(inputFile)
94-
if err != nil {
95-
fmt.Printf("Error loading xcstrings file: %v\n", err)
96-
return err
97-
}
98-
99-
// Override source language if specified
100-
if sourceLang != "" {
101-
xcstrings.SourceLanguage = sourceLang
102-
}
103-
104-
// Create translator
10538
provider := translator.NewBaiduTranslator(appID, appSecret)
106-
107-
// Create translation service
108-
service := translator.NewTranslationService(
109-
provider,
110-
concurrency,
111-
300*time.Second, // 5 minute timeout
112-
)
113-
114-
// Run translation
115-
if verbose {
116-
fmt.Println("Starting translation...")
117-
}
118-
ctx := context.Background()
119-
var responses []model.TranslationResponse
120-
for _, target := range targetLangs {
121-
reqs := translator.CreateTranslationRequestsForLanguage(xcstrings, target)
122-
if len(reqs) == 0 {
123-
continue
124-
}
125-
126-
if verbose {
127-
fmt.Printf("Translating to %s (%d strings)...\n", target, len(reqs))
128-
}
129-
130-
progress := translator.NewVerboseProgressReporter(target, len(reqs), verbose)
131-
batchResponses, err := service.TranslateBatch(ctx, reqs, progress)
132-
responses = append(responses, batchResponses...)
133-
if err != nil {
134-
fmt.Printf("Translation failed for %s: %v\n", target, err)
135-
return nil
136-
}
137-
}
138-
139-
if len(responses) == 0 {
140-
fmt.Println("No strings to translate. Exiting.")
141-
return nil
142-
}
143-
144-
// Process results
145-
successCount := 0
146-
errorCount := 0
147-
for _, resp := range responses {
148-
if resp.Error != nil {
149-
if verbose {
150-
fmt.Printf("Error translating %s to %s: %v\n", resp.Key, resp.TargetLanguage, resp.Error)
151-
}
152-
errorCount++
153-
} else {
154-
successCount++
155-
}
156-
}
157-
158-
if verbose {
159-
fmt.Printf("Translation completed: %d successful, %d failed\n", successCount, errorCount)
160-
}
161-
162-
if errorCount > 0 {
163-
fmt.Println("Errors detected during translation. Stopping without applying translations.")
164-
return nil
165-
}
166-
167-
// Apply translations
168-
if verbose {
169-
fmt.Println("Applying translations...")
170-
}
171-
translator.ApplyTranslations(xcstrings, responses)
172-
173-
// Save output
174-
if verbose {
175-
fmt.Printf("Saving output to %s...\n", outputFile)
176-
}
177-
err = model.SaveXCStrings(outputFile, xcstrings)
178-
if err != nil {
179-
fmt.Printf("Error saving output file: %v\n", err)
180-
return err
181-
}
182-
183-
fmt.Printf("Translation completed successfully!\n")
184-
fmt.Printf("Results saved to: %s\n", outputFile)
185-
return nil
39+
return runTranslation(g, "Baidu Translate", provider, 300*time.Second)
18640
}

cmd/common.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/fdddf/xcstrings-translator/internal/model"
9+
"github.com/fdddf/xcstrings-translator/internal/translator"
10+
11+
"github.com/spf13/cobra"
12+
"github.com/spf13/viper"
13+
)
14+
15+
// globalOptions holds the shared input/output/language settings, resolved from
16+
// flags first and falling back to the config file / environment via viper.
17+
type globalOptions struct {
18+
InputFile string
19+
OutputFile string
20+
SourceLang string
21+
TargetLangs []string
22+
Concurrency int
23+
Verbose bool
24+
}
25+
26+
// resolveGlobalOptions reads the shared persistent flags for a command.
27+
func resolveGlobalOptions(cmd *cobra.Command) globalOptions {
28+
return globalOptions{
29+
InputFile: stringFlag(cmd, "input", "global.input_file"),
30+
OutputFile: stringFlag(cmd, "output", "global.output_file"),
31+
SourceLang: stringFlag(cmd, "source-language", "global.source_language"),
32+
TargetLangs: stringSliceFlag(cmd, "target-languages", "global.target_languages"),
33+
Concurrency: intFlag(cmd, "concurrency", "global.concurrency"),
34+
Verbose: boolFlag(cmd, "verbose", "global.verbose"),
35+
}
36+
}
37+
38+
// The *Flag helpers return the flag value when the user set it explicitly,
39+
// otherwise the value resolved by viper (config file / env / default).
40+
func stringFlag(cmd *cobra.Command, name, viperKey string) string {
41+
if cmd.Flags().Changed(name) {
42+
v, _ := cmd.Flags().GetString(name)
43+
return v
44+
}
45+
return viper.GetString(viperKey)
46+
}
47+
48+
func stringSliceFlag(cmd *cobra.Command, name, viperKey string) []string {
49+
if cmd.Flags().Changed(name) {
50+
v, _ := cmd.Flags().GetStringSlice(name)
51+
return v
52+
}
53+
return viper.GetStringSlice(viperKey)
54+
}
55+
56+
func intFlag(cmd *cobra.Command, name, viperKey string) int {
57+
if cmd.Flags().Changed(name) {
58+
v, _ := cmd.Flags().GetInt(name)
59+
return v
60+
}
61+
return viper.GetInt(viperKey)
62+
}
63+
64+
func boolFlag(cmd *cobra.Command, name, viperKey string) bool {
65+
if cmd.Flags().Changed(name) {
66+
v, _ := cmd.Flags().GetBool(name)
67+
return v
68+
}
69+
return viper.GetBool(viperKey)
70+
}
71+
72+
func float64Flag(cmd *cobra.Command, name, viperKey string) float64 {
73+
if cmd.Flags().Changed(name) {
74+
v, _ := cmd.Flags().GetFloat64(name)
75+
return v
76+
}
77+
return viper.GetFloat64(viperKey)
78+
}
79+
80+
// runTranslation executes the shared load → translate → apply → save pipeline
81+
// used by every provider subcommand.
82+
func runTranslation(g globalOptions, providerName string, provider model.TranslationProvider, timeout time.Duration) error {
83+
if g.InputFile == "" {
84+
return fmt.Errorf("input file path is required; set --input or global.input_file in the config")
85+
}
86+
if g.OutputFile == "" {
87+
return fmt.Errorf("output file path is required; set --output or global.output_file in the config")
88+
}
89+
90+
if g.Verbose {
91+
fmt.Printf("Starting %s with:\n", providerName)
92+
fmt.Printf(" Input file: %s\n", g.InputFile)
93+
fmt.Printf(" Output file: %s\n", g.OutputFile)
94+
fmt.Printf(" Source language: %s\n", g.SourceLang)
95+
fmt.Printf(" Target languages: %v\n", g.TargetLangs)
96+
fmt.Printf(" Concurrency: %d\n", g.Concurrency)
97+
fmt.Println("Loading xcstrings file...")
98+
}
99+
100+
xcstrings, err := model.LoadXCStrings(g.InputFile)
101+
if err != nil {
102+
return fmt.Errorf("loading xcstrings file: %w", err)
103+
}
104+
105+
if g.SourceLang != "" {
106+
xcstrings.SourceLanguage = g.SourceLang
107+
}
108+
109+
service := translator.NewTranslationService(provider, g.Concurrency, timeout)
110+
ctx := context.Background()
111+
112+
if g.Verbose {
113+
fmt.Println("Starting translation...")
114+
}
115+
116+
var responses []model.TranslationResponse
117+
for _, target := range g.TargetLangs {
118+
reqs := translator.CreateTranslationRequestsForLanguage(xcstrings, target)
119+
if len(reqs) == 0 {
120+
continue
121+
}
122+
123+
if g.Verbose {
124+
fmt.Printf("Translating to %s (%d strings)...\n", target, len(reqs))
125+
}
126+
127+
progress := translator.NewVerboseProgressReporter(target, len(reqs), g.Verbose)
128+
batch, err := service.TranslateBatch(ctx, reqs, progress)
129+
responses = append(responses, batch...)
130+
if err != nil {
131+
return fmt.Errorf("translation failed for %s: %w", target, err)
132+
}
133+
}
134+
135+
if len(responses) == 0 {
136+
fmt.Println("No strings to translate. Exiting.")
137+
return nil
138+
}
139+
140+
successCount, errorCount := 0, 0
141+
for _, resp := range responses {
142+
if resp.Error != nil {
143+
if g.Verbose {
144+
fmt.Printf("Error translating %s to %s: %v\n", resp.Key, resp.TargetLanguage, resp.Error)
145+
}
146+
errorCount++
147+
} else {
148+
successCount++
149+
}
150+
}
151+
152+
if g.Verbose {
153+
fmt.Printf("Translation completed: %d successful, %d failed\n", successCount, errorCount)
154+
}
155+
156+
if errorCount > 0 {
157+
fmt.Println("Errors detected during translation. Stopping without applying translations.")
158+
return nil
159+
}
160+
161+
if g.Verbose {
162+
fmt.Println("Applying translations...")
163+
}
164+
translator.ApplyTranslations(xcstrings, responses)
165+
166+
if g.Verbose {
167+
fmt.Printf("Saving output to %s...\n", g.OutputFile)
168+
}
169+
if err := model.SaveXCStrings(g.OutputFile, xcstrings); err != nil {
170+
return fmt.Errorf("saving output file: %w", err)
171+
}
172+
173+
fmt.Println("Translation completed successfully!")
174+
fmt.Printf("Results saved to: %s\n", g.OutputFile)
175+
return nil
176+
}

0 commit comments

Comments
 (0)