├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── googletrans.go ├── googletrans_bench_test.go ├── googletrans_test.go ├── tk ├── tk.go └── tk_test.go ├── tkk ├── tkk.go ├── tkk_bench_test.go └── tkk_test.go └── transcookie ├── transcookie.go └── transcookie_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 mind1949 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export GO111MODULE=on 2 | fmt: 3 | gofmt -w -s . 4 | go mod tidy 5 | test: 6 | go test tkk/* 7 | go test tk/* 8 | go test transcookie/* 9 | go test . 10 | bench: 11 | go test tkk/* -bench=. -run=NONE -benchmem 12 | go test . -bench=. -run=NONE -benchmem -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Googletrans 2 | [![language](https://img.shields.io/badge/language-Golang-blue)](https://golang.org/) 3 | [![Documentation](https://godoc.org/github.com/mind1949/googletrans?status.svg)](https://godoc.org/github.com/mind1949/googletrans) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/mind1949/googletrans)](https://goreportcard.com/report/github.com/mind1949/googletrans) 5 | 6 | G文⚡️: Concurrency-safe, free and unlimited golang library that implemented Google Translate API. 7 | 8 | Inspired by [py-googletrans](https://github.com/ssut/py-googletrans). 9 | 10 | # Features 11 | * Out of the box 12 | * Auto language detection 13 | * Customizable service URL 14 | 15 | # Installation 16 | 17 | ``` 18 | go get -u github.com/mind1949/googletrans 19 | ``` 20 | 21 | # Usage 22 | 23 | ## Detect language 24 | ```golang 25 | package main 26 | 27 | import ( 28 | "fmt" 29 | 30 | "github.com/mind1949/googletrans" 31 | ) 32 | 33 | func main() { 34 | detected, err := googletrans.Detect("hello googletrans") 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | format := "language: %q, confidence: %0.2f\n" 40 | fmt.Printf(format, detected.Lang, detected.Confidence) 41 | } 42 | 43 | // Output: 44 | // language: "en", confidence: 1.00 45 | ``` 46 | 47 | ## Translate 48 | ```golang 49 | package main 50 | 51 | import ( 52 | "fmt" 53 | 54 | "github.com/mind1949/googletrans" 55 | "golang.org/x/text/language" 56 | ) 57 | 58 | func main() { 59 | params := googletrans.TranslateParams{ 60 | Src: "auto", 61 | Dest: language.SimplifiedChinese.String(), 62 | Text: "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. ", 63 | } 64 | translated, err := googletrans.Translate(params) 65 | if err != nil { 66 | panic(err) 67 | } 68 | fmt.Printf("text: %q \npronunciation: %q", translated.Text, translated.Pronunciation) 69 | } 70 | 71 | // Output: 72 | // text: "Go是一种开放源代码编程语言,可轻松构建简单,可靠且高效的软件。" 73 | // pronunciation: "Go shì yī zhǒng kāifàng yuán dàimǎ biānchéng yǔyán, kě qīngsōng gòujiàn jiǎndān, kěkào qiě gāoxiào de ruǎnjiàn." 74 | ``` 75 | 76 | ## Customize service URLs 77 | ```golang 78 | package main 79 | 80 | import "github.com/mind1949/googletrans" 81 | 82 | func main() { 83 | serviceURLs := []string{ 84 | "https://translate.google.com/", 85 | "https://translate.google.pl/"} 86 | googletrans.Append(serviceURLs...) 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mind1949/googletrans 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mind1949/googletrans/82beff2a0296ae22f62f31a72fe57eed9921c15c/go.sum -------------------------------------------------------------------------------- /googletrans.go: -------------------------------------------------------------------------------- 1 | package googletrans 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "math/rand" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | "strings" 12 | "text/scanner" 13 | "time" 14 | 15 | "github.com/mind1949/googletrans/tk" 16 | "github.com/mind1949/googletrans/tkk" 17 | "github.com/mind1949/googletrans/transcookie" 18 | ) 19 | 20 | const ( 21 | defaultServiceURL = "https://translate.google.cn" 22 | ) 23 | 24 | var ( 25 | emptyTranlated = Translated{} 26 | emptyDetected = Detected{} 27 | emptyRawTranslated = rawTranslated{} 28 | 29 | defaultTranslator = New() 30 | ) 31 | 32 | // Translate uses defaultTranslator to translate params.text 33 | func Translate(params TranslateParams) (Translated, error) { 34 | return defaultTranslator.Translate(params) 35 | } 36 | 37 | // Detect uses defaultTranslator to detect language 38 | func Detect(text string) (Detected, error) { 39 | return defaultTranslator.Detect(text) 40 | } 41 | 42 | // Append appends serviceURLs to defaultTranslator's serviceURLs 43 | func Append(serviceURLs ...string) { 44 | defaultTranslator.Append(serviceURLs...) 45 | } 46 | 47 | // TranslateParams represents translate params 48 | type TranslateParams struct { 49 | Src string `json:"src"` // source language (default: auto) 50 | Dest string `json:"dest"` // destination language 51 | Text string `json:"text"` // text for translating 52 | } 53 | 54 | // Translated represents translated result 55 | type Translated struct { 56 | Params TranslateParams `json:"params"` 57 | Text string `json:"text"` // translated text 58 | Pronunciation string `json:"pronunciation"` // pronunciation of translated text 59 | } 60 | 61 | // Detected represents language detection result 62 | type Detected struct { 63 | Lang string `json:"lang"` // detected language 64 | Confidence float64 `json:"confidence"` // the confidence of detection result (0.00 to 1.00) 65 | } 66 | 67 | type rawTranslated struct { 68 | translated struct { 69 | text string 70 | pronunciation string 71 | } 72 | detected struct { 73 | originalLanguage string 74 | confidence float64 75 | } 76 | } 77 | 78 | // Translator is responsible for translation 79 | type Translator struct { 80 | clt *http.Client 81 | serviceURLs []string 82 | tkkCache tkk.Cache 83 | } 84 | 85 | // New initializes a Translator 86 | func New(serviceURLs ...string) *Translator { 87 | var has bool 88 | for i := 0; i < len(serviceURLs); i++ { 89 | if serviceURLs[i] == defaultServiceURL { 90 | has = true 91 | break 92 | } 93 | } 94 | if !has { 95 | serviceURLs = append(serviceURLs, defaultServiceURL) 96 | } 97 | 98 | return &Translator{ 99 | clt: &http.Client{}, 100 | serviceURLs: serviceURLs, 101 | tkkCache: tkk.NewCache(random(serviceURLs)), 102 | } 103 | } 104 | 105 | // Translate translates text from src language to dest language 106 | func (t *Translator) Translate(params TranslateParams) (Translated, error) { 107 | if params.Src == "" { 108 | params.Src = "auto" 109 | } 110 | 111 | transData, err := t.do(params) 112 | if err != nil { 113 | return emptyTranlated, err 114 | } 115 | 116 | return Translated{ 117 | Params: params, 118 | Text: transData.translated.text, 119 | Pronunciation: transData.translated.pronunciation, 120 | }, nil 121 | } 122 | 123 | // Detect detects text's language 124 | func (t *Translator) Detect(text string) (Detected, error) { 125 | transData, err := t.do(TranslateParams{ 126 | Src: "auto", 127 | Dest: "en", 128 | Text: text, 129 | }) 130 | if err != nil { 131 | return emptyDetected, err 132 | } 133 | return Detected{ 134 | Lang: transData.detected.originalLanguage, 135 | Confidence: transData.detected.confidence, 136 | }, nil 137 | } 138 | 139 | func (t *Translator) do(params TranslateParams) (rawTranslated, error) { 140 | req, err := t.buildTransRequest(params) 141 | if err != nil { 142 | return emptyRawTranslated, err 143 | } 144 | 145 | transService := req.URL.Scheme + "://" + req.URL.Hostname() 146 | var resp *http.Response 147 | for try := 0; try < 3; try++ { 148 | cookie, err := transcookie.Get(transService) 149 | if err != nil { 150 | return emptyRawTranslated, err 151 | } 152 | req.AddCookie(&cookie) 153 | resp, err = t.clt.Do(req) 154 | if err != nil { 155 | return emptyRawTranslated, err 156 | } 157 | 158 | if resp.StatusCode == http.StatusOK { 159 | break 160 | } 161 | 162 | if resp.StatusCode == http.StatusTooManyRequests { 163 | _, err = transcookie.Update(transService, 3*time.Second) 164 | if err != nil { 165 | return emptyRawTranslated, err 166 | } 167 | } 168 | } 169 | if resp.StatusCode != http.StatusOK { 170 | return emptyRawTranslated, fmt.Errorf("failed to get translation result, err: %s", resp.Status) 171 | } 172 | 173 | data, err := ioutil.ReadAll(resp.Body) 174 | if err != nil { 175 | return emptyRawTranslated, err 176 | } 177 | resp.Body.Close() 178 | 179 | result, err := t.parseRawTranslated(data) 180 | if err != nil { 181 | return emptyRawTranslated, err 182 | } 183 | 184 | return result, nil 185 | } 186 | 187 | func (t *Translator) buildTransRequest(params TranslateParams) (request *http.Request, err error) { 188 | tkk, err := t.tkkCache.Get() 189 | if err != nil { 190 | return nil, err 191 | } 192 | tk, _ := tk.Get(params.Text, tkk) 193 | 194 | u, err := url.Parse(t.randomServiceURL() + "/translate_a/single") 195 | if err != nil { 196 | return nil, err 197 | } 198 | 199 | if params.Src == "" { 200 | params.Src = "auto" 201 | } 202 | queries := url.Values{} 203 | for k, v := range map[string]string{ 204 | "client": "webapp", 205 | "sl": params.Src, 206 | "tl": params.Dest, 207 | "hl": params.Dest, 208 | "ie": "UTF-8", 209 | "oe": "UTF-8", 210 | "otf": "1", 211 | "ssel": "0", 212 | "tsel": "0", 213 | "kc": "7", 214 | "tk": tk, 215 | } { 216 | queries.Add(k, v) 217 | } 218 | dts := []string{"at", "bd", "ex", "ld", "md", "qca", "rw", "rm", "ss", "t"} 219 | for i := 0; i < len(dts); i++ { 220 | queries.Add("dt", dts[i]) 221 | } 222 | 223 | q := url.Values{} 224 | q.Add("q", params.Text) 225 | 226 | // If the length of the url of the get request exceeds 2000, change to a post request 227 | if len(u.String()+"?"+queries.Encode()+q.Encode()) >= 2000 { 228 | u.RawQuery = queries.Encode() 229 | request, err = http.NewRequest(http.MethodPost, u.String(), strings.NewReader(q.Encode())) 230 | if err != nil { 231 | return nil, err 232 | } 233 | request.Header.Add("Content-Type", "application/x-www-form-urlencoded") 234 | } else { 235 | queries.Add("q", params.Text) 236 | u.RawQuery = queries.Encode() 237 | request, err = http.NewRequest(http.MethodGet, u.String(), nil) 238 | if err != nil { 239 | return nil, err 240 | } 241 | } 242 | 243 | return request, nil 244 | } 245 | 246 | func (*Translator) parseRawTranslated(data []byte) (result rawTranslated, err error) { 247 | var s scanner.Scanner 248 | s.Init(bytes.NewReader(data)) 249 | var ( 250 | coord = []int{-1} 251 | textBuilder strings.Builder 252 | ) 253 | for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() { 254 | switch tok { 255 | case '[': 256 | coord[len(coord)-1]++ 257 | coord = append(coord, -1) 258 | case ']': 259 | coord = coord[:len(coord)-1] 260 | case ',': 261 | // no-op 262 | default: 263 | tokText := s.TokenText() 264 | coord[len(coord)-1]++ 265 | 266 | if len(coord) == 4 && coord[1] == 0 && coord[3] == 0 { 267 | if tokText != "null" { 268 | textBuilder.WriteString(tokText[1 : len(tokText)-1]) 269 | } 270 | } 271 | if len(coord) == 4 && coord[0] == 0 && coord[1] == 0 && coord[2] == 1 && coord[3] == 2 { 272 | if tokText != "null" { 273 | result.translated.pronunciation = tokText[1 : len(tokText)-1] 274 | } 275 | } 276 | if len(coord) == 4 && coord[0] == 0 && coord[1] == 0 && coord[3] == 2 { 277 | if tokText != "null" { 278 | result.translated.pronunciation = tokText[1 : len(tokText)-1] 279 | } 280 | } 281 | if len(coord) == 2 && coord[0] == 0 && coord[1] == 2 { 282 | result.detected.originalLanguage = tokText[1 : len(tokText)-1] 283 | } 284 | if len(coord) == 2 && coord[0] == 0 && coord[1] == 6 { 285 | result.detected.confidence, _ = strconv.ParseFloat(s.TokenText(), 64) 286 | } 287 | } 288 | } 289 | result.translated.text = textBuilder.String() 290 | 291 | return result, nil 292 | } 293 | 294 | // Append appends serviceURLS to t's serviceURLs 295 | func (t *Translator) Append(serviceURLs ...string) { 296 | t.serviceURLs = append(t.serviceURLs, serviceURLs...) 297 | } 298 | 299 | func (t *Translator) randomServiceURL() (serviceURL string) { 300 | return random(t.serviceURLs) 301 | } 302 | 303 | func random(list []string) string { 304 | i := rand.Intn(len(list)) 305 | return list[i] 306 | } 307 | -------------------------------------------------------------------------------- /googletrans_bench_test.go: -------------------------------------------------------------------------------- 1 | package googletrans 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkTranslate(b *testing.B) { 8 | for i := 0; i < b.N; i++ { 9 | params := TranslateParams{ 10 | Src: "auto", 11 | Dest: "zh-CN", 12 | Text: "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. ", 13 | } 14 | Translate(params) 15 | } 16 | } 17 | 18 | func BenchmarkParseRawTranslated(b *testing.B) { 19 | data := []byte(rawTraslatedStr) 20 | var t *Translator 21 | for i := 0; i < b.N; i++ { 22 | t.parseRawTranslated(data) 23 | } 24 | } 25 | 26 | var rawTraslatedStr = ` 27 | [[["Go中的字符串,字节,符文和字符\n\n","Strings, bytes, runes and characters in Go\n\n",null,null,3,null,null,[[] 28 | ] 29 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 30 | ] 31 | ] 32 | ] 33 | ,["罗伯·派克\n","Rob Pike\n",null,null,3,null,null,[[] 34 | ] 35 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 36 | ] 37 | ] 38 | ] 39 | ,["2013年10月23日\n","23 October 2013\n",null,null,3,null,null,[[] 40 | ] 41 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 42 | ] 43 | ] 44 | ] 45 | ,["介绍\n\n","Introduction\n\n",null,null,1] 46 | ,["上一篇博客文章使用许多示例说明了切片在Go中的工作原理,以说明其实现的机制。","The previous blog post explained how slices work in Go, using a number of examples to illustrate the mechanism behind their implementation.",null,null,3,null,null,[[] 47 | ] 48 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 49 | ] 50 | ] 51 | ] 52 | ,["在此背景下,本文讨论了Go中的字符串。","Building on that background, this post discusses strings in Go.",null,null,3,null,null,[[] 53 | ] 54 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 55 | ] 56 | ] 57 | ] 58 | ,["最初,字符串对于博客文章而言似乎太简单了,但是要想很好地使用它们,不仅需要了解它们的工作原理,还需要了解字节,字符和符文之间的区别,以及Unicode和UTF- ","At first, strings might seem too simple a topic for a blog post, but to use them well requires understanding not only how they work, but also the difference between a byte, a character, and a rune, the difference between Unicode and UTF-",null,null,3,null,null,[[] 59 | ] 60 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 61 | ] 62 | ] 63 | ] 64 | ,["8,字符串和字符串文字之间的区别,以及其他更细微的区别。\n\n","8, the difference between a string and a string literal, and other even more subtle distinctions.\n\n",null,null,3,null,null,[[] 65 | ] 66 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 67 | ] 68 | ] 69 | ] 70 | ,["处理该主题的一种方法是将其视为对以下常见问题的回答:“当我在位置n处索引Go字符串时,为什么不得到第n个字符?”","One way to approach this topic is to think of it as an answer to the frequently asked question, \"When I index a Go string at position n, why don't I get the nth character?\"",null,null,3,null,null,[[] 71 | ] 72 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 73 | ] 74 | ] 75 | ] 76 | ,["如您所见,这个问题使我们获得了许多有关文本在现代世界中如何工作的细节。\n\n","As you'll see, this question leads us to many details about how text works in the modern world.\n\n",null,null,3,null,null,[[] 77 | ] 78 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 79 | ] 80 | ] 81 | ] 82 | ,["Joel Spolsky的著名博客文章,绝对绝对是每个软件开发人员绝对肯定地了解Unicode和字符集(无借口!),是其中一个独立于Go的极好的介绍。","An excellent introduction to some of these issues, independent of Go, is Joel Spolsky's famous blog post, The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!).",null,null,3,null,null,[[] 83 | ] 84 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 85 | ] 86 | ] 87 | ] 88 | ,["他提出的许多观点将在这里得到回应。\n","Many of the points he raises will be echoed here.\n",null,null,3,null,null,[[] 89 | ] 90 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 91 | ] 92 | ] 93 | ] 94 | ,["什么是琴弦?\n\n","What is a string?\n\n",null,null,3,null,null,[[] 95 | ] 96 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 97 | ] 98 | ] 99 | ] 100 | ,["让我们从一些基础知识开始。\n\n","Let's start with some basics.\n\n",null,null,3,null,null,[[] 101 | ] 102 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 103 | ] 104 | ] 105 | ] 106 | ,["在Go中,字符串实际上是只读的字节片。","In Go, a string is in effect a read-only slice of bytes.",null,null,3,null,null,[[] 107 | ] 108 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 109 | ] 110 | ] 111 | ] 112 | ,["如果您不确定某个字节是什么字节或如何工作,请阅读上一篇博客文章;","If you're at all uncertain about what a slice of bytes is or how it works, please read the previous blog post;",null,null,3,null,null,[[] 113 | ] 114 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 115 | ] 116 | ] 117 | ] 118 | ,["我们在这里假设您有。\n\n","we'll assume here that you have.\n\n",null,null,3,null,null,[[] 119 | ] 120 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 121 | ] 122 | ] 123 | ] 124 | ,["重要的是要预先声明字符串包含任意字节。","It's important to state right up front that a string holds arbitrary bytes.",null,null,3,null,null,[[] 125 | ] 126 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 127 | ] 128 | ] 129 | ] 130 | ,["不需要保留Unicode文本,UTF-8文本或任何其他预定义格式。","It is not required to hold Unicode text, UTF-8 text, or any other predefined format.",null,null,3,null,null,[[] 131 | ] 132 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 133 | ] 134 | ] 135 | ] 136 | ,["就字符串的内容而言,它完全相当于一个字节片。\n\n","As far as the content of a string is concerned, it is exactly equivalent to a slice of bytes.\n\n",null,null,3,null,null,[[] 137 | ] 138 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 139 | ] 140 | ] 141 | ] 142 | ,["这是一个字符串文字(稍后将详细介绍),该文字使用\\ xNN表示法定义一个包含一些特殊字节值的字符串常量。 ","Here is a string literal (more about those soon) that uses the \\xNN notation to define a string constant holding some peculiar byte values.",null,null,3,null,null,[[] 143 | ] 144 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 145 | ] 146 | ] 147 | ] 148 | ,["(当然,字节的范围是十六进制值00到FF,包括两端)。\n\n ","(Of course, bytes range from hexadecimal values 00 through FF, inclusive.)\n\n ",null,null,3,null,null,[[] 149 | ] 150 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 151 | ] 152 | ] 153 | ] 154 | ,["const sample \u003d“ \\ xbd \\ xb2 \\ x3d \\ xbc \\ x20 \\ xe2 \\ x8c \\ x98”\n\n","const sample \u003d \"\\xbd\\xb2\\x3d\\xbc\\x20\\xe2\\x8c\\x98\"\n\n",null,null,3,null,null,[[] 155 | ] 156 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 157 | ] 158 | ] 159 | ] 160 | ,["打印字符串\n\n","Printing strings\n\n",null,null,3,null,null,[[] 161 | ] 162 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 163 | ] 164 | ] 165 | ] 166 | ,["由于示例字符串中的某些字节不是有效的ASCII,甚至不是有效的UTF-8,因此直接打印字符串将产生难看的输出。","Because some of the bytes in our sample string are not valid ASCII, not even valid UTF-8, printing the string directly will produce ugly output.",null,null,3,null,null,[[] 167 | ] 168 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 169 | ] 170 | ] 171 | ] 172 | ,["简单的打印声明\n\n ","The simple print statement\n\n ",null,null,3,null,null,[[] 173 | ] 174 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 175 | ] 176 | ] 177 | ] 178 | ,["fmt.Println(样本)\n\n","fmt.Println(sample)\n\n",null,null,3,null,null,[[] 179 | ] 180 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 181 | ] 182 | ] 183 | ] 184 | ,["产生这种混乱(其确切外观随环境而变化):\n\n ","produces this mess (whose exact appearance varies with the environment):\n\n",null,null,3,null,null,[[] 185 | ] 186 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 187 | ] 188 | ] 189 | ] 190 | ,["��\u003d�⌘\n\n","��\u003d� ⌘\n\n",null,null,3,null,null,[[] 191 | ] 192 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 193 | ] 194 | ] 195 | ] 196 | ,["要找出该字符串的真正含义,我们需要将其拆开并检查各个部分。","To find out what that string really holds, we need to take it apart and examine the pieces.",null,null,3,null,null,[[] 197 | ] 198 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 199 | ] 200 | ] 201 | ] 202 | ,["有几种方法可以做到这一点。","There are several ways to do this.",null,null,1] 203 | ,["最明显的是遍历其内容并逐个拉出字节,如以下for循环所示:\n\n ","The most obvious is to loop over its contents and pull out the bytes individually, as in this for loop:\n\n ",null,null,3,null,null,[[] 204 | ] 205 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 206 | ] 207 | ] 208 | ] 209 | ,["对于我:\u003d 0; ","for i :\u003d 0;",null,null,3,null,null,[[] 210 | ] 211 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 212 | ] 213 | ] 214 | ] 215 | ,["i \u003clen(样本);","i \u003c len(sample);",null,null,3,null,null,[[] 216 | ] 217 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 218 | ] 219 | ] 220 | ] 221 | ,["我++ {\n ","i++ {\n ",null,null,3,null,null,[[] 222 | ] 223 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 224 | ] 225 | ] 226 | ] 227 | ,["fmt.Printf(“%x”,sample [i])\n ","fmt.Printf(\"%x \", sample[i])\n ",null,null,3,null,null,[[] 228 | ] 229 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 230 | ] 231 | ] 232 | ] 233 | ,["}\n\n","}\n\n",null,null,3,null,null,[[] 234 | ] 235 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 236 | ] 237 | ] 238 | ] 239 | ,["如前所述,对字符串进行索引访问的是单个字节,而不是字符。","As implied up front, indexing a string accesses individual bytes, not characters.",null,null,3,null,null,[[] 240 | ] 241 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 242 | ] 243 | ] 244 | ] 245 | ,["我们将在下面详细返回该主题。","We'll return to that topic in detail below.",null,null,3,null,null,[[] 246 | ] 247 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 248 | ] 249 | ] 250 | ] 251 | ,["现在,让我们仅保留字节。","For now, let's stick with just the bytes.",null,null,3,null,null,[[] 252 | ] 253 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 254 | ] 255 | ] 256 | ] 257 | ,["这是逐字节循环的输出:\n\n","This is the output from the byte-by-byte loop:\n\n",null,null,3,null,null,[[] 258 | ] 259 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 260 | ] 261 | ] 262 | ] 263 | ,["bd b2 3d bc 20 e2 8c 98\n\n","bd b2 3d bc 20 e2 8c 98\n\n",null,null,3,null,null,[[] 264 | ] 265 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 266 | ] 267 | ] 268 | ] 269 | ,["注意各个字节如何与定义字符串的十六进制转义符匹配。\n\n","Notice how the individual bytes match the hexadecimal escapes that defined the string.\n\n",null,null,3,null,null,[[] 270 | ] 271 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 272 | ] 273 | ] 274 | ] 275 | ,["为混乱的字符串生成可显示的输出的一种较短方法是使用fmt.Printf的%x(十六进制)格式动词。","A shorter way to generate presentable output for a messy string is to use the %x (hexadecimal) format verb of fmt.Printf.",null,null,3,null,null,[[] 276 | ] 277 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 278 | ] 279 | ] 280 | ] 281 | ,["它只是将字符串的顺序字节转储为十六进制数字,每个字节两个。\n\n ","It just dumps out the sequential bytes of the string as hexadecimal digits, two per byte.\n\n ",null,null,3,null,null,[[] 282 | ] 283 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 284 | ] 285 | ] 286 | ] 287 | ,["fmt.Printf(“%x \\ n”,示例)\n\n","fmt.Printf(\"%x\\n\", sample)\n\n",null,null,3,null,null,[[] 288 | ] 289 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 290 | ] 291 | ] 292 | ] 293 | ,["将其输出与上面的输出进行比较:\n\n","Compare its output to that above:\n\n",null,null,3,null,null,[[] 294 | ] 295 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 296 | ] 297 | ] 298 | ] 299 | ,["bdb23dbc20e28c98\n\n","bdb23dbc20e28c98\n\n",null,null,3,null,null,[[] 300 | ] 301 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 302 | ] 303 | ] 304 | ] 305 | ,["一个不错的技巧是使用该格式的“空格”标志,在%和x之间放置一个空格。","A nice trick is to use the \"space\" flag in that format, putting a space between the % and the x.",null,null,3,null,null,[[] 306 | ] 307 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 308 | ] 309 | ] 310 | ] 311 | ,["将此处使用的格式字符串与上面的格式字符串进行比较,\n\n ","Compare the format string used here to the one above,\n\n ",null,null,3,null,null,[[] 312 | ] 313 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 314 | ] 315 | ] 316 | ] 317 | ,["fmt.Printf(“%x \\ n”,示例)\n\n","fmt.Printf(\"% x\\n\", sample)\n\n",null,null,3,null,null,[[] 318 | ] 319 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 320 | ] 321 | ] 322 | ] 323 | ,["并注意字节之间如何留出空格,从而使结果不那么强悍:\n\n","and notice how the bytes come out with spaces between, making the result a little less imposing:\n\n",null,null,3,null,null,[[] 324 | ] 325 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 326 | ] 327 | ] 328 | ] 329 | ,["bd b2 3d bc 20 e2 8c 98\n\n","bd b2 3d bc 20 e2 8c 98\n\n",null,null,3,null,null,[[] 330 | ] 331 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 332 | ] 333 | ] 334 | ] 335 | ,["还有更多。 ","There's more.",null,null,1] 336 | ,["%q(带引号)动词将转义字符串中所有不可打印的字节序列,因此输出无歧义。\n\n ","The %q (quoted) verb will escape any non-printable byte sequences in a string so the output is unambiguous.\n\n ",null,null,3,null,null,[[] 337 | ] 338 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 339 | ] 340 | ] 341 | ] 342 | ,["fmt.Printf(“%q \\ n”,示例)\n\n","fmt.Printf(\"%q\\n\", sample)\n\n",null,null,3,null,null,[[] 343 | ] 344 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 345 | ] 346 | ] 347 | ] 348 | ,["当大部分字符串可理解为文本但有一些特殊的含义可以根除时,此技术很方便。","This technique is handy when much of the string is intelligible as text but there are peculiarities to root out;",null,null,3,null,null,[[] 349 | ] 350 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 351 | ] 352 | ] 353 | ] 354 | ,["它产生:\n\n","it produces:\n\n",null,null,3,null,null,[[] 355 | ] 356 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 357 | ] 358 | ] 359 | ] 360 | ,["“ \\ xbd \\ xb2 \u003d \\ xbc⌘”\n\n","\"\\xbd\\xb2\u003d\\xbc ⌘\"\n\n",null,null,3,null,null,[[] 361 | ] 362 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 363 | ] 364 | ] 365 | ] 366 | ,["如果我们斜视一下,我们可以看到噪声中隐藏的是一个ASCII等号以及规则的空格,最后出现了著名的瑞典“景点”符号。","If we squint at that, we can see that buried in the noise is one ASCII equals sign, along with a regular space, and at the end appears the well-known Swedish \"Place of Interest\" symbol.",null,null,3,null,null,[[] 367 | ] 368 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 369 | ] 370 | ] 371 | ] 372 | ,["该符号的Unicode值为U + 2318,由空格后的字节(十六进制值20)编码为UTF-8:e2 8c 98。\n\n","That symbol has Unicode value U+2318, encoded as UTF-8 by the bytes after the space (hex value 20): e2 8c 98.\n\n",null,null,3,null,null,[[] 373 | ] 374 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 375 | ] 376 | ] 377 | ] 378 | ,["如果我们对字符串中的陌生值不熟悉或感到困惑,可以对%q动词使用“加号”标志。","If we are unfamiliar or confused by strange values in the string, we can use the \"plus\" flag to the %q verb.",null,null,3,null,null,[[] 379 | ] 380 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 381 | ] 382 | ] 383 | ] 384 | ,["此标志使输出在解释UTF-8时不仅转义不可打印的序列,而且转义所有非ASCII字节。","This flag causes the output to escape not only non-printable sequences, but also any non-ASCII bytes, all while interpreting UTF-8.",null,null,3,null,null,[[] 385 | ] 386 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 387 | ] 388 | ] 389 | ] 390 | ,["结果是它公开了格式正确的UTF-8的Unicode值,该值表示字符串中的非ASCII数据:\n\n ","The result is that it exposes the Unicode values of properly formatted UTF-8 that represents non-ASCII data in the string:\n\n ",null,null,3,null,null,[[] 391 | ] 392 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 393 | ] 394 | ] 395 | ] 396 | ,["fmt.Printf(“%+ q \\ n”,示例)\n\n","fmt.Printf(\"%+q\\n\", sample)\n\n",null,null,3,null,null,[[] 397 | ] 398 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 399 | ] 400 | ] 401 | ] 402 | ,["使用这种格式,瑞典符号的Unicode值显示为\\ u转义:\n\n","With that format, the Unicode value of the Swedish symbol shows up as a \\u escape:\n\n",null,null,3,null,null,[[] 403 | ] 404 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 405 | ] 406 | ] 407 | ] 408 | ,["“ \\ xbd \\ xb2 \u003d \\ xbc \\ u2318”\n\n","\"\\xbd\\xb2\u003d\\xbc \\u2318\"\n\n",null,null,3,null,null,[[] 409 | ] 410 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 411 | ] 412 | ] 413 | ] 414 | ,["在调试字符串的内容时,这些打印技术很不错,并且在下面的讨论中会很方便。","These printing techiques are good to know when debugging the contents of strings, and will be handy in the discussion that follows.",null,null,3,null,null,[[] 415 | ] 416 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 417 | ] 418 | ] 419 | ] 420 | ,["值得指出的是,所有这些方法对于字节片的行为与对字符串的行为完全相同。\n\n","It's worth pointing out as well that all these methods behave exactly the same for byte slices as they do for strings.\n\n",null,null,3,null,null,[[] 421 | ] 422 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 423 | ] 424 | ] 425 | ] 426 | ,["这是我们列出的全套打印选项,以完整的程序形式显示,您可以在浏览器中直接运行(和编辑):\n\n","Here's the full set of printing options we've listed, presented as a complete program you can run (and edit) right in the browser:\n\n",null,null,3,null,null,[[] 427 | ] 428 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 429 | ] 430 | ] 431 | ] 432 | ,["包米","package m",null,null,3,null,null,[[] 433 | ] 434 | ,[[["b3ad15e7a0073e77814019b341d18493","en_zh_2019q3.md"] 435 | ] 436 | ] 437 | ] 438 | ,[null,null,"Go zhōng de zìfú chuàn, zì jié, fú wén hé zìfú\n\nluō bó·pàikè\n2013 nián 10 yuè 23 rì\njièshào\n\nshàng yī piān bókè wénzhāng shǐyòng xǔduō shìlì shuōmíngliǎo qiēpiàn zài Go zhōng de gōngzuò yuánlǐ, yǐ shuōmíng qí shíxiàn de jīzhì. Zài cǐ bèijǐng xià, běnwén tǎolùnle Go zhōng de zìfú chuàn. Zuìchū, zìfú chuàn duìyú bókè wénzhāng ér yán sìhū tài jiǎndānle, dànshì yào xiǎng hěn hǎo dì shǐyòng tāmen, bùjǐn xūyào liǎojiě tāmen de gōngzuò yuánlǐ, hái xūyào liǎojiě zì jié, zìfú hé fúwénzhī jiān de qūbié, yǐjí Unicode hé UTF- 8, zìfú chuàn hé zìfú chuàn wénzì zhī jiān de qūbié, yǐjí qítā gèng xìwéi de qūbié.\n\nChǔlǐ gāi zhǔtí de yī zhǒng fāngfǎ shì jiāng qí shì wéi duì yǐxià chángjiàn wèntí de huídá:“Dāng wǒ zài wèizhì n chù suǒyǐn Go zìfú chuàn shí, wèishéme bù dédào dì n gè zìfú?” Rú nín suǒ jiàn, zhège wèntí shǐ wǒmen huòdéle xǔduō yǒuguān wénběn zài xiàndài shìjiè zhōng rúhé gōngzuò de xìjié.\n\nJoel Spolsky de zhùmíng bókè wénzhāng, juéduì juéduì shì měi gè ruǎnjiàn kāifā rényuán juéduì kěndìng dì liǎojiě Unicode hé zìfú jí (wú jièkǒu!), Shì qízhōng yīgè dúlì yú Go de jí hǎo de jièshào. Tā tíchū de xǔduō guāndiǎn jiàng zài zhèlǐ dédào huíyīng.\nShénme shì qín xián?\n\nRàng wǒmen cóng yīxiē jīchǔ zhīshì kāishǐ.\n\nZài Go zhōng, zìfú chuàn shíjì shang shì zhǐ dú de zì jié piàn. Rúguǒ nín bù quèdìng mǒu gè zì jié shì shénme zì jié huò rúhé gōngzuò, qǐng yuèdú shàng yī piān bókè wénzhāng; wǒmen zài zhèlǐ jiǎshè nín yǒu.\n\nZhòngyào de shì yào yùxiān shēngmíng zìfú chuàn bāohán rènyì zì jié. Bù xūyào bǎoliú Unicode wénběn,UTF-8 wénběn huò rènhé qítā yù dìngyì géshì. Jiù zìfú chuàn de nèiróng ér yán, tā wánquán xiāngdāng yú yīgè zì jié piàn.\n\nZhè shì yīgè zìfú chuàn wénzì (shāo hòu jiāng xiángxì jièshào), gāi wénzì shǐyòng\\ xNN biǎoshì fǎ dìngyì yīgè bāohán yīxiē tèshū zì jié zhí de zìfú chuàn chángliàng. (Dāngrán, zì jié de fànwéi shì shíliù jìn zhì zhí 00 dào FF, bāokuò liǎng duān).\n\n Const sample \u003d“ \\ xbd\\ xb2\\ x3d\\ xbc\\ x20\\ xe2\\ x8c\\ x98”\n\ndǎyìn zìfú chuàn\n\nyóuyú shìlì zìfú chuàn zhōng de mǒu xiē zì jié bùshì yǒuxiào de ASCII, shènzhì bùshì yǒuxiào de UTF-8, yīncǐ zhíjiē dǎyìn zìfú chuàn jiāng chǎnshēng nánkàn de shūchū. Jiǎndān de dǎyìn shēngmíng\n\n fmt.Println(yàngběn)\n\nchǎnshēng zhè zhǒng hǔnluàn (qí quèqiè wàiguān suí huánjìng ér biànhuà):\n\n ��\u003d�⌘\n\nYào zhǎo chū gāi zìfú chuàn de zhēnzhèng hányì, wǒmen xūyào jiāng qí chāi kāi bìng jiǎnchá gège bùfèn. Yǒu jǐ zhǒng fāngfǎ kěyǐ zuò dào zhè yīdiǎn. Zuì míngxiǎn de shì biànlì qí nèiróng bìng zhúgè lā chū zì jié, rú yǐxià for xúnhuán suǒ shì:\n\n Duìyú wǒ:\u003d 0; I \u003clen(yàngběn); wǒ ++ {\n fmt.Printf(“%x”,sample [i])\n }\n\nrú qián suǒ shù, duì zìfú chuàn jìnxíng suǒyǐn fǎngwèn de shì dāngè zì jié, ér bùshì zìfú. Wǒmen jiàng zài xiàmiàn xiángxì fǎnhuí gāi zhǔtí. Xiànzài, ràng wǒmen jǐn bǎoliú zì jié. Zhè shì zhú zì jié xúnhuán de shūchū:\n\nBd b2 3d bc 20 e2 8c 98\n\nzhùyì gège zì jié rúhé yǔ dìngyì zìfú chuàn de shíliù jìn zhì zhuǎn yì fú pǐpèi.\n\nWèi hǔnluàn de zìfú chuàn shēngchéng kě xiǎnshì de shūchū de yī zhǒng jiào duǎn fāngfǎ shì shǐyòng fmt.Printf de%x(shíliù jìn zhì) géshì dòngcí. Tā zhǐshì jiāng zìfú chuàn de shùnxù zì jié zhuǎn chǔ wèi shíliù jìn zhì shùzì, měi gè zì jié liǎng gè.\n\n Fmt.Printf(“%x\\ n”, shìlì)\n\njiāng qí shūchū yǔ shàngmiàn de shūchū jìnxíng bǐjiào:\n\nBdb23dbc20e28c98\n\nyīgè bùcuò de jìqiǎo shì shǐyòng gāi géshì de “kònggé” biāozhì, zài%hé x zhī jiān fàngzhì yīgè kònggé. Jiāng cǐ chù shǐyòng de géshì zìfú chuàn yǔ shàngmiàn de géshì zìfú chuàn jìnxíng bǐjiào,\n\n fmt.Printf(“%x\\ n”, shìlì)\n\nbìng zhùyì zì jié zhī jiān rúhé liú chū kònggé, cóng'ér shǐ jiéguǒ bù nàme qiánghàn:\n\nBd b2 3d bc 20 e2 8c 98\n\nhái yǒu gèng duō. %Q(dài yǐnhào) dòngcí jiāng zhuǎn yì zìfú chuàn zhōng suǒyǒu bùkě dǎyìn de zì jié xùliè, yīncǐ shūchū wú qíyì.\n\n Fmt.Printf(“%q\\ n”, shìlì)\n\ndāng dà bùfèn zìfú chuàn kě lǐjiě wèi wénběn dàn yǒu yīxiē tèshū de hányì kěyǐ gēnchú shí, cǐ jìshù hěn fāngbiàn. Tā chǎnshēng:\n\n“ \\ Xbd\\ xb2 \u003d\\ xbc⌘”\n\nrúguǒ wǒmen xiéshì yīxià, wǒmen kěyǐ kàn dào zàoshēng zhōng yǐncáng de shì yīgè ASCII děng hào yǐjí guīzé de kònggé, zuìhòu chūxiànle zhùmíng de ruìdiǎn “jǐngdiǎn” fúhào. Gāi fúhào de Unicode zhí wèi U + 2318, yóu kònggé hòu de zì jié (shíliù jìn zhì zhí 20) biānmǎ wèi UTF-8:E2 8c 98.\n\nRúguǒ wǒmen duì zìfú chuàn zhōng de mòshēng zhí bù shúxī huò gǎndào kùnhuò, kěyǐ duì%q dòngcí shǐyòng “jiā hào” biāozhì. Cǐ biāozhì shǐ shūchū zài jiěshì UTF-8 shí bùjǐn zhuǎn yì bùkě dǎyìn de xùliè, érqiě zhuǎn yì suǒyǒu fēi ASCII zì jié. Jiéguǒ shì tā gōngkāile géshì zhèngquè de UTF-8 de Unicode zhí, gāi zhí biǎoshì zìfú chuàn zhōng de fēi ASCII shùjù:\n\n Fmt.Printf(“%+ q\\ n”, shìlì)\n\nshǐyòng zhè zhǒng géshì, ruìdiǎn fúhào de Unicode zhí xiǎnshì wèi\\ u zhuǎn yì:\n\n“ \\ Xbd\\ xb2 \u003d\\ xbc\\ u2318”\n\nzài tiáoshì zìfú chuàn de nèiróng shí, zhèxiē dǎyìn jìshù hěn bùcuò, bìngqiě zài xiàmiàn de tǎolùn zhōng huì hěn fāngbiàn. Zhídé zhǐchū de shì, suǒyǒu zhèxiē fāngfǎ duìyú zì jié piàn de xíngwéi yǔ duì zìfú chuàn de xíngwéi wánquán xiāngtóng.\n\nZhè shì wǒmen liè chū de quántào dǎyìn xuǎnxiàng, yǐ wánzhěng de chéngxù xíngshì xiǎnshì, nín kěyǐ zài liúlǎn qì zhōng zhíjiē yùnxíng (hé biānjí):\n\nBāo mǐ"] 439 | ] 440 | ,null,"en",null,null,null,1.0,[] 441 | ,[["en"] 442 | ,null,[1.0] 443 | ,["en"] 444 | ] 445 | ] 446 | ` 447 | -------------------------------------------------------------------------------- /googletrans_test.go: -------------------------------------------------------------------------------- 1 | package googletrans 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDo(t *testing.T) { 8 | params := TranslateParams{ 9 | Src: "auto", 10 | Dest: "zh-CN", 11 | Text: "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. ", 12 | } 13 | transData, err := defaultTranslator.do(params) 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | t.Logf("%+v\n", transData) 18 | } 19 | 20 | func TestTranslate(t *testing.T) { 21 | params := TranslateParams{ 22 | Src: "auto", 23 | Dest: "zh-CN", 24 | Text: "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. ", 25 | } 26 | translated, err := defaultTranslator.Translate(params) 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | t.Logf("%+v\n", translated) 31 | } 32 | 33 | func TestDetect(t *testing.T) { 34 | text := "Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. " 35 | detected, err := defaultTranslator.Detect(text) 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | t.Logf("%+v\n", detected) 40 | } 41 | -------------------------------------------------------------------------------- /tk/tk.go: -------------------------------------------------------------------------------- 1 | // Package tk generates google translate tk 2 | package tk 3 | 4 | import ( 5 | "errors" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | // ErrInvalidTkk means function Get’s param tkk is invalid 12 | ErrInvalidTkk = errors.New("Param tkk is invalid") 13 | ) 14 | 15 | // Get generates google translate tk 16 | func Get(s string, tkk string) (tk string, err error) { 17 | _, err = strconv.ParseFloat(tkk, 64) 18 | if err != nil { 19 | return "", ErrInvalidTkk 20 | } 21 | 22 | var a []int 23 | for _, vRune := range s { 24 | v := int(vRune) 25 | if v < 0x10000 { 26 | a = append(a, v) 27 | } else { 28 | a = append(a, (v-0x10000)/0x400+0xD800) 29 | a = append(a, (v-0x10000)%0x400+0xDC00) 30 | } 31 | } 32 | 33 | var e []int 34 | for g := 0; g < len(a); g++ { 35 | l := a[g] 36 | if l < 128 { 37 | e = append(e, l) 38 | } else { 39 | if l < 2048 { 40 | e = append(e, l>>6|192) 41 | } else { 42 | if (l&64512) == 55296 && g+1 < len(a) && a[g+1]&64512 == 56320 { 43 | g++ 44 | l = 65536 + ((l & 1023) << 10) + (a[g] & 1023) 45 | e = append(e, l>>18|240) 46 | e = append(e, l>>12&63|128) 47 | } else { 48 | e = append(e, l>>12|224) 49 | } 50 | e = append(e, l>>6&63|128) 51 | } 52 | e = append(e, l&63|128) 53 | } 54 | } 55 | 56 | var ( 57 | tkkl int 58 | tkkpaire []int 59 | ) 60 | for _, str := range strings.Split(tkk, ".") { 61 | tkkpaire = append(tkkpaire, s2int(str)) 62 | } 63 | if len(tkkpaire) > 1 { 64 | tkkl = tkkpaire[0] 65 | } 66 | 67 | var tkklc = tkkl 68 | for i := 0; i < len(e); i++ { 69 | tkklc += e[i] 70 | tkklc = xr(tkklc, "+-a^+6") 71 | } 72 | tkklc = xr(tkklc, "+-3^+b+-f") 73 | 74 | if len(tkkpaire) > 1 { 75 | tkklc ^= tkkpaire[1] 76 | } else { 77 | tkklc ^= 0 78 | } 79 | 80 | if tkklc < 0 { 81 | tkklc = (tkklc & 2147483647) + 2147483648 82 | } 83 | 84 | tkklc %= 1000000 85 | 86 | return strconv.Itoa(tkklc) + "." + strconv.Itoa(tkklc^tkkl), nil 87 | } 88 | 89 | func xr(a int, b string) int { 90 | for c := 0; c < len(b)-2; c += 3 { 91 | d := string(b[c+2]) 92 | var dd int 93 | if "a" <= d { 94 | dd = int(d[0]) - 87 95 | } else { 96 | dd = s2int(d) 97 | } 98 | 99 | if "+" == string(b[c+1]) { 100 | dd = (a % 0x100000000) >> dd 101 | } else { 102 | dd = a << dd 103 | } 104 | 105 | if "+" == string(b[c]) { 106 | a = (a + dd) & 4294967295 107 | } else { 108 | a = a ^ dd 109 | } 110 | } 111 | return a 112 | } 113 | 114 | func s2int(s string) int { 115 | i, _ := strconv.Atoi(s) 116 | return i 117 | } 118 | -------------------------------------------------------------------------------- /tk/tk_test.go: -------------------------------------------------------------------------------- 1 | package tk 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGet(t *testing.T) { 8 | tkk := "443916.547221231" 9 | expect := "68957.510801" 10 | tk, err := Get("hello\u00A0world", tkk) 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | if tk != expect { 15 | t.Errorf("wrong tk, expect: %q, got: %q", expect, tk) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tkk/tkk.go: -------------------------------------------------------------------------------- 1 | // Package tkk gets google translate tkk 2 | package tkk 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "math" 9 | "net/http" 10 | "regexp" 11 | "strconv" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // Get gets tkk 17 | func Get() (string, error) { 18 | return defaultCache.Get() 19 | } 20 | 21 | // Set sets google translation url 22 | func Set(googleTransURL string) { 23 | defaultCache.Set(googleTransURL) 24 | } 25 | 26 | const defaultServiceURL = "https://translate.google.cn" 27 | 28 | var ( 29 | defaultCache = NewCache(defaultServiceURL) 30 | 31 | // ErrNotFound couldn't found tkk 32 | ErrNotFound = errors.New("couldn't found tkk from google translation url") 33 | 34 | tkkRegexp = regexp.MustCompile(`tkk:'(\d+\.\d+)'`) 35 | ) 36 | 37 | // Cache is responsible for getting google translte tkk 38 | type Cache interface { 39 | Set(googleTransURL string) 40 | Get() (tkk string, err error) 41 | } 42 | 43 | // NewCache initializes a cache 44 | func NewCache(serviceURL string) Cache { 45 | if serviceURL == "" { 46 | serviceURL = defaultServiceURL 47 | } 48 | 49 | token := make(chan struct{}, 1) 50 | token <- struct{}{} 51 | cache := &tkkCache{ 52 | v: "0", 53 | u: serviceURL, 54 | 55 | m: &sync.RWMutex{}, 56 | cond: sync.NewCond(&sync.Mutex{}), 57 | token: token, 58 | } 59 | 60 | return cache 61 | } 62 | 63 | type tkkCache struct { 64 | v string // google translate tkk 65 | u string // google translation url 66 | 67 | m *sync.RWMutex 68 | cond *sync.Cond 69 | token chan struct{} // update token 70 | } 71 | 72 | // Set sets google translation url 73 | func (t *tkkCache) Set(googleTransURL string) { 74 | t.u = googleTransURL 75 | } 76 | 77 | // Get gets tkk 78 | func (t *tkkCache) Get() (tkk string, err error) { 79 | t.m.RLock() 80 | isvalid := t.isvalid() 81 | t.m.RUnlock() 82 | if isvalid { 83 | return t.v, nil 84 | } 85 | 86 | return t.update() 87 | } 88 | 89 | func (t *tkkCache) isvalid() bool { 90 | now := math.Floor(float64( 91 | time.Now().Unix() * 1000 / 3600000), 92 | ) 93 | ttkf64, err := strconv.ParseFloat(t.v, 64) 94 | if err != nil { 95 | return false 96 | } 97 | if now != math.Floor(ttkf64) { 98 | return false 99 | } 100 | 101 | return true 102 | } 103 | 104 | // update gets tkk from t.u 105 | func (t *tkkCache) update() (string, error) { 106 | // only one goroutine is allowed to obtain the update token at the same time 107 | // other goroutines can only wait until the update of this goroutine ends 108 | select { 109 | case <-t.token: 110 | // no-op 111 | default: 112 | t.cond.L.Lock() 113 | defer t.cond.L.Unlock() 114 | t.cond.Wait() 115 | if t.isvalid() { 116 | return t.v, nil 117 | } 118 | <-t.token 119 | } 120 | t.m.Lock() 121 | defer func() { 122 | t.m.Unlock() 123 | t.token <- struct{}{} 124 | }() 125 | 126 | // try to get tkk within timeout 127 | var ( 128 | start = time.Now() 129 | sleep = 1 * time.Second 130 | timeout = 1 * time.Minute 131 | 132 | err error 133 | ) 134 | for time.Now().Sub(start) < timeout { 135 | t.v, err = func() (string, error) { 136 | req, err := http.NewRequest(http.MethodGet, t.u, nil) 137 | if err != nil { 138 | return "", err 139 | } 140 | resp, err := http.DefaultClient.Do(req) 141 | if err != nil { 142 | return "", err 143 | } 144 | defer resp.Body.Close() 145 | 146 | if resp.StatusCode >= 400 { 147 | format := "couldn't found tkk from google translation url, status code: %d" 148 | err = fmt.Errorf(format, resp.StatusCode) 149 | return "", err 150 | } 151 | 152 | body, err := ioutil.ReadAll(resp.Body) 153 | if err != nil { 154 | return "", err 155 | } 156 | data := string(body) 157 | if !tkkRegexp.MatchString(data) { 158 | return "", ErrNotFound 159 | } 160 | 161 | tkk := tkkRegexp.FindStringSubmatch(data)[1] 162 | return tkk, nil 163 | }() 164 | if err == nil { 165 | // if the update is successful, 166 | // notify all goroutines waiting for the update 167 | t.cond.Broadcast() 168 | return t.v, nil 169 | } 170 | 171 | time.Sleep(sleep) 172 | } 173 | if err != nil { 174 | // if the update fails, 175 | // notify one goroutine that is waiting to perform the update 176 | t.cond.Signal() 177 | return "", err 178 | } 179 | 180 | return t.v, nil 181 | } 182 | -------------------------------------------------------------------------------- /tkk/tkk_bench_test.go: -------------------------------------------------------------------------------- 1 | package tkk 2 | 3 | import "testing" 4 | 5 | func BenchmarkGet(b *testing.B) { 6 | for i := 0; i < b.N; i++ { 7 | Get() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tkk/tkk_test.go: -------------------------------------------------------------------------------- 1 | package tkk 2 | 3 | import "testing" 4 | 5 | func TestGet(t *testing.T) { 6 | tkk, err := Get() 7 | if err != nil { 8 | t.Error(err) 9 | } 10 | if tkk == "" { 11 | t.Error("get invalid tkk") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /transcookie/transcookie.go: -------------------------------------------------------------------------------- 1 | // Package transcookie just for caching google translation services' cookies 2 | package transcookie 3 | 4 | import ( 5 | "errors" 6 | "net/http" 7 | "net/url" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var ( 13 | defaultCookiesCache = newCache() 14 | 15 | emptyCookie = http.Cookie{} 16 | 17 | // ErrInvalidServiceURL service url is invalid 18 | ErrInvalidServiceURL = errors.New("invalid translate google service url") 19 | ) 20 | 21 | // Get gets cookie from defaultCookiesCache 22 | // for example: Get("https://translate.google.com") 23 | func Get(serviceURL string) (http.Cookie, error) { 24 | return defaultCookiesCache.get(serviceURL) 25 | } 26 | 27 | // Update updates defaultCookiesCache's cookie 28 | func Update(serviceURL string, sleep time.Duration) (http.Cookie, error) { 29 | return defaultCookiesCache.update(serviceURL, sleep) 30 | } 31 | 32 | // transCookiesCache caches google tranlation services' cookies 33 | type transCookiesCache struct { 34 | clt *http.Client 35 | 36 | m sync.RWMutex 37 | cookies map[string]http.Cookie 38 | } 39 | 40 | func newCache() *transCookiesCache { 41 | return &transCookiesCache{ 42 | clt: &http.Client{}, 43 | cookies: make(map[string]http.Cookie), 44 | } 45 | } 46 | 47 | func (c *transCookiesCache) get(serviceURL string) (http.Cookie, error) { 48 | u, err := url.Parse(serviceURL) 49 | if err != nil { 50 | return emptyCookie, ErrInvalidServiceURL 51 | } 52 | hostname := u.Hostname() 53 | if len(hostname) <= len("translate.google") || hostname[:len("translate.google")] != "translate.google" { 54 | return emptyCookie, ErrInvalidServiceURL 55 | } 56 | 57 | c.m.RLock() 58 | cookie, ok := c.cookies[hostname[len("translate"):]] 59 | c.m.RUnlock() 60 | if ok && cookie.Expires.After(time.Now()) { 61 | return cookie, nil 62 | } 63 | 64 | return c.update(serviceURL, 0) 65 | } 66 | 67 | func (c *transCookiesCache) update(serviceURL string, sleep time.Duration) (http.Cookie, error) { 68 | c.m.Lock() 69 | defer c.m.Unlock() 70 | 71 | time.Sleep(sleep) 72 | response, err := c.clt.Get(serviceURL) 73 | if err != nil { 74 | return emptyCookie, err 75 | } 76 | response.Body.Close() 77 | cookieStr := response.Header.Get("Set-Cookie") 78 | cookie, err := c.parseCookieStr(cookieStr) 79 | if err != nil { 80 | return emptyCookie, err 81 | } 82 | c.cookies[cookie.Domain] = cookie 83 | 84 | return cookie, nil 85 | } 86 | 87 | func (*transCookiesCache) parseCookieStr(cookieStr string) (http.Cookie, error) { 88 | return parseCookieStr(cookieStr) 89 | } 90 | 91 | // parseCookieStr 92 | // for example: 93 | // cookieStr="NID=204=Au7rQwn2eharnT1rtKsoQl32M2ASoamoFj5Rk8LKHZgg7YZfo54k88aqBVcUEYxcLKjpSU5dNgGTrRAu4Uiv7G3fIAeT3L87gsJCdqg_dCJ9tMHTufW8pHIUD1KgCDwUSIH60d4cWVsukZpai43pm9vHr3SLHCQk9ueEpYJ5Cx8; expires=Thu, 25-Feb-2021 15:15:28 GMT; path=/; domain=.google.cn; HttpOnly" 94 | func parseCookieStr(cookieStr string) (http.Cookie, error) { 95 | var ( 96 | l, m, r int 97 | cookie = http.Cookie{HttpOnly: true} 98 | ) 99 | for r < len(cookieStr) { 100 | for m < len(cookieStr) && cookieStr[m] != '=' { 101 | m++ 102 | } 103 | if m >= len(cookieStr) { 104 | break 105 | } 106 | k := cookieStr[l:m] 107 | 108 | for r < len(cookieStr) && cookieStr[r] != ';' { 109 | r++ 110 | } 111 | v := cookieStr[m+1 : r] 112 | 113 | switch k { 114 | case "expires": 115 | var err error 116 | cookie.Expires, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", v) 117 | if err != nil { 118 | return emptyCookie, err 119 | } 120 | case "path": 121 | cookie.Path = v 122 | case "domain": 123 | cookie.Domain = v 124 | default: 125 | cookie.Name = k 126 | cookie.Value = v 127 | } 128 | 129 | for r < len(cookieStr) && (cookieStr[r] == ' ' || cookieStr[r] == ';') { 130 | r++ 131 | } 132 | l = r 133 | m = r 134 | } 135 | 136 | return cookie, nil 137 | } 138 | -------------------------------------------------------------------------------- /transcookie/transcookie_test.go: -------------------------------------------------------------------------------- 1 | package transcookie 2 | 3 | import "testing" 4 | 5 | func TestParseCookieStr(t *testing.T) { 6 | cookieStr := "NID=204=Au7rQwn2eharnT1rtKsoQl32M2ASoamoFj5Rk8LKHZgg7YZfo54k88aqBVcUEYxcLKjpSU5dNgGTrRAu4Uiv7G3fIAeT3L87gsJCdqg_dCJ9tMHTufW8pHIUD1KgCDwUSIH60d4cWVsukZpai43pm9vHr3SLHCQk9ueEpYJ5Cx8; expires=Thu, 25-Feb-2021 15:15:28 GMT; path=/; domain=.google.cn; HttpOnly" 7 | cookie, err := parseCookieStr(cookieStr) 8 | if err != nil { 9 | t.Error(err) 10 | } 11 | t.Logf("%+v\n", cookie) 12 | } 13 | 14 | func TestGet(t *testing.T) { 15 | cookie, err := Get("https://translate.google.cn") 16 | if err != nil { 17 | t.Error(err) 18 | } 19 | t.Logf("%+v\n", cookie) 20 | } 21 | --------------------------------------------------------------------------------