Skip to main content

Translation Service

The Translation service provides a single reusable entry point for storing translations across any translatable model.
It centralizes validation, model lookup, translation persistence, and unified response output.

When to Use Translation Service

Use Translation service when you need:

  • Shared set-translation logic: avoid duplicating translation loops in multiple package services
  • Request-driven validation: validate payloads with a dedicated translation request (SetTranslationRequest)
  • Model-agnostic behavior: reuse one implementation for attributes, products, posts, or any translatable model
  • Consistent responses: always return JobMetric\PackageCore\Output\Response
  • Optional domain-specific not-found errors: map missing model IDs to your package exceptions via callback

Example scenarios:

  • Attribute package: Attribute::setTranslation() and AttributeValue::setTranslation()
  • Product package: set translation for product entities from panel or API
  • Any package service with translatable_id + translation payload

Namespace

JobMetric\Translation\Services\Translation

Bound Service Key

The service is registered as a singleton in container with key:

translation

You can access it via:

app('translation')

or with facade:

JobMetric\Translation\Facades\Translation
  • Form Request: JobMetric\Translation\Http\Requests\SetTranslationRequest
  • Helper: translation_set(...)
  • Facade: JobMetric\Translation\Facades\Translation

Method Signature

public function setTranslation(
array $data,
array $requestContext,
string $modelClass,
string $message,
int $status = 200,
?Closure $notFoundExceptionFactory = null
): Response

Parameters

  • $data
    Incoming payload, typically:

    • locale (string)
    • translatable_id (int)
    • translation (array of locale => fields)
  • $requestContext
    Context passed to SetTranslationRequest:

    • model_class (class-string)
    • table (string)
    • attribute_path (string for label translation path with {field})
  • $modelClass
    Target model class to fetch and translate (must be translatable).

  • $message
    Success message for the final Response.

  • $status
    Success status code (default 200).

  • $notFoundExceptionFactory
    Optional callback: fn(int $id, ModelNotFoundException $e): Throwable to throw package-specific exceptions.

Quick Start (Service Call)

use Illuminate\Database\Eloquent\ModelNotFoundException;
use JobMetric\Translation\Facades\Translation;
use App\Models\Product;
use App\Exceptions\ProductNotFoundException;

$response = Translation::setTranslation(
data: [
'locale' => 'en',
'translatable_id' => 12,
'translation' => [
'en' => ['name' => 'Product Name'],
'fa' => ['name' => 'نام محصول'],
],
],
requestContext: [
'model_class' => Product::class,
'table' => 'products',
'attribute_path' => 'product::base.form.product.fields.{field}.title',
],
modelClass: Product::class,
message: trans('product::base.messages.product.set_translation'),
status: 200,
notFoundExceptionFactory: fn (int $id, ModelNotFoundException $e) => new ProductNotFoundException($id, previous: $e)
);

Better Practical Examples

Example 1: Inside a Package Service Method (Real Pattern)

Use this pattern when your package service already has its own domain exceptions and message keys.

use Illuminate\Database\Eloquent\ModelNotFoundException;
use JobMetric\PackageCore\Output\Response;
use JobMetric\Product\Exceptions\ProductNotFoundException;
use JobMetric\Product\Models\Product as ProductModel;

public function setTranslation(array $data): Response
{
return translation_set(
data: $data,
requestContext: [
'model_class' => ProductModel::class,
'table' => config('product.tables.product'),
'attribute_path' => 'product::base.form.product.fields.{field}.title',
],
modelClass: ProductModel::class,
message: trans('product::base.messages.product.set_translation'),
status: 200,
notFoundExceptionFactory: fn (int $id, ModelNotFoundException $e) => new ProductNotFoundException($id, previous: $e)
);
}

Example 2: Controller Endpoint Using Facade

Use this when translation is exposed directly from controller layer.

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use JobMetric\Translation\Facades\Translation as TranslationFacade;
use App\Models\Post;

public function setPostTranslation(Request $request, int $id): JsonResponse
{
$result = TranslationFacade::setTranslation(
data: array_merge($request->all(), ['translatable_id' => $id]),
requestContext: [
'model_class' => Post::class,
'table' => 'posts',
'attribute_path' => 'post::base.form.post.fields.{field}.title',
],
modelClass: Post::class,
message: trans('post::base.messages.post.set_translation')
);

return response()->json([
'ok' => $result->ok,
'message' => $result->message,
'status' => $result->status,
], $result->status);
}

Example 3: Multi-Locale Payload (Expected Shape)

$data = [
'locale' => 'en',
'translatable_id' => 55,
'translation' => [
'en' => [
'name' => 'Running Shoes',
'summary' => 'Comfortable lightweight shoes',
],
'fa' => [
'name' => 'کفش دویدن',
'summary' => 'کفش سبک و راحت',
],
],
];

You can pass this payload to service/facade/helper; validation and persistence flow is the same.


Example 4: Domain-Specific Not Found Mapping

If you skip notFoundExceptionFactory, missing record throws ModelNotFoundException. If you need package-level exception consistency, map it explicitly:

notFoundExceptionFactory: fn (int $id, ModelNotFoundException $e) =>
new ProductNotFoundException($id, previous: $e)

This is recommended for package APIs that already expose custom exception types.

Helper Usage

$response = translation_set(
data: $data,
requestContext: [
'model_class' => Product::class,
'table' => 'products',
'attribute_path' => 'product::base.form.product.fields.{field}.title',
],
modelClass: Product::class,
message: trans('product::base.messages.product.set_translation')
);

Helper Example with Minimal Boilerplate

$response = translation_set(
data: [
'locale' => 'fa',
'translatable_id' => 9,
'translation' => [
'fa' => ['name' => 'نام فارسی'],
],
],
requestContext: [
'model_class' => \App\Models\Brand::class,
'table' => 'brands',
'attribute_path' => 'brand::base.form.brand.fields.{field}.title',
],
modelClass: \App\Models\Brand::class,
message: trans('brand::base.messages.brand.set_translation')
);

Validation Flow

Translation::setTranslation() always validates via:

JobMetric\Translation\Http\Requests\SetTranslationRequest

This means consumers do not inject custom request classes; the request implementation remains centralized inside laravel-translation.

Output

On success, returns:

Response::make(true, $message, null, $status)

Error Behavior

  • Invalid payload -> ValidationException
  • Missing model -> ModelNotFoundException (or your mapped exception through callback)
  • Non-translatable model -> ModelHasTranslationNotFoundException
  • Translation persistence errors -> propagated (Throwable)

Best Practices

  1. Keep requestContext.model_class and modelClass aligned.
  2. Always pass package-specific success messages for better API/UI feedback.
  3. Use notFoundExceptionFactory to keep domain exceptions consistent.
  4. Reuse translation_set(...) in services for shorter, cleaner code.
  5. Keep attribute_path aligned with your package language keys to get clean validation labels.
  6. Pass a table name from config (config('package.tables...')) instead of hardcoded values where possible.