From 82e656821dcce1c2ae58c681065db17c169aa4dc Mon Sep 17 00:00:00 2001 From: LinkinStars Date: Wed, 17 Jul 2024 11:04:20 +0800 Subject: [PATCH] feat(cmd): add i18n command --- cmd/command.go | 32 +++++++- internal/cli/i18n.go | 158 ++++++++++++++++++++++++++++++++++++++++ internal/cli/install.go | 20 +++-- 3 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 internal/cli/i18n.go diff --git a/cmd/command.go b/cmd/command.go index 03194dc3..b8d9c561 100644 --- a/cmd/command.go +++ b/cmd/command.go @@ -49,6 +49,10 @@ var ( upgradeVersion string // The fields that need to be set to the default value configFields []string + // i18nSourcePath i18n from path + i18nSourcePath string + // i18nTargetPath i18n to path + i18nTargetPath string ) func init() { @@ -68,7 +72,11 @@ func init() { configCmd.Flags().StringSliceVarP(&configFields, "with", "w", []string{}, "the fields that need to be set to the default value, eg: -w allow_password_login") - for _, cmd := range []*cobra.Command{initCmd, checkCmd, runCmd, dumpCmd, upgradeCmd, buildCmd, pluginCmd, configCmd} { + i18nCmd.Flags().StringVarP(&i18nSourcePath, "source", "s", "", "i18n source path, eg: -f ./i18n/source") + + i18nCmd.Flags().StringVarP(&i18nTargetPath, "target", "t", "", "i18n target path, eg: -t ./i18n/target") + + for _, cmd := range []*cobra.Command{initCmd, checkCmd, runCmd, dumpCmd, upgradeCmd, buildCmd, pluginCmd, configCmd, i18nCmd} { rootCmd.AddCommand(cmd) } } @@ -271,6 +279,28 @@ To run answer, use: } }, } + + // i18nCmd used to merge i18n files + i18nCmd = &cobra.Command{ + Use: "i18n", + Short: "overwrite i18n files", + Long: `Merge i18n files from plugins to original i18n files. It will overwrite the original i18n files`, + Run: func(_ *cobra.Command, _ []string) { + if err := cli.ReplaceI18nFilesLocal(i18nTargetPath); err != nil { + fmt.Printf("replace i18n files failed %v", err) + } else { + fmt.Printf("replace i18n files successfully\n") + } + + fmt.Printf("try to merge i18n files from %q to %q\n", i18nSourcePath, i18nTargetPath) + + if err := cli.MergeI18nFilesLocal(i18nTargetPath, i18nSourcePath); err != nil { + fmt.Printf("merge i18n files failed %v", err) + } else { + fmt.Printf("merge i18n files successfully\n") + } + }, + } ) // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/internal/cli/i18n.go b/internal/cli/i18n.go new file mode 100644 index 00000000..f498d58d --- /dev/null +++ b/internal/cli/i18n.go @@ -0,0 +1,158 @@ +package cli + +import ( + "fmt" + "github.com/apache/incubator-answer/i18n" + "github.com/apache/incubator-answer/pkg/dir" + "github.com/apache/incubator-answer/pkg/writer" + "gopkg.in/yaml.v3" + "os" + "path/filepath" + "strings" +) + +type YamlPluginContent struct { + Plugin map[string]any `yaml:"plugin"` +} + +// ReplaceI18nFilesLocal replace i18n files +func ReplaceI18nFilesLocal(i18nDir string) error { + i18nList, err := i18n.I18n.ReadDir(".") + if err != nil { + fmt.Println(err.Error()) + return err + } + fmt.Printf("[i18n] find i18n bundle %d\n", len(i18nList)) + for _, item := range i18nList { + path := filepath.Join(i18nDir, item.Name()) + content, err := i18n.I18n.ReadFile(item.Name()) + if err != nil { + continue + } + exist := dir.CheckFileExist(path) + if exist { + fmt.Printf("[i18n] install %s file exist, try to replace it\n", item.Name()) + if err = os.Remove(path); err != nil { + fmt.Println(err) + } + } + fmt.Printf("[i18n] install %s bundle...\n", item.Name()) + err = writer.WriteFile(path, string(content)) + if err != nil { + fmt.Printf("[i18n] install %s bundle fail: %s\n", item.Name(), err.Error()) + } else { + fmt.Printf("[i18n] install %s bundle success\n", item.Name()) + } + } + return nil +} + +// MergeI18nFilesLocal merge i18n files +func MergeI18nFilesLocal(originalI18nDir, targetI18nDir string) (err error) { + pluginAllTranslations := make(map[string]*YamlPluginContent) + + err = findI18nFileInDir(pluginAllTranslations, targetI18nDir) + if err != nil { + return err + } + + entries, err := os.ReadDir(originalI18nDir) + if err != nil { + return err + } + + for _, file := range entries { + // ignore directory + if file.IsDir() { + continue + } + // ignore non-YAML file + filename := file.Name() + if filepath.Ext(filename) != ".yaml" && filename != "i18n.yaml" { + continue + } + + // if plugin don't have this translation file, ignore it + if pluginAllTranslations[filename] == nil { + continue + } + + out, _ := yaml.Marshal(pluginAllTranslations[filename]) + + buf, err := os.OpenFile(filepath.Join(originalI18nDir, filename), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Printf("[i18n] read translation file failed: %s %s\n", filename, err) + continue + } + + _, _ = buf.WriteString("\n") + _, _ = buf.Write(out) + _ = buf.Close() + fmt.Printf("[i18n] merge i18n file: %s success\n", filename) + } + + return nil +} + +// find i18n file in dir +func findI18nFileInDir(pluginAllTranslations map[string]*YamlPluginContent, i18nDir string) error { + // if i18n dir is not i18n, find deeper + dirBase := filepath.Base(i18nDir) + if dirBase != "i18n" { + if strings.HasPrefix(dirBase, ".") { + return nil + } + // find all i18n dir in target dir + targetDirs, err := os.ReadDir(i18nDir) + if err != nil { + return err + } + + for _, targetDir := range targetDirs { + if targetDir.IsDir() { + if err := findI18nFileInDir(pluginAllTranslations, filepath.Join(i18nDir, targetDir.Name())); err != nil { + fmt.Printf("[i18n] find i18n file in dir failed: %s %s\n", targetDir.Name(), err) + } + } + } + return nil + } + + fmt.Printf("[i18n] find i18n file in dir: %s\n", i18nDir) + + // if i18nDir is i18n, find all yaml files + entries, err := os.ReadDir(i18nDir) + if err != nil { + return err + } + + for _, file := range entries { + // ignore directory + if file.IsDir() { + continue + } + // ignore non-YAML file + if filepath.Ext(file.Name()) != ".yaml" { + continue + } + buf, err := os.ReadFile(filepath.Join(i18nDir, file.Name())) + if err != nil { + fmt.Printf("[i18n] read translation file failed: %s %s\n", file.Name(), err) + continue + } + + translation := &YamlPluginContent{} + if err = yaml.Unmarshal(buf, translation); err != nil { + fmt.Printf("[i18n] unmarshal translation file failed: %s %s\n", file.Name(), err) + continue + } + + if pluginAllTranslations[file.Name()] == nil { + pluginAllTranslations[file.Name()] = &YamlPluginContent{Plugin: make(map[string]any)} + } + for k, v := range translation.Plugin { + pluginAllTranslations[file.Name()].Plugin[k] = v + } + } + return nil +} diff --git a/internal/cli/install.go b/internal/cli/install.go index 128f28c9..48b87843 100644 --- a/internal/cli/install.go +++ b/internal/cli/install.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "sync" "github.com/apache/incubator-answer/configs" "github.com/apache/incubator-answer/i18n" @@ -37,10 +38,11 @@ const ( ) var ( - ConfigFileDir = "/conf/" - UploadFilePath = "/uploads/" - I18nPath = "/i18n/" - CacheDir = "/cache/" + ConfigFileDir = "/conf/" + UploadFilePath = "/uploads/" + I18nPath = "/i18n/" + CacheDir = "/cache/" + formatAllPathONCE sync.Once ) // GetConfigFilePath get config file path @@ -49,10 +51,12 @@ func GetConfigFilePath() string { } func FormatAllPath(dataDirPath string) { - ConfigFileDir = filepath.Join(dataDirPath, ConfigFileDir) - UploadFilePath = filepath.Join(dataDirPath, UploadFilePath) - I18nPath = filepath.Join(dataDirPath, I18nPath) - CacheDir = filepath.Join(dataDirPath, CacheDir) + formatAllPathONCE.Do(func() { + ConfigFileDir = filepath.Join(dataDirPath, ConfigFileDir) + UploadFilePath = filepath.Join(dataDirPath, UploadFilePath) + I18nPath = filepath.Join(dataDirPath, I18nPath) + CacheDir = filepath.Join(dataDirPath, CacheDir) + }) } // InstallAllInitialEnvironment install all initial environment