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()andAttributeValue::setTranslation() - Product package: set translation for product entities from panel or API
- Any package service with
translatable_id+translationpayload
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
Related Built-ins
- 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 toSetTranslationRequest: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 finalResponse. -
$status
Success status code (default200). -
$notFoundExceptionFactory
Optional callback:fn(int $id, ModelNotFoundException $e): Throwableto 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
- Keep
requestContext.model_classandmodelClassaligned. - Always pass package-specific success messages for better API/UI feedback.
- Use
notFoundExceptionFactoryto keep domain exceptions consistent. - Reuse
translation_set(...)in services for shorter, cleaner code. - Keep
attribute_pathaligned with your package language keys to get clean validation labels. - Pass a table name from config (
config('package.tables...')) instead of hardcoded values where possible.