Skip to content

Commit

Permalink
Add glossary related endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
cluttrdev committed Feb 17, 2023
1 parent 39b241a commit 841da08
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 30 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- `version` command displaying the version and optionally commit of the cli

### Changed

- `verbose` flag can be passed multiple times for different verbosity levels

## [0.2.0] - 2023-02-13

### Added
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ module github.com/cluttrdev/deepl-go

go 1.19

require github.com/spf13/cobra v1.6.1
require (
github.com/rodaine/table v1.1.0
github.com/spf13/cobra v1.6.1
golang.org/x/text v0.7.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/text v0.7.0 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
136 changes: 136 additions & 0 deletions pkg/api/glossaries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package deepl

import (
"encoding/csv"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)

type GlossaryEntry struct {
Source string
Target string
}

type GlossaryInfo struct {
GlossaryId string `json:"glossary_id"`
Name string `json:"name"`
Ready bool `json:"ready"`
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
CreationTime string `json:"creation_time"`
EntryCount int `json:"entry_count"`
}

func (t *Translator) CreateGlossary(name string, sourceLang string, targetLang string, entries []GlossaryEntry) (*GlossaryInfo, error) {
vals := make(url.Values)

vals.Set("name", name)
vals.Set("source_lang", sourceLang)
vals.Set("target_lang", targetLang)
vals.Set("entries_format", "tsv")

var entriesTSV = make([]string, 0, len(entries))
for _, entry := range entries {
entriesTSV = append(entriesTSV, fmt.Sprintf("%s\t%s", entry.Source, entry.Target))
}
vals.Set("entries", strings.Join(entriesTSV, "\n"))

res, err := t.callAPI("POST", "glossaries", vals, nil)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusCreated {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

var glossary GlossaryInfo
if err := json.NewDecoder(res.Body).Decode(&glossary); err != nil {
return nil, err
}

return &glossary, nil
}

func (t *Translator) ListGlossaries() ([]GlossaryInfo, error) {
res, err := t.callAPI("GET", "glossaries", nil, nil)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

var response struct {
Glossaries []GlossaryInfo `json:"glossaries"`
}
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return nil, err
}

return response.Glossaries, nil
}

func (t *Translator) GetGlossary(glossaryId string) (*GlossaryInfo, error) {
endpoint := fmt.Sprintf("glossaries/%s", glossaryId)

res, err := t.callAPI("GET", endpoint, nil, nil)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

var glossary GlossaryInfo
if err := json.NewDecoder(res.Body).Decode(&glossary); err != nil {
return nil, err
}

return &glossary, nil
}

func (t *Translator) DeleteGlossary(glossaryId string) error {
endpoint := fmt.Sprintf("glossaries/%s", glossaryId)

res, err := t.callAPI("DELETE", endpoint, nil, nil)
if err != nil {
return err
} else if res.StatusCode != http.StatusNoContent {
return HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

return nil
}

func (t *Translator) GetGlossaryEntries(glossaryId string) ([]GlossaryEntry, error) {
endpoint := fmt.Sprintf("glossaries/%s/entries", glossaryId)

headers := http.Header{}
headers.Set("Accept", "text/tab-separated-values")

res, err := t.callAPI("GET", endpoint, nil, headers)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

r := csv.NewReader(res.Body)
r.Comma = '\t'
records, err := r.ReadAll()
if err != nil {
return nil, err
}

entries := make([]GlossaryEntry, 0, len(records))
for _, rec := range records {
entries = append(entries, GlossaryEntry{Source: rec[0], Target: rec[1]})
}

return entries, nil
}
18 changes: 7 additions & 11 deletions pkg/api/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ const (

type Client struct {
httpClient *http.Client
baseURL string
authKey string
}

func NewClient(baseURL string, authKey string, timeout time.Duration) *Client {
Expand All @@ -26,8 +24,6 @@ func NewClient(baseURL string, authKey string, timeout time.Duration) *Client {

return &Client{
httpClient: client,
baseURL: baseURL,
authKey: authKey,
}
}

Expand All @@ -44,19 +40,19 @@ func (err HTTPError) Error() string {
}
}

func (c *Client) do(method string, endpoint string, params url.Values) (*http.Response, error) {
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", c.baseURL, endpoint), strings.NewReader(params.Encode()))
func (c *Client) do(method string, url string, data url.Values, headers http.Header) (*http.Response, error) {
req, err := http.NewRequest(method, url, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}

req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Authorization", fmt.Sprintf("DeepL-Auth-Key %s", c.authKey))
for k, vs := range headers {
for _, v := range vs {
req.Header.Add(k, v)
}
}

res, err := c.httpClient.Do(req)
if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}

return res, err
}
29 changes: 28 additions & 1 deletion pkg/api/languages.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deepl

import (
"encoding/json"
"net/http"
"net/url"
)

Expand All @@ -11,16 +12,23 @@ type Language struct {
SupportsFormality bool `json:"supports_formality"`
}

type LanguagePair struct {
SourceLang string `json:"source_lang"`
TargetLang string `json:"target_lang"`
}

func (t *Translator) GetLanguages(langType string) ([]Language, error) {
vals := make(url.Values)

if langType != "" {
vals.Set("type", langType)
}

res, err := t.httpClient.do("GET", "languages", vals)
res, err := t.callAPI("GET", "languages", vals, nil)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

Expand All @@ -31,3 +39,22 @@ func (t *Translator) GetLanguages(langType string) ([]Language, error) {

return languages, nil
}

func (t *Translator) GetGlossaryLanguagePairs() ([]LanguagePair, error) {
res, err := t.callAPI("GET", "glossary-language-pairs", nil, nil)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

var response struct {
SupportedLanguages []LanguagePair `json:"supported_languages"`
}
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return nil, err
}

return response.SupportedLanguages, nil
}
13 changes: 7 additions & 6 deletions pkg/api/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deepl

import (
"encoding/json"
"net/http"
"net/url"
)

Expand All @@ -10,10 +11,6 @@ type Translation struct {
Text string `json:"text"`
}

type translationResponse struct {
Translations []Translation `json:"translations"`
}

type TranslateOption func(url.Values)

// The language to be translated.
Expand Down Expand Up @@ -101,13 +98,17 @@ func (t *Translator) TranslateText(texts []string, targetLang string, options ..
opt(vals)
}

res, err := t.httpClient.do("POST", "translate", vals)
res, err := t.callAPI("POST", "translate", vals, nil)
if err != nil {
return nil, err
} else if res.StatusCode != http.StatusOK {
return nil, HTTPError{StatusCode: res.StatusCode}
}
defer res.Body.Close()

var response translationResponse
var response struct {
Translations []Translation `json:"translations"`
}
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return nil, err
}
Expand Down
23 changes: 23 additions & 0 deletions pkg/api/translator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package deepl

import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
Expand All @@ -11,6 +14,8 @@ type TranslatorOptions struct {

type Translator struct {
httpClient *Client
serverURL string
authKey string
}

func NewTranslator(authKey string, options TranslatorOptions) *Translator {
Expand All @@ -28,9 +33,27 @@ func NewTranslator(authKey string, options TranslatorOptions) *Translator {

return &Translator{
httpClient: httpClient,
serverURL: serverURL,
authKey: authKey,
}
}

func (t *Translator) callAPI(method string, endpoint string, data url.Values, headers http.Header) (*http.Response, error) {
url := fmt.Sprintf("%s/%s", t.serverURL, endpoint)

if headers == nil {
headers = make(http.Header)
}
headers.Set("Authorization", fmt.Sprintf("DeepL-Auth-Key %s", t.authKey))
if _, ok := headers["Content-Type"]; !ok {
headers.Set("Content-Type", "application/x-www-form-urlencoded")
}

res, err := t.httpClient.do(method, url, data, headers)

return res, err
}

func authKeyIsFreeAccount(authKey string) bool {
return strings.HasSuffix(authKey, ":fx")
}
Loading

0 comments on commit 841da08

Please sign in to comment.