package main import ( "encoding/json" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" "github.com/jamesread/golure/pkg/dirs" log "github.com/sirupsen/logrus" ) type LanguageFilev1 struct { SchemaVersion int `json:"schemaVersion"` Translations map[string]string `json:"translations"` } type CombinedTranslationsOutput struct { Messages map[string]map[string]string `json:"messages"` } func main() { combinedContent := getCombinedLanguageContent() jsonData, err := json.Marshal(combinedContent) if err != nil { log.Fatalf("Error marshalling combined language content: %v", err) } err = os.WriteFile("combined_output.json", jsonData, 0644) if err != nil { log.Fatalf("Error saving combined language content to file: %v", err) } log.Infof("Combined language content saved to combined_output.json") } func getLanguageDir() string { dirsToSearch := []string{ "../lang", "../../../../lang/", // Relative to this file, for unit tests "/app/lang/", } dir, _ := dirs.GetFirstExistingDirectory("lang", dirsToSearch) return dir } func getCombinedLanguageContent() *CombinedTranslationsOutput { output := &CombinedTranslationsOutput{ Messages: make(map[string]map[string]string), } languageDir := getLanguageDir() files, err := os.ReadDir(languageDir) if err != nil { log.Errorf("Error reading language directory %s: %v", languageDir, err) return output } for _, file := range filterLanguageFiles(files) { languageName := strings.Replace(file.Name(), ".yaml", "", 1) fullPath := filepath.Join(languageDir, file.Name()) log.Infof("Loading language file: %s", fullPath) content, err := os.ReadFile(fullPath) if err != nil { log.Errorf("Error reading language file %s: %v", fullPath, err) continue } var yamlData LanguageFilev1 err = yaml.Unmarshal(content, &yamlData) if err != nil { log.Errorf("Error reading language file %s: %v", fullPath, err) continue } output.Messages[languageName] = yamlData.Translations } validateTranslations(output) return output } // getReferenceKeys returns the keys from the "en" translation as the reference set. func getReferenceKeys(messages map[string]map[string]string) map[string]bool { enTranslations, exists := messages["en"] if !exists { return nil } referenceKeys := make(map[string]bool, len(enTranslations)) for key := range enTranslations { referenceKeys[key] = true } return referenceKeys } // findMissingKeys returns the keys that are in referenceKeys but not in translations. func findMissingKeys(referenceKeys map[string]bool, translations map[string]string) []string { missing := make([]string, 0) for key := range referenceKeys { if _, exists := translations[key]; !exists { missing = append(missing, key) } } return missing } // findExtraKeys returns the keys that are in translations but not in referenceKeys. func findExtraKeys(referenceKeys map[string]bool, translations map[string]string) []string { extra := make([]string, 0) for key := range translations { if !referenceKeys[key] { extra = append(extra, key) } } return extra } // validateTranslations checks all translations against the "en" reference and prints warnings for missing and extra keys. func validateTranslations(output *CombinedTranslationsOutput) { referenceKeys := getReferenceKeys(output.Messages) if referenceKeys == nil { log.Warnf("No 'en' translation found, skipping validation") return } for langName, translations := range output.Messages { if langName == "en" { continue } missing := findMissingKeys(referenceKeys, translations) if len(missing) > 0 { log.Warnf("Translation '%s' is missing %d key(s): %v", langName, len(missing), missing) } extra := findExtraKeys(referenceKeys, translations) if len(extra) > 0 { log.Warnf("Translation '%s' has %d extra key(s) not in 'en': %v", langName, len(extra), extra) } } } func filterLanguageFiles(files []os.DirEntry) []os.DirEntry { ret := make([]os.DirEntry, 0) for _, file := range files { if file.IsDir() { continue } if !strings.HasSuffix(file.Name(), ".yaml") { continue } ret = append(ret, file) } return ret } func parseAcceptLanguages(headerLanguage string) []string { acceptLanguages := make([]string, 0) for _, lang := range strings.Split(headerLanguage, ",") { lang = strings.TrimSpace(lang) acceptLanguages = append(acceptLanguages, lang) } return acceptLanguages }