Skip to content

Commit 6f6ee6a

Browse files
authored
Adds command to find missing translations (#10)
* Adds command to find missing translations * wip
1 parent 27a0f82 commit 6f6ee6a

File tree

58 files changed

+886
-112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+886
-112
lines changed

README.md

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,38 @@ php artisan vendor:publish --tag="translation-linter-config"
2828

2929
You should read through the config, which serves as additional documentation and make changes as needed.
3030

31+
## Missing Command
32+
This reads through all your code and finds all your language function usage.
33+
Then attempts to find matches in your language files and will output any
34+
keys in your code that do not exist as a language key.
35+
36+
```sh
37+
$ php artisan translation:missing
38+
39+
ERROR 3 missing translations found.
40+
41+
+--------+--------------------------------+---------------------+
42+
| Locale | Key | File |
43+
+--------+--------------------------------+---------------------+
44+
| en | Missing PHP Class | app/ExampleJson.php |
45+
| en | Only Missing English PHP Class | app/ExampleJson.php |
46+
| de | Missing PHP Class | app/ExampleJson.php |
47+
+--------+--------------------------------+---------------------+
48+
```
49+
50+
You can generate a baseline file which will be used to ignore specific keys with the
51+
`--generate-baseline` or `-b` command options:
52+
53+
```sh
54+
$ php artisan translation:missing --generate-baseline
55+
56+
INFO Baseline file written with 49 translation keys.
57+
58+
$ php artisan translation:missing
59+
60+
INFO No missing translations found!
61+
```
62+
3163
## Unused Command
3264
This reads through all your code and finds all your language function usage.
3365
Then attempts to find matches in your language files and will output any
@@ -65,20 +97,6 @@ $ php artisan translation:unused
6597
INFO No unused translations found!
6698
```
6799

68-
## Roadmap
69-
- [x] Supports JSON and PHP translation files
70-
- You can enable / disable file types in the config
71-
- You can add your own custom file readers
72-
- [x] Supports multiple locales
73-
- [x] Supports parsing many code types
74-
- Default: php, js and vue
75-
- You can add more file extensions in the config
76-
- [x] [Unused Command](#unused-command)
77-
- [ ] Missing Command - _coming soon_
78-
- [ ] Orphaned Command - _coming soon_
79-
- [ ] Lint Command - _coming soon_
80-
- This would run all of the other commands in a single command.
81-
82100
## Testing
83101

84102
```bash

config/translation-linter.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,50 @@
8888
],
8989
],
9090

91+
'missing' => [
92+
/*
93+
|--------------------------------------------------------------------------
94+
| Baseline File
95+
|--------------------------------------------------------------------------
96+
|
97+
| This is the location of the baseline file that is used to ignore specific
98+
| translation keys. You can generate this file by using the `--generate-baseline`
99+
| option when running the command. You should commit this file.
100+
|
101+
*/
102+
'baseline' => lang_path('.lint/missing.json'),
103+
104+
/*
105+
|--------------------------------------------------------------------------
106+
| Output Fields
107+
|--------------------------------------------------------------------------
108+
|
109+
| The following array lists the "fields" that are displayed by the command
110+
| when missing translations are found. Set any of these to `false` to hide
111+
| them from the output or change all to `false` to not show anything.
112+
|
113+
*/
114+
'fields' => [
115+
'locale' => true,
116+
'key' => true,
117+
'file' => true,
118+
],
119+
120+
/*
121+
|--------------------------------------------------------------------------
122+
| Missing Language Filters
123+
|--------------------------------------------------------------------------
124+
|
125+
| The following array lists the "filters" that will be used to filter out
126+
| erroneously detected missing translations.
127+
|
128+
| All filters must implement the filter interface or they will be skipped:
129+
| \Fidum\LaravelTranslationLinter\Contracts\Filter
130+
|
131+
*/
132+
'filters' => [],
133+
],
134+
91135
'unused' => [
92136
/*
93137
|--------------------------------------------------------------------------
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Fidum\LaravelTranslationLinter\Collections;
4+
5+
use Fidum\LaravelTranslationLinter\Contracts\Collections\ApplicationFileCollection as ApplicationFileCollectionContract;
6+
use Fidum\LaravelTranslationLinter\Data\ApplicationFileObject;
7+
use Illuminate\Support\Collection;
8+
9+
/**
10+
* @method self __construct(ApplicationFileObject[] $items = null)
11+
* @method self push(ApplicationFileObject $object)
12+
*/
13+
class ApplicationFileCollection extends Collection implements ApplicationFileCollectionContract
14+
{
15+
public function containsKey(string $key): bool
16+
{
17+
return $this->some(function (ApplicationFileObject $object) use ($key) {
18+
return $object->namespaceHintedKey === $key;
19+
});
20+
}
21+
22+
public function doesntContainKey(string $key): bool
23+
{
24+
return ! $this->containsKey($key);
25+
}
26+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Fidum\LaravelTranslationLinter\Collections\Concerns;
4+
5+
use Fidum\LaravelTranslationLinter\Contracts\Collections\FieldCollection as FieldCollectionContract;
6+
use Illuminate\Support\Str;
7+
8+
trait CollectsFields
9+
{
10+
public function enabled(): FieldCollectionContract
11+
{
12+
return $this->filter()->keys();
13+
}
14+
15+
public function headers(): array
16+
{
17+
return $this->enabled()
18+
->map(fn ($v) => Str::headline($v))
19+
->toArray();
20+
}
21+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Fidum\LaravelTranslationLinter\Collections\Concerns;
4+
5+
use Fidum\LaravelTranslationLinter\Contracts\Filters\Filter;
6+
use Fidum\LaravelTranslationLinter\Data\ResultObject;
7+
use http\Exception\InvalidArgumentException;
8+
9+
trait CollectsFilters
10+
{
11+
public function shouldReport(ResultObject $object): bool
12+
{
13+
return $this->every(function (string $filterClass) use ($object) {
14+
$interface = Filter::class;
15+
16+
if (is_subclass_of($filterClass, $interface)) {
17+
/** @var Filter $filter */
18+
$filter = app($filterClass);
19+
20+
return $filter->shouldReport($object);
21+
}
22+
23+
throw new InvalidArgumentException("Filter [$filterClass] needs to implement [$interface].");
24+
});
25+
}
26+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Fidum\LaravelTranslationLinter\Collections;
4+
5+
use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFields;
6+
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFieldCollection as MissingFieldCollectionContract;
7+
use Illuminate\Support\Collection;
8+
9+
class MissingFieldCollection extends Collection implements MissingFieldCollectionContract
10+
{
11+
use CollectsFields;
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Fidum\LaravelTranslationLinter\Collections;
4+
5+
use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFilters;
6+
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFilterCollection as MissingFilterCollectionContract;
7+
use Illuminate\Support\Collection;
8+
9+
class MissingFilterCollection extends Collection implements MissingFilterCollectionContract
10+
{
11+
use CollectsFilters;
12+
}

src/Collections/UnusedFieldCollection.php

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,11 @@
22

33
namespace Fidum\LaravelTranslationLinter\Collections;
44

5-
use Fidum\LaravelTranslationLinter\Contracts\Collections\FieldCollection as FieldCollectionContract;
5+
use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFields;
66
use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFieldCollection as UnusedFieldCollectionContract;
77
use Illuminate\Support\Collection;
8-
use Illuminate\Support\Str;
98

109
class UnusedFieldCollection extends Collection implements UnusedFieldCollectionContract
1110
{
12-
public function enabled(): FieldCollectionContract
13-
{
14-
return $this->filter()->keys();
15-
}
16-
17-
public function headers(): array
18-
{
19-
return $this->enabled()
20-
->map(fn ($v) => Str::headline($v))
21-
->toArray();
22-
}
11+
use CollectsFields;
2312
}

src/Collections/UnusedFilterCollection.php

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,11 @@
22

33
namespace Fidum\LaravelTranslationLinter\Collections;
44

5+
use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFilters;
56
use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFilterCollection as UnusedFilterCollectionContract;
6-
use Fidum\LaravelTranslationLinter\Contracts\Filters\Filter;
7-
use Fidum\LaravelTranslationLinter\Data\ResultObject;
8-
use http\Exception\InvalidArgumentException;
97
use Illuminate\Support\Collection;
108

119
class UnusedFilterCollection extends Collection implements UnusedFilterCollectionContract
1210
{
13-
public function shouldReport(ResultObject $object): bool
14-
{
15-
return $this->every(function (string $filterClass) use ($object) {
16-
$interface = Filter::class;
17-
18-
if (is_subclass_of($filterClass, $interface)) {
19-
/** @var Filter $filter */
20-
$filter = app($filterClass);
21-
22-
return $filter->shouldReport($object);
23-
}
24-
25-
throw new InvalidArgumentException("Filter [$filterClass] needs to implement [$interface].");
26-
});
27-
}
11+
use CollectsFilters;
2812
}

src/Commands/MissingCommand.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Fidum\LaravelTranslationLinter\Commands;
4+
5+
use Fidum\LaravelTranslationLinter\Collections\ResultObjectCollection;
6+
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFieldCollection;
7+
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFilterCollection;
8+
use Fidum\LaravelTranslationLinter\Contracts\Linters\MissingTranslationLinter;
9+
use Fidum\LaravelTranslationLinter\Data\ResultObject;
10+
use Fidum\LaravelTranslationLinter\Filters\IgnoreKeysFromMissingBaselineFileFilter;
11+
use Fidum\LaravelTranslationLinter\Writers\MissingBaselineFileWriter;
12+
use Illuminate\Console\Command;
13+
14+
class MissingCommand extends Command
15+
{
16+
public $signature = 'translation:missing
17+
{paths?* : One or more absolute paths to files you specifically want to scan for missing keys.}
18+
{--b|generate-baseline : Generate a baseline file from the missing keys.}';
19+
20+
public $description = 'Finds unused language keys.';
21+
22+
public function handle(
23+
MissingBaselineFileWriter $writer,
24+
MissingFieldCollection $fields,
25+
MissingFilterCollection $filters,
26+
MissingTranslationLinter $linter,
27+
): int {
28+
$baseline = (bool) $this->option('generate-baseline');
29+
$results = $linter->execute();
30+
31+
if ($baseline) {
32+
$results = $results->whereShouldReport($filters);
33+
34+
$writer->execute($results);
35+
36+
$this->components->info("Baseline file written with {$results->count()} translation keys.");
37+
38+
return self::SUCCESS;
39+
}
40+
41+
$filters->push(IgnoreKeysFromMissingBaselineFileFilter::class);
42+
43+
$results = $results
44+
->when($this->argument('paths'), function (ResultObjectCollection $items, array $files) {
45+
return $items->filter(fn (ResultObject $object) => in_array($object->file->getPathname(), $files));
46+
})
47+
->whereShouldReport($filters);
48+
49+
if ($results->isEmpty()) {
50+
$this->components->info('No missing translations found!');
51+
52+
return self::SUCCESS;
53+
}
54+
55+
$this->components->error(sprintf('%d missing translations found', $results->count()));
56+
$this->table($fields->headers(), $results->toCommandTableOutputArray($fields));
57+
58+
return self::FAILURE;
59+
}
60+
}

0 commit comments

Comments
 (0)