import_export.go raw

   1  package ut
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"os"
   7  	"path/filepath"
   8  
   9  	"io"
  10  
  11  	"github.com/go-playground/locales"
  12  )
  13  
  14  type translation struct {
  15  	Locale           string      `json:"locale"`
  16  	Key              interface{} `json:"key"` // either string or integer
  17  	Translation      string      `json:"trans"`
  18  	PluralType       string      `json:"type,omitempty"`
  19  	PluralRule       string      `json:"rule,omitempty"`
  20  	OverrideExisting bool        `json:"override,omitempty"`
  21  }
  22  
  23  const (
  24  	cardinalType = "Cardinal"
  25  	ordinalType  = "Ordinal"
  26  	rangeType    = "Range"
  27  )
  28  
  29  // ImportExportFormat is the format of the file import or export
  30  type ImportExportFormat uint8
  31  
  32  // supported Export Formats
  33  const (
  34  	FormatJSON ImportExportFormat = iota
  35  )
  36  
  37  // Export writes the translations out to a file on disk.
  38  //
  39  // NOTE: this currently only works with string or int translations keys.
  40  func (t *UniversalTranslator) Export(format ImportExportFormat, dirname string) error {
  41  
  42  	_, err := os.Stat(dirname)
  43  	if err != nil {
  44  
  45  		if !os.IsNotExist(err) {
  46  			return err
  47  		}
  48  
  49  		if err = os.MkdirAll(dirname, 0744); err != nil {
  50  			return err
  51  		}
  52  	}
  53  
  54  	// build up translations
  55  	var trans []translation
  56  	var b []byte
  57  	var ext string
  58  
  59  	for _, locale := range t.translators {
  60  
  61  		for k, v := range locale.(*translator).translations {
  62  			trans = append(trans, translation{
  63  				Locale:      locale.Locale(),
  64  				Key:         k,
  65  				Translation: v.text,
  66  			})
  67  		}
  68  
  69  		for k, pluralTrans := range locale.(*translator).cardinalTanslations {
  70  
  71  			for i, plural := range pluralTrans {
  72  
  73  				// leave enough for all plural rules
  74  				// but not all are set for all languages.
  75  				if plural == nil {
  76  					continue
  77  				}
  78  
  79  				trans = append(trans, translation{
  80  					Locale:      locale.Locale(),
  81  					Key:         k.(string),
  82  					Translation: plural.text,
  83  					PluralType:  cardinalType,
  84  					PluralRule:  locales.PluralRule(i).String(),
  85  				})
  86  			}
  87  		}
  88  
  89  		for k, pluralTrans := range locale.(*translator).ordinalTanslations {
  90  
  91  			for i, plural := range pluralTrans {
  92  
  93  				// leave enough for all plural rules
  94  				// but not all are set for all languages.
  95  				if plural == nil {
  96  					continue
  97  				}
  98  
  99  				trans = append(trans, translation{
 100  					Locale:      locale.Locale(),
 101  					Key:         k.(string),
 102  					Translation: plural.text,
 103  					PluralType:  ordinalType,
 104  					PluralRule:  locales.PluralRule(i).String(),
 105  				})
 106  			}
 107  		}
 108  
 109  		for k, pluralTrans := range locale.(*translator).rangeTanslations {
 110  
 111  			for i, plural := range pluralTrans {
 112  
 113  				// leave enough for all plural rules
 114  				// but not all are set for all languages.
 115  				if plural == nil {
 116  					continue
 117  				}
 118  
 119  				trans = append(trans, translation{
 120  					Locale:      locale.Locale(),
 121  					Key:         k.(string),
 122  					Translation: plural.text,
 123  					PluralType:  rangeType,
 124  					PluralRule:  locales.PluralRule(i).String(),
 125  				})
 126  			}
 127  		}
 128  
 129  		switch format {
 130  		case FormatJSON:
 131  			b, err = json.MarshalIndent(trans, "", "    ")
 132  			ext = ".json"
 133  		}
 134  
 135  		if err != nil {
 136  			return err
 137  		}
 138  
 139  		err = os.WriteFile(filepath.Join(dirname, fmt.Sprintf("%s%s", locale.Locale(), ext)), b, 0644)
 140  		if err != nil {
 141  			return err
 142  		}
 143  
 144  		trans = trans[0:0]
 145  	}
 146  
 147  	return nil
 148  }
 149  
 150  // Import reads the translations out of a file or directory on disk.
 151  //
 152  // NOTE: this currently only works with string or int translations keys.
 153  func (t *UniversalTranslator) Import(format ImportExportFormat, dirnameOrFilename string) error {
 154  
 155  	fi, err := os.Stat(dirnameOrFilename)
 156  	if err != nil {
 157  		return err
 158  	}
 159  
 160  	processFn := func(filename string) error {
 161  
 162  		f, err := os.Open(filename)
 163  		if err != nil {
 164  			return err
 165  		}
 166  		defer f.Close()
 167  
 168  		return t.ImportByReader(format, f)
 169  	}
 170  
 171  	if !fi.IsDir() {
 172  		return processFn(dirnameOrFilename)
 173  	}
 174  
 175  	// recursively go through directory
 176  	walker := func(path string, info os.FileInfo, err error) error {
 177  
 178  		if info.IsDir() {
 179  			return nil
 180  		}
 181  
 182  		switch format {
 183  		case FormatJSON:
 184  			// skip non JSON files
 185  			if filepath.Ext(info.Name()) != ".json" {
 186  				return nil
 187  			}
 188  		}
 189  
 190  		return processFn(path)
 191  	}
 192  
 193  	return filepath.Walk(dirnameOrFilename, walker)
 194  }
 195  
 196  // ImportByReader imports the the translations found within the contents read from the supplied reader.
 197  //
 198  // NOTE: generally used when assets have been embedded into the binary and are already in memory.
 199  func (t *UniversalTranslator) ImportByReader(format ImportExportFormat, reader io.Reader) error {
 200  
 201  	b, err := io.ReadAll(reader)
 202  	if err != nil {
 203  		return err
 204  	}
 205  
 206  	var trans []translation
 207  
 208  	switch format {
 209  	case FormatJSON:
 210  		err = json.Unmarshal(b, &trans)
 211  	}
 212  
 213  	if err != nil {
 214  		return err
 215  	}
 216  
 217  	for _, tl := range trans {
 218  
 219  		locale, found := t.FindTranslator(tl.Locale)
 220  		if !found {
 221  			return &ErrMissingLocale{locale: tl.Locale}
 222  		}
 223  
 224  		pr := stringToPR(tl.PluralRule)
 225  
 226  		if pr == locales.PluralRuleUnknown {
 227  
 228  			err = locale.Add(tl.Key, tl.Translation, tl.OverrideExisting)
 229  			if err != nil {
 230  				return err
 231  			}
 232  
 233  			continue
 234  		}
 235  
 236  		switch tl.PluralType {
 237  		case cardinalType:
 238  			err = locale.AddCardinal(tl.Key, tl.Translation, pr, tl.OverrideExisting)
 239  		case ordinalType:
 240  			err = locale.AddOrdinal(tl.Key, tl.Translation, pr, tl.OverrideExisting)
 241  		case rangeType:
 242  			err = locale.AddRange(tl.Key, tl.Translation, pr, tl.OverrideExisting)
 243  		default:
 244  			return &ErrBadPluralDefinition{tl: tl}
 245  		}
 246  
 247  		if err != nil {
 248  			return err
 249  		}
 250  	}
 251  
 252  	return nil
 253  }
 254  
 255  func stringToPR(s string) locales.PluralRule {
 256  
 257  	switch s {
 258  	case "Zero":
 259  		return locales.PluralRuleZero
 260  	case "One":
 261  		return locales.PluralRuleOne
 262  	case "Two":
 263  		return locales.PluralRuleTwo
 264  	case "Few":
 265  		return locales.PluralRuleFew
 266  	case "Many":
 267  		return locales.PluralRuleMany
 268  	case "Other":
 269  		return locales.PluralRuleOther
 270  	default:
 271  		return locales.PluralRuleUnknown
 272  	}
 273  
 274  }
 275