Pricing comparison
Real pricing based on monthly character volume. All prices in USD.
| Volume | Google Translate | DeepL | Langbly | You Save |
|---|---|---|---|---|
| 500K chars/mo | $0 | Check live plan | $0 | Both included/evaluation |
| 1M chars/mo | $10 | Check live plan | $2.50 | 75% vs Google |
| 5M chars/mo | $90 | Check live plan | $22.50 | 75% vs Google |
| 25M chars/mo | $490 | Check live plan | $122.50 | 75% vs Google |
| 100M chars/mo | $1,990 | Above Growth limit | $497.50 | 75% vs Google |
Feature comparison
| Feature | Google Translate | DeepL | Langbly |
|---|---|---|---|
| Official Go SDK | |||
| Go module | cloud.google.com/go/translate | Community packages | None needed, use net/http |
| Extra dependencies | Auth, gRPC, x/text | Varies by package | Zero, standard library |
| Auth method | Service account JSON | API key header | Bearer API key header |
| Plain REST call works | Yes, with OAuth token | ||
| context.Context support | |||
| Configurable http.Client | Via option package | Package dependent | Yours, always |
| Auto-retry on 429/5xx | Built into client | Package dependent | Write ~20 lines, see below |
| Response shape | data.translations[] | translations[] | data.translations[] |
| Google v2 compatible | |||
| Auto source detection | |||
| HTML-safe mode | format: "html" | ||
| Languages | 100+ | Focused set | 100+ |
| Published usage rate | $20/1M after 500K | Check live Growth plan | $5/1M after 500K |
| Included/evaluation usage | 500K/mo | Developer: 1M total | 500K/mo |
| Translation quality | NMT | NMT + AI | Next-gen AI |
| Context-aware | Limited |
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.