Skip to content

Commit

Permalink
Adds command to find missing translations (#10)
Browse files Browse the repository at this point in the history
* Adds command to find missing translations

* wip
  • Loading branch information
dmason30 authored Nov 23, 2023
1 parent 27a0f82 commit 6f6ee6a
Show file tree
Hide file tree
Showing 58 changed files with 886 additions and 112 deletions.
46 changes: 32 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@ php artisan vendor:publish --tag="translation-linter-config"

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

## Missing Command
This reads through all your code and finds all your language function usage.
Then attempts to find matches in your language files and will output any
keys in your code that do not exist as a language key.

```sh
$ php artisan translation:missing

ERROR 3 missing translations found.

+--------+--------------------------------+---------------------+
| Locale | Key | File |
+--------+--------------------------------+---------------------+
| en | Missing PHP Class | app/ExampleJson.php |
| en | Only Missing English PHP Class | app/ExampleJson.php |
| de | Missing PHP Class | app/ExampleJson.php |
+--------+--------------------------------+---------------------+
```

You can generate a baseline file which will be used to ignore specific keys with the
`--generate-baseline` or `-b` command options:

```sh
$ php artisan translation:missing --generate-baseline

INFO Baseline file written with 49 translation keys.

$ php artisan translation:missing

INFO No missing translations found!
```

## Unused Command
This reads through all your code and finds all your language function usage.
Then attempts to find matches in your language files and will output any
Expand Down Expand Up @@ -65,20 +97,6 @@ $ php artisan translation:unused
INFO No unused translations found!
```

## Roadmap
- [x] Supports JSON and PHP translation files
- You can enable / disable file types in the config
- You can add your own custom file readers
- [x] Supports multiple locales
- [x] Supports parsing many code types
- Default: php, js and vue
- You can add more file extensions in the config
- [x] [Unused Command](#unused-command)
- [ ] Missing Command - _coming soon_
- [ ] Orphaned Command - _coming soon_
- [ ] Lint Command - _coming soon_
- This would run all of the other commands in a single command.

## Testing

```bash
Expand Down
44 changes: 44 additions & 0 deletions config/translation-linter.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,50 @@
],
],

'missing' => [
/*
|--------------------------------------------------------------------------
| Baseline File
|--------------------------------------------------------------------------
|
| This is the location of the baseline file that is used to ignore specific
| translation keys. You can generate this file by using the `--generate-baseline`
| option when running the command. You should commit this file.
|
*/
'baseline' => lang_path('.lint/missing.json'),

/*
|--------------------------------------------------------------------------
| Output Fields
|--------------------------------------------------------------------------
|
| The following array lists the "fields" that are displayed by the command
| when missing translations are found. Set any of these to `false` to hide
| them from the output or change all to `false` to not show anything.
|
*/
'fields' => [
'locale' => true,
'key' => true,
'file' => true,
],

/*
|--------------------------------------------------------------------------
| Missing Language Filters
|--------------------------------------------------------------------------
|
| The following array lists the "filters" that will be used to filter out
| erroneously detected missing translations.
|
| All filters must implement the filter interface or they will be skipped:
| \Fidum\LaravelTranslationLinter\Contracts\Filter
|
*/
'filters' => [],
],

'unused' => [
/*
|--------------------------------------------------------------------------
Expand Down
26 changes: 26 additions & 0 deletions src/Collections/ApplicationFileCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Fidum\LaravelTranslationLinter\Collections;

use Fidum\LaravelTranslationLinter\Contracts\Collections\ApplicationFileCollection as ApplicationFileCollectionContract;
use Fidum\LaravelTranslationLinter\Data\ApplicationFileObject;
use Illuminate\Support\Collection;

/**
* @method self __construct(ApplicationFileObject[] $items = null)
* @method self push(ApplicationFileObject $object)
*/
class ApplicationFileCollection extends Collection implements ApplicationFileCollectionContract
{
public function containsKey(string $key): bool
{
return $this->some(function (ApplicationFileObject $object) use ($key) {
return $object->namespaceHintedKey === $key;
});
}

public function doesntContainKey(string $key): bool
{
return ! $this->containsKey($key);
}
}
21 changes: 21 additions & 0 deletions src/Collections/Concerns/CollectsFields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Fidum\LaravelTranslationLinter\Collections\Concerns;

use Fidum\LaravelTranslationLinter\Contracts\Collections\FieldCollection as FieldCollectionContract;
use Illuminate\Support\Str;

trait CollectsFields
{
public function enabled(): FieldCollectionContract
{
return $this->filter()->keys();
}

public function headers(): array
{
return $this->enabled()
->map(fn ($v) => Str::headline($v))
->toArray();
}
}
26 changes: 26 additions & 0 deletions src/Collections/Concerns/CollectsFilters.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Fidum\LaravelTranslationLinter\Collections\Concerns;

use Fidum\LaravelTranslationLinter\Contracts\Filters\Filter;
use Fidum\LaravelTranslationLinter\Data\ResultObject;
use http\Exception\InvalidArgumentException;

trait CollectsFilters
{
public function shouldReport(ResultObject $object): bool
{
return $this->every(function (string $filterClass) use ($object) {
$interface = Filter::class;

if (is_subclass_of($filterClass, $interface)) {
/** @var Filter $filter */
$filter = app($filterClass);

return $filter->shouldReport($object);
}

throw new InvalidArgumentException("Filter [$filterClass] needs to implement [$interface].");
});
}
}
12 changes: 12 additions & 0 deletions src/Collections/MissingFieldCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Fidum\LaravelTranslationLinter\Collections;

use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFields;
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFieldCollection as MissingFieldCollectionContract;
use Illuminate\Support\Collection;

class MissingFieldCollection extends Collection implements MissingFieldCollectionContract
{
use CollectsFields;
}
12 changes: 12 additions & 0 deletions src/Collections/MissingFilterCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Fidum\LaravelTranslationLinter\Collections;

use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFilters;
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFilterCollection as MissingFilterCollectionContract;
use Illuminate\Support\Collection;

class MissingFilterCollection extends Collection implements MissingFilterCollectionContract
{
use CollectsFilters;
}
15 changes: 2 additions & 13 deletions src/Collections/UnusedFieldCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,11 @@

namespace Fidum\LaravelTranslationLinter\Collections;

use Fidum\LaravelTranslationLinter\Contracts\Collections\FieldCollection as FieldCollectionContract;
use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFields;
use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFieldCollection as UnusedFieldCollectionContract;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class UnusedFieldCollection extends Collection implements UnusedFieldCollectionContract
{
public function enabled(): FieldCollectionContract
{
return $this->filter()->keys();
}

public function headers(): array
{
return $this->enabled()
->map(fn ($v) => Str::headline($v))
->toArray();
}
use CollectsFields;
}
20 changes: 2 additions & 18 deletions src/Collections/UnusedFilterCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,11 @@

namespace Fidum\LaravelTranslationLinter\Collections;

use Fidum\LaravelTranslationLinter\Collections\Concerns\CollectsFilters;
use Fidum\LaravelTranslationLinter\Contracts\Collections\UnusedFilterCollection as UnusedFilterCollectionContract;
use Fidum\LaravelTranslationLinter\Contracts\Filters\Filter;
use Fidum\LaravelTranslationLinter\Data\ResultObject;
use http\Exception\InvalidArgumentException;
use Illuminate\Support\Collection;

class UnusedFilterCollection extends Collection implements UnusedFilterCollectionContract
{
public function shouldReport(ResultObject $object): bool
{
return $this->every(function (string $filterClass) use ($object) {
$interface = Filter::class;

if (is_subclass_of($filterClass, $interface)) {
/** @var Filter $filter */
$filter = app($filterClass);

return $filter->shouldReport($object);
}

throw new InvalidArgumentException("Filter [$filterClass] needs to implement [$interface].");
});
}
use CollectsFilters;
}
60 changes: 60 additions & 0 deletions src/Commands/MissingCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Fidum\LaravelTranslationLinter\Commands;

use Fidum\LaravelTranslationLinter\Collections\ResultObjectCollection;
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFieldCollection;
use Fidum\LaravelTranslationLinter\Contracts\Collections\MissingFilterCollection;
use Fidum\LaravelTranslationLinter\Contracts\Linters\MissingTranslationLinter;
use Fidum\LaravelTranslationLinter\Data\ResultObject;
use Fidum\LaravelTranslationLinter\Filters\IgnoreKeysFromMissingBaselineFileFilter;
use Fidum\LaravelTranslationLinter\Writers\MissingBaselineFileWriter;
use Illuminate\Console\Command;

class MissingCommand extends Command
{
public $signature = 'translation:missing
{paths?* : One or more absolute paths to files you specifically want to scan for missing keys.}
{--b|generate-baseline : Generate a baseline file from the missing keys.}';

public $description = 'Finds unused language keys.';

public function handle(
MissingBaselineFileWriter $writer,
MissingFieldCollection $fields,
MissingFilterCollection $filters,
MissingTranslationLinter $linter,
): int {
$baseline = (bool) $this->option('generate-baseline');
$results = $linter->execute();

if ($baseline) {
$results = $results->whereShouldReport($filters);

$writer->execute($results);

$this->components->info("Baseline file written with {$results->count()} translation keys.");

return self::SUCCESS;
}

$filters->push(IgnoreKeysFromMissingBaselineFileFilter::class);

$results = $results
->when($this->argument('paths'), function (ResultObjectCollection $items, array $files) {
return $items->filter(fn (ResultObject $object) => in_array($object->file->getPathname(), $files));
})
->whereShouldReport($filters);

if ($results->isEmpty()) {
$this->components->info('No missing translations found!');

return self::SUCCESS;
}

$this->components->error(sprintf('%d missing translations found', $results->count()));
$this->table($fields->headers(), $results->toCommandTableOutputArray($fields));

return self::FAILURE;
}
}
18 changes: 18 additions & 0 deletions src/Contracts/Collections/ApplicationFileCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Fidum\LaravelTranslationLinter\Contracts\Collections;

use Fidum\LaravelTranslationLinter\Data\ApplicationFileObject;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Enumerable;

/**
* @method self __construct(ApplicationFileObject[] $items = null)
* @method self push(ApplicationFileObject $item)
*/
interface ApplicationFileCollection extends Arrayable, Enumerable
{
public function containsKey(string $key): bool;

public function doesntContainKey(string $key): bool;
}
5 changes: 5 additions & 0 deletions src/Contracts/Collections/MissingFieldCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace Fidum\LaravelTranslationLinter\Contracts\Collections;

interface MissingFieldCollection extends FieldCollection {}
5 changes: 5 additions & 0 deletions src/Contracts/Collections/MissingFilterCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace Fidum\LaravelTranslationLinter\Contracts\Collections;

interface MissingFilterCollection extends FilterCollection {}
5 changes: 5 additions & 0 deletions src/Contracts/Linters/MissingTranslationLinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace Fidum\LaravelTranslationLinter\Contracts\Linters;

interface MissingTranslationLinter extends TranslationLinter {}
10 changes: 10 additions & 0 deletions src/Contracts/Linters/TranslationLinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Fidum\LaravelTranslationLinter\Contracts\Linters;

use Fidum\LaravelTranslationLinter\Contracts\Collections\ResultObjectCollection;

interface TranslationLinter
{
public function execute(): ResultObjectCollection;
}
7 changes: 1 addition & 6 deletions src/Contracts/Linters/UnusedTranslationLinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,4 @@

namespace Fidum\LaravelTranslationLinter\Contracts\Linters;

use Fidum\LaravelTranslationLinter\Contracts\Collections\ResultObjectCollection;

interface UnusedTranslationLinter
{
public function execute(): ResultObjectCollection;
}
interface UnusedTranslationLinter extends TranslationLinter {}
Loading

0 comments on commit 6f6ee6a

Please sign in to comment.