generate.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // Copyright 2017 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package pipeline
  5. import (
  6. "fmt"
  7. "go/build"
  8. "io"
  9. "path/filepath"
  10. "regexp"
  11. "sort"
  12. "strings"
  13. "text/template"
  14. "golang.org/x/text/collate"
  15. "golang.org/x/text/feature/plural"
  16. "golang.org/x/text/internal"
  17. "golang.org/x/text/internal/catmsg"
  18. "golang.org/x/text/internal/gen"
  19. "golang.org/x/text/language"
  20. "golang.org/x/tools/go/loader"
  21. )
  22. var transRe = regexp.MustCompile(`messages\.(.*)\.json`)
  23. // Generate writes a Go file that defines a Catalog with translated messages.
  24. // Translations are retrieved from s.Messages, not s.Translations, so it
  25. // is assumed Merge has been called.
  26. func (s *State) Generate() error {
  27. path := s.Config.GenPackage
  28. if path == "" {
  29. path = "."
  30. }
  31. isDir := path[0] == '.'
  32. prog, err := loadPackages(&loader.Config{}, []string{path})
  33. if err != nil {
  34. return wrap(err, "could not load package")
  35. }
  36. pkgs := prog.InitialPackages()
  37. if len(pkgs) != 1 {
  38. return errorf("more than one package selected: %v", pkgs)
  39. }
  40. pkg := pkgs[0].Pkg.Name()
  41. cw, err := s.generate()
  42. if err != nil {
  43. return err
  44. }
  45. if !isDir {
  46. gopath := filepath.SplitList(build.Default.GOPATH)[0]
  47. path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].Pkg.Path()))
  48. }
  49. if filepath.IsAbs(s.Config.GenFile) {
  50. path = s.Config.GenFile
  51. } else {
  52. path = filepath.Join(path, s.Config.GenFile)
  53. }
  54. cw.WriteGoFile(path, pkg) // TODO: WriteGoFile should return error.
  55. return err
  56. }
  57. // WriteGen writes a Go file with the given package name to w that defines a
  58. // Catalog with translated messages. Translations are retrieved from s.Messages,
  59. // not s.Translations, so it is assumed Merge has been called.
  60. func (s *State) WriteGen(w io.Writer, pkg string) error {
  61. cw, err := s.generate()
  62. if err != nil {
  63. return err
  64. }
  65. _, err = cw.WriteGo(w, pkg, "")
  66. return err
  67. }
  68. // Generate is deprecated; use (*State).Generate().
  69. func Generate(w io.Writer, pkg string, extracted *Messages, trans ...Messages) (n int, err error) {
  70. s := State{
  71. Extracted: *extracted,
  72. Translations: trans,
  73. }
  74. cw, err := s.generate()
  75. if err != nil {
  76. return 0, err
  77. }
  78. return cw.WriteGo(w, pkg, "")
  79. }
  80. func (s *State) generate() (*gen.CodeWriter, error) {
  81. // Build up index of translations and original messages.
  82. translations := map[language.Tag]map[string]Message{}
  83. languages := []language.Tag{}
  84. usedKeys := map[string]int{}
  85. for _, loc := range s.Messages {
  86. tag := loc.Language
  87. if _, ok := translations[tag]; !ok {
  88. translations[tag] = map[string]Message{}
  89. languages = append(languages, tag)
  90. }
  91. for _, m := range loc.Messages {
  92. if !m.Translation.IsEmpty() {
  93. for _, id := range m.ID {
  94. if _, ok := translations[tag][id]; ok {
  95. warnf("Duplicate translation in locale %q for message %q", tag, id)
  96. }
  97. translations[tag][id] = m
  98. }
  99. }
  100. }
  101. }
  102. // Verify completeness and register keys.
  103. internal.SortTags(languages)
  104. langVars := []string{}
  105. for _, tag := range languages {
  106. langVars = append(langVars, strings.Replace(tag.String(), "-", "_", -1))
  107. dict := translations[tag]
  108. for _, msg := range s.Extracted.Messages {
  109. for _, id := range msg.ID {
  110. if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() {
  111. if _, ok := usedKeys[msg.Key]; !ok {
  112. usedKeys[msg.Key] = len(usedKeys)
  113. }
  114. break
  115. }
  116. // TODO: log missing entry.
  117. warnf("%s: Missing entry for %q.", tag, id)
  118. }
  119. }
  120. }
  121. cw := gen.NewCodeWriter()
  122. x := &struct {
  123. Fallback language.Tag
  124. Languages []string
  125. }{
  126. Fallback: s.Extracted.Language,
  127. Languages: langVars,
  128. }
  129. if err := lookup.Execute(cw, x); err != nil {
  130. return nil, wrap(err, "error")
  131. }
  132. keyToIndex := []string{}
  133. for k := range usedKeys {
  134. keyToIndex = append(keyToIndex, k)
  135. }
  136. sort.Strings(keyToIndex)
  137. fmt.Fprint(cw, "var messageKeyToIndex = map[string]int{\n")
  138. for _, k := range keyToIndex {
  139. fmt.Fprintf(cw, "%q: %d,\n", k, usedKeys[k])
  140. }
  141. fmt.Fprint(cw, "}\n\n")
  142. for i, tag := range languages {
  143. dict := translations[tag]
  144. a := make([]string, len(usedKeys))
  145. for _, msg := range s.Extracted.Messages {
  146. for _, id := range msg.ID {
  147. if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() {
  148. m, err := assemble(&msg, &trans.Translation)
  149. if err != nil {
  150. return nil, wrap(err, "error")
  151. }
  152. _, leadWS, trailWS := trimWS(msg.Key)
  153. if leadWS != "" || trailWS != "" {
  154. m = catmsg.Affix{
  155. Message: m,
  156. Prefix: leadWS,
  157. Suffix: trailWS,
  158. }
  159. }
  160. // TODO: support macros.
  161. data, err := catmsg.Compile(tag, nil, m)
  162. if err != nil {
  163. return nil, wrap(err, "error")
  164. }
  165. key := usedKeys[msg.Key]
  166. if d := a[key]; d != "" && d != data {
  167. warnf("Duplicate non-consistent translation for key %q, picking the one for message %q", msg.Key, id)
  168. }
  169. a[key] = string(data)
  170. break
  171. }
  172. }
  173. }
  174. index := []uint32{0}
  175. p := 0
  176. for _, s := range a {
  177. p += len(s)
  178. index = append(index, uint32(p))
  179. }
  180. cw.WriteVar(langVars[i]+"Index", index)
  181. cw.WriteConst(langVars[i]+"Data", strings.Join(a, ""))
  182. }
  183. return cw, nil
  184. }
  185. func assemble(m *Message, t *Text) (msg catmsg.Message, err error) {
  186. keys := []string{}
  187. for k := range t.Var {
  188. keys = append(keys, k)
  189. }
  190. sort.Strings(keys)
  191. var a []catmsg.Message
  192. for _, k := range keys {
  193. t := t.Var[k]
  194. m, err := assemble(m, &t)
  195. if err != nil {
  196. return nil, err
  197. }
  198. a = append(a, &catmsg.Var{Name: k, Message: m})
  199. }
  200. if t.Select != nil {
  201. s, err := assembleSelect(m, t.Select)
  202. if err != nil {
  203. return nil, err
  204. }
  205. a = append(a, s)
  206. }
  207. if t.Msg != "" {
  208. sub, err := m.Substitute(t.Msg)
  209. if err != nil {
  210. return nil, err
  211. }
  212. a = append(a, catmsg.String(sub))
  213. }
  214. switch len(a) {
  215. case 0:
  216. return nil, errorf("generate: empty message")
  217. case 1:
  218. return a[0], nil
  219. default:
  220. return catmsg.FirstOf(a), nil
  221. }
  222. }
  223. func assembleSelect(m *Message, s *Select) (msg catmsg.Message, err error) {
  224. cases := []string{}
  225. for c := range s.Cases {
  226. cases = append(cases, c)
  227. }
  228. sortCases(cases)
  229. caseMsg := []interface{}{}
  230. for _, c := range cases {
  231. cm := s.Cases[c]
  232. m, err := assemble(m, &cm)
  233. if err != nil {
  234. return nil, err
  235. }
  236. caseMsg = append(caseMsg, c, m)
  237. }
  238. ph := m.Placeholder(s.Arg)
  239. switch s.Feature {
  240. case "plural":
  241. // TODO: only printf-style selects are supported as of yet.
  242. return plural.Selectf(ph.ArgNum, ph.String, caseMsg...), nil
  243. }
  244. return nil, errorf("unknown feature type %q", s.Feature)
  245. }
  246. func sortCases(cases []string) {
  247. // TODO: implement full interface.
  248. sort.Slice(cases, func(i, j int) bool {
  249. switch {
  250. case cases[i] != "other" && cases[j] == "other":
  251. return true
  252. case cases[i] == "other" && cases[j] != "other":
  253. return false
  254. }
  255. // the following code relies on '<' < '=' < any letter.
  256. return cmpNumeric(cases[i], cases[j]) == -1
  257. })
  258. }
  259. var cmpNumeric = collate.New(language.Und, collate.Numeric).CompareString
  260. var lookup = template.Must(template.New("gen").Parse(`
  261. import (
  262. "golang.org/x/text/language"
  263. "golang.org/x/text/message"
  264. "golang.org/x/text/message/catalog"
  265. )
  266. type dictionary struct {
  267. index []uint32
  268. data string
  269. }
  270. func (d *dictionary) Lookup(key string) (data string, ok bool) {
  271. p, ok := messageKeyToIndex[key]
  272. if !ok {
  273. return "", false
  274. }
  275. start, end := d.index[p], d.index[p+1]
  276. if start == end {
  277. return "", false
  278. }
  279. return d.data[start:end], true
  280. }
  281. func init() {
  282. dict := map[string]catalog.Dictionary{
  283. {{range .Languages}}"{{.}}": &dictionary{index: {{.}}Index, data: {{.}}Data },
  284. {{end}}
  285. }
  286. fallback := language.MustParse("{{.Fallback}}")
  287. cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
  288. if err != nil {
  289. panic(err)
  290. }
  291. message.DefaultCatalog = cat
  292. }
  293. `))