diff --git a/svc_key_test.go b/svc_key_test.go index 4d0bc15..05ff35a 100644 --- a/svc_key_test.go +++ b/svc_key_test.go @@ -530,6 +530,115 @@ func TestKeyService_Create_AutomationsDisabled(t *testing.T) { } } +func TestKeyService_Create_PluralTranslationEncoded(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc( + fmt.Sprintf("/projects/%s/keys", testProjectID), + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + testMethod(t, r, "POST") + testHeader(t, r, apiTokenHeader, testApiToken) + data := `{ + "keys": [ + { + "key_name": "index.welcome", + "description": "Index app welcome", + "platforms": [ + "web" + ], + "translations": [ + { + "language_iso": "en", + "translation": { + "one": "oneText", + "other": "otherText" + } + } + ], + "is_plural": true + } + ] + }` + + req := new(bytes.Buffer) + _ = json.Compact(req, []byte(data)) + + testBody(t, r, req.String()) + + _, _ = fmt.Fprint(w, `{ + "project_id": "3002780358964f9bab5a92.87762498", + "keys": [ + { + "key_id": 331223, + "created_at": "2018-12-31 12:00:00 (Etc/UTC)", + "created_at_timestamp": 1546257600, + "key_name": { + "ios": "index.welcome", + "android": "index.welcome", + "web": "index.welcome", + "other": "index.welcome" + }, + "filenames": { + "ios": "", + "android": "", + "web": "", + "other": "" + }, + "description": "Index app welcome", + "platforms": [ + "web" + ], + "tags": [], + "comments": [], + "screenshots": [], + "translations": [ + { + "translation_id": 444921, + "key_id": 331223, + "language_iso": "en", + "translation": "Welcome", + "modified_by": 420, + "modified_by_email": "user@mycompany.com", + "modified_at": "2018-12-31 12:00:00 (Etc/UTC)", + "modified_at_timestamp": 1546257600, + "is_reviewed": false, + "reviewed_by": 0, + "words": 0 + } + ] + } + ], + "errors": [ + { + "message": "This key name is already taken", + "code": 400, + "key": { + "key_name": "index.hello" + } + } + ] + }`) + }) + + _, _ = client.Keys().Create(testProjectID, []NewKey{ + { + KeyName: "index.welcome", + Description: "Index app welcome", + Platforms: []string{"web"}, + IsPlural: true, + Translations: []NewTranslation{ + { + LanguageISO: "en", + Translation: "{\"one\":\"oneText\",\"other\":\"otherText\"}", + }, + }, + }, + }) + +} + func TestKeyService_Delete(t *testing.T) { client, mux, _, teardown := setup() defer teardown() diff --git a/svc_translation.go b/svc_translation.go index a987255..f031312 100644 --- a/svc_translation.go +++ b/svc_translation.go @@ -1,8 +1,8 @@ package lokalise import ( + "encoding/json" "fmt" - "github.com/go-resty/resty/v2" "github.com/google/go-querystring/query" ) @@ -53,6 +53,62 @@ type NewTranslation struct { MergeCustomTranslationStatuses bool `json:"merge_custom_translation_statuses,omitempty"` } +func (t NewTranslation) MarshalJSON() ([]byte, error) { + type Alias NewTranslation + + var translation interface{} = t.Translation + + if json.Valid([]byte(t.Translation)) { + _ = json.Unmarshal([]byte(t.Translation), &translation) + } + + return json.Marshal(&struct { + LanguageISO string `json:"language_iso"` + Translation interface{} `json:"translation"` + Alias + }{ + LanguageISO: t.LanguageISO, + Translation: translation, + Alias: (Alias)(t), + }) +} + +func (t *NewTranslation) UnmarshalJSON(data []byte) error { + type Alias NewTranslation + aux := &struct { + LanguageISO string `json:"language_iso"` + Translation interface{} `json:"translation"` + *Alias + }{ + Alias: (*Alias)(t), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + switch aux.Translation.(type) { + case map[string]interface{}: + marshal, err := json.Marshal(aux.Translation) + if err != nil { + return err + } + t.Translation = string(marshal) + case string: + t.Translation = fmt.Sprintf("%+v", aux.Translation) + default: + return fmt.Errorf("NewTranslation tranlation type is of unknown type") + } + + t.LanguageISO = aux.LanguageISO + t.IsFuzzy = aux.IsFuzzy + t.IsReviewed = aux.IsReviewed + t.CustomTranslationStatusIds = aux.CustomTranslationStatusIds + t.MergeCustomTranslationStatuses = aux.MergeCustomTranslationStatuses + + return nil +} + type UpdateTranslation struct { Translation string `json:"translation"` IsFuzzy *bool `json:"is_fuzzy,omitempty"` @@ -60,6 +116,58 @@ type UpdateTranslation struct { CustomTranslationStatusIDs []string `json:"custom_translation_status_ids,omitempty"` } +func (t UpdateTranslation) MarshalJSON() ([]byte, error) { + type Alias UpdateTranslation + + var translation interface{} = t.Translation + + if json.Valid([]byte(t.Translation)) { + _ = json.Unmarshal([]byte(t.Translation), &translation) + } + + return json.Marshal(&struct { + Translation interface{} `json:"translation"` + Alias + }{ + Translation: translation, + Alias: (Alias)(t), + }) +} + +func (t *UpdateTranslation) UnmarshalJSON(data []byte) error { + type Alias UpdateTranslation + aux := &struct { + LanguageISO string `json:"language_iso"` + Translation interface{} `json:"translation"` + *Alias + }{ + Alias: (*Alias)(t), + } + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + switch aux.Translation.(type) { + case map[string]interface{}: + marshal, err := json.Marshal(aux.Translation) + if err != nil { + return err + } + t.Translation = string(marshal) + case string: + t.Translation = fmt.Sprintf("%+v", aux.Translation) + default: + return fmt.Errorf("NewTranslation tranlation type is of unknown type") + } + + t.IsFuzzy = aux.IsFuzzy + t.IsReviewed = aux.IsReviewed + t.CustomTranslationStatusIDs = aux.CustomTranslationStatusIDs + + return nil +} + type TranslationsResponse struct { Paged Translations []Translation `json:"translations"` diff --git a/svc_translation_test.go b/svc_translation_test.go index c55031c..362e2e8 100644 --- a/svc_translation_test.go +++ b/svc_translation_test.go @@ -210,3 +210,156 @@ func TestTranslationService_Update(t *testing.T) { t.Errorf("Translations.Update returned %+v, want %+v", r.Translation, want) } } + +func TestNewTranslation_MarshalJSON(t *testing.T) { + translations := []NewTranslation{ + { + LanguageISO: "en", + Translation: "simple text", + }, + { + LanguageISO: "en", + Translation: "{\"one\":\"oneText\",\"other\":\"otherText\"}", + }, + } + + want := JsonCompact(` + [ + { + "language_iso": "en", + "translation": "simple text" + }, + { + "language_iso": "en", + "translation": { + "one": "oneText", + "other": "otherText" + } + } + ]`) + + marshal, err := json.Marshal(translations) + if err != nil { + t.Errorf("NewTranslation marshalling returned error %s", err) + } + + if string(marshal) != want { + t.Errorf("NewTranslation marshalling mismatch: %+v, want %+v", string(marshal), want) + } +} + +func TestUpdateTranslation_MarshalJSON(t *testing.T) { + translations := []UpdateTranslation{ + { + Translation: "simple text", + }, + { + Translation: "{\"one\":\"oneText\",\"other\":\"otherText\"}", + }, + } + + want := JsonCompact(` + [ + { + "translation": "simple text" + }, + { + "translation": { + "one": "oneText", + "other": "otherText" + } + } + ]`) + + marshal, err := json.Marshal(translations) + if err != nil { + t.Errorf("UpdateTranslation marshalling returned error %s", err) + } + + if string(marshal) != want { + t.Errorf("UpdateTranslation marshalling mismatch: %+v, want %+v", string(marshal), want) + } +} + +func TestNewTranslation_UnmarshalJSON(t *testing.T) { + raw := ` + [ + { + "language_iso": "en", + "translation": "simple text" + }, + { + "language_iso": "en", + "translation": { + "one": "oneText", + "other": "otherText" + } + } + ]` + + want := []NewTranslation{ + { + LanguageISO: "en", + Translation: "simple text", + }, + { + LanguageISO: "en", + Translation: "{\"one\":\"oneText\",\"other\":\"otherText\"}", + }, + } + + var result []NewTranslation + + err := json.Unmarshal([]byte(raw), &result) + if err != nil { + t.Errorf("NewTranslation unmarshalling returned error %s", err) + } + + if !reflect.DeepEqual(want, result) { + t.Errorf("NewTranslation unmarshalling mismatch: %+v, want %+v", result, want) + } +} + +func TestUpdateTranslation_UnmarshalJSON(t *testing.T) { + raw := ` + [ + { + "translation": "simple text" + }, + { + "translation": { + "one": "oneText", + "other": "otherText" + } + } + ]` + + want := []UpdateTranslation{ + { + Translation: "simple text", + }, + { + Translation: "{\"one\":\"oneText\",\"other\":\"otherText\"}", + }, + } + + var result []UpdateTranslation + + err := json.Unmarshal([]byte(raw), &result) + if err != nil { + t.Errorf("UpdateTranslation unmarshalling returned error %s", err) + } + + if !reflect.DeepEqual(want, result) { + t.Errorf("UpdateTranslation unmarshalling mismatch: %+v, want %+v", result, want) + } +} + +func JsonCompact(text string) string { + compactedBuffer := new(bytes.Buffer) + err := json.Compact(compactedBuffer, []byte(text)) + if err != nil { + panic(fmt.Sprintf("Invalid test data definition %+v", err)) + } + return compactedBuffer.String() +}