import_export.go 5.6 KB

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