123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- // Copyright 2017 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package pipeline
- import (
- "fmt"
- "go/build"
- "io"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
- "text/template"
- "golang.org/x/text/collate"
- "golang.org/x/text/feature/plural"
- "golang.org/x/text/internal"
- "golang.org/x/text/internal/catmsg"
- "golang.org/x/text/internal/gen"
- "golang.org/x/text/language"
- "golang.org/x/tools/go/loader"
- )
- var transRe = regexp.MustCompile(`messages\.(.*)\.json`)
- // Generate writes a Go file that defines a Catalog with translated messages.
- // Translations are retrieved from s.Messages, not s.Translations, so it
- // is assumed Merge has been called.
- func (s *State) Generate() error {
- path := s.Config.GenPackage
- if path == "" {
- path = "."
- }
- isDir := path[0] == '.'
- prog, err := loadPackages(&loader.Config{}, []string{path})
- if err != nil {
- return wrap(err, "could not load package")
- }
- pkgs := prog.InitialPackages()
- if len(pkgs) != 1 {
- return errorf("more than one package selected: %v", pkgs)
- }
- pkg := pkgs[0].Pkg.Name()
- cw, err := s.generate()
- if err != nil {
- return err
- }
- if !isDir {
- gopath := filepath.SplitList(build.Default.GOPATH)[0]
- path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].Pkg.Path()))
- }
- if filepath.IsAbs(s.Config.GenFile) {
- path = s.Config.GenFile
- } else {
- path = filepath.Join(path, s.Config.GenFile)
- }
- cw.WriteGoFile(path, pkg) // TODO: WriteGoFile should return error.
- return err
- }
- // WriteGen writes a Go file with the given package name to w that defines a
- // Catalog with translated messages. Translations are retrieved from s.Messages,
- // not s.Translations, so it is assumed Merge has been called.
- func (s *State) WriteGen(w io.Writer, pkg string) error {
- cw, err := s.generate()
- if err != nil {
- return err
- }
- _, err = cw.WriteGo(w, pkg, "")
- return err
- }
- // Generate is deprecated; use (*State).Generate().
- func Generate(w io.Writer, pkg string, extracted *Messages, trans ...Messages) (n int, err error) {
- s := State{
- Extracted: *extracted,
- Translations: trans,
- }
- cw, err := s.generate()
- if err != nil {
- return 0, err
- }
- return cw.WriteGo(w, pkg, "")
- }
- func (s *State) generate() (*gen.CodeWriter, error) {
- // Build up index of translations and original messages.
- translations := map[language.Tag]map[string]Message{}
- languages := []language.Tag{}
- usedKeys := map[string]int{}
- for _, loc := range s.Messages {
- tag := loc.Language
- if _, ok := translations[tag]; !ok {
- translations[tag] = map[string]Message{}
- languages = append(languages, tag)
- }
- for _, m := range loc.Messages {
- if !m.Translation.IsEmpty() {
- for _, id := range m.ID {
- if _, ok := translations[tag][id]; ok {
- warnf("Duplicate translation in locale %q for message %q", tag, id)
- }
- translations[tag][id] = m
- }
- }
- }
- }
- // Verify completeness and register keys.
- internal.SortTags(languages)
- langVars := []string{}
- for _, tag := range languages {
- langVars = append(langVars, strings.Replace(tag.String(), "-", "_", -1))
- dict := translations[tag]
- for _, msg := range s.Extracted.Messages {
- for _, id := range msg.ID {
- if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() {
- if _, ok := usedKeys[msg.Key]; !ok {
- usedKeys[msg.Key] = len(usedKeys)
- }
- break
- }
- // TODO: log missing entry.
- warnf("%s: Missing entry for %q.", tag, id)
- }
- }
- }
- cw := gen.NewCodeWriter()
- x := &struct {
- Fallback language.Tag
- Languages []string
- }{
- Fallback: s.Extracted.Language,
- Languages: langVars,
- }
- if err := lookup.Execute(cw, x); err != nil {
- return nil, wrap(err, "error")
- }
- keyToIndex := []string{}
- for k := range usedKeys {
- keyToIndex = append(keyToIndex, k)
- }
- sort.Strings(keyToIndex)
- fmt.Fprint(cw, "var messageKeyToIndex = map[string]int{\n")
- for _, k := range keyToIndex {
- fmt.Fprintf(cw, "%q: %d,\n", k, usedKeys[k])
- }
- fmt.Fprint(cw, "}\n\n")
- for i, tag := range languages {
- dict := translations[tag]
- a := make([]string, len(usedKeys))
- for _, msg := range s.Extracted.Messages {
- for _, id := range msg.ID {
- if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() {
- m, err := assemble(&msg, &trans.Translation)
- if err != nil {
- return nil, wrap(err, "error")
- }
- _, leadWS, trailWS := trimWS(msg.Key)
- if leadWS != "" || trailWS != "" {
- m = catmsg.Affix{
- Message: m,
- Prefix: leadWS,
- Suffix: trailWS,
- }
- }
- // TODO: support macros.
- data, err := catmsg.Compile(tag, nil, m)
- if err != nil {
- return nil, wrap(err, "error")
- }
- key := usedKeys[msg.Key]
- if d := a[key]; d != "" && d != data {
- warnf("Duplicate non-consistent translation for key %q, picking the one for message %q", msg.Key, id)
- }
- a[key] = string(data)
- break
- }
- }
- }
- index := []uint32{0}
- p := 0
- for _, s := range a {
- p += len(s)
- index = append(index, uint32(p))
- }
- cw.WriteVar(langVars[i]+"Index", index)
- cw.WriteConst(langVars[i]+"Data", strings.Join(a, ""))
- }
- return cw, nil
- }
- func assemble(m *Message, t *Text) (msg catmsg.Message, err error) {
- keys := []string{}
- for k := range t.Var {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- var a []catmsg.Message
- for _, k := range keys {
- t := t.Var[k]
- m, err := assemble(m, &t)
- if err != nil {
- return nil, err
- }
- a = append(a, &catmsg.Var{Name: k, Message: m})
- }
- if t.Select != nil {
- s, err := assembleSelect(m, t.Select)
- if err != nil {
- return nil, err
- }
- a = append(a, s)
- }
- if t.Msg != "" {
- sub, err := m.Substitute(t.Msg)
- if err != nil {
- return nil, err
- }
- a = append(a, catmsg.String(sub))
- }
- switch len(a) {
- case 0:
- return nil, errorf("generate: empty message")
- case 1:
- return a[0], nil
- default:
- return catmsg.FirstOf(a), nil
- }
- }
- func assembleSelect(m *Message, s *Select) (msg catmsg.Message, err error) {
- cases := []string{}
- for c := range s.Cases {
- cases = append(cases, c)
- }
- sortCases(cases)
- caseMsg := []interface{}{}
- for _, c := range cases {
- cm := s.Cases[c]
- m, err := assemble(m, &cm)
- if err != nil {
- return nil, err
- }
- caseMsg = append(caseMsg, c, m)
- }
- ph := m.Placeholder(s.Arg)
- switch s.Feature {
- case "plural":
- // TODO: only printf-style selects are supported as of yet.
- return plural.Selectf(ph.ArgNum, ph.String, caseMsg...), nil
- }
- return nil, errorf("unknown feature type %q", s.Feature)
- }
- func sortCases(cases []string) {
- // TODO: implement full interface.
- sort.Slice(cases, func(i, j int) bool {
- switch {
- case cases[i] != "other" && cases[j] == "other":
- return true
- case cases[i] == "other" && cases[j] != "other":
- return false
- }
- // the following code relies on '<' < '=' < any letter.
- return cmpNumeric(cases[i], cases[j]) == -1
- })
- }
- var cmpNumeric = collate.New(language.Und, collate.Numeric).CompareString
- var lookup = template.Must(template.New("gen").Parse(`
- import (
- "golang.org/x/text/language"
- "golang.org/x/text/message"
- "golang.org/x/text/message/catalog"
- )
- type dictionary struct {
- index []uint32
- data string
- }
- func (d *dictionary) Lookup(key string) (data string, ok bool) {
- p, ok := messageKeyToIndex[key]
- if !ok {
- return "", false
- }
- start, end := d.index[p], d.index[p+1]
- if start == end {
- return "", false
- }
- return d.data[start:end], true
- }
- func init() {
- dict := map[string]catalog.Dictionary{
- {{range .Languages}}"{{.}}": &dictionary{index: {{.}}Index, data: {{.}}Data },
- {{end}}
- }
- fallback := language.MustParse("{{.Fallback}}")
- cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
- if err != nil {
- panic(err)
- }
- message.DefaultCatalog = cat
- }
- `))
|