/ lokalize2android.go
lokalize2android.go
1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "encoding/xml" 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "strings" 13 "text/scanner" 14 ) 15 16 type Resources struct { 17 Strings []String `xml:"string"` 18 StringArrays []StringArray `xml:"string-array"` 19 Plurals []Plurals `xml:"plural"` 20 21 XMLName struct{} `xml:"resources"` 22 } 23 24 type String struct { 25 Name string `xml:"name,attr"` 26 Value string `xml:",innerxml"` 27 } 28 29 type StringArray struct { 30 Name string `xml:"name,attr"` 31 Items []string `xml:"item"` 32 } 33 34 type Plurals struct { 35 Name string `xml:"name,attr"` 36 Items []PluralItem `xml:"item"` 37 } 38 39 type PluralItem struct { 40 Quantity string `xml:"quantity,attr"` 41 Value string `xml:",innerxml"` 42 } 43 44 var pluralTypes = []string{"zero", "one", "two", "few", "many", "other"} 45 46 func (l *Resources) UnmarshalJSON(b []byte) error { 47 var ts map[string]interface{} 48 if err := json.Unmarshal(b, &ts); err != nil { 49 return err 50 } 51 52 for k, v := range ts { 53 switch v.(type) { 54 case string: 55 str := String{Name: processKey(k), Value: processTranslation(v.(string))} 56 l.Strings = append(l.Strings, str) 57 case []interface{}: 58 strs := StringArray{Name: processKey(k)} 59 for _, str := range v.([]interface{}) { 60 strs.Items = append(strs.Items, processTranslation(str.(string))) 61 } 62 l.StringArrays = append(l.StringArrays, strs) 63 case map[string]interface{}: 64 pl := Plurals{Name: processKey(k)} 65 for _, pt := range pluralTypes { 66 if str, ok := v.(map[string]interface{})[pt]; ok { 67 pl.Items = append(pl.Items, PluralItem{Quantity: pt, Value: processTranslation(str.(string))}) 68 } 69 } 70 l.Plurals = append(l.Plurals, pl) 71 default: 72 return fmt.Errorf("can't handle %q: %q", k, v) 73 } 74 } 75 76 return nil 77 } 78 79 func processKey(k string) string { 80 return strings.ReplaceAll(k, "-", "_") 81 } 82 83 // processTranslation transforms {{ and }} into XML surrounding tags for placeholders. 84 func processTranslation(v string) string { 85 var ( 86 s scanner.Scanner 87 b bytes.Buffer 88 ph bytes.Buffer 89 ) 90 91 s.Init(strings.NewReader(v)) 92 s.Filename = "translation" 93 s.Whitespace = 0 94 s.Mode = scanner.ScanStrings 95 96 insideph := false 97 lastOpen := -1 98 lastClosed := -1 99 for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { 100 switch tok { 101 case '{': 102 if 1 == s.Pos().Column - lastOpen{ 103 insideph = true 104 } 105 lastOpen = s.Pos().Column 106 case '}': 107 if 1 == s.Pos().Column - lastClosed { 108 b.WriteString(fmt.Sprintf(`<xliff:g id="%s" />`, ph.String())) 109 ph.Reset() 110 insideph = false 111 } 112 lastClosed = s.Pos().Column 113 default: 114 if !insideph { 115 b.WriteRune(tok) 116 } else { 117 ph.WriteRune(tok) 118 } 119 } 120 } 121 return b.String() 122 } 123 124 func main() { 125 l := log.New(os.Stderr, "", log.Flags()) 126 127 var r io.Reader 128 if len(os.Args) == 2 { 129 f, err := os.Open(os.Args[1]) 130 if err != nil { 131 l.Fatalf("error reading file: %q", err) 132 } 133 134 defer func() { 135 err = f.Close() 136 if err != nil { 137 l.Fatalf("error closing file: %q", err) 138 } 139 }() 140 141 r = f 142 } else { 143 r = bufio.NewReader(os.Stdin) 144 } 145 146 var rs Resources 147 if err := json.NewDecoder(r).Decode(&rs); err != nil { 148 l.Fatalf("error parsing json: %q", err) 149 } 150 151 b, err := xml.MarshalIndent(rs, "", "\t") 152 if err != nil { 153 l.Fatalf("error serializing xml: %q", err) 154 } 155 fmt.Println(string(b)) 156 }