Prevent deleting a language (core and translations models) whenever any
record still references it, and fix the foreign key that made renaming a
language code fail.
Protection layers (core only):
- LanguagePolicy (shared by both Language models via Gate::policy): delete
and forceDelete are denied while the language is in use; all other
abilities stay allowed.
- LanguageContentObserver: hard safety net that throws a ValidationException
on deleting, covering bulk/programmatic paths the policy cannot.
- ChecksLanguageContent trait: single source of truth checking content,
settings, menus, form submissions, form actions, the domain_language
pivot, translated attributes, translations and language rules. Uses
withTrashed() for the soft-deletable dependents so a soft-deleted row
still blocks deletion (the FK references the physical row).
Also:
- laravel-translations LanguageObserver guards its own data (translations
and translated attributes) framework-agnostically.
- Move the core default-reassignment from deleting to deleted so a blocked
delete no longer mutates the default flag (partial side effect).
- Add ON UPDATE CASCADE to translated_attributes.code (core migration +
laravel-translations stub/migration) so renaming languages.code no longer
throws a 1451 integrity-constraint violation.
Tests cover both Language models across every dependent branch, soft-delete
behaviour, combinations, bulk delete, blocked-delete side effects, and
rename-cascade across all language_code foreign keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>