├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── config
└── translatable.php
├── phpunit.xml
├── src
├── Exceptions
│ └── MissingTranslationsException.php
├── HasTranslations.php
├── Scopes
│ └── JoinTranslationScope.php
├── Services
│ └── TranslationSavingService.php
└── TranslatableServiceProvider.php
└── tests
├── Feature
└── TranslationTest.php
└── TestCase.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /.idea/
3 | composer.lock
4 |
5 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | build:
2 | nodes:
3 | analysis:
4 | project_setup:
5 | override:
6 | - 'true'
7 | tests:
8 | override:
9 | - php-scrutinizer-run
10 | - command: ./vendor/bin/phpunit --coverage-clover=coverage.clover
11 | coverage:
12 | file: coverage.clover
13 | format: php-clover
14 | checks:
15 | php: true
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.2
5 | - 7.3
6 |
7 | env:
8 | matrix:
9 | - COMPOSER_FLAGS="--prefer-lowest"
10 | - COMPOSER_FLAGS=""
11 |
12 | before_script:
13 | - travis_retry composer self-update
14 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source
15 |
16 | script:
17 | - phpunit --coverage-text --coverage-clover=coverage.clover
18 |
19 | after_script:
20 | - php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Koen Hoeijmakers
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Translatable
2 | [](https://packagist.org/packages/koenhoeijmakers/laravel-translatable)
3 | [](https://scrutinizer-ci.com/g/koenhoeijmakers/laravel-translatable/build-status/master)
4 | [](https://scrutinizer-ci.com/g/koenhoeijmakers/laravel-translatable/?branch=master)
5 | [](https://scrutinizer-ci.com/g/koenhoeijmakers/laravel-translatable/?branch=master)
6 | [](https://github.com/koenhoeijmakers/laravel-translatable)
7 | [](https://packagist.org/packages/koenhoeijmakers/laravel-translatable)
8 |
9 | A fresh new way to handle Model translations, the translations are joined into the Model
10 | instead of making you query a relation or get every single attribute's translation one by one.
11 |
12 | ## Installation
13 | Require the package.
14 | ```sh
15 | composer require koenhoeijmakers/laravel-translatable
16 | ```
17 |
18 | ... and optionally publish the config.
19 | ```sh
20 | php artisan vendor:publish --provider="KoenHoeijmakers\LaravelTranslatable\TranslatableServiceProvider"
21 | ```
22 |
23 | ## Usage
24 | ### Setting up a translatable Model.
25 | Start off by creating a migration and a Model,
26 | we'll go with the `Animal` Model and the corresponding `AnimalTranslation` Model.
27 |
28 | #### Migrations
29 | ```php
30 | Schema::create('animals', function (Blueprint $table) {
31 | $table->increments('id');
32 | $table->timestamps();
33 | });
34 | ```
35 |
36 | Always have a `locale` and a `foreign_key` to the original Model, in our case `animal_id`.
37 |
38 | ```php
39 | Schema::create('animal_translations', function (Blueprint $table) {
40 | $table->increments('id');
41 | $table->unsignedInteger('animal_id');
42 | $table->string('locale');
43 | $table->string('name');
44 | $table->timestamps();
45 |
46 | $table->unique(['locale', 'animal_id']);
47 | $table->foreign('animal_id')->references('id')->on('animals');
48 | });
49 | ```
50 |
51 | #### Models
52 | Register the trait on the Model, and add the columns that should be translated to the `$translatable` property,
53 | **But also make them fillable**, this is because the saving is handled through events,
54 | this way we don't have to change the `save` method and makes the package more interoperable.
55 |
56 | > So make sure the `$translatable` columns are also `$fillable` on both Models.
57 |
58 | ```php
59 | use Illuminate\Database\Eloquent\Model;
60 | use KoenHoeijmakers\LaravelTranslatable\HasTranslations;
61 |
62 | class Animal extends Model
63 | {
64 | use HasTranslations;
65 |
66 | protected $translatable = ['name'];
67 |
68 | protected $fillable = ['name'];
69 | }
70 | ```
71 |
72 | ```php
73 | use Illuminate\Database\Eloquent\Model;
74 |
75 | class AnimalTranslation extends Model
76 | {
77 | protected $fillable = ['name'];
78 | }
79 | ```
80 |
81 | This is pretty much all there is to it, but you can read more about the package down here.
82 |
83 | ## About
84 | What makes this package so special is the way it handles the translations,
85 | how it retrieves them, how it stores them, and how it queries them.
86 |
87 | ### Querying
88 | Due to how the package handles the translations, querying is a piece of cake,
89 | while for other packages you would have a `->whereTranslation('nl', 'column', '=', 'foo')` method.
90 |
91 | But in this package you can just do `->where('column', '=', 'foo')` and it'll know what to query, just query how you used to!
92 |
93 | ### Retrieving
94 | When you retrieve a Model from the database, the package will join the translation table with the translation of the current locale `config/app.php`.
95 |
96 | This makes it so that any translated column acts like it is "native" to the Model,
97 | due to this we don't have to override a lot of methods on the Model which is a big plus.
98 |
99 | Need the Model in a different language? Call `$model->translate('nl')` and you are done. Now you would like to save the `nl` translation? just call `->update()`. The Model knows in which locale it is loaded and it will handle it accordingly.
100 |
101 | ```php
102 | $animal = Animal::query()->find(1);
103 |
104 | $animal->translate('nl')->update(['name' => 'Aap']);
105 | ```
106 |
107 | ### Storing
108 | You will be storing your translations as if they're attributes on the Model, so this will work like a charm:
109 | ```php
110 | Animal::query()->create(['name' => 'Monkey']);
111 | ```
112 |
113 | But i hear you, you would like to store multiple translations in one request! In that so you can use the `->storeTranslation()` or the `->storeTranslations()` method.
114 |
115 | ```php
116 | $animal = Animal::query()->create(['name' => 'Monkey']);
117 |
118 | $animal->storeTranslation('nl', [
119 | 'name' => 'Aap',
120 | ]);
121 |
122 | $animal->storeTranslations([
123 | 'nl' => [
124 | 'name' => 'Aap',
125 | ],
126 | 'de' => [
127 | 'name' => 'Affe',
128 | ],
129 | ]);
130 | ```
131 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "koenhoeijmakers/laravel-translatable",
3 | "description": "Laravel Translations",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Koen Hoeijmakers",
8 | "email": "koen@hoeijmakers.me"
9 | }
10 | ],
11 | "minimum-stability": "stable",
12 | "require": {
13 | "php": ">=7.2.5",
14 | "laravel/framework": "^7.4"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "^8.5",
18 | "orchestra/testbench": "^5.0",
19 | "mockery/mockery": "^1.3.1"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "KoenHoeijmakers\\LaravelTranslatable\\": "src/"
24 | }
25 | },
26 | "autoload-dev": {
27 | "psr-4": {
28 | "KoenHoeijmakers\\LaravelTranslatable\\Tests\\": "tests/"
29 | }
30 | },
31 | "extra": {
32 | "laravel": {
33 | "providers": [
34 | "KoenHoeijmakers\\LaravelTranslatable\\TranslatableServiceProvider"
35 | ]
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/config/translatable.php:
--------------------------------------------------------------------------------
1 | true,
13 |
14 | /*
15 | |
16 | | The locale key name, used in the translation tables.
17 | |
18 | */
19 | 'locale_key_name' => 'locale',
20 | ];
21 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests/
15 |
16 |
17 |
18 |
19 | ./src
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Exceptions/MissingTranslationsException.php:
--------------------------------------------------------------------------------
1 | rememberTranslationForModel($model);
36 | });
37 |
38 | self::saved(function ($model) {
39 | app(TranslationSavingService::class)->storeTranslationOnModel($model);
40 |
41 | $model->refreshTranslation();
42 | });
43 | }
44 |
45 | self::deleting(function ($model) {
46 | $model->purgeTranslations();
47 | });
48 |
49 | self::addGlobalScope(new JoinTranslationScope());
50 | }
51 |
52 | /**
53 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
54 | */
55 | public function translations(): HasMany
56 | {
57 | return $this->hasMany($this->getTranslationModel(), $this->getTranslationForeignKey());
58 | }
59 |
60 | /**
61 | * Check if the translation by the given locale exists.
62 | *
63 | * @param string $locale
64 | * @return bool
65 | */
66 | public function translationExists(string $locale): bool
67 | {
68 | return $this->translations()->where($this->getLocaleKeyName(), $locale)->exists();
69 | }
70 |
71 | /**
72 | * Purge the translations.
73 | *
74 | * @return mixed
75 | */
76 | public function purgeTranslations()
77 | {
78 | return $this->translations()->delete();
79 | }
80 |
81 | /**
82 | * Get the translation model.
83 | *
84 | * @return string
85 | */
86 | public function getTranslationModel(): string
87 | {
88 | return property_exists($this, 'translationModel')
89 | ? $this->translationModel
90 | : get_class($this) . $this->getTranslationModelSuffix();
91 | }
92 |
93 | /**
94 | * Get the translation model suffix.
95 | *
96 | * @return string
97 | */
98 | protected function getTranslationModelSuffix(): string
99 | {
100 | return 'Translation';
101 | }
102 |
103 | /**
104 | * Get the translation table.
105 | *
106 | * @return string
107 | */
108 | public function getTranslationTable(): string
109 | {
110 | $model = $this->getTranslationModel();
111 |
112 | return (new $model())->getTable();
113 | }
114 |
115 | /**
116 | * Get the translation foreign key.
117 | *
118 | * @return string
119 | */
120 | public function getTranslationForeignKey()
121 | {
122 | return property_exists($this, 'translationForeignKey') ? $this->translationForeignKey : $this->getForeignKey();
123 | }
124 |
125 | /**
126 | * Get the translatable.
127 | *
128 | * @return array
129 | * @throws \KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException
130 | */
131 | public function getTranslatable(): array
132 | {
133 | if (! isset($this->translatable)) {
134 | throw new MissingTranslationsException('Model "' . get_class($this) . '" is missing translations');
135 | }
136 |
137 | return $this->translatable;
138 | }
139 |
140 | /**
141 | * Get the translatable attributes.
142 | *
143 | * @return array
144 | */
145 | public function getTranslatableAttributes(): array
146 | {
147 | return Arr::only($this->getAttributes(), $this->translatable);
148 | }
149 |
150 | /**
151 | * @param string $locale
152 | * @param array $attributes
153 | * @return \Illuminate\Database\Eloquent\Model
154 | */
155 | public function storeTranslation(string $locale, array $attributes = [])
156 | {
157 | if (! is_null($model = $this->translations()->where($this->getLocaleKeyName(), $locale)->first())) {
158 | $model->update($attributes);
159 |
160 | return $model;
161 | }
162 |
163 | $model = $this->translations()->make($attributes);
164 | $model->setAttribute($this->getLocaleKeyName(), $locale);
165 | $model->save();
166 |
167 | return $model;
168 | }
169 |
170 | /**
171 | * Store many translations at once.
172 | *
173 | * @param array $translations
174 | * @return $this
175 | */
176 | public function storeTranslations(array $translations)
177 | {
178 | foreach ($translations as $locale => $translation) {
179 | $this->storeTranslation($locale, $translation);
180 | }
181 |
182 | return $this;
183 | }
184 |
185 | /**
186 | * @param string $locale
187 | * @return \Illuminate\Database\Eloquent\Model|self
188 | */
189 | public function getTranslation(string $locale)
190 | {
191 | return $this->translations()->where($this->getLocaleKeyName(), $locale)->first();
192 | }
193 |
194 | /**
195 | * @param string $locale
196 | * @param string $name
197 | * @return mixed
198 | */
199 | public function getTranslationValue(string $locale, string $name)
200 | {
201 | return $this->translations()->where($this->getLocaleKeyName(), $locale)->value($name);
202 | }
203 |
204 | /**
205 | * The locale key name.
206 | *
207 | * @return string
208 | */
209 | public function getLocaleKeyName(): string
210 | {
211 | return property_exists($this, 'localeKeyName')
212 | ? $this->localeKeyName
213 | : config()->get('translatable.locale_key_name', 'locale');
214 | }
215 |
216 | /**
217 | * Get the locale.
218 | *
219 | * @return string
220 | */
221 | public function getLocale(): string
222 | {
223 | return null !== $this->currentLocale
224 | ? $this->currentLocale
225 | : app()->getLocale();
226 | }
227 |
228 | /**
229 | * Refresh the translation (in the current locale).
230 | *
231 | * @return \Illuminate\Database\Eloquent\Model|null|HasTranslations
232 | * @throws \KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException
233 | */
234 | public function refreshTranslation()
235 | {
236 | if (! $this->exists) {
237 | return null;
238 | }
239 |
240 | $attributes = Arr::only(
241 | $this->newQuery()->findOrFail($this->getKey())->attributes, $this->getTranslatable()
242 | );
243 |
244 | foreach ($attributes as $key => $value) {
245 | $this->setAttribute($key, $value);
246 | }
247 |
248 | $this->syncOriginal();
249 |
250 | return $this;
251 | }
252 |
253 | /**
254 | * Translate the model to the given locale.
255 | *
256 | * @param string $locale
257 | * @return \Illuminate\Database\Eloquent\Model|null
258 | */
259 | public function translate(string $locale)
260 | {
261 | if (! $this->exists) {
262 | return null;
263 | }
264 |
265 | $this->currentLocale = $locale;
266 |
267 | return $this->refreshTranslation();
268 | }
269 |
270 | /**
271 | * Format the translated columns.
272 | *
273 | * @return array
274 | * @throws \KoenHoeijmakers\LaravelTranslatable\Exceptions\MissingTranslationsException
275 | */
276 | public function formatTranslatableColumnsForSelect(): array
277 | {
278 | $table = $this->getTranslationTable();
279 |
280 | return array_map(function ($item) use ($table) {
281 | return $table . '.' . $item;
282 | }, $this->getTranslatable());
283 | }
284 |
285 | /**
286 | * Get a new query builder that doesn't have any global scopes (except the JoinTranslationScope).
287 | *
288 | * @return \Illuminate\Database\Eloquent\Builder
289 | */
290 | public function newQueryWithoutScopes(): Builder
291 | {
292 | return parent::newQueryWithoutScopes()
293 | ->withGlobalScope(JoinTranslationScope::class, new JoinTranslationScope());
294 | }
295 |
296 | /**
297 | * Retrieve the model for a bound value.
298 | *
299 | * @param mixed $value
300 | * @param null $field
301 | * @return \Illuminate\Database\Eloquent\Model|null
302 | */
303 | public function resolveRouteBinding($value, $field = null)
304 | {
305 | $field = $field ?? $this->getRouteKeyName();
306 |
307 | return $this->newQuery()->where($this->getTable() . '.' . $field, $value)->first();
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/src/Scopes/JoinTranslationScope.php:
--------------------------------------------------------------------------------
1 | leftJoin($model->getTranslationTable(), function (JoinClause $join) use ($model) {
24 | $join->on(
25 | $model->getTable() . '.' . $model->getKeyName(),
26 | $model->getTranslationTable() . '.' . $model->getForeignKey()
27 | )->where($model->getLocaleKeyName(), $model->getLocale());
28 | })->addSelect($model->getTable() . '.*', ...$model->formatTranslatableColumnsForSelect());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Services/TranslationSavingService.php:
--------------------------------------------------------------------------------
1 | getTranslatableAttributes();
25 |
26 | $this->rememberTranslation($this->getModelIdentifier($model), $attributes);
27 |
28 | foreach (array_keys($attributes) as $attribute) {
29 | $model->offsetUnset($attribute);
30 | }
31 | }
32 |
33 | /**
34 | * Store the remembered translation for the given model.
35 | *
36 | * @param \Illuminate\Database\Eloquent\Model|\KoenHoeijmakers\LaravelTranslatable\HasTranslations $model
37 | * @return void
38 | */
39 | public function storeTranslationOnModel(Model $model)
40 | {
41 | $identifier = $this->getModelIdentifier($model);
42 |
43 | $model->storeTranslation(
44 | $model->getLocale(),
45 | $this->pullRememberedTranslation($identifier)
46 | );
47 | }
48 |
49 | /**
50 | * Remember the translation on the given key.
51 | *
52 | * @param string $key
53 | * @param array $attributes
54 | * @return void
55 | */
56 | public function rememberTranslation(string $key, array $attributes)
57 | {
58 | $this->translations[$key] = $attributes;
59 | }
60 |
61 | /**
62 | * Pull the translation on the given key.
63 | *
64 | * @param string $key
65 | * @return mixed
66 | */
67 | public function pullRememberedTranslation(string $key)
68 | {
69 | $value = $this->translations[$key];
70 |
71 | unset($this->translations[$key]);
72 |
73 | return $value;
74 | }
75 |
76 | /**
77 | * Get an unique identifier for the given model.
78 | *
79 | * @param \Illuminate\Database\Eloquent\Model $model
80 | * @return string
81 | */
82 | protected function getModelIdentifier(Model $model): string
83 | {
84 | return spl_object_hash($model);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/TranslatableServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
18 | __DIR__ . ' /../config/translatable.php' => config_path('translatable.php'),
19 | ], 'config');
20 | }
21 |
22 | /**
23 | * Registers the package's services.
24 | *
25 | * @return void
26 | */
27 | public function register()
28 | {
29 | $this->app->singleton(TranslationSavingService::class, function () {
30 | return new TranslationSavingService();
31 | });
32 |
33 | $this->mergeConfigFrom(__DIR__ . '/../config/translatable.php', 'translatable');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Feature/TranslationTest.php:
--------------------------------------------------------------------------------
1 | create([
18 | 'name' => 'Monkey',
19 | ]);
20 |
21 | $this->assertTrue($model->getAttribute('name') === 'Monkey');
22 | $this->assertDatabaseHas('test_models', ['id' => $model->getKey()]);
23 | $this->assertDatabaseHas('test_model_translations', ['test_model_id' => $model->getKey(), 'name' => 'Monkey']);
24 | }
25 |
26 | public function testTranslationsCanBeSavedViaStoreTranslation()
27 | {
28 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
29 | $model = TestModel::query()->create([
30 | 'name' => 'Monkey',
31 | ]);
32 |
33 | $model->storeTranslation('nl', ['name' => 'Aap']);
34 |
35 | $this->assertDatabaseHas('test_models', ['id' => $model->getKey()]);
36 | $this->assertDatabaseHas('test_model_translations', ['test_model_id' => $model->getKey(), 'name' => 'Aap']);
37 | }
38 |
39 | public function testTranslationsCanBeSavedViaStoreTranslationMethod()
40 | {
41 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
42 | $model = TestModel::query()->create([
43 | 'name' => 'Monkey',
44 | ]);
45 |
46 | $model->storeTranslation('nl', ['name' => 'Aap']);
47 |
48 | $this->assertDatabaseHas('test_models', ['id' => $model->getKey()]);
49 | $this->assertTrue($model->translationExists('nl'));
50 | }
51 |
52 | public function testTranslationsCanBeSavedViaStoreTranslationsMethod()
53 | {
54 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
55 | $model = TestModel::query()->create([
56 | 'name' => 'Monkey',
57 | ]);
58 |
59 | $model->storeTranslations([
60 | 'nl' => ['name' => 'Aap'],
61 | 'de' => ['name' => 'Affe'],
62 | ]);
63 |
64 | $this->assertDatabaseHas('test_models', ['id' => $model->getKey()]);
65 | $this->assertTrue($model->translationExists('nl'));
66 | $this->assertTrue($model->translationExists('de'));
67 | }
68 |
69 | public function testCanRetrieveADifferentTranslation()
70 | {
71 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
72 | $model = TestModel::query()->create([
73 | 'name' => 'Monkey',
74 | ]);
75 |
76 | $model->storeTranslation('nl', ['name' => 'Aap']);
77 |
78 | $this->assertTrue($model->translationExists('nl'));
79 |
80 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
81 | $model = TestModel::query()->find(1);
82 |
83 | $model->translate('nl');
84 |
85 | $this->assertEquals($model->getLocale(), 'nl');
86 | $this->assertEquals($model->getAttribute('name'), 'Aap');
87 | }
88 |
89 | public function testCanRetrieveADifferentTranslationAndItsUpdatedInThatLocale()
90 | {
91 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
92 | $model = TestModel::query()->create([
93 | 'name' => 'Monkey',
94 | ]);
95 |
96 | $model->storeTranslation('nl', ['name' => 'Aap']);
97 |
98 | $this->assertTrue($model->translationExists('nl'));
99 |
100 | $model->translate('nl');
101 |
102 | $this->assertEquals($model->getLocale(), 'nl');
103 | $this->assertEquals($model->getAttribute('name'), 'Aap');
104 |
105 | $model->update(['name' => 'Gorilla']);
106 |
107 | $this->assertEquals($model->getAttribute('name'), 'Gorilla');
108 | $this->assertEquals(TestModel::query()->find(1)->getAttribute('name'), 'Monkey');
109 | }
110 |
111 | public function testCanPurgeTranslations()
112 | {
113 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
114 | $model = TestModel::query()->create([
115 | 'name' => 'Monkey',
116 | ]);
117 |
118 | $this->assertTrue($model->translationExists('en'));
119 |
120 | $model->purgeTranslations();
121 |
122 | $this->assertFalse($model->translationExists('en'));
123 | }
124 |
125 | public function testModelGetsDeletedAndTranslationsArePurged()
126 | {
127 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
128 | $model = TestModel::query()->create([
129 | 'name' => 'Monkey',
130 | ]);
131 |
132 | $this->assertTrue($model->translationExists('en'));
133 |
134 | $model->delete();
135 |
136 | $this->assertFalse($model->translationExists('en'));
137 | $this->assertDatabaseMissing('test_models', [$model->getKeyName() => $model->getKey()]);
138 | }
139 |
140 | public function testCanGetTranslationModel()
141 | {
142 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
143 | $model = TestModel::query()->create([
144 | 'name' => 'Monkey',
145 | ]);
146 |
147 | $this->assertInstanceOf(TestModelTranslation::class, $model->getTranslation('en'));
148 | }
149 |
150 | public function testCanGetTranslationValue()
151 | {
152 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
153 | $model = TestModel::query()->create([
154 | 'name' => 'Monkey',
155 | ]);
156 |
157 | $this->assertEquals('Monkey', $model->getTranslationValue('en', 'name'));
158 | }
159 |
160 | public function testRefreshingTranslationsOnANonExistingModelReturnsNull()
161 | {
162 | $model = new TestModel();
163 |
164 | $this->assertNull($model->refreshTranslation());
165 | }
166 |
167 | public function testTranslatingANonExistingModelReturnsNull()
168 | {
169 | $model = new TestModel();
170 |
171 | $this->assertNull($model->translate('nl'));
172 | }
173 |
174 | public function testResolvingRouteBindingsReturnsCorrectModel()
175 | {
176 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
177 | $model = TestModel::query()->create([
178 | 'name' => 'Monkey',
179 | ]);
180 |
181 | $this->assertTrue($model->is($model->resolveRouteBinding($model->getKey())));
182 | }
183 |
184 | public function testTranslationModelCanBeOverridden()
185 | {
186 | $model = new TestModelWithTranslationModelOverride();
187 |
188 | $this->assertSame($model->getTranslationTable(), 'test_model_translation_differents');
189 | }
190 |
191 | public function testModelIsMissingTranslations()
192 | {
193 | $model = new TestModelWithoutTranslations();
194 |
195 | $this->expectException(MissingTranslationsException::class);
196 |
197 | $model->getTranslatable();
198 | }
199 |
200 | public function testCanOverrideLocaleKey()
201 | {
202 | $model = new TestModelLocaleKey();
203 |
204 | $this->assertEquals('lang', $model->getLocaleKeyName());
205 | }
206 |
207 | public function testCanAddSelect()
208 | {
209 | /** @var \KoenHoeijmakers\LaravelTranslatable\Tests\Feature\TestModel $model */
210 | $model = TestModel::query()->create([
211 | 'name' => 'Monkey',
212 | ]);
213 |
214 | $result = TestModel::query()->select('test_model_translations.id AS translation_id')->first();
215 |
216 | $this->assertEquals($model->getTranslation('en')->getKey(), $result->translation_id);
217 | }
218 | }
219 |
220 | class TestModel extends Model
221 | {
222 | use HasTranslations;
223 |
224 | protected $fillable = ['name'];
225 |
226 | protected $translatable = ['name'];
227 | }
228 |
229 | class TestModelTranslation extends Model
230 | {
231 | protected $fillable = ['name'];
232 | }
233 |
234 | class TestModelWithTranslationModelOverride extends Model
235 | {
236 | use HasTranslations;
237 |
238 | protected $table = 'test_models';
239 |
240 | protected $fillable = ['name'];
241 |
242 | protected $translatable = ['name'];
243 |
244 | protected $translationModel = TestModelTranslationDifferent::class;
245 | }
246 |
247 | class TestModelTranslationDifferent extends Model
248 | {
249 | protected $fillable = ['name'];
250 | }
251 |
252 | class TestModelWithoutTranslations extends Model
253 | {
254 | use HasTranslations;
255 |
256 | protected $fillable = ['name'];
257 |
258 | protected $table = 'test_models';
259 | }
260 |
261 | class TestModelLocaleKey extends Model
262 | {
263 | use HasTranslations;
264 |
265 | protected $localeKeyName = 'lang';
266 |
267 | protected $fillable = ['name'];
268 |
269 | protected $table = 'test_models';
270 | }
271 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | setUpDatabase();
17 | }
18 |
19 | protected function getPackageProviders($app)
20 | {
21 | return [TranslatableServiceProvider::class];
22 | }
23 |
24 | protected function setUpDatabase()
25 | {
26 | Schema::create('test_models', function (Blueprint $table) {
27 | $table->increments('id');
28 | $table->timestamps();
29 | });
30 |
31 | Schema::create('test_model_translations', function (Blueprint $table) {
32 | $table->increments('id');
33 | $table->unsignedInteger('test_model_id');
34 | $table->string('locale');
35 | $table->string('name');
36 | $table->timestamps();
37 |
38 | $table->unique(['locale', 'test_model_id']);
39 | $table->foreign('test_model_id')->references('id')->on('test_models');
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------