Concepts

One row per locale

The trait does not add a translations table or JSON columns. A translation is a row in the same table:

id pid locale title
1 null nl Over ons
2 1 en About us
3 1 de Über uns
  • The source row has no pid.
  • A translation has the id of the source row in pid.
  • Every row has its own slug, status and relations, so a translation is a full page of its own.
  • The package adds no foreign key and no unique index; the migration is yours. The quick start has an example.

hasTranslation(), getTranslation() and getAllTranslations() work from the source row and from any translation of it.

Which fields are translated

protected $translatableFields = ['title', 'content', 'excerpt'];

protected $htmlFields = ['content'];

Without $translatableFields the trait uses title, content, description and excerpt; without $htmlFields it uses content and description. Override getTranslatableFields() or getHtmlFields() when the list depends on the model.

A field in $htmlFields is sent with format set to html, so the tags survive. Everything else is sent as plain text.

A failed API call never throws

The service catches every exception around the HTTP call, writes one line to the log with Log::error() and returns null, or [] for a batch.

Situation translate(), translateHtml() translateBatch() In the log
No API key null [] nothing
Empty input null [] nothing
HTTP 4xx or 5xx null [] Google Translate failed: and the response body
Connection error or timeout null [] Google Translate failed: and the message of the exception
HTTP 2xx without a translation in the body null [] nothing

The prefix is Google Translate HTML failed: for translateHtml() and Google Translate batch failed: for a batch.

The package sets no timeout and does not retry. Laravel’s HTTP client default applies, which is 30 seconds.

What can still throw is everything around the API call: createTranslation() inserts a row, so a missing column value or a model without $fillable raises the usual Eloquent exception. See Troubleshooting.

So check the return value. createTranslation() returns null when there is no key or no field could be translated. When some fields fail, the translation is created with the fields that did succeed; fillMissingTranslations() fills the rest later.

The API key

The key is sent in the X-goog-api-key header, only to https://translation.googleapis.com/language/translate/v2. It is never part of the URL, so an error message that quotes the URL does not leak it into the log.

The service reads the key when it is constructed. It is a singleton, so a key you change at runtime is only picked up after app()->forgetInstance(GoogleTranslateService::class).

Costs

The package adds no costs of its own. Google charges for the characters you send, for every target locale again; the current rates are on Google’s pricing page. The trait avoids sending the same text twice:

  • createTranslation() returns the existing row when the locale already exists.
  • fillMissingTranslations() skips the fields that already have a value.
  • translateBatch() sends many strings in one request; that saves round trips, not characters.

There is no cache. When you translate the same loose strings more than once, cache the result yourself.

Translated HTML is external input

The source may be your own content, but the result comes from an external service. Sanitise translated HTML the way you sanitise the source before you render it unescaped.

Reading the settings

Darvis\LaravelGoogleTranslate\Support\GoogleTranslateConfig is the one place that reads the config: apiKey(), sourceLocale() and targetLocales(). The service exposes the last two as getSourceLocale() and getTargetLocales().