Skip to content

Commit 9ece34b

Browse files
committed
wip searchcode
1 parent 4db48ed commit 9ece34b

17 files changed

+349
-29
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
"deeplcom/deepl-php": "^1.7",
2424
"illuminate/contracts": "^10.0||^11.0",
2525
"openai-php/laravel": "^0.10.1",
26-
"spatie/laravel-package-tools": "^1.16"
26+
"spatie/laravel-package-tools": "^1.16",
27+
"spatie/regex": "^3.1",
28+
"symfony/finder": "^7.1"
2729
},
2830
"require-dev": {
2931
"laravel/pint": "^1.14",

config/translator.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
'lang_path' => lang_path(),
88

9+
/**
10+
* Auto sort translations keys after each manipulations: translate, grammar, ...
11+
*/
12+
'sort_keys' => false,
13+
914
'translate' => [
1015
'service' => 'openai',
1116
'services' => [
@@ -35,4 +40,17 @@
3540
],
3641
],
3742

43+
'searchcode' => [
44+
'service' => 'regex',
45+
46+
'services' => [
47+
'regex' => [
48+
'paths' => [
49+
app_path(),
50+
resource_path(),
51+
],
52+
],
53+
],
54+
],
55+
3856
];

src/Exceptions/TranslatorServiceException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ public static function missingGrammarService(): self
1515
{
1616
return new self('The grammar service is missing. Please define a grammar service in configs.');
1717
}
18+
19+
public static function missingSearchcodeService(): self
20+
{
21+
return new self('The searchcode service is missing. Please define a searchcode service in configs.');
22+
}
1823
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Elegantly\Translator\Services\SearchCode;
4+
5+
use Spatie\Regex\MatchResult;
6+
use Spatie\Regex\Regex;
7+
use Symfony\Component\Finder\Finder;
8+
use Symfony\Component\Finder\SplFileInfo;
9+
10+
class RegexService implements SearchCodeServiceInterface
11+
{
12+
public static $patterns = [
13+
'/__\(["\'](?<key>[a-z0-9.\-_]+)["\']\)/i',
14+
];
15+
16+
public function __construct(
17+
public array $paths
18+
) {
19+
//
20+
}
21+
22+
public function finder(): Finder
23+
{
24+
return Finder::create()
25+
->in($this->paths)
26+
->followLinks()
27+
->ignoreDotFiles(true)
28+
->ignoreVCS(true)
29+
->ignoreUnreadableDirs(true)
30+
->exclude('vendor')
31+
->exclude('node_modules')
32+
->name('*.php')
33+
->files();
34+
}
35+
36+
/**
37+
* @return string[]
38+
*/
39+
public static function scanCode(string $code): array
40+
{
41+
$chunks = collect(explode("\n", $code));
42+
43+
return $chunks
44+
->flatMap(function (string $code) {
45+
return collect(static::$patterns)
46+
->flatMap(fn (string $pattern) => Regex::matchAll($pattern, $code)->results())
47+
->map(fn (MatchResult $matchResult) => $matchResult->group('key'));
48+
})
49+
->toArray();
50+
}
51+
52+
public function translationsByFiles(): array
53+
{
54+
return collect($this->finder())
55+
->keyBy(fn (SplFileInfo $file) => $file->getRealPath())
56+
->map(fn (SplFileInfo $file) => static::scanCode($file->getContents()))
57+
->filter()
58+
->toArray();
59+
}
60+
61+
public function filesByTranslations(): array
62+
{
63+
$translations = $this->translationsByFiles();
64+
65+
$results = [];
66+
67+
foreach ($translations as $file => $keys) {
68+
foreach ($keys as $key) {
69+
70+
$results[$key] = [
71+
'count' => ($results[$key]['count'] ?? 0) + 1,
72+
'files' => array_unique([
73+
...$results[$key]['files'] ?? [],
74+
$file,
75+
]),
76+
];
77+
}
78+
}
79+
80+
return $results;
81+
}
82+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Elegantly\Translator\Services\SearchCode;
4+
5+
interface SearchCodeServiceInterface
6+
{
7+
public function translationsByFiles(): array;
8+
9+
/**
10+
* @return array<string, array{ count: int, files: string[] }>
11+
*/
12+
public function filesByTranslations(): array;
13+
}

src/Translator.php

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Elegantly\Translator\Exceptions\TranslatorException;
66
use Elegantly\Translator\Exceptions\TranslatorServiceException;
77
use Elegantly\Translator\Services\Grammar\GrammarServiceInterface;
8+
use Elegantly\Translator\Services\SearchCode\SearchCodeServiceInterface;
89
use Elegantly\Translator\Services\Translate\TranslateServiceInterface;
910
use Illuminate\Contracts\Filesystem\Filesystem;
1011
use Illuminate\Support\Facades\File;
@@ -15,27 +16,28 @@ public function __construct(
1516
public Filesystem $storage,
1617
public ?TranslateServiceInterface $translateService = null,
1718
public ?GrammarServiceInterface $grammarService = null,
19+
public ?SearchCodeServiceInterface $searchcodeService = null,
1820
) {
1921
//
2022
}
2123

2224
/**
2325
* @return string[]
2426
*/
25-
public function getLanguages(): array
27+
public function getLocales(): array
2628
{
27-
return $this->getLocales();
29+
return collect($this->storage->allDirectories())
30+
->sort(SORT_NATURAL)
31+
->values()
32+
->toArray();
2833
}
2934

3035
/**
3136
* @return string[]
3237
*/
33-
public function getLocales(): array
38+
public function getLanguages(): array
3439
{
35-
return collect($this->storage->allDirectories())
36-
->sort(SORT_NATURAL)
37-
->values()
38-
->toArray();
40+
return $this->getLocales();
3941
}
4042

4143
public function getNamespaces(string $locale): array
@@ -62,6 +64,8 @@ public function getTranslations(string $locale, string $namespace): Translations
6264
}
6365

6466
/**
67+
* Return all the translations keys present in the reference locale but not in the other ones
68+
*
6569
* @return array<string, array<string, array>>
6670
*/
6771
public function getAllMissingTranslations(
@@ -96,6 +100,30 @@ public function getMissingTranslations(
96100
return $referenceTranslations->getMissingTranslationsIn($targetTranslations);
97101
}
98102

103+
/**
104+
* Retreives the translations keys from locale not used in any file
105+
*/
106+
public function getDeadTranslations(
107+
string $locale,
108+
string $namespace,
109+
?SearchCodeServiceInterface $service = null,
110+
): array {
111+
$service ??= $this->searchcodeService;
112+
113+
if (! $service) {
114+
throw TranslatorServiceException::missingSearchcodeService();
115+
}
116+
117+
$definedTranslationsKeys = $this
118+
->getTranslations($locale, $namespace)
119+
->dot()
120+
->keys();
121+
122+
$usedTranslationsKeys = array_keys($service->filesByTranslations());
123+
124+
return $definedTranslationsKeys->filter(fn (string $key) => ! in_array("{$namespace}.{$key}", $usedTranslationsKeys))->toArray();
125+
}
126+
99127
/**
100128
* @param array<string|int, string|int|float|array|null> $values
101129
*/
@@ -117,22 +145,15 @@ function (Translations $translations) use ($values) {
117145
$translations->set($key, $value);
118146
}
119147

148+
if (config('translator.sort_keys')) {
149+
$translations->sortNatural();
150+
}
151+
120152
return $translations;
121153
}
122154
)->only(array_keys($values));
123155
}
124156

125-
public function setTranslation(
126-
string $locale,
127-
string $namespace,
128-
string $key,
129-
string|array|int|float|null $value,
130-
): Translations {
131-
return $this->setTranslations($locale, $namespace, [
132-
$key => $value,
133-
]);
134-
}
135-
136157
public function translateTranslations(
137158
string $referenceLocale,
138159
string $targetLocale,
@@ -173,6 +194,10 @@ function (Translations $translations) use ($referenceLocale, $targetLocale, $nam
173194
$translations->set($key, $value);
174195
}
175196

197+
if (config('translator.sort_keys')) {
198+
$translations->sortNatural();
199+
}
200+
176201
return $translations;
177202
}
178203
)->only($keys);
@@ -205,10 +230,25 @@ public function fixGrammarTranslations(
205230
$translations->set($key, $value);
206231
}
207232

233+
if (config('translator.sort_keys')) {
234+
$translations->sortNatural();
235+
}
236+
208237
return $translations;
209238
});
210239
}
211240

241+
public function setTranslation(
242+
string $locale,
243+
string $namespace,
244+
string $key,
245+
string|array|int|float|null $value,
246+
): Translations {
247+
return $this->setTranslations($locale, $namespace, [
248+
$key => $value,
249+
]);
250+
}
251+
212252
public function translateTranslation(
213253
string $referenceLocale,
214254
string $targetLocale,

src/TranslatorServiceProvider.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Elegantly\Translator\Commands\TranslateTranslationsCommand;
99
use Elegantly\Translator\Services\Grammar\GrammarServiceInterface;
1010
use Elegantly\Translator\Services\Grammar\OpenAiService as GrammarOpenAiService;
11+
use Elegantly\Translator\Services\SearchCode\RegexService;
12+
use Elegantly\Translator\Services\SearchCode\SearchCodeServiceInterface;
1113
use Elegantly\Translator\Services\Translate\DeepLService;
1214
use Elegantly\Translator\Services\Translate\OpenAiService;
1315
use Elegantly\Translator\Services\Translate\TranslateServiceInterface;
@@ -45,6 +47,7 @@ public function registeringPackage()
4547
]),
4648
translateService: static::getTranslateServiceFromConfig(),
4749
grammarService: static::getGrammarServiceFromConfig(),
50+
searchcodeService: static::getSearchcodeServiceFromConfig(),
4851
);
4952
});
5053
}
@@ -79,4 +82,17 @@ public static function getGrammarServiceFromConfig(?string $serviceName = null):
7982
default => new $service,
8083
};
8184
}
85+
86+
public static function getSearchcodeServiceFromConfig(?string $serviceName = null): SearchCodeServiceInterface
87+
{
88+
$service = $serviceName ?? config('translator.searchcode.service');
89+
90+
return match ($service) {
91+
'regex', RegexService::class => new RegexService(
92+
config('translator.searchcode.services.regex.paths')
93+
),
94+
'', null => null,
95+
default => new $service,
96+
};
97+
}
8298
}

tests/Feature/TranslatorTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use Elegantly\Translator\Services\SearchCode\RegexService;
34
use Elegantly\Translator\Translator;
45

56
it('gets locales', function () {
@@ -29,6 +30,11 @@
2930
$translator->getTranslations('fr', 'messages')->toArray()
3031
)->toBe([
3132
'add' => 'Ajouter',
33+
'dummy' => [
34+
'class' => 'class factice',
35+
'component' => 'composant factice',
36+
'view' => 'vue factice',
37+
],
3238
'empty' => 'Vide',
3339
'hello' => 'Bonjour',
3440
'home' => [
@@ -37,6 +43,7 @@
3743
'title' => 'Titre',
3844
],
3945
'missing' => 'Absent',
46+
4047
]);
4148
});
4249

@@ -78,6 +85,31 @@
7885
]);
7986
});
8087

88+
it('finds dead translations', function () {
89+
$translator = new Translator(
90+
storage: $this->getStorage(),
91+
searchcodeService: new RegexService([
92+
$this->getAppPath(),
93+
$this->getResourcesPath(),
94+
])
95+
);
96+
97+
$dead = $translator->getDeadTranslations(
98+
locale: 'fr',
99+
namespace: 'messages'
100+
);
101+
102+
expect($dead)->toBe([
103+
'hello',
104+
'add',
105+
'home.title',
106+
'home.end',
107+
'home.missing',
108+
'empty',
109+
'missing',
110+
]);
111+
});
112+
81113
it('sets translations', function () {
82114
$translator = new Translator(
83115
storage: $this->getStorage(),

0 commit comments

Comments
 (0)