main.go (6518B)
1 package main 2 3 import ( 4 "context" 5 "flag" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "strings" 11 12 fsdb "git.defalsify.org/vise.git/db/fs" 13 "git.defalsify.org/vise.git/debug" 14 "git.defalsify.org/vise.git/lang" 15 "git.defalsify.org/vise.git/logging" 16 "git.defalsify.org/vise.git/resource" 17 ) 18 19 var ( 20 logg = logging.NewVanilla() 21 writeDomains = []string{ 22 resource.PoDomain, 23 resource.TemplateKeyPoDomain, 24 resource.MenuKeyPoDomain, 25 } 26 writeDomainReady = make(map[string]bool) 27 ) 28 29 type translator struct { 30 langs []lang.Language 31 ctx context.Context 32 rs resource.Resource 33 newline bool 34 d string 35 } 36 37 func newTranslator(ctx context.Context, rs resource.Resource, outPath string, newline bool) *translator { 38 return &translator{ 39 ctx: ctx, 40 rs: rs, 41 d: outPath, 42 newline: newline, 43 } 44 } 45 46 func (tr *translator) ensureFileNameFor(ln lang.Language, domain string) (string, error) { 47 fileName := domain + ".po" 48 p := path.Join(tr.d, ln.Code) 49 err := os.MkdirAll(p, 0700) 50 if err != nil { 51 return "", err 52 } 53 return path.Join(p, fileName), nil 54 } 55 56 // skip default*.po for translations other than default 57 func (tr *translator) writersFor(ln lang.Language) ([]io.WriteCloser, error) { 58 var r []io.WriteCloser 59 _, ready := writeDomainReady[ln.Code] 60 for _, v := range writeDomains { 61 fp, err := tr.ensureFileNameFor(ln, v) 62 if err != nil { 63 return r, err 64 } 65 if !ready { 66 w, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 67 if err != nil { 68 return r, err 69 } 70 s := fmt.Sprintf(`msgid "" 71 msgstr "" 72 "Content-Type: text/plain; charset=UTF-8\n" 73 "Language: %s\n" 74 75 `, ln.Code) 76 _, err = w.Write([]byte(s)) 77 if err != nil { 78 return r, err 79 } 80 w.Close() 81 } 82 w, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND, 0644) 83 logg.DebugCtxf(tr.ctx, "writer", "fp", fp) 84 if err != nil { 85 return r, err 86 } 87 r = append(r, w) 88 } 89 writeDomainReady[ln.Code] = true 90 return r, nil 91 } 92 93 func (tr *translator) writeTranslation(w io.Writer, sym string, msgid string, msgstr string) error { 94 s := fmt.Sprintf(`#: vise_node.%s 95 msgid "" 96 %s 97 msgstr "" 98 %s 99 100 `, sym, msgid, msgstr) 101 _, err := w.Write([]byte(s)) 102 if err != nil { 103 return err 104 } 105 return nil 106 } 107 108 func (tr *translator) closeWriters(writers []io.WriteCloser) { 109 for _, w := range writers { 110 w.Close() 111 } 112 } 113 114 // TODO: DRY; merge with menuFunc 115 func (tr *translator) nodeFunc(node *debug.Node) error { 116 var def string 117 for i, ln := range tr.langs { 118 var s string 119 ww, err := tr.writersFor(ln) 120 defer tr.closeWriters(ww) 121 if err != nil { 122 return fmt.Errorf("failed writers for lang '%s': %v", ln.Code, err) 123 } 124 ctx := context.WithValue(tr.ctx, "Language", ln) 125 r, err := tr.rs.GetTemplate(ctx, node.Name) 126 if err == nil { 127 logg.TraceCtxf(tr.ctx, "template found", "lang", ln, "node", node.Name) 128 for i, v := range strings.Split(r, "\n") { 129 if i > 0 { 130 if tr.newline { 131 s += fmt.Sprintf("\t\"\\n\"\n") 132 } 133 } 134 s += fmt.Sprintf("\t\"%s\"\n", v) 135 } 136 if def == "" { 137 def = fmt.Sprintf("\t\"%s\"\n", node.Name) 138 err = tr.writeTranslation(ww[1], node.Name, def, s) 139 } 140 if i == 0 { 141 def = s 142 } 143 err = tr.writeTranslation(ww[0], node.Name, def, s) 144 if err != nil { 145 return err 146 } 147 } else { 148 logg.DebugCtxf(tr.ctx, "no template found", "node", node.Name, "lang", ln) 149 } 150 } 151 return nil 152 } 153 154 // TODO: drop the multiline gen 155 func (tr *translator) menuFunc(sym string) error { 156 var def string 157 for i, ln := range tr.langs { 158 var s string 159 ww, err := tr.writersFor(ln) 160 defer tr.closeWriters(ww) 161 if err != nil { 162 return fmt.Errorf("failed writers for lang '%s': %v", ln.Code, err) 163 } 164 ctx := context.WithValue(tr.ctx, "Language", ln) 165 r, err := tr.rs.GetMenu(ctx, sym) 166 if err == nil { 167 logg.TraceCtxf(tr.ctx, "menu found", "lang", ln, "menu", sym) 168 for i, v := range strings.Split(r, "\n") { 169 if i > 0 { 170 if tr.newline { 171 s += fmt.Sprintf("\t\"\\n\"\n") 172 } 173 } 174 s += fmt.Sprintf("\t\"%s\"\n", v) 175 } 176 if def == "" { 177 def = fmt.Sprintf("\t\"%s\"\n", sym) 178 err = tr.writeTranslation(ww[2], sym, def, s) 179 } 180 if i == 0 { 181 def = s 182 } 183 err = tr.writeTranslation(ww[0], sym, def, s) 184 if err != nil { 185 return err 186 } 187 } else { 188 logg.DebugCtxf(tr.ctx, "no menu found", "menu", sym, "lang", ln) 189 } 190 } 191 return nil 192 } 193 194 func (tr *translator) AddLang(ln lang.Language) error { 195 var err error 196 tr.langs = append(tr.langs, ln) 197 return err 198 } 199 200 type langVar struct { 201 v []lang.Language 202 } 203 204 func (lv *langVar) Set(s string) error { 205 v, err := lang.LanguageFromCode(s) 206 if err != nil { 207 return err 208 } 209 lv.v = append(lv.v, v) 210 return err 211 } 212 213 func (lv *langVar) String() string { 214 var s []string 215 for _, v := range lv.v { 216 s = append(s, v.Code) 217 } 218 return strings.Join(s, ",") 219 } 220 221 func (lv *langVar) Langs() []lang.Language { 222 return lv.v 223 } 224 225 func main() { 226 var dir string 227 var outDir string 228 var root string 229 var newline bool 230 var langs langVar 231 232 flag.StringVar(&dir, "d", ".", "node resource dir to read from") 233 flag.StringVar(&outDir, "o", "locale", "output directory") 234 flag.StringVar(&root, "root", "root", "entry point symbol") 235 flag.BoolVar(&newline, "newline", false, "insert newlines in multiline strings") 236 flag.Var(&langs, "l", "process for language") 237 flag.Parse() 238 239 fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir) 240 241 err := os.MkdirAll(outDir, 0700) 242 if err != nil { 243 fmt.Fprintf(os.Stderr, "output dir create error: %v", err) 244 os.Exit(1) 245 } 246 247 ctx := context.Background() 248 rsStore := fsdb.NewFsDb() 249 err = rsStore.Connect(ctx, dir) 250 if err != nil { 251 fmt.Fprintf(os.Stderr, "resource db connect error: %v", err) 252 os.Exit(1) 253 } 254 255 rs := resource.NewDbResource(rsStore) 256 257 tr := newTranslator(ctx, rs, outDir, newline) 258 for _, ln := range langs.Langs() { 259 logg.DebugCtxf(ctx, "lang", "lang", ln) 260 err = tr.AddLang(ln) 261 if err != nil { 262 fmt.Fprintf(os.Stderr, "add language failed for %s: %v", ln.Code, err) 263 os.Exit(1) 264 } 265 } 266 267 nm := debug.NewNodeMap(root) 268 err = nm.Run(ctx, rs) 269 if err != nil { 270 fmt.Fprintf(os.Stderr, "node tree process fail: %v", err) 271 os.Exit(1) 272 } 273 274 for k, v := range debug.NodeIndex { 275 err = tr.nodeFunc(&v) 276 if err != nil { 277 fmt.Fprintf(os.Stderr, "translate process error for node %s: %v", k, err) 278 os.Exit(1) 279 } 280 } 281 282 for k, _ := range debug.MenuIndex { 283 logg.Tracef("processing menu", "sym", k) 284 err = tr.menuFunc(k) 285 if err != nil { 286 fmt.Fprintf(os.Stderr, "translate process error for menu %s: %v", k, err) 287 os.Exit(1) 288 } 289 } 290 291 }