You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

315 lines
7.4 KiB

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