חזרה לבלוג
Terminal window showing a Composer install command for the PolyLingo PHP SDK alongside a short PHP translate call and its JSON output.

תרגום תוכן מובנה מ-PHP עם PolyLingo SDK

By Robert M

Translate structured content from PHP with the PolyLingo SDK

The PolyLingo PHP SDK is now available on Packagist. Install it with Composer, hand it a string of plain text, Markdown, JSON, or HTML, and get back translations in every language you need — without writing raw HTTP or worrying about your structure getting mangled in transit.

This post covers installation, authentication, and the full surface of the SDK: synchronous translation, batch requests, async jobs, and error handling.


Installation

Requires PHP 7.4 or later. Install via Composer:

composer require usepolylingo/polylingo

The SDK depends on guzzlehttp/guzzle ^7.8 and psr/http-client ^1.0. Both are pulled in automatically.


Setting up the client

Create a single PolyLingo instance and reuse it across your application. The only required option is your API key:

<?php
use PolyLingo\PolyLingo;

$client = new PolyLingo([
    'apiKey' => getenv('POLYLINGO_API_KEY'),
]);

Store your API key in an environment variable. Never hardcode it or commit it to version control.

Two optional settings worth knowing about:

$client = new PolyLingo([
    'apiKey'  => getenv('POLYLINGO_API_KEY'),
    'baseURL' => 'https://api.usepolylingo.com/v1', // default, override for self-hosted instances
    'timeout' => 120_000,                           // milliseconds, default is 120000 (2 minutes)
]);

Translating content

Plain text, Markdown, JSON, or HTML

Pass your content and an array of target language codes to translate(). The format field tells the SDK what kind of content it's dealing with:

$result = $client->translate([
    'content' => '# Hello',
    'targets' => ['es', 'fr', 'de'],
    'format'  => 'markdown',
]);

$es     = $result['translations']['es'];
$tokens = $result['usage']['total_tokens'];

The format option accepts plain, markdown, json, or html. If you omit it, the API auto-detects the format from the content. You can also pass a source language hint and a model value of standard (default) or advanced.

Format preservation is the key behaviour here. For json content, only string values are translated. Keys, nesting, arrays, and non-string types come back exactly as you sent them. For markdown, headings stay headings, code blocks are left verbatim, and link URLs are not touched. For html, tags and attributes are preserved and only text nodes are translated.

Translating a JSON locale file

A common use case is translating a locale file. Send the whole object as a JSON string:

$source = json_decode(file_get_contents('messages/en.json'), true);

$result = $client->translate([
    'content' => json_encode($source),
    'format'  => 'json',
    'targets' => ['de', 'fr', 'ja'],
]);

foreach (['de', 'fr', 'ja'] as $locale) {
    $translated = json_decode($result['translations'][$locale], true);
    file_put_contents(
        "messages/{$locale}.json",
        json_encode($translated, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n"
    );
}

One request handles all three locales. Keys are untouched in every output file.


Batch requests

Use batch() when you have multiple separate content items to translate. You can send up to 100 items in a single request, each with its own id and optional format:

$batch = $client->batch([
    'items' => [
        ['id' => 'hero_title',    'content' => 'Welcome back',              'format' => 'plain'],
        ['id' => 'hero_subtitle', 'content' => 'Here is what is new today', 'format' => 'plain'],
        ['id' => 'cta',           'content' => 'Get started',               'format' => 'plain'],
    ],
    'targets' => ['es', 'fr'],
]);

foreach ($batch['results'] as $row) {
    echo $row['id'] . ': ' . $row['translations']['es'] . "\n";
}

All items share the same targets array. The response preserves the id you passed in for each item, so you can map results back to your original data without relying on order.


Async jobs

For long-running translations (large documents, many targets, or both) the jobs API accepts a request, returns immediately with a job_id, and lets you poll for the result. The SDK handles this in two ways.

Enqueue and poll manually

$accepted = $client->jobs->create([
    'content' => file_get_contents('long-article.md'),
    'targets' => ['es', 'fr', 'de', 'ja', 'zh'],
    'format'  => 'markdown',
]);

$jobId = $accepted['job_id'];

// Poll until the job reaches a terminal status
do {
    sleep(5);
    $state = $client->jobs->get($jobId);
} while ($state['status'] === 'pending' || $state['status'] === 'processing');

if ($state['status'] === 'complete') {
    $translations = $state['translations'];
}

One call that polls until done

If you don't need manual control, jobs->translate() handles the polling loop for you:

$done = $client->jobs->translate([
    'content' => file_get_contents('long-article.md'),
    'targets' => ['es', 'fr', 'de'],
    'format'  => 'markdown',

    // Optional overrides (defaults shown):
    // 'pollInterval' => 5000,       // ms between polls, default 5000
    // 'timeout'      => 1_200_000,  // total wait budget, default 20 minutes
    // 'onProgress'   => function (?int $queuePosition) {
    //     echo "Queue position: {$queuePosition}\n";
    // },
]);

$translations = $done['translations'];
$usage        = $done['usage'];

The onProgress callback fires on each poll with the current queue position if the API returns one, or null if not available. Use it to log progress or update a UI.


Utility endpoints

Three lightweight endpoints need no parameters beyond authentication:

$health = $client->health();
// ['status' => 'ok', 'timestamp' => '...']

$langs = $client->languages();
// ['languages' => [['code' => 'en', 'name' => 'English', 'rtl' => false], ...]]

$usage = $client->usage();
// ['usage' => ['tokens_used' => 12000, 'tokens_remaining' => 88000, ...]]

GET /health and GET /languages require no API key. GET /usage returns token consumption for the current calendar month for the authenticated account.


Error handling

All SDK errors extend PolyLingo\Errors\PolyLingoException. Catch the specific subtypes you want to handle differently:

use PolyLingo\Errors\AuthException;
use PolyLingo\Errors\JobFailedException;
use PolyLingo\Errors\PolyLingoException;
use PolyLingo\Errors\RateLimitException;

try {
    $result = $client->translate([
        'content' => '# Hello',
        'targets' => ['es'],
        'format'  => 'markdown',
    ]);
} catch (AuthException $e) {
    // HTTP 401 — invalid, missing, or revoked API key
} catch (RateLimitException $e) {
    // HTTP 429 — per-minute limit reached
    $retryAfter = $e->getRetryAfter(); // int|null seconds
} catch (JobFailedException $e) {
    // Async job reached a failed terminal status
    $jobId = $e->getJobId();
} catch (PolyLingoException $e) {
    // All other API errors
    $httpStatus = $e->getHttpStatus();
    $errorCode  = $e->getErrorCode(); // e.g. "invalid_request", "translation_error"
}

RateLimitException::getRetryAfter() returns the number of seconds to wait before retrying if the API includes that header, or null if not present. JobFailedException::getJobId() gives you the ID of the failed job so you can log it or surface it to the user.


Quick reference

MethodEndpointAuth required
$client->health()GET /healthNo
$client->languages()GET /languagesNo
$client->translate()POST /translateYes
$client->batch()POST /translate/batchYes
$client->usage()GET /usageYes
$client->jobs->create()POST /jobsYes
$client->jobs->get($id)GET /jobs/:idYes
$client->jobs->translate()POST /jobs + pollingYes

Get started

The SDK is on Packagist at usepolylingo/polylingo. Full API documentation is at usepolylingo.com/docs.

The free tier includes 50,000 tokens per month. No credit card required.

composer require usepolylingo/polylingo

Get your API key