├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ ├── php-cs-fixer.yml │ ├── update-changelog.yml │ ├── run-tests.yml │ └── dependabot-auto-merge.yml ├── .editorconfig ├── src ├── Events │ └── TranslationHasBeenSetEvent.php ├── Exceptions │ └── AttributeIsNotTranslatable.php ├── Facades │ └── Translatable.php ├── TranslatableServiceProvider.php ├── Translatable.php └── HasTranslations.php ├── LICENSE.md ├── composer.json ├── README.md ├── .phpunit.cache └── test-results └── CHANGELOG.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: spatie 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/Events/TranslationHasBeenSetEvent.php: -------------------------------------------------------------------------------- 1 | getTranslatableAttributes()); 12 | 13 | return new static("Cannot translate attribute `{$key}` as it's not one of the translatable attributes: `$translatableAttributes`"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Facades/Translatable.php: -------------------------------------------------------------------------------- 1 | name('laravel-translatable'); 15 | } 16 | 17 | public function packageRegistered(): void 18 | { 19 | $this->app->singleton(Translatable::class, fn () => new Translatable); 20 | $this->app->bind('translatable', Translatable::class); 21 | 22 | Factory::macro('translations', function (string|array $locales, mixed $value) { 23 | return is_array($value) 24 | ? array_combine((array) $locales, $value) 25 | : array_fill_keys((array) $locales, $value); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Translatable.php: -------------------------------------------------------------------------------- 1 | fallbackLocale = $fallbackLocale; 32 | $this->fallbackAny = $fallbackAny; 33 | $this->missingKeyCallback = $missingKeyCallback; 34 | 35 | return $this; 36 | } 37 | 38 | public function allowNullForTranslation(bool $allowNullForTranslation = true): self 39 | { 40 | $this->allowNullForTranslation = $allowNullForTranslation; 41 | 42 | return $this; 43 | } 44 | 45 | public function allowEmptyStringForTranslation(bool $allowEmptyStringForTranslation = true): self 46 | { 47 | $this->allowEmptyStringForTranslation = $allowEmptyStringForTranslation; 48 | 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | fail-fast: true 10 | matrix: 11 | os: [ubuntu-latest] 12 | php: [8.5, 8.4, 8.3] 13 | laravel: [12.*, 11.*] 14 | stability: [prefer-stable] 15 | include: 16 | - laravel: 12.* 17 | testbench: 10.* 18 | - laravel: 11.* 19 | testbench: 9.* 20 | 21 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 22 | 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v6 26 | 27 | - name: Setup PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php }} 31 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick 32 | coverage: none 33 | 34 | - name: Install dependencies 35 | run: | 36 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction 37 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 38 | 39 | - name: Execute tests 40 | run: vendor/bin/pest 41 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: dependabot-auto-merge 2 | on: pull_request_target 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | 14 | - name: Dependabot metadata 15 | id: metadata 16 | uses: dependabot/fetch-metadata@v2.4.0 17 | with: 18 | github-token: "${{ secrets.GITHUB_TOKEN }}" 19 | compat-lookup: true 20 | 21 | - name: Auto-merge Dependabot PRs for semver-minor updates 22 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}} 23 | run: gh pr merge --auto --merge "$PR_URL" 24 | env: 25 | PR_URL: ${{github.event.pull_request.html_url}} 26 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 27 | 28 | - name: Auto-merge Dependabot PRs for semver-patch updates 29 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 30 | run: gh pr merge --auto --merge "$PR_URL" 31 | env: 32 | PR_URL: ${{github.event.pull_request.html_url}} 33 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | 35 | - name: Auto-merge Dependabot PRs for Action major versions when compatibility is higher than 90% 36 | if: ${{steps.metadata.outputs.package-ecosystem == 'github_actions' && steps.metadata.outputs.update-type == 'version-update:semver-major' && steps.metadata.outputs.compatibility-score >= 90}} 37 | run: gh pr merge --auto --merge "$PR_URL" 38 | env: 39 | PR_URL: ${{github.event.pull_request.html_url}} 40 | GH_TOKEN: ${{secrets.GITHUB_TOKEN}} 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-translatable", 3 | "description": "A trait to make an Eloquent model hold translations", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-translatable", 7 | "translate", 8 | "eloquent", 9 | "model", 10 | "i8n", 11 | "multilingual" 12 | ], 13 | "homepage": "https://github.com/spatie/laravel-translatable", 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Freek Van der Herten", 18 | "email": "freek@spatie.be", 19 | "homepage": "https://spatie.be", 20 | "role": "Developer" 21 | }, 22 | { 23 | "name": "Sebastian De Deyne", 24 | "email": "sebastian@spatie.be", 25 | "homepage": "https://spatie.be", 26 | "role": "Developer" 27 | } 28 | ], 29 | "require": { 30 | "php": "^8.3", 31 | "illuminate/database": "^11.0|^12.0", 32 | "illuminate/support": "^11.0|^12.0", 33 | "spatie/laravel-package-tools": "^1.92.7" 34 | }, 35 | "require-dev": { 36 | "friendsofphp/php-cs-fixer": "^3.90", 37 | "mockery/mockery": "^1.6.12", 38 | "orchestra/testbench": "^9.0|^10.0", 39 | "pestphp/pest": "^4.0.0" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "Spatie\\Translatable\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Spatie\\Translatable\\Test\\": "tests" 49 | } 50 | }, 51 | "scripts": { 52 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", 53 | "test": "vendor/bin/pest" 54 | }, 55 | "config": { 56 | "allow-plugins": { 57 | "pestphp/pest-plugin": true 58 | }, 59 | "sort-packages": true 60 | }, 61 | "extra": { 62 | "laravel": { 63 | "providers": [ 64 | "Spatie\\Translatable\\TranslatableServiceProvider" 65 | ] 66 | }, 67 | "aliases": { 68 | "Translatable": "Spatie\\Translatable\\Facades\\Translatable" 69 | } 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Logo for laravel-translatable 6 | 7 | 8 | 9 |

A trait to make Eloquent models translatable

10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-translatable.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-translatable) 12 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 13 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-translatable/run-tests.yml) 14 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-translatable.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-translatable) 15 | 16 |
17 | 18 | This package contains a trait `HasTranslations` to make Eloquent models translatable. Translations are stored as json. There is no extra table needed to hold them. 19 | 20 | ```php 21 | use Illuminate\Database\Eloquent\Model; 22 | use Spatie\Translatable\HasTranslations; 23 | 24 | class NewsItem extends Model 25 | { 26 | use HasTranslations; 27 | 28 | public $translatable = ['name']; // translatable attributes 29 | 30 | // ... 31 | } 32 | ``` 33 | 34 | After the trait is applied on the model you can do these things: 35 | 36 | ```php 37 | $newsItem = new NewsItem; 38 | $newsItem 39 | ->setTranslation('name', 'en', 'Name in English') 40 | ->setTranslation('name', 'nl', 'Naam in het Nederlands') 41 | ->save(); 42 | 43 | $newsItem->name; // Returns 'Name in English' given that the current app locale is 'en' 44 | $newsItem->getTranslation('name', 'nl'); // returns 'Naam in het Nederlands' 45 | 46 | app()->setLocale('nl'); 47 | $newsItem->name; // Returns 'Naam in het Nederlands' 48 | 49 | $newsItem->getTranslations('name'); // returns an array of all name translations 50 | 51 | // You can translate nested keys of a JSON column using the -> notation 52 | // First, add the path to the $translatable array, e.g., 'meta->description' 53 | $newsItem 54 | ->setTranslation('meta->description', 'en', 'Description in English') 55 | ->setTranslation('meta->description', 'nl', 'Beschrijving in het Nederlands') 56 | ->save(); 57 | 58 | $attributeKey = 'meta->description'; 59 | $newsItem->$attributeKey; // Returns 'Description in English' 60 | $newsItem->getTranslation('meta->description', 'nl'); // Returns 'Beschrijving in het Nederlands' 61 | ``` 62 | 63 | Also providing scoped queries for retrieving records based on locales 64 | 65 | ```php 66 | // Returns all news items with a name in English 67 | NewsItem::whereLocale('name', 'en')->get(); 68 | 69 | // Returns all news items with a name in English or Dutch 70 | NewsItem::whereLocales('name', ['en', 'nl'])->get(); 71 | 72 | // Returns all news items that has name in English with value `Name in English` 73 | NewsItem::query()->whereJsonContainsLocale('name', 'en', 'Name in English')->get(); 74 | 75 | // Returns all news items that has name in English or Dutch with value `Name in English` 76 | NewsItem::query()->whereJsonContainsLocales('name', ['en', 'nl'], 'Name in English')->get(); 77 | 78 | // The last argument is the "operand" which you can tweak to achieve something like this: 79 | 80 | // Returns all news items that has name in English with value like `Name in...` 81 | NewsItem::query()->whereJsonContainsLocale('name', 'en', 'Name in%', 'like')->get(); 82 | 83 | // Returns all news items that has name in English or Dutch with value like `Name in...` 84 | NewsItem::query()->whereJsonContainsLocales('name', ['en', 'nl'], 'Name in%', 'like')->get(); 85 | ``` 86 | 87 | ## Support us 88 | 89 | [](https://spatie.be/github-ad-click/laravel-translatable) 90 | 91 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 92 | 93 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 94 | 95 | ## Documentation 96 | 97 | All documentation is available [on our documentation site](https://spatie.be/docs/laravel-translatable). 98 | 99 | ## Testing 100 | 101 | ```bash 102 | composer test 103 | ``` 104 | 105 | ## Contributing 106 | 107 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 108 | 109 | ## Security 110 | 111 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 112 | 113 | ## Postcardware 114 | 115 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 116 | 117 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 118 | 119 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 120 | 121 | ## Credits 122 | 123 | - [Freek Van der Herten](https://github.com/freekmurze) 124 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 125 | - [All Contributors](../../contributors) 126 | 127 | We got the idea to store translations as json in a column from [Mohamed Said](https://github.com/themsaid). Parts of the readme of [his multilingual package](https://github.com/themsaid/laravel-multilingual) were used in this readme. 128 | 129 | ## Alternatives 130 | 131 | - [DB-Fields-Translations](https://github.com/Afzaal565/DB-Fields-Translations) 132 | 133 | ## License 134 | 135 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 136 | -------------------------------------------------------------------------------- /.phpunit.cache/test-results: -------------------------------------------------------------------------------- 1 | {"version":"pest_4.1.3","defects":[],"times":{"P\\Tests\\EventTest::__pest_evaluable_it_will_fire_an_event_when_a_translation_has_been_set":0.007,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_package_fallback_locale_translation_when_getting_an_unknown_locale":0.004,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_default_fallback_locale_translation_when_getting_an_unknown_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_provides_a_flog_to_not_return_fallback_locale_translation_when_getting_an_unknown_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_fallback_locale_translation_when_getting_an_unknown_locale_and_fallback_is_true":0.005,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_execute_callback_fallback_when_getting_an_unknown_locale_and_fallback_callback_is_enabled":0.003,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_use_callback_fallback_return_value_as_translation":0.001,"P\\Tests\\TranslatableTest::__pest_evaluable_it_wont_use_callback_fallback_return_value_as_translation_if_it_is_not_a_string":0.004,"P\\Tests\\TranslatableTest::__pest_evaluable_it_wont_execute_callback_fallback_when_getting_an_existing_translation":0.02,"P\\Tests\\TranslatableTest::__pest_evaluable_it_wont_fail_if_callback_fallback_throw_exception":0.002,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_an_empty_string_when_getting_an_unknown_locale_and_fallback_is_not_set":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_an_empty_string_when_getting_an_unknown_locale_and_fallback_is_empty":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_save_a_translated_attribute":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_translated_values_when_creating_a_model":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_save_multiple_translations":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_the_value_of_the_current_locale_when_using_the_property":0.001,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_get_all_translations_in_one_go":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_get_specified_translations_in_one_go":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_get_all_translations_for_all_translatable_attributes_in_one_go":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_get_specified_translations_for_all_translatable_attributes_in_one_go":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_get_the_locales_which_have_a_translation":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_forget_a_translation":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_forget_all_translations_of_field":0.001,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_forget_all_translations_of_field_and_make_field_null":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_forget_a_field_with_mutator_translation":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_forget_all_translations":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_throw_an_exception_when_trying_to_translate_an_untranslatable_attribute":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_is_compatible_with_accessors_on_non_translatable_attributes":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_use_accessors_on_translated_attributes":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_be_converted_to_array_when_using_accessors_on_translated_attributes":0.001,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_use_mutators_on_translated_attributes":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_translations_for_default_language":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_multiple_translations_at_once":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_check_if_an_attribute_is_translatable":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_check_if_an_attribute_has_translation":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_correctly_set_a_field_when_a_mutator_is_defined":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_multiple_translations_when_a_mutator_is_defined":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_multiple_translations_on_field_when_a_mutator_is_defined":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_uses_the_attribute_to_mutate_the_translated_value":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_translate_a_field_based_on_the_translations_of_another_one":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_handle_null_value_from_database":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_get_all_translations":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_fallback_locale_translation_when_getting_an_empty_translation_from_the_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_correct_translation_value_if_value_is_set_to_zero":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_not_return_fallback_value_if_value_is_set_to_zero":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_not_remove_zero_value_of_other_locale_in_database":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_be_translated_based_on_given_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_and_fetch_attributes_based_on_set_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_replace_translations":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_use_any_locale_if_given_locale_not_set":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_set_translation_when_fallback_any_set":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_fallback_translation_when_fallback_any_set":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_provides_a_flog_to_not_return_any_translation_when_getting_an_unknown_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_default_fallback_locale_translation_when_getting_an_unknown_locale_with_fallback_any":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_will_return_all_locales_when_getting_all_translations":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_queries_the_database_whether_a_locale_exists":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_queries_the_database_for_multiple_locales":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_queries_the_database_whether_a_value_exists_in_a_locale":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_queries_the_database_whether_a_value_exists_in_a_multiple_locales":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_disable_attribute_locale_fallback_on_a_per_model_basis":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_fallback_locale_on_model":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_translations_macro_meets_expectations with data set \"(['english'], 'en', 'english')\"":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_translations_macro_meets_expectations with data set \"(['english', 'english'], ['en', 'nl'], 'english')\"":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_translations_macro_meets_expectations with data set \"(['english', 'dutch'], ['en', 'nl'], ['english', 'dutch'])\"":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_return_null_when_the_underlying_attribute_in_database_is_null":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_return_locales_with_empty_string_translations_when_allowEmptyStringForTranslation_is_true":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_not_return_locales_with_empty_string_translations_when_allowEmptyStringForTranslation_is_false":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_return_locales_with_null_translations_when_allowNullForTranslation_is_true":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_not_return_locales_with_null_translations_when_allowNullForTranslation_is_false":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_an_array_list_as_value_for_translation_using__setTranslation_":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_an_array_list_as_value_for_translation_using_default_local":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_treat_an_empty_array_as_value_for_clearing_translations":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_can_set_and_retrieve_translations_for_nested_fields":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_uses_mutators_for_setting_and_getting_translated_values_of_nested_fields":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_return_null_when_translation_is_null_and_allowNullForTranslation_is_true":0,"P\\Tests\\TranslatableTest::__pest_evaluable_it_should_return_empty_string_when_translation_is_null_and_allowNullForTranslation_is_false":0}} -------------------------------------------------------------------------------- /src/HasTranslations.php: -------------------------------------------------------------------------------- 1 | mergeCasts( 20 | array_fill_keys($this->getTranslatableAttributes(), 'array'), 21 | ); 22 | } 23 | 24 | public static function usingLocale(string $locale): self 25 | { 26 | return (new self)->setLocale($locale); 27 | } 28 | 29 | public function useFallbackLocale(): bool 30 | { 31 | if (property_exists($this, 'useFallbackLocale')) { 32 | return $this->useFallbackLocale; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | public function getAttributeValue($key): mixed 39 | { 40 | if (! $this->isTranslatableAttribute($key)) { 41 | return parent::getAttributeValue($key); 42 | } 43 | 44 | return $this->getTranslation($key, $this->getLocale(), $this->useFallbackLocale()); 45 | } 46 | 47 | protected function mutateAttributeForArray($key, $value): mixed 48 | { 49 | if (! $this->isTranslatableAttribute($key)) { 50 | return parent::mutateAttributeForArray($key, $value); 51 | } 52 | 53 | $translations = $this->getTranslations($key); 54 | 55 | return array_map(fn ($value) => parent::mutateAttributeForArray($key, $value), $translations); 56 | } 57 | 58 | public function setAttribute($key, $value) 59 | { 60 | if (! $this->isTranslatableAttribute($key)) { 61 | return parent::setAttribute($key, $value); 62 | } 63 | 64 | if (is_array($value) && (! array_is_list($value) || count($value) === 0)) { 65 | return $this->setTranslations($key, $value); 66 | } 67 | 68 | return $this->setTranslation($key, $this->getLocale(), $value); 69 | } 70 | 71 | public function translate(string $key, string $locale = '', bool $useFallbackLocale = true): mixed 72 | { 73 | return $this->getTranslation($key, $locale, $useFallbackLocale); 74 | } 75 | 76 | public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true): mixed 77 | { 78 | $normalizedLocale = $this->normalizeLocale($key, $locale, $useFallbackLocale); 79 | 80 | $isKeyMissingFromLocale = ($locale !== $normalizedLocale); 81 | 82 | $translations = $this->getTranslations($key); 83 | 84 | $baseKey = Str::before($key, '->'); // get base key in case it is JSON nested key 85 | 86 | $translatableConfig = app(Translatable::class); 87 | 88 | if (is_null($this->getAttributeFromArray($baseKey))) { 89 | $translation = null; 90 | } else { 91 | $translation = isset($translations[$normalizedLocale]) ? $translations[$normalizedLocale] : null; 92 | $translation ??= ($translatableConfig->allowNullForTranslation) ? null : ''; 93 | } 94 | 95 | if ($isKeyMissingFromLocale && $translatableConfig->missingKeyCallback) { 96 | try { 97 | $callbackReturnValue = ($translatableConfig->missingKeyCallback)($this, $key, $locale, $translation, $normalizedLocale); 98 | if (is_string($callbackReturnValue)) { 99 | $translation = $callbackReturnValue; 100 | } 101 | } catch (Exception) { 102 | // prevent the fallback to crash 103 | } 104 | } 105 | 106 | $key = str_replace('->', '-', $key); 107 | 108 | if ($this->hasGetMutator($key)) { 109 | return $this->mutateAttribute($key, $translation); 110 | } 111 | 112 | if ($this->hasAttributeMutator($key)) { 113 | return $this->mutateAttributeMarkedAttribute($key, $translation); 114 | } 115 | 116 | return $translation; 117 | } 118 | 119 | public function getTranslationWithFallback(string $key, string $locale): mixed 120 | { 121 | return $this->getTranslation($key, $locale, true); 122 | } 123 | 124 | public function getTranslationWithoutFallback(string $key, string $locale): mixed 125 | { 126 | return $this->getTranslation($key, $locale, false); 127 | } 128 | 129 | public function getTranslations(?string $key = null, ?array $allowedLocales = null): array 130 | { 131 | if ($key !== null) { 132 | $this->guardAgainstNonTranslatableAttribute($key); 133 | $translatableConfig = app(Translatable::class); 134 | 135 | if ($this->isNestedKey($key)) { 136 | [$key, $nestedKey] = explode('.', str_replace('->', '.', $key), 2); 137 | } 138 | 139 | return array_filter( 140 | Arr::get($this->fromJson($this->getAttributeFromArray($key)), $nestedKey ?? null, []), 141 | fn ($value, $locale) => $this->filterTranslations($value, $locale, $allowedLocales, $translatableConfig->allowNullForTranslation, $translatableConfig->allowEmptyStringForTranslation), 142 | ARRAY_FILTER_USE_BOTH, 143 | ); 144 | } 145 | 146 | return array_reduce($this->getTranslatableAttributes(), function ($result, $item) use ($allowedLocales) { 147 | $result[$item] = $this->getTranslations($item, $allowedLocales); 148 | 149 | return $result; 150 | }); 151 | } 152 | 153 | public function setTranslation(string $key, string $locale, $value): self 154 | { 155 | $this->guardAgainstNonTranslatableAttribute($key); 156 | 157 | $translations = $this->getTranslations($key); 158 | 159 | $oldValue = $translations[$locale] ?? ''; 160 | 161 | $mutatorKey = str_replace('->', '-', $key); 162 | 163 | if ($this->hasSetMutator($mutatorKey)) { 164 | $method = 'set'.Str::studly($mutatorKey).'Attribute'; 165 | 166 | $this->{$method}($value, $locale); 167 | 168 | $value = $this->attributes[$key]; 169 | } elseif ($this->hasAttributeSetMutator($mutatorKey)) { // handle new attribute mutator 170 | $this->setAttributeMarkedMutatedAttributeValue($mutatorKey, $value); 171 | 172 | $value = $this->attributes[$mutatorKey]; 173 | } 174 | 175 | $translations[$locale] = $value; 176 | 177 | if ($this->isNestedKey($key)) { 178 | unset($this->attributes[$key], $this->attributes[$mutatorKey]); 179 | 180 | $this->fillJsonAttribute($key, $translations); 181 | } else { 182 | $this->attributes[$key] = json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 183 | } 184 | 185 | event(new TranslationHasBeenSetEvent($this, $key, $locale, $oldValue, $value)); 186 | 187 | return $this; 188 | } 189 | 190 | public function setTranslations(string $key, array $translations): self 191 | { 192 | $this->guardAgainstNonTranslatableAttribute($key); 193 | 194 | if (! empty($translations)) { 195 | foreach ($translations as $locale => $translation) { 196 | $this->setTranslation($key, $locale, $translation); 197 | } 198 | } else { 199 | $this->attributes[$key] = $this->asJson([]); 200 | } 201 | 202 | return $this; 203 | } 204 | 205 | public function forgetTranslation(string $key, string $locale): self 206 | { 207 | $translations = $this->getTranslations($key); 208 | 209 | unset( 210 | $translations[$locale], 211 | $this->$key 212 | ); 213 | 214 | $this->setTranslations($key, $translations); 215 | 216 | return $this; 217 | } 218 | 219 | public function forgetTranslations(string $key, bool $asNull = false): self 220 | { 221 | $this->guardAgainstNonTranslatableAttribute($key); 222 | 223 | collect($this->getTranslatedLocales($key))->each(function (string $locale) use ($key) { 224 | $this->forgetTranslation($key, $locale); 225 | }); 226 | 227 | if ($asNull) { 228 | $this->attributes[$key] = null; 229 | } 230 | 231 | return $this; 232 | } 233 | 234 | public function forgetAllTranslations(string $locale): self 235 | { 236 | collect($this->getTranslatableAttributes())->each(function (string $attribute) use ($locale) { 237 | $this->forgetTranslation($attribute, $locale); 238 | }); 239 | 240 | return $this; 241 | } 242 | 243 | public function getTranslatedLocales(string $key): array 244 | { 245 | return array_keys($this->getTranslations($key)); 246 | } 247 | 248 | public function isNestedKey(string $key): bool 249 | { 250 | return str_contains($key, '->'); 251 | } 252 | 253 | public function isTranslatableAttribute(string $key): bool 254 | { 255 | return in_array($key, $this->getTranslatableAttributes()); 256 | } 257 | 258 | public function hasTranslation(string $key, ?string $locale = null): bool 259 | { 260 | $locale = $locale ?: $this->getLocale(); 261 | 262 | return isset($this->getTranslations($key)[$locale]); 263 | } 264 | 265 | public function replaceTranslations(string $key, array $translations): self 266 | { 267 | foreach ($this->getTranslatedLocales($key) as $locale) { 268 | $this->forgetTranslation($key, $locale); 269 | } 270 | 271 | $this->setTranslations($key, $translations); 272 | 273 | return $this; 274 | } 275 | 276 | protected function guardAgainstNonTranslatableAttribute(string $key): void 277 | { 278 | if (! $this->isTranslatableAttribute($key)) { 279 | throw AttributeIsNotTranslatable::make($key, $this); 280 | } 281 | } 282 | 283 | protected function normalizeLocale(string $key, string $locale, bool $useFallbackLocale): string 284 | { 285 | $translatedLocales = $this->getTranslatedLocales($key); 286 | 287 | if (in_array($locale, $translatedLocales)) { 288 | return $locale; 289 | } 290 | 291 | if (! $useFallbackLocale) { 292 | return $locale; 293 | } 294 | 295 | if (method_exists($this, 'getFallbackLocale')) { 296 | $fallbackLocale = $this->getFallbackLocale(); 297 | } 298 | 299 | $fallbackConfig = app(Translatable::class); 300 | 301 | $fallbackLocale ??= $fallbackConfig->fallbackLocale ?? config('app.fallback_locale'); 302 | 303 | if (! is_null($fallbackLocale) && in_array($fallbackLocale, $translatedLocales)) { 304 | return $fallbackLocale; 305 | } 306 | 307 | if (! empty($translatedLocales) && $fallbackConfig->fallbackAny) { 308 | return $translatedLocales[0]; 309 | } 310 | 311 | return $locale; 312 | } 313 | 314 | protected function filterTranslations(mixed $value = null, ?string $locale = null, ?array $allowedLocales = null, bool $allowNull = false, bool $allowEmptyString = false): bool 315 | { 316 | if ($value === null && ! $allowNull) { 317 | return false; 318 | } 319 | 320 | if ($value === '' && ! $allowEmptyString) { 321 | return false; 322 | } 323 | 324 | if ($allowedLocales === null) { 325 | return true; 326 | } 327 | 328 | if (! in_array($locale, $allowedLocales)) { 329 | return false; 330 | } 331 | 332 | return true; 333 | } 334 | 335 | public function setLocale(string $locale): self 336 | { 337 | $this->translationLocale = $locale; 338 | 339 | return $this; 340 | } 341 | 342 | public function getLocale(): string 343 | { 344 | return $this->translationLocale ?: config('app.locale'); 345 | } 346 | 347 | public function getTranslatableAttributes(): array 348 | { 349 | return is_array($this->translatable) 350 | ? $this->translatable 351 | : []; 352 | } 353 | 354 | public function translations(): Attribute 355 | { 356 | return Attribute::get(function () { 357 | return collect($this->getTranslatableAttributes()) 358 | ->mapWithKeys(function (string $key) { 359 | return [$key => $this->getTranslations($key)]; 360 | }) 361 | ->toArray(); 362 | }); 363 | } 364 | 365 | public function locales(): array 366 | { 367 | return array_unique( 368 | array_reduce($this->getTranslatableAttributes(), function ($result, $item) { 369 | return array_merge($result, $this->getTranslatedLocales($item)); 370 | }, []) 371 | ); 372 | } 373 | 374 | public function scopeWhereLocale(Builder $query, string $column, string $locale): void 375 | { 376 | $query->whereNotNull("{$column}->{$locale}"); 377 | } 378 | 379 | public function scopeWhereLocales(Builder $query, string $column, array $locales): void 380 | { 381 | $query->where(function (Builder $query) use ($column, $locales) { 382 | foreach ($locales as $locale) { 383 | $query->orWhereNotNull("{$column}->{$locale}"); 384 | } 385 | }); 386 | } 387 | 388 | public function scopeWhereJsonContainsLocale(Builder $query, string $column, string $locale, mixed $value, string $operand = '='): void 389 | { 390 | $query->where("{$column}->{$locale}", $operand, $value); 391 | } 392 | 393 | public function scopeWhereJsonContainsLocales(Builder $query, string $column, array $locales, mixed $value, string $operand = '='): void 394 | { 395 | $query->where(function (Builder $query) use ($column, $locales, $value, $operand) { 396 | foreach ($locales as $locale) { 397 | $query->orWhere("{$column}->{$locale}", $operand, $value); 398 | } 399 | }); 400 | } 401 | 402 | /** 403 | * @deprecated 404 | */ 405 | public static function whereLocale(string $column, string $locale): Builder 406 | { 407 | return static::query()->whereNotNull("{$column}->{$locale}"); 408 | } 409 | 410 | /** 411 | * @deprecated 412 | */ 413 | public static function whereLocales(string $column, array $locales): Builder 414 | { 415 | return static::query()->where(function (Builder $query) use ($column, $locales) { 416 | foreach ($locales as $locale) { 417 | $query->orWhereNotNull("{$column}->{$locale}"); 418 | } 419 | }); 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-translatable` will be documented in this file 4 | 5 | ## 6.12.0 - 2025-11-24 6 | 7 | ### What's Changed 8 | 9 | * Bump aglipanci/laravel-pint-action from 2.4 to 2.5 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/489 10 | * Bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/496 11 | * Update README.md by @Afzaal565 in https://github.com/spatie/laravel-translatable/pull/497 12 | * Bump stefanzweifel/git-auto-commit-action from 5 to 6 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/500 13 | * Bump aglipanci/laravel-pint-action from 2.5 to 2.6 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/504 14 | * Update issue template by @AlexVanderbist in https://github.com/spatie/laravel-translatable/pull/507 15 | * Bump stefanzweifel/git-auto-commit-action from 6 to 7 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/508 16 | * Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/506 17 | * Fix method call to use instance instead of static by @IndyIndyIndy in https://github.com/spatie/laravel-translatable/pull/510 18 | * Bump actions/checkout from 5 to 6 by @dependabot[bot] in https://github.com/spatie/laravel-translatable/pull/511 19 | 20 | ### New Contributors 21 | 22 | * @Afzaal565 made their first contribution in https://github.com/spatie/laravel-translatable/pull/497 23 | * @AlexVanderbist made their first contribution in https://github.com/spatie/laravel-translatable/pull/507 24 | * @IndyIndyIndy made their first contribution in https://github.com/spatie/laravel-translatable/pull/510 25 | 26 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.11.4...6.12.0 27 | 28 | ## 6.11.4 - 2025-02-20 29 | 30 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.11.3...6.11.4 31 | 32 | ## 6.11.3 - 2025-02-14 33 | 34 | ### What's Changed 35 | 36 | * Allow null value in translations if allowNullForTranslation is true by @dont-know-php in https://github.com/spatie/laravel-translatable/pull/488 37 | 38 | ### New Contributors 39 | 40 | * @dont-know-php made their first contribution in https://github.com/spatie/laravel-translatable/pull/488 41 | 42 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.10.2...6.11.3 43 | 44 | ## 6.10.2 - 2025-02-03 45 | 46 | ### What's Changed 47 | 48 | * Fix casts on initialization of HasTranslation by @thaqebon in https://github.com/spatie/laravel-translatable/pull/486 49 | 50 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.10.1...6.10.2 51 | 52 | ## 6.10.1 - 2025-01-31 53 | 54 | ### What's Changed 55 | 56 | * Handle null database values as null in translations by @alipadron in https://github.com/spatie/laravel-translatable/pull/479 57 | 58 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.10.0...6.10.1 59 | 60 | ## 6.10.0 - 2025-01-31 61 | 62 | ### What's Changed 63 | 64 | * Support clearing translations using an empty array by @alipadron in https://github.com/spatie/laravel-translatable/pull/478 65 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-translatable/pull/484 66 | * Add support for nested key translations by @thaqebon in https://github.com/spatie/laravel-translatable/pull/483 67 | 68 | ### New Contributors 69 | 70 | * @thaqebon made their first contribution in https://github.com/spatie/laravel-translatable/pull/483 71 | 72 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.9.3...6.10.0 73 | 74 | ## 6.9.3 - 2024-12-16 75 | 76 | ### What's Changed 77 | 78 | * Revert return value change when column value is `null` by @vencelkatai in https://github.com/spatie/laravel-translatable/pull/474 79 | 80 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.9.2...6.9.3 81 | 82 | ## 6.9.2 - 2024-12-11 83 | 84 | ### What's Changed 85 | 86 | * Improve `setAttribute` to handle array list as value for translation by @alipadron in https://github.com/spatie/laravel-translatable/pull/469 87 | 88 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.9.1...6.9.2 89 | 90 | ## 6.9.1 - 2024-12-11 91 | 92 | ### What's Changed 93 | 94 | * Fix attribute mutators by @vencelkatai in https://github.com/spatie/laravel-translatable/pull/470 95 | 96 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.9.0...6.9.1 97 | 98 | ## 6.9.0 - 2024-12-09 99 | 100 | ### What's Changed 101 | 102 | * PHP 8.4 deprecates implicitly nullable parameter types. by @selfsimilar in https://github.com/spatie/laravel-translatable/pull/458 103 | * Add .idea to .gitignore, PHP CS Fixer to dev dependencies, and rename PHP CS Fixer config by @alipadron in https://github.com/spatie/laravel-translatable/pull/466 104 | * Allow configuration for handling null and empty strings in translations (Fixes #456) by @alipadron in https://github.com/spatie/laravel-translatable/pull/465 105 | 106 | ### New Contributors 107 | 108 | * @selfsimilar made their first contribution in https://github.com/spatie/laravel-translatable/pull/458 109 | * @alipadron made their first contribution in https://github.com/spatie/laravel-translatable/pull/466 110 | 111 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.8.0...6.9.0 112 | 113 | ## 6.8.0 - 2024-07-24 114 | 115 | ### What's Changed 116 | 117 | * Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-translatable/pull/453 118 | * Added operand for json scopes by @rcerljenko in https://github.com/spatie/laravel-translatable/pull/454 119 | 120 | ### New Contributors 121 | 122 | * @rcerljenko made their first contribution in https://github.com/spatie/laravel-translatable/pull/454 123 | 124 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.7.1...6.8.0 125 | 126 | ## 6.7.1 - 2024-05-14 127 | 128 | ### What's Changed 129 | 130 | * fix: PHPDoc block in Translatable facade by @kyryl-bogach in https://github.com/spatie/laravel-translatable/pull/448 131 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.1.0 by @dependabot in https://github.com/spatie/laravel-translatable/pull/446 132 | 133 | ### New Contributors 134 | 135 | * @kyryl-bogach made their first contribution in https://github.com/spatie/laravel-translatable/pull/448 136 | 137 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.7.0...6.7.1 138 | 139 | ## 6.7.0 - 2024-05-13 140 | 141 | ### What's Changed 142 | 143 | * Add method comment to Facade for IDE autocompletion by @Muetze42 in https://github.com/spatie/laravel-translatable/pull/438 144 | * Docs: add type declarations `array $translatable` by @fahrim in https://github.com/spatie/laravel-translatable/pull/441 145 | * [FEAT] add ability for filtering a column's locale or multiple locale… by @AbdelrahmanBl in https://github.com/spatie/laravel-translatable/pull/447 146 | 147 | ### New Contributors 148 | 149 | * @fahrim made their first contribution in https://github.com/spatie/laravel-translatable/pull/441 150 | * @AbdelrahmanBl made their first contribution in https://github.com/spatie/laravel-translatable/pull/447 151 | 152 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.6.2...6.7.0 153 | 154 | ## 6.6.2 - 2024-03-01 155 | 156 | ### What's Changed 157 | 158 | * Fix toArray when using accessors on translatable attributes by @vencelkatai in https://github.com/spatie/laravel-translatable/pull/437 159 | 160 | ### New Contributors 161 | 162 | * @vencelkatai made their first contribution in https://github.com/spatie/laravel-translatable/pull/437 163 | 164 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.6.1...6.6.2 165 | 166 | ## 6.6.1 - 2024-02-26 167 | 168 | ### What's Changed 169 | 170 | * fix: allow raw searchable umlauts by @Muetze42 in https://github.com/spatie/laravel-translatable/pull/436 171 | 172 | ### New Contributors 173 | 174 | * @Muetze42 made their first contribution in https://github.com/spatie/laravel-translatable/pull/436 175 | 176 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.6.0...6.6.1 177 | 178 | ## 6.6.0 - 2024-02-23 179 | 180 | ### What's Changed 181 | 182 | * Add laravel 11 support by @mokhosh in https://github.com/spatie/laravel-translatable/pull/434 183 | 184 | ### New Contributors 185 | 186 | * @mokhosh made their first contribution in https://github.com/spatie/laravel-translatable/pull/434 187 | 188 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.5.5...6.6.0 189 | 190 | ## 6.5.5 - 2023-12-06 191 | 192 | ### What's Changed 193 | 194 | * Revert "Keep null value" by @mabdullahsari in https://github.com/spatie/laravel-translatable/pull/428 195 | 196 | ### New Contributors 197 | 198 | * @mabdullahsari made their first contribution in https://github.com/spatie/laravel-translatable/pull/428 199 | 200 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.5.4...6.5.5 201 | 202 | ## 6.5.4 - 2023-12-01 203 | 204 | ### What's Changed 205 | 206 | * Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/spatie/laravel-translatable/pull/413 207 | * Keep the number of translations even with null values by @sdebacker in https://github.com/spatie/laravel-translatable/pull/427 208 | * Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/spatie/laravel-translatable/pull/418 209 | 210 | ### New Contributors 211 | 212 | * @sdebacker made their first contribution in https://github.com/spatie/laravel-translatable/pull/427 213 | 214 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.5.3...6.5.4 215 | 216 | ## 6.5.3 - 2023-07-19 217 | 218 | ### What's Changed 219 | 220 | - Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/laravel-translatable/pull/398 221 | - handle new attribute mutator :boom: by @messi89 in https://github.com/spatie/laravel-translatable/pull/402 222 | 223 | ### New Contributors 224 | 225 | - @messi89 made their first contribution in https://github.com/spatie/laravel-translatable/pull/402 226 | 227 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.5.2...6.5.3 228 | 229 | ## 6.5.2 - 2023-06-20 230 | 231 | ### What's Changed 232 | 233 | - Bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 by @dependabot in https://github.com/spatie/laravel-translatable/pull/394 234 | - Convert static methods to scopes by @gdebrauwer in https://github.com/spatie/laravel-translatable/pull/396 235 | 236 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.5.1...6.5.2 237 | 238 | ## 6.5.1 - 2023-05-06 239 | 240 | ### What's Changed 241 | 242 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/laravel-translatable/pull/389 243 | - Add getFallbackLocale method by @gdebrauwer in https://github.com/spatie/laravel-translatable/pull/391 244 | 245 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.5.0...6.5.1 246 | 247 | ## 6.5.0 - 2023-04-20 248 | 249 | ### What's Changed 250 | 251 | - update customize-the-toarray-method.md by @moham96 in https://github.com/spatie/laravel-translatable/pull/387 252 | - Add macro for `$this->translations()` in factories by @bram-pkg in https://github.com/spatie/laravel-translatable/pull/382 253 | 254 | ### New Contributors 255 | 256 | - @moham96 made their first contribution in https://github.com/spatie/laravel-translatable/pull/387 257 | - @bram-pkg made their first contribution in https://github.com/spatie/laravel-translatable/pull/382 258 | 259 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.4.0...6.5.0 260 | 261 | ## 6.4.0 - 2023-03-19 262 | 263 | ### What's Changed 264 | 265 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-translatable/pull/376 266 | - Fix badge with `tests` status in `README.md` by @gomzyakov in https://github.com/spatie/laravel-translatable/pull/377 267 | - Update README.md by @alirezasalehizadeh in https://github.com/spatie/laravel-translatable/pull/381 268 | - Enable fallback locale on a per model basis by @yoeriboven in https://github.com/spatie/laravel-translatable/pull/380 269 | 270 | ### New Contributors 271 | 272 | - @gomzyakov made their first contribution in https://github.com/spatie/laravel-translatable/pull/377 273 | - @alirezasalehizadeh made their first contribution in https://github.com/spatie/laravel-translatable/pull/381 274 | - @yoeriboven made their first contribution in https://github.com/spatie/laravel-translatable/pull/380 275 | 276 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.3.0...6.4.0 277 | 278 | ## 6.3.0 - 2023-01-14 279 | 280 | ### What's Changed 281 | 282 | - Laravel 10.x support by @erikn69 in https://github.com/spatie/laravel-translatable/pull/374 283 | 284 | ### New Contributors 285 | 286 | - @erikn69 made their first contribution in https://github.com/spatie/laravel-translatable/pull/374 287 | 288 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.2.0...6.3.0 289 | 290 | ## 6.2.0 - 2022-12-23 291 | 292 | ### What's Changed 293 | 294 | - Add Dependabot Automation by @patinthehat in https://github.com/spatie/laravel-translatable/pull/366 295 | - Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/laravel-translatable/pull/367 296 | - Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/laravel-translatable/pull/368 297 | - Added whereLocale and whereLocales methods by @ahmetbarut in https://github.com/spatie/laravel-translatable/pull/370 298 | 299 | ### New Contributors 300 | 301 | - @dependabot made their first contribution in https://github.com/spatie/laravel-translatable/pull/368 302 | 303 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.1.0...6.2.0 304 | 305 | ## 6.1.0 - 2022-10-21 306 | 307 | ### What's Changed 308 | 309 | - PHPUnit to Pest Converter by @freekmurze in https://github.com/spatie/laravel-translatable/pull/335 310 | - Fix typo in "Getting and setting translations" by @sami-cha in https://github.com/spatie/laravel-translatable/pull/346 311 | - Fix typo in advanced usage docs directory name by @greatislander in https://github.com/spatie/laravel-translatable/pull/347 312 | - Fixed example for forgetAllTranslations() method. by @odeland in https://github.com/spatie/laravel-translatable/pull/348 313 | - added locales method by @ahmetbarut in https://github.com/spatie/laravel-translatable/pull/361 314 | 315 | ### New Contributors 316 | 317 | - @sami-cha made their first contribution in https://github.com/spatie/laravel-translatable/pull/346 318 | - @greatislander made their first contribution in https://github.com/spatie/laravel-translatable/pull/347 319 | - @odeland made their first contribution in https://github.com/spatie/laravel-translatable/pull/348 320 | - @ahmetbarut made their first contribution in https://github.com/spatie/laravel-translatable/pull/361 321 | 322 | **Full Changelog**: https://github.com/spatie/laravel-translatable/compare/6.0.0...6.1.0 323 | 324 | ## 6.0.0 - 2022-03-07 325 | 326 | - improved fallback customisations 327 | - modernized code base 328 | - drop support for Laravel 8 329 | 330 | ## 5.2.0 - 2022-01-13 331 | 332 | - support Laravel 9 333 | 334 | ## 5.0.3 - 2021-10-04 335 | 336 | - solve the string value issue in filterTranslations method (#300) 337 | 338 | ## 5.0.2 - 2021-09-28 339 | 340 | - specify locales in get translations method (#299) 341 | 342 | ## 5.0.1 - 2021-07-15 343 | 344 | - fix return types of getTranslation (#286) 345 | 346 | ## 5.0.0 - 2021-03-26 347 | 348 | - require PHP 8+ 349 | - convert syntax to PHP 8 350 | - drop support for PHP 7.x 351 | - drop support for Laravel 6.x 352 | - implement `spatie/laravel-package-tools` 353 | 354 | ## 4.6.0 - 2020-11-19 355 | 356 | - add support for PHP 8.0 (#241) 357 | - drop support for Laravel 5.8 (#241) 358 | 359 | ## 4.5.2 - 2020-10-22 360 | 361 | - revert #235 362 | 363 | ## 4.5.1 - 2020-10-22 364 | 365 | - use string casting for translatable columns (#235) 366 | 367 | ## 4.5.0 2020-10-03 368 | 369 | - add replaceTranslations method (#231) 370 | 371 | ## 4.4.3 - 2020-10-2 372 | 373 | - rename `withLocale` to `usingLocale` 374 | 375 | ## 4.4.2 - 2020-10-02 376 | 377 | - elegant syntax update (#229) 378 | 379 | ## 4.4.1 - 2020-09-06 380 | 381 | - add support for Laravel 8 (#226) 382 | 383 | ## 4.4.0 - 2020-07-09 384 | 385 | - make possible to set multiple translations on mutator model field with array (#216) 386 | 387 | ## 4.3.2 - 2020-04-30 388 | 389 | - fix `forgetTranslation` & `forgetAllTranslations` on fields with mutator (#205) 390 | 391 | ## 4.3.1 - 2020-03-07 392 | 393 | - Lumen fix (#201) 394 | 395 | ## 4.3.0 - 2020-03-02 396 | 397 | - add support for Laravel 7 398 | 399 | ## 4.2.2 - 2020-01-20 400 | 401 | - open up for non-model objects (#186) 402 | 403 | ## 4.2.1 - 2019-10-03 404 | 405 | - add third param to translate method (#177) 406 | 407 | ## 4.2.0 - 2019-09-04 408 | 409 | - make compatible with Laravel 6 410 | 411 | ## 4.1.4 - 2019-08-28 412 | 413 | - re-added the `translatable.fallback_local` config which overrule `app.fallback_local` (see https://github.com/spatie/laravel-translatable/issues/170) 414 | 415 | ## 4.1.3 - 2019-06-16 416 | 417 | - improve dependencies 418 | 419 | ## 4.1.2 - 2019-06-06 420 | 421 | - allow false and true values in translations 422 | 423 | ## 4.1.1 - 2019-02-27 424 | 425 | - fix service provider error 426 | 427 | ## 4.1.0 - 2019-02-27 428 | 429 | - drop support for Laravel 5.7 and below 430 | - drop support for PHP 7.1 and below 431 | 432 | ## 4.0.0 - 2019-02-27 433 | 434 | - `app.fallback_local` will now be used (see #148) 435 | 436 | ## 3.1.3 - 2019-02-27 437 | 438 | - add support for Laravel 5.8 439 | 440 | ## 3.1.2 - 2019-01-05 441 | 442 | - add `hasTranslation` 443 | 444 | ## 3.1.1 - 2018-12-18 445 | 446 | - allow 0 to be used as a translation value 447 | 448 | ## 3.1.0 - 2018-11-29 449 | 450 | - allow `getTranslations` to return other things than strings 451 | 452 | ## 3.0.1 - 2018-09-18 453 | 454 | - fix regarding empty locales 455 | 456 | ## 3.0.0 - 2018-09-16 457 | 458 | - added `translations` accessor 459 | - dropped support for PHP 7.0 460 | 461 | ## 2.2.1 - 2018-08-24 462 | 463 | - add support for Laravel 5.7 464 | 465 | ## 2.2.0 - 2018-03-09 466 | 467 | - made it possible to get all translations in one go 468 | 469 | ## 2.1.5 - 2018-02-28 470 | 471 | - better handling of `null` values 472 | 473 | ## 2.1.4 - 2018-02-08 474 | 475 | - add support for L5.6 476 | 477 | ## 2.1.3 - 2018-01-24 478 | 479 | - make locale handling more flexible 480 | 481 | ## 2.1.2 - 2017-12-24 482 | 483 | - fix for using translations within translations 484 | 485 | ## 2.1.1 - 2017-12-20 486 | 487 | - fix event `key` attribute 488 | - fix support for mutators 489 | 490 | ## 2.1.0 - 2017-09-21 491 | 492 | - added support for setting a translation directly through the property 493 | 494 | ## 2.0.0 - 2017-08-30 495 | 496 | - added support for Laravel 5.5, dropped support for all older versions 497 | - rename config file from `laravel-translatable` to `translatable` 498 | 499 | ## 1.3.0 - 2017-06-12 500 | 501 | - add `forgetAllTranslations` 502 | 503 | ## 1.2.2 - 2016-01-27 504 | 505 | - improve support for fallback locale 506 | 507 | ## 1.2.1 - 2016-01-23 508 | 509 | - improve compatibility for Laravel 5.4 510 | 511 | ## 1.2.0 - 2016-01-23 512 | 513 | - add compatibility for Laravel 5.4 514 | 515 | ## 1.1.2 - 2016-10-02 516 | 517 | - made `isTranslatableAttribute` public 518 | 519 | ## 1.1.1 - 2016-08-24 520 | 521 | - add L5.3 compatibility 522 | 523 | ## 1.1.0 - 2016-05-02 524 | 525 | - added support for a fallback locale 526 | 527 | ## 1.0.0 - 2016-04-10 528 | 529 | - initial release 530 | --------------------------------------------------------------------------------