TranslationArrayRequest Trait
The TranslationArrayRequest trait composes validation rules and human‑friendly attribute labels for a translation payload. If multiple locales are present under translation, rules are generated for each locale; otherwise, it falls back to app()->getLocale().
When to Use TranslationArrayRequest
Use TranslationArrayRequest when you need:
- Array-based translation validation: Your translation payload uses a simple array structure
- Single or multiple locales: Works with one locale (app locale) or multiple locales in the payload
- Automatic rule generation: Automatically generates validation rules for each locale and field
- Primary field uniqueness: Enforces uniqueness for a primary field (default:
name) usingTranslationFieldExistRule - Human-friendly labels: Automatically generates attribute labels for translation fields
- Simple form requests: Perfect for straightforward translation validation scenarios
Example scenarios:
- Creating or updating products with translations in a simple array format
- Blog post forms with title and content translations
- Category management with name and description translations
- Simple content management forms with basic translation fields
- Forms where translation structure is straightforward and doesn't require complex validation
This document focuses only on request-side validation and attribute labels. No database or tests here. All examples include expected output blocks you can compare against during development.
What It Does
- Detects locales from the incoming payload:
- If
translationis an array: uses its keys as locales (e.g.,en,fa). - Otherwise: uses
app()->getLocale().
- If
- For every locale:
- Adds a container rule:
translation.{locale} => array - Enforces a primary translated field (default
name) as:
required|string + TranslationFieldExistRule(per-locale uniqueness) - Adds optional string rules for all other fields returned by
translationAllowFields()on the target model.
- Adds a container rule:
- Builds attribute labels for each
translation.{locale}.{field}key using:- Special labels for meta fields (
meta_title,meta_description,meta_keywords). - A custom translation scope containing
{field}(if provided). - A sensible fallback: a title‑cased field name.
- Special labels for meta fields (
Quick Start (Typical FormRequest)
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use JobMetric\Translation\Http\Requests\TranslationArrayRequest;
class StorePostRequest extends FormRequest
{
use TranslationArrayRequest;
public function authorize(): bool
{
return true;
}
public function rules(): array
{
$rules = [
'slug' => 'required|string',
];
// Build translation rules based on payload and model contract
$this->renderTranslationFiled(
$rules,
$this->all(), // request data
\App\Models\Post::class, // model exposing translationAllowFields()
'title', // primary field
null, // object_id (null on create)
null, // parent_id (optional scope)
[] // parent_where (optional constraints)
);
return $rules;
}
public function attributes(): array
{
$attributes = [
'slug' => 'Slug',
];
// Produce readable labels. Pass a scope containing "{field}" if you want templated labels.
$this->renderTranslationAttribute(
$attributes,
$this->all(),
\App\Models\Post::class,
null // or e.g. 'validation.attributes.translation_field' with "{field}" placeholder
);
return $attributes;
}
}
Note: Method names are
renderTranslationFiledandrenderTranslationAttribute(keep them exactly as implemented). The target model must implementtranslationAllowFields().
Example: Resulting rules() Output (Multiple Locales)
Assumptions:
- Incoming payload contains:
{
"slug": "hello-world",
"translation": {
"en": { "title": "Hello", "summary": "First post" },
"fa": { "title": "سلام", "summary": "اولین پست" }
}
} App\Models\Post::translationAllowFields()returns:['title', 'summary', 'meta_title', 'meta_description']- Primary field is
title
Code (excerpt):
$rules = ['slug' => 'required|string'];
$this->renderTranslationFiled(
$rules,
$payload,
\App\Models\Post::class,
'title',
null,
null,
[]
);
return $rules;
Expected Output (print_r($rules)):
Array
(
[slug] => required|string
[translation] => array
[translation.en] => array
[translation.en.title] => Array
(
[0] => required
[1] => string
[2] => JobMetric\Translation\Rules\TranslationFieldExistRule Object (...)
)
[translation.en.summary] => string|nullable|sometimes
[translation.en.meta_title] => string|nullable|sometimes
[translation.en.meta_description] => string|nullable|sometimes
[translation.fa] => array
[translation.fa.title] => Array
(
[0] => required
[1] => string
[2] => JobMetric\Translation\Rules\TranslationFieldExistRule Object (...)
)
[translation.fa.summary] => string|nullable|sometimes
[translation.fa.meta_title] => string|nullable|sometimes
[translation.fa.meta_description] => string|nullable|sometimes
)
Example: Resulting rules() Output (No Locales Provided)
Assumptions:
- Incoming payload does not have
translationkey, or it is not an array. app()->getLocale()returnsen.translationAllowFields()returns['name', 'description']- Primary field is
name(default).
Expected Output (excerpt):
Array
(
[translation] => array
[translation.en] => array
[translation.en.name] => Array
(
[0] => required
[1] => string
[2] => JobMetric\Translation\Rules\TranslationFieldExistRule Object (...)
)
[translation.en.description] => string|nullable|sometimes
)
Example: Resulting attributes() Output (Multiple Locales)
Using the first example payload and fields, with no $trans_scope provided (so it falls back to title‑cased names).
Code (excerpt):
$attributes = ['slug' => 'Slug'];
$this->renderTranslationAttribute(
$attributes,
$payload,
\App\Models\Post::class,
null
);
return $attributes;
Expected Output (print_r($attributes)):
Array
(
[slug] => Slug
[translation.en.title] => Title
[translation.en.summary] => Summary
[translation.en.meta_title] => Meta title
[translation.en.meta_description] => Meta description
[translation.fa.title] => Title
[translation.fa.summary] => Summary
[translation.fa.meta_title] => Meta title
[translation.fa.meta_description] => Meta description
)
When
$trans_scopeis provided (e.g.,'validation.attributes.translation_field'with{field}inside), the{field}placeholder is replaced per field and the resulting translation string is used.
Primary Field and Uniqueness
- The primary field (default
name, or your override liketitle) is enforced per locale with:
required|string + TranslationFieldExistRule. - All other fields from
translationAllowFields()are attached as:
string|nullable|sometimes.
You control the list of fields by implementing translationAllowFields() on your model.
End-to-End Minimal Flow
- Implement
translationAllowFields()on the target model (e.g., return['title','summary', ...]). - In your
FormRequest::rules(), callrenderTranslationFiled($rules, $this->all(), Model::class, 'title', ...). - In
FormRequest::attributes(), callrenderTranslationAttribute($attributes, $this->all(), Model::class, $scopeOrNull). - Done — your validator now understands
translation.{locale}.{field}for each locale present in the payload (or the app locale).
Summary
TranslationArrayRequest makes multi-locale translation validation straightforward when your payload looks like:
{
"translation": {
"en": { "title": "...", "summary": "..." },
"fa": { "title": "...", "summary": "..." }
}
}
It automatically:
- Detects locales from the payload (or uses the app locale)
Related Documentation
- HasTranslation - Core trait for multilingual models
- TranslationFieldExistRule - Validation rule for unique translations
- MultiTranslationArrayRequest - Enhanced version for multi-locale scenarios
- TranslationTypeObjectRequest - Typeify-based validation for complex schemas
- TranslationResource - JSON resource for API responses
- Enforces a primary translated field with per-locale uniqueness
- Adds optional string rules for other allowed fields
- Generates readable attribute labels for clean error messages