500K characters included every month. Add payment details to start.
Go Developer Guide

Best Translation API
for Go

Compare Google Translate, DeepL, and Langbly from a Go perspective. Dependencies, auth, context support, worker pools, retries, and what each one costs per million characters.

Payment details required. No charge within 500K characters/month.

Pricing comparison

Real pricing based on monthly character volume. All prices in USD.

VolumeGoogle TranslateDeepLLangblyYou Save
500K chars/mo$0Check live plan$0
Both included/evaluation
1M chars/mo$10Check live plan$2.50
75% vs Google
5M chars/mo$90Check live plan$22.50
75% vs Google
25M chars/mo$490Check live plan$122.50
75% vs Google
100M chars/mo$1,990Above Growth limit$497.50
75% vs Google

Feature comparison

FeatureGoogle TranslateDeepLLangbly
Official Go SDK
Go modulecloud.google.com/go/translateCommunity packagesNone needed, use net/http
Extra dependenciesAuth, gRPC, x/textVaries by packageZero, standard library
Auth methodService account JSONAPI key headerBearer API key header
Plain REST call worksYes, with OAuth token
context.Context support
Configurable http.ClientVia option packagePackage dependentYours, always
Auto-retry on 429/5xxBuilt into clientPackage dependentWrite ~20 lines, see below
Response shapedata.translations[]translations[]data.translations[]
Google v2 compatible
Auto source detection
HTML-safe modeformat: "html"
Languages100+Focused set100+
Published usage rate$20/1M after 500KCheck live Growth plan$5/1M after 500K
Included/evaluation usage500K/moDeveloper: 1M total500K/mo
Translation qualityNMTNMT + AINext-gen AI
Context-awareLimited

Translating text in Go without an SDK

Go developers are used to reaching for the standard library first, and translation is one of those cases where you really do not need a vendor package. Langbly speaks the Google Translate v2 protocol, so a single POST with net/http and encoding/json is the whole integration. There is no official Langbly Go module, and that is deliberate: the API surface is small enough that a struct and thirty lines of code beat a dependency you have to keep updated.

Google Translate (cloud.google.com/go/translate):

import (
	"context"
	"fmt"
	"log"

	"cloud.google.com/go/translate"
	"golang.org/x/text/language"
	"google.golang.org/api/option"
)

func main() {
	ctx := context.Background()

	// Needs a GCP project, an enabled API, and a downloaded key file.
	client, err := translate.NewClient(ctx,
		option.WithCredentialsFile("service-account.json"))
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	target, err := language.Parse("nl")
	if err != nil {
		log.Fatal(err)
	}

	out, err := client.Translate(ctx, []string{"Hello world"}, target, nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(out[0].Text)
}

That pulls in the Google auth stack, gRPC transports, and golang.org/x/text. Fine if you are already deep in GCP, heavy if translation is one feature in an otherwise lean service.

Langbly (standard library only):

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

type translateRequest struct {
	Q      string `json:"q"`
	Target string `json:"target"`
	Source string `json:"source,omitempty"` // optional, omit to auto-detect
	Format string `json:"format,omitempty"` // "text" (default) or "html"
}

type translateResponse struct {
	Data struct {
		Translations []struct {
			TranslatedText         string `json:"translatedText"`
			DetectedSourceLanguage string `json:"detectedSourceLanguage"`
		} `json:"translations"`
	} `json:"data"`
}

func main() {
	body, err := json.Marshal(translateRequest{
		Q:      "Hello world",
		Target: "nl",
	})
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest(
		http.MethodPost,
		"https://api.langbly.com/language/translate/v2",
		bytes.NewReader(body),
	)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
		log.Fatalf("langbly: %s: %s", resp.Status, msg)
	}

	var out translateResponse
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		log.Fatal(err)
	}
	if len(out.Data.Translations) == 0 {
		log.Fatal("langbly: no translations returned")
	}

	t := out.Data.Translations[0]
	fmt.Println(t.TranslatedText)         // Hallo wereld
	fmt.Println(t.DetectedSourceLanguage) // en
}

The response is exactly the Google Translate v2 envelope:

{"data":{"translations":[{"translatedText":"Hallo wereld","detectedSourceLanguage":"en"}]}}

If you already have Go code hitting the Google v2 endpoint, swapping the base URL and the auth header is the entire migration. Your structs stay as they are. Langbly does ship official SDKs for Python, JavaScript/TypeScript, and PHP if other services in your stack need them.

A reusable client with timeouts and context

The snippet above is fine for a one-off. In a real service you want a small client type: one http.Client you control, connection reuse, a typed error you can branch on, and context.Context threaded through so a cancelled request actually cancels the outbound call.

package langbly

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"
)

const defaultEndpoint = "https://api.langbly.com/language/translate/v2"

// Client is safe for concurrent use by multiple goroutines.
type Client struct {
	APIKey     string
	Endpoint   string
	HTTPClient *http.Client
}

func New(apiKey string) *Client {
	return &Client{
		APIKey:   apiKey,
		Endpoint: defaultEndpoint,
		HTTPClient: &http.Client{
			Timeout: 20 * time.Second,
			Transport: &http.Transport{
				MaxIdleConns:        100,
				MaxIdleConnsPerHost: 32,
				IdleConnTimeout:     90 * time.Second,
			},
		},
	}
}

type Result struct {
	Text           string
	DetectedSource string
}

// APIError carries the HTTP status so callers can decide what is retryable.
type APIError struct {
	StatusCode int
	Body       string
}

func (e *APIError) Error() string {
	return fmt.Sprintf("langbly: status %d: %s", e.StatusCode, e.Body)
}

func (c *Client) Translate(ctx context.Context, text, target string) (Result, error) {
	payload, err := json.Marshal(map[string]string{
		"q":      text,
		"target": target,
	})
	if err != nil {
		return Result{}, err
	}

	req, err := http.NewRequestWithContext(
		ctx, http.MethodPost, c.Endpoint, bytes.NewReader(payload),
	)
	if err != nil {
		return Result{}, err
	}
	req.Header.Set("Authorization", "Bearer "+c.APIKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return Result{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
		return Result{}, &APIError{StatusCode: resp.StatusCode, Body: string(body)}
	}

	var out struct {
		Data struct {
			Translations []struct {
				TranslatedText         string `json:"translatedText"`
				DetectedSourceLanguage string `json:"detectedSourceLanguage"`
			} `json:"translations"`
		} `json:"data"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return Result{}, fmt.Errorf("langbly: decode response: %w", err)
	}
	if len(out.Data.Translations) == 0 {
		return Result{}, errors.New("langbly: no translations returned")
	}

	t := out.Data.Translations[0]
	return Result{Text: t.TranslatedText, DetectedSource: t.DetectedSourceLanguage}, nil
}

Calling it from a handler stays boring, which is the point:

client := langbly.New(os.Getenv("LANGBLY_API_KEY"))

ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()

res, err := client.Translate(ctx, "Hello world", "nl")
if err != nil {
	var apiErr *langbly.APIError
	if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusTooManyRequests {
		http.Error(w, "rate limited", http.StatusServiceUnavailable)
		return
	}
	http.Error(w, "translation failed", http.StatusBadGateway)
	return
}

fmt.Fprintln(w, res.Text) // Hallo wereld

Because the endpoint is Google v2 compatible, the same client works against Google Translate by changing the endpoint and the auth scheme. Useful if you want to A/B two providers behind one interface before committing.

Concurrent batches, retries, and keeping the bill down

Go's real advantage here is throughput. Translating a few thousand product descriptions is a worker pool problem, not an API problem. The pattern below bounds concurrency, preserves input order, retries only what deserves a retry, and returns a joined error so one bad row does not sink the batch.

package langbly

import (
	"context"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"sync"
	"time"
)

// TranslateBatch keeps at most "workers" requests in flight and returns
// results in the same order as the input slice.
func (c *Client) TranslateBatch(
	ctx context.Context, texts []string, target string, workers int,
) ([]string, error) {
	if workers < 1 {
		workers = 8
	}
	if workers > len(texts) {
		workers = len(texts)
	}

	results := make([]string, len(texts))
	failures := make([]error, len(texts))
	jobs := make(chan int)

	var wg sync.WaitGroup
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for idx := range jobs {
				res, err := c.translateWithRetry(ctx, texts[idx], target, 4)
				if err != nil {
					failures[idx] = fmt.Errorf("item %d: %w", idx, err)
					continue
				}
				results[idx] = res.Text
			}
		}()
	}

	for i := range texts {
		select {
		case jobs <- i:
		case <-ctx.Done():
			close(jobs)
			wg.Wait()
			return nil, ctx.Err()
		}
	}
	close(jobs)
	wg.Wait()

	// errors.Join returns nil when every entry is nil.
	return results, errors.Join(failures...)
}

func (c *Client) translateWithRetry(
	ctx context.Context, text, target string, attempts int,
) (Result, error) {
	backoff := 250 * time.Millisecond
	var lastErr error

	for attempt := 0; attempt < attempts; attempt++ {
		res, err := c.Translate(ctx, text, target)
		if err == nil {
			return res, nil
		}
		lastErr = err

		if !retryable(err) {
			return Result{}, err
		}

		// Full jitter on top of exponential backoff avoids
		// every worker waking up at the same instant.
		jitter := time.Duration(rand.Int63n(int64(backoff)))
		select {
		case <-ctx.Done():
			return Result{}, ctx.Err()
		case <-time.After(backoff + jitter):
		}
		backoff *= 2
	}

	return Result{}, fmt.Errorf(
		"langbly: gave up after %d attempts: %w", attempts, lastErr)
}

func retryable(err error) bool {
	if errors.Is(err, context.Canceled) ||
		errors.Is(err, context.DeadlineExceeded) {
		return false
	}

	var apiErr *APIError
	if errors.As(err, &apiErr) {
		return apiErr.StatusCode == http.StatusTooManyRequests ||
			apiErr.StatusCode >= http.StatusInternalServerError
	}

	// Transport level failures (reset connection, DNS blip) are worth a retry.
	return true
}

Usage, with a deadline for the whole batch:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

translated, err := client.TranslateBatch(ctx, descriptions, "de", 12)
if err != nil {
	log.Printf("batch finished with errors: %v", err)
}

Throughput is only half the story. Google and Langbly both include the first 500,000 characters each month. At 5M total characters, their current standard text totals are $90 and $22.50; at 100M they are $1,990 and $497.50. DeepL requires its live Growth subscription and overage for a fair comparison.

Frequently asked questions

Is there an official Langbly Go SDK?

No, and you do not need one. The API is a single JSON POST endpoint, so net/http and encoding/json cover it in about thirty lines with zero dependencies. Official SDKs exist for Python, JavaScript/TypeScript, and PHP. For Go, copy the client type from this page into your project and you are done.

How do I translate text in Go?

POST to https://api.langbly.com/language/translate/v2 with an Authorization: Bearer header and a JSON body containing "q" and "target". Decode the response into a struct matching {"data":{"translations":[{"translatedText":"..."}]}}. Set "source" if you want to skip auto-detection, and "format":"html" to keep markup intact.

What is the best translation API for Go developers?

It depends on what you already run. Google has an official Go client but requires GCP authentication. DeepL has no official Go library, so you may write HTTP calls. Langbly is Google v2 compatible, works with the standard library, and publishes a $5 per million additional input character rate after its included usage.

How do I translate many strings concurrently in Go?

Use a worker pool with a bounded number of goroutines reading from a jobs channel, writing results into a pre-sized slice indexed by position so ordering is preserved. Wrap each call in retry with exponential backoff and jitter, retrying only 429 and 5xx responses. The full pattern is in the concurrency section above.

Is the Google Translate API free for Go projects?

Google Cloud Translation includes the first 500,000 characters each month, then charges $20 per million for standard NMT. Langbly also includes the first 500,000 input characters each month and charges $5 per million additional input characters. DeepL Developer includes one million characters in total; check the live Growth plan for recurring use.

Ready to try Langbly?

500K characters included every month. Migrate in minutes.

Same API as Google Translate v2. Just change the URL.