Google Cloud Translation API in PHP: API key, REST client and the official library
Advertisement
Adding automatic translation to a PHP site, whether it is a product description shown in Hindi, a support message, or a content tool, is a few lines of code once the Google Cloud setup is done. This guide covers both ways to do it: a small REST client using an API key and cURL (works in core PHP, CodeIgniter, Laravel or anything else), and the official google/cloud-translate library. It also covers the things tutorials skip: restricting the key, batching text, caching results so you do not pay twice for the same sentence, and handling errors.
How this was tested. The REST client was run against a small local mock server that follows the request and response shapes in Google's documentation (a POST to /language/translate/v2 with a JSON body, an API key, and a data.translations[].translatedText response). I did not call Google's real service because it needs your own project and billing. For the library, I installed google/cloud-translate 2.3.7 with Composer and confirmed that the classes and methods named below exist and that a request object serialises as expected, but I did not make a billed call. Always run your first requests with a short test string.
Two editions: Basic and Advanced
| Basic (v2) | Advanced (v3) | |
|---|---|---|
| Endpoint | https://translation.googleapis.com/language/translate/v2 |
.../v3/projects/PROJECT_ID/locations/global:translateText |
| Authentication | API key (or OAuth) | service account / OAuth (API keys are not the usual route) |
| Extra features | text translation and language detection | glossaries, batch document translation, custom models |
| PHP library | REST calls (no PHP v2 client in current library versions) | google/cloud-translate v3 clients |
| Best for | simple text translation | larger or more controlled projects |
Start with Basic if you only need to turn strings into another language. Choose Advanced when you need glossaries (forcing "invoice" to always be translated a particular way) or batch translation of documents.
Step 1: set up Google Cloud
- Sign in to the Google Cloud console and create a project.
- Enable billing for the project. The Translation API is a paid service (with a free monthly allowance that Google defines on its pricing page, which can change). Set a budget alert so a bug in a loop cannot surprise you.
- Enable the Cloud Translation API under APIs & Services > Library.
- Create credentials: APIs & Services > Credentials > Create credentials > API key.
- Restrict the key immediately. Under the key's settings, set API restrictions to "Cloud Translation API" only. Add application restrictions: an IP address restriction for server-side PHP is ideal. An unrestricted key that leaks can be abused on your bill.
Keep the key out of your code repository. Put it in an environment variable (GOOGLE_TRANSLATE_KEY) or a config file outside the web root. If it ever leaks, delete it in the console and make a new one.
Step 2: a REST client in PHP
This class sends strings to the Basic endpoint and returns the translations. It needs only PHP 8 with the curl and json extensions.
<?php
declare(strict_types=1);
final class Translator
{
public function __construct(
private string $apiKey,
private string $endpoint = 'https://translation.googleapis.com/language/translate/v2',
private string $cacheDir = __DIR__ . '/cache',
) {}
/**
* Translate one or more strings. Returns the translations in the same order.
*
* @param string|list<string> $text
* @return list<array{text:string, detected:?string}>
*/
public function translate(string|array $text, string $target, ?string $source = null): array
{
$texts = (array) $text;
$body = ['q' => array_values($texts), 'target' => $target, 'format' => 'text'];
if ($source !== null) { $body['source'] = $source; }
$ch = curl_init($this->endpoint . '?key=' . rawurlencode($this->apiKey));
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json; charset=utf-8'],
CURLOPT_POSTFIELDS => json_encode($body, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($raw === false) {
throw new RuntimeException('Translation request failed: ' . $err);
}
$data = json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
if ($status !== 200) {
throw new RuntimeException('Translation API error ' . $status . ': ' . ($data['error']['message'] ?? 'unknown'));
}
return array_map(
fn(array $t) => ['text' => $t['translatedText'], 'detected' => $t['detectedSourceLanguage'] ?? null],
$data['data']['translations']
);
}
/** One string, cached on disk so the same text is never billed twice. */
public function translateCached(string $text, string $target, ?string $source = null): string
{
if (!is_dir($this->cacheDir)) { mkdir($this->cacheDir, 0775, true); }
$file = $this->cacheDir . '/' . hash('sha256', "$source|$target|$text") . '.txt';
if (is_file($file)) { return (string) file_get_contents($file); }
$result = $this->translate($text, $target, $source)[0]['text'];
file_put_contents($file, $result);
return $result;
}
}
Using it:
$t = new Translator(getenv('GOOGLE_TRANSLATE_KEY'));
$out = $t->translate(['Hello world', 'Good morning'], target: 'hi', source: 'en');
echo $out[0]['text']; // the Hindi translation of "Hello world"
$auto = $t->translate('Hello world', 'hi'); // no source given: Google detects it
echo $auto[0]['detected']; // en
What the parts do:
qis the text. It can be an array, so you can send many strings in one request. Google limits the number of strings per request (the documentation says 128), so split long lists into chunks.targetis the language you want, as an ISO-639 code (hiHindi,esSpanish,frFrench,deGerman,jaJapanese).sourceis optional; leave it out and the API detects the language and tells you indetectedSourceLanguage.format: 'text'tells the API your input is plain text. The default is HTML, in which case characters like quotes can come back as HTML entities such as'. If you do translate HTML (product descriptions with tags), use'html'and keep your tags; the API leaves the markup alone.JSON_UNESCAPED_UNICODEkeeps the request readable when the input is not English.- Errors throw an exception that includes Google's own message.
In my mock run the client produced, for two strings, one request and an array of two results; the single-string call with no source returned detected: "en". A second call to translateCached with the same text made no extra API call: three calls were made in total for five translation requests. A wrong key raised Translation API error 400: API key not valid. Please pass a valid API key., and an unreachable server raised Translation request failed: Failed to connect....
Step 3: use it in a page
<?php
require __DIR__ . '/Translator.php';
$languages = ['hi' => 'Hindi', 'es' => 'Spanish', 'fr' => 'French'];
$lang = $_GET['lang'] ?? 'hi';
if (!isset($languages[$lang])) { $lang = 'hi'; } // never trust the parameter
$text = "Welcome to our store. Free shipping on orders over 999 rupees.";
try {
$translated = (new Translator(getenv('GOOGLE_TRANSLATE_KEY')))->translateCached($text, $lang, 'en');
} catch (Throwable $e) {
error_log($e->getMessage());
$translated = $text; // fall back to the original
}
echo '<p>', htmlspecialchars($translated, ENT_QUOTES, 'UTF-8'), '</p>';
Three habits matter here. First, validate the language against a list you control. Second, escape the output with htmlspecialchars, because a translation is still external text. Third, fall back to the original when the API fails, so a broken translation service never breaks the page.
In a CodeIgniter 4 app, put the same class in app/Libraries/Translator.php with a namespace (namespace App\Libraries;) and read the key from .env with env('GOOGLE_TRANSLATE_KEY'). In Laravel, put it in app/Services, bind it in a service provider and read the key from config/services.php, which in turn reads .env. The code inside does not change.
Option B: the official PHP library (Advanced, v3)
Google's google/cloud-translate package provides the v3 client. Install it with Composer:
composer require google/cloud-translate
The v3 client authenticates with a service account rather than an API key. In the console, create a service account with the Cloud Translation API User role, download its JSON key file, keep it outside the web root, and point the environment variable GOOGLE_APPLICATION_CREDENTIALS at it. Then:
<?php
require __DIR__ . '/vendor/autoload.php';
use Google\Cloud\Translate\V3\Client\TranslationServiceClient;
use Google\Cloud\Translate\V3\TranslateTextRequest;
$projectId = 'my-project';
$client = new TranslationServiceClient();
$request = (new TranslateTextRequest())
->setParent(TranslationServiceClient::locationName($projectId, 'global'))
->setContents(['Hello world'])
->setSourceLanguageCode('en')
->setTargetLanguageCode('hi')
->setMimeType('text/plain');
try {
$response = $client->translateText($request);
foreach ($response->getTranslations() as $translation) {
echo $translation->getTranslatedText(), "\n";
}
} finally {
$client->close();
}
I confirmed with google/cloud-translate 2.3.7 that TranslationServiceClient::locationName('my-project', 'global') returns projects/my-project/locations/global and that the request object serialises to contents, mimeType, sourceLanguageCode, targetLanguageCode and parent. The client also offers detectLanguage, getSupportedLanguages and batchTranslateText. Older tutorials use a Google\Cloud\Translate\V2\TranslateClient class with an API key; that class is not in the current v2.x library, which is why this post uses direct REST calls for the Basic edition.
Which option should you use? For a handful of strings and a small site, the REST client has fewer moving parts (no gRPC or credential files). For glossaries, batch jobs and Google's recommended production setup, use the library with a service account.
Cost and quality advice
- Cache everything. The same text should be translated once. Store translations in the database next to the original (keyed by language), or in files as above.
- Translate on save, not on every view. Translate when content changes, then serve the stored result.
- Send batches instead of one request per sentence.
- Set a budget alert and a quota in the console, and restrict the key.
- Do not translate code, brand names or placeholders. Wrap those so they are not changed: replace
{name}with a token before sending and put it back after, or send HTML and mark the part withtranslate="no". - Machine translation is a draft. For legal or marketing text that matters, have a person review it.
- Tell users which text is automatically translated.
Common errors
| Symptom | Likely cause |
|---|---|
| HTTP 400 "API key not valid" | wrong key, or the key restrictions exclude the Translation API |
| HTTP 403 "has not been used in project" or billing disabled | the API is not enabled, or billing is off |
| HTTP 403 "IP address restriction" | your server's address is not in the key's allowed list |
| HTTP 429 | quota exceeded; slow down or request more quota |
Translated text shows ' or " |
HTML mode; use format: 'text' and decode entities if needed |
| "Invalid language" | the target is not a supported code |
| Empty result for empty input | skip empty strings before sending |
| Library says credentials not found | GOOGLE_APPLICATION_CREDENTIALS is not set for the PHP process (PHP-FPM needs the variable in its pool config) |
Next steps
If you are new to calling APIs from PHP, work through the free PHP course first. The pattern in this post (key, request, JSON, error handling) is the same as in the eBay Browse API guide, and the PHP interview questions for experienced developers include a worked example of building and calling a small JSON API. When your site is ready, see how to deploy from Git to shared hosting. For a client-side look at JSON responses, use the JSON Formatter.