// Copyright (C) 2022 The Syncthing Authors. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. //go:build ignore // +build ignore package main import ( "encoding/json" "fmt" "io" "log" "net/http" "os" "regexp" "sort" "strings" ) type stat struct { Code string `json:"code"` Name string `json:"name"` Total int `json:"total"` Translated int `json:"translated"` Fuzzy int `json:"fuzzy"` } type translation map[string]any func main() { log.SetFlags(log.Lshortfile) token := os.Getenv("WEBLATE_TOKEN") if token == "" { log.Fatal("Need environment variable WEBLATE_TOKEN") } curValidLangs := make(map[string]struct{}) for _, lang := range loadValidLangs() { curValidLangs[lang] = struct{}{} } log.Println(curValidLangs) resp := req("https://hosted.weblate.org/exports/stats/syncthing/gui/?format=json", token) var stats []stat err := json.NewDecoder(resp.Body).Decode(&stats) if err != nil { log.Fatal(err) } resp.Body.Close() names := make(map[string]string) var langs []string for _, stat := range stats { code := reformatLanguageCode(stat.Code) pct := 100 * stat.Translated / stat.Total if _, valid := curValidLangs[code]; pct < 75 || !valid && pct < 95 { log.Printf("Language %q too low completion ratio %d%%", code, pct) } else { langs = append(langs, code) names[code] = stat.Name } if code == "en" { continue } log.Printf("Updating language %q", code) resp := req("https://hosted.weblate.org/api/translations/syncthing/gui/"+stat.Code+"/file/", token) bs, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } resp.Body.Close() var t translation if err := json.Unmarshal(bs, &t); err != nil { log.Fatal(err) } fd, err := os.Create("lang-" + code + ".json") if err != nil { log.Fatal(err) } fd.Write(bs) fd.Close() } saveValidLangs(langs) saveLanguageNames(names) } func reformatLanguageCode(origCode string) string { switch origCode { case "ko": return "ko-KR" case "nb_NO": return "nb" case "ro": return "ro-RO" case "zh_Hans": return "zh-CN" case "zh_Hant": return "zh-TW" case "zh_Hant_HK": return "zh-HK" default: return strings.Replace(origCode, "_", "-", 1) } } func saveValidLangs(langs []string) { sort.Strings(langs) fd, err := os.Create("valid-langs.js") if err != nil { log.Fatal(err) } fmt.Fprint(fd, "var validLangs = ") json.NewEncoder(fd).Encode(langs) fd.Close() } func saveLanguageNames(names map[string]string) { fd, err := os.Create("prettyprint.js") if err != nil { log.Fatal(err) } fmt.Fprint(fd, "var langPrettyprint = ") json.NewEncoder(fd).Encode(names) fd.Close() } func req(url, token string) *http.Response { req, err := http.NewRequest("GET", url, nil) if err != nil { log.Fatal(err) } req.Header.Set("Authorization", "Token "+token) resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } return resp } func loadValidLangs() []string { fd, err := os.Open("valid-langs.js") if err != nil { log.Fatal(err) } defer fd.Close() bs, err := io.ReadAll(fd) if err != nil { log.Fatal(err) } var langs []string exp := regexp.MustCompile(`\[([a-zA-Z@",-_]+)\]`) if matches := exp.FindSubmatch(bs); len(matches) == 2 { langs = strings.Split(string(matches[1]), ",") for i := range langs { // Remove quotes langs[i] = langs[i][1 : len(langs[i])-1] } } return langs }