| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package main
- import (
- "encoding/json"
- "os"
- "path/filepath"
- "sort"
- "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 {
- Comment string `json:"_comment"`
- Messages map[string]map[string]string `json:"messages"`
- }
- func main() {
- combinedContent := getCombinedLanguageContent()
- sortedContent := sortTranslations(combinedContent)
- jsonData, err := json.MarshalIndent(sortedContent, "", " ")
- 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")
- }
- // sortTranslations creates a new structure with sorted keys for deterministic output.
- func sortTranslations(input *CombinedTranslationsOutput) *CombinedTranslationsOutput {
- sorted := &CombinedTranslationsOutput{
- Comment: input.Comment,
- Messages: make(map[string]map[string]string),
- }
- // Sort language names
- langNames := make([]string, 0, len(input.Messages))
- for langName := range input.Messages {
- langNames = append(langNames, langName)
- }
- sort.Strings(langNames)
- // For each language, sort the translation keys
- for _, langName := range langNames {
- translations := input.Messages[langName]
- sortedTranslations := make(map[string]string)
- keys := make([]string, 0, len(translations))
- for key := range translations {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- for _, key := range keys {
- sortedTranslations[key] = translations[key]
- }
- sorted.Messages[langName] = sortedTranslations
- }
- return sorted
- }
- 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{
- Comment: "This file is generated. Please re-generate this file using 'make' when you update a translation.",
- 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
- }
|