gettext.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // go run .
  2. package main
  3. import (
  4. "bufio"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "regexp"
  9. "runtime"
  10. "strings"
  11. "github.com/uozi-tech/cosy/logger"
  12. )
  13. // Directories to exclude
  14. var excludeDirs = []string{
  15. ".devcontainer", ".github", ".idea", ".pnpm-store",
  16. ".vscode", "app", "query", "tmp", "cmd",
  17. }
  18. // Regular expression to match import statements for translation package
  19. var importRegex = regexp.MustCompile(`import\s+\(\s*((?:.|\n)*?)\s*\)|\s*import\s+(.*?)\s+".*?(?:internal/translation|github\.com/0xJacky/Nginx-UI/internal/translation)"`)
  20. var singleImportRegex = regexp.MustCompile(`\s*(?:(\w+)\s+)?".*?(?:internal/translation|github\.com/0xJacky/Nginx-UI/internal/translation)"`)
  21. func main() {
  22. logger.Init("release")
  23. // Start scanning from the project root
  24. _, file, _, ok := runtime.Caller(0)
  25. if !ok {
  26. logger.Error("Unable to get the current file")
  27. return
  28. }
  29. root := filepath.Join(filepath.Dir(file), "../../")
  30. calls := make(map[string]bool)
  31. // Scan all Go files
  32. err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
  33. if err != nil {
  34. return err
  35. }
  36. // Skip excluded directories
  37. for _, dir := range excludeDirs {
  38. if strings.Contains(path, dir) {
  39. if info.IsDir() {
  40. return filepath.SkipDir
  41. }
  42. return nil
  43. }
  44. }
  45. // Only process Go files
  46. if !info.IsDir() && strings.HasSuffix(path, ".go") {
  47. findTranslationC(path, calls)
  48. }
  49. return nil
  50. })
  51. if err != nil {
  52. logger.Errorf("Error walking the path: %v\n", err)
  53. return
  54. }
  55. // Generate a single TS file
  56. generateSingleTSFile(root, calls)
  57. logger.Infof("Found %d translation messages\n", len(calls))
  58. }
  59. // findTranslationC finds all translation.C calls in a file and adds them to the calls map
  60. func findTranslationC(filePath string, calls map[string]bool) {
  61. // Read the entire file content
  62. content, err := os.ReadFile(filePath)
  63. if err != nil {
  64. logger.Errorf("Error reading file %s: %v\n", filePath, err)
  65. return
  66. }
  67. fileContent := string(content)
  68. // Find the translation package alias from import statements
  69. alias := findTranslationAlias(fileContent)
  70. if alias == "" {
  71. // No translation package imported, skip this file
  72. return
  73. }
  74. // Create regex pattern based on the alias
  75. pattern := fmt.Sprintf(`%s\.C\(\s*["']([^"']*(?:\\["'][^"']*)*)["']`, alias)
  76. cCallRegex := regexp.MustCompile(pattern)
  77. // Handle backtick strings separately (multi-line strings)
  78. backtickPattern := fmt.Sprintf(`%s\.C\(\s*\x60([^\x60]*)\x60`, alias)
  79. backtickRegex := regexp.MustCompile(backtickPattern)
  80. // Find all matches with regular quotes
  81. matches := cCallRegex.FindAllStringSubmatch(fileContent, -1)
  82. for _, match := range matches {
  83. if len(match) >= 2 {
  84. message := match[1]
  85. // Clean up the message (remove escaped quotes, etc.)
  86. message = strings.ReplaceAll(message, "\\\"", "\"")
  87. message = strings.ReplaceAll(message, "\\'", "'")
  88. // Add to the map if not already present
  89. if _, exists := calls[message]; !exists {
  90. calls[message] = true
  91. }
  92. }
  93. }
  94. // Find all matches with backticks
  95. backtickMatches := backtickRegex.FindAllStringSubmatch(fileContent, -1)
  96. for _, match := range backtickMatches {
  97. if len(match) >= 2 {
  98. message := match[1]
  99. // Add to the map if not already present
  100. if _, exists := calls[message]; !exists {
  101. calls[message] = true
  102. }
  103. }
  104. }
  105. }
  106. // findTranslationAlias finds the alias for the translation package in import statements
  107. func findTranslationAlias(fileContent string) string {
  108. // Default alias
  109. alias := "translation"
  110. // Find import blocks
  111. matches := importRegex.FindAllStringSubmatch(fileContent, -1)
  112. for _, match := range matches {
  113. if len(match) >= 3 && match[1] != "" {
  114. // This is a block import, search inside it
  115. imports := match[1]
  116. singleMatches := singleImportRegex.FindAllStringSubmatch(imports, -1)
  117. for _, singleMatch := range singleMatches {
  118. if len(singleMatch) >= 2 && singleMatch[1] != "" {
  119. // Custom alias found
  120. return singleMatch[1]
  121. }
  122. }
  123. } else if len(match) >= 3 && match[2] != "" {
  124. // This is a single-line import
  125. singleMatch := singleImportRegex.FindAllStringSubmatch(match[2], -1)
  126. if len(singleMatch) > 0 && len(singleMatch[0]) >= 2 && singleMatch[0][1] != "" {
  127. // Custom alias found
  128. return singleMatch[0][1]
  129. }
  130. }
  131. }
  132. return alias
  133. }
  134. // generateSingleTSFile generates a single TS file with all translation messages
  135. func generateSingleTSFile(root string, calls map[string]bool) {
  136. outputPath := filepath.Join(root, "app/src/language/generate.ts")
  137. // Create the directory if it doesn't exist
  138. err := os.MkdirAll(filepath.Dir(outputPath), 0755)
  139. if err != nil {
  140. logger.Errorf("Error creating directory: %v\n", err)
  141. return
  142. }
  143. // Create the output file
  144. file, err := os.Create(outputPath)
  145. if err != nil {
  146. logger.Errorf("Error creating file: %v\n", err)
  147. return
  148. }
  149. defer file.Close()
  150. writer := bufio.NewWriter(file)
  151. // Write the header
  152. writer.WriteString("// This file is auto-generated. DO NOT EDIT MANUALLY.\n\n")
  153. writer.WriteString("export const msg = [\n")
  154. // Write each translation message
  155. for message := range calls {
  156. // Escape single quotes in the message for JavaScript
  157. escapedMessage := strings.ReplaceAll(message, "'", "\\'")
  158. writer.WriteString(fmt.Sprintf(" $gettext('%s'),\n", escapedMessage))
  159. }
  160. writer.WriteString("]\n")
  161. writer.Flush()
  162. logger.Infof("Generated TS file at %s\n", outputPath)
  163. }