blackstone / laravel-model-translations-sync
Synchronize Laravel model translations between models, language_lines, files and external translation flows.
Package info
github.com/blackstone/laravel-model-translations-sync
pkg:composer/blackstone/laravel-model-translations-sync
Requires
- php: ^8.2|^8.3|^8.4
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/filesystem: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- spatie/laravel-translatable: ^6.11
- spatie/laravel-translation-loader: ^2.8
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.5
README
Laravel package for synchronizing translations between:
- translatable Eloquent model JSON attributes via
spatie/laravel-translatable language_linesviaspatie/laravel-translation-loader- local Laravel
langfiles - external translation services such as Crowdin, Lokalise or custom project commands
Pipeline target:
Models -> DB (language_lines) -> external service -> DB -> Models -> Files
Requirements
- PHP 8.3, 8.4+
- Laravel 11 or 12
spatie/laravel-translatablespatie/laravel-translation-loader
Installation
composer require blackstone/laravel-model-translations-sync
Publish config:
php artisan vendor:publish --tag=model-translations-config
Model setup
Add HasTranslations from Spatie and the package trait:
use Illuminate\Database\Eloquent\Model; use Spatie\Translatable\HasTranslations; use BlackstonePro\ModelTranslationsSync\Traits\ModelTranslatable; class Product extends Model { use HasTranslations; use ModelTranslatable; protected $fillable = ['title', 'description']; public array $translatable = ['title', 'description']; }
Default behavior:
getModelTranslationNamespace()returns snake_case model basenamegetTranslatableAttributesForSync()returns$this->translatable
You can override both methods in the model if needed.
Storage format in language_lines
Model translations are stored as:
group = models.{namespace}key = {id}.{attribute}text = {"en":"...", "fr":"..."}
Example:
group = models.product
key = 12.title
text = {"en":"iPhone","fr":"iPhone"}
Configuration
Published config file: config/model-translations.php
Main options:
models.auto_discover: discover models from configured pathsmodels.paths: directories for model discoverymodels.list: explicit model class listmodels.namespace_map: manual namespace to model mappinglocales: supported locales for syncdefault_locale: fallback localeignore_groups: groups ignored during file exportexport_path: destination for generated lang filesexport.overwrite,export.pretty_print,export.sort_keyssync.stop_on_error: stop or continue when a pipeline step failssync.pipeline: ordered list of artisan commands for the sync pipeline
Commands
Export models to language_lines:
php artisan translations:export-models {model?} {--fresh} {--chunk=500} {--dry-run}
Import model translations from language_lines:
php artisan translations:import-models {--locale=} {--group=} {--chunk=500} {--dry-run}
Export DB translations to local files:
php artisan translations:export-files {--dry-run}
Import local files back into DB:
php artisan translations:import-files {--dry-run}
Run the full pipeline:
php artisan translations:sync {--dry-run}
Pipeline is config-driven. Example:
'sync' => [ 'stop_on_error' => true, 'pipeline' => [ ['command' => 'translations:export-models', 'enabled' => true], ['command' => 'crowdin:upload', 'enabled' => true], ['command' => 'crowdin:download', 'enabled' => true], ['command' => 'translations:import-models', 'enabled' => true], ['command' => 'translations:export-files', 'enabled' => true], ], ],
translations:sync executes enabled commands in order, warns about missing or failing steps, and stops only when sync.stop_on_error is true.
Generating translatable JSON migrations
The package can generate a snapshot-based Laravel migration for safely converting existing scalar/text translatable columns into JSON columns without runtime model scanning inside the migration.
php artisan translations:make-translatable-migration convert_translatable_fields_to_json --locale=en
Available options:
php artisan translations:make-translatable-migration
{name?}
{--paths=*}
{--locale=en}
{--chunk=500}
{--force}
The command:
- discovers models using
Spatie\Translatable\HasTranslations - filters only real DB columns
- writes a fixed snapshot of models, tables, primary keys and translatable attributes
- generates a self-contained migration in
database/migrations
The generated migration:
- creates
__json_tmpcolumns inup() - wraps old scalar values as
{"<locale>":"value"} - renames temp JSON columns back to the original names
- restores values back to
textcolumns indown()using the chosen locale
Source of truth
translations:export-models: models are source of truthtranslations:import-models:language_linesis source of truthtranslations:export-files:language_linesis source of truthtranslations:import-files: lang files are source of truth
File formats
Supported export/import targets:
resources/lang/{locale}/*.phpresources/lang/{locale}.jsonresources/lang/{locale}/models.php
Model translations in models.php are structured as:
return [ 'product' => [ 12 => [ 'title' => 'iPhone', 'description' => 'Smartphone', ], ], ];
Testing
composer test