Skip to content

Commit 9ce4481

Browse files
authoredMay 16, 2024··
feat!: read vendor files (#10)
* feat!: read vendor folders and merge data * Fix styling * feat: add type * feat: added config flag whether to load vendor files or not --------- Co-authored-by: marcoraddatz <marcoraddatz@users.noreply.github.com>
1 parent 2fd555b commit 9ce4481

File tree

3 files changed

+189
-33
lines changed

3 files changed

+189
-33
lines changed
 

‎config/locale-via-api.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* We cache the locale for a certain amount of time.
66
* To disable caching, set driver to 'array'.
77
*/
8-
'cache' => [
8+
'cache' => [
99
/**
1010
* The cache driver to use.
1111
* You can use any driver supported by Laravel.
@@ -26,10 +26,18 @@
2626
'prefix' => 'locale-via-api:',
2727
],
2828

29+
/**
30+
* Load vendor files.
31+
* If set to true, the package will load the vendor files.
32+
* If set to false, you have to load the vendor files yourself.
33+
* DEFAULT: true
34+
*/
35+
'load_vendor_files' => true,
36+
2937
/**
3038
* Add your supported locales here.
3139
*/
32-
'locales' => [
40+
'locales' => [
3341
'en',
3442
'de',
3543
],

‎src/Controllers/GetLocaleController.php

+115-24
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,145 @@
1010

1111
class GetLocaleController extends Controller
1212
{
13+
private const LOCALE_NOT_FOUND = 'Locale not found.';
14+
1315
/**
16+
* Handle the incoming request.
17+
*
1418
* @throws \Throwable
1519
*/
1620
public function __invoke(string $locale): JsonResponse
1721
{
18-
// Doesn't match our whitelist
19-
abort_unless(in_array($locale, config('locale-via-api.locales'), true), 404);
20-
21-
// Might be on whitelist, but doesn't exist
22-
abort_unless(File::exists(lang_path($locale)), 404);
22+
// Ensure locale is valid and exists
23+
$this->ensureLocaleIsValid($locale);
24+
$this->ensureLocaleExists($locale);
2325

24-
$data = Cache::driver(config('locale-via-api.cache.driver', 'sync'))->remember(
25-
config('locale-via-api.cache.prefix', 'locale-via-api:') . $locale,
26+
// Get cached data or generate it if not present
27+
$data = Cache::driver(config('locale-via-api.cache.driver', 'array'))->remember(
28+
$this->getCacheKey($locale),
2629
config('locale-via-api.cache.duration', 3600),
2730
function () use ($locale) {
28-
return $this->getLocaleData($locale);
29-
});
31+
return $this->getMergedLocaleData($locale);
32+
}
33+
);
3034

31-
return response()->json([
32-
'data' => $data,
33-
'meta' => [
34-
'hash' => md5(json_encode($data)),
35-
],
36-
]);
35+
// Return JSON response
36+
return $this->createJsonResponse($data);
3737
}
3838

39+
/**
40+
* Ensure the locale is valid.
41+
*
42+
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
43+
*/
44+
private function ensureLocaleIsValid(string $locale): void
45+
{
46+
abort_unless(in_array($locale, config('locale-via-api.locales'), true), 404, self::LOCALE_NOT_FOUND);
47+
}
48+
49+
/**
50+
* Ensure the locale directory exists.
51+
*
52+
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
53+
*/
54+
private function ensureLocaleExists(string $locale): void
55+
{
56+
abort_unless(File::exists(lang_path($locale)), 404, self::LOCALE_NOT_FOUND);
57+
}
58+
59+
/**
60+
* Get the cache key for the given locale.
61+
*/
62+
private function getCacheKey(string $locale): string
63+
{
64+
return sprintf('%s%s', config('locale-via-api.cache.prefix', 'locale-via-api:'), $locale);
65+
}
66+
67+
/**
68+
* Get merged locale data.
69+
*/
70+
private function getMergedLocaleData(string $locale): array
71+
{
72+
$data = $this->getLocaleData($locale);
73+
74+
if (config('locale-via-api.load_vendor_files', true)) {
75+
// Get vendor directories
76+
$vendorLocales = File::directories(lang_path('vendor'));
77+
78+
foreach ($vendorLocales as $vendorLocale) {
79+
$vendorName = basename($vendorLocale);
80+
$data = array_merge_recursive(
81+
$data,
82+
$this->getVendorLocaleData(sprintf('vendor/%s/%s', $vendorName, $locale), $vendorName)
83+
);
84+
}
85+
}
86+
87+
ksort($data);
88+
89+
return $data;
90+
}
91+
92+
/**
93+
* Get locale data from files.
94+
*/
3995
protected function getLocaleData(string $locale): array
96+
{
97+
return $this->loadLocaleFiles(lang_path($locale));
98+
}
99+
100+
/**
101+
* Get vendor locale data from files.
102+
*/
103+
protected function getVendorLocaleData(string $path, string $vendorName): array
104+
{
105+
return $this->loadLocaleFiles(lang_path($path), sprintf('vendor.%s', $vendorName));
106+
}
107+
108+
/**
109+
* Load locale files from a given path.
110+
*/
111+
protected function loadLocaleFiles(string $directory, string $prefix = ''): array
40112
{
41113
$data = [];
42-
$files = File::allFiles(lang_path($locale));
43114

44-
foreach ($files as $file) {
45-
$fileName = Str::before($file->getFilename(), '.');
115+
if (! File::exists($directory)) {
116+
return $data;
117+
}
118+
119+
$files = File::allFiles($directory);
46120

47-
// No support for JSON files right now
121+
foreach ($files as $file) {
48122
if ($file->getExtension() !== 'php') {
49123
continue;
50124
}
51125

52-
// This is a directory
53-
if (! Str::is($locale, $file->getRelativePath())) {
54-
$fileName = Str::replace('/', '.', Str::before($file->getRelativePathname(), '.'));
55-
$fileName = Str::replace($locale . '.', '', $fileName);
126+
$relativePath = Str::replaceFirst($directory . DIRECTORY_SEPARATOR, '', $file->getPathname());
127+
$fileName = Str::before($relativePath, '.');
128+
129+
// Convert the relative path to a dot notation key
130+
$key = Str::replace(DIRECTORY_SEPARATOR, '.', $fileName);
131+
132+
if ($prefix) {
133+
$key = sprintf('%s.%s', $prefix, $key);
56134
}
57135

58-
$data[$fileName] = File::getRequire($file);
136+
$data[$key] = File::getRequire($file);
59137
}
60138

61139
return $data;
62140
}
141+
142+
/**
143+
* Create a JSON response.
144+
*/
145+
private function createJsonResponse(array $data): JsonResponse
146+
{
147+
return response()->json([
148+
'data' => $data,
149+
'meta' => [
150+
'hash' => md5(json_encode($data)),
151+
],
152+
]);
153+
}
63154
}

‎tests/Feature/GetLocaleTest.php

+64-7
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22

33
use Empuxa\LocaleViaApi\Controllers\GetLocaleController;
44
use Illuminate\Support\Facades\Cache;
5+
use Illuminate\Support\Facades\File;
56

67
beforeEach(function () {
7-
File::deleteDirectory(lang_path() . '/en');
8+
File::deleteDirectory(lang_path('en'));
9+
File::deleteDirectory(lang_path('vendor/test-plugin'));
810

911
if (! File::exists(lang_path('en'))) {
10-
File::makeDirectory(lang_path() . '/en', 0755, true);
11-
File::put(lang_path() . '/en/test.php', "<?php return ['title' => 'Test'];");
12+
File::makeDirectory(lang_path('en'), 0755, true);
13+
File::makeDirectory(lang_path('vendor/test-plugin/en'), 0755, true);
14+
15+
File::put(lang_path('en/test.php'), "<?php return ['title' => 'Test'];");
1216
}
1317
});
1418

1519
it('uses cache for storing locale data', function () {
1620
$locale = 'en';
1721

18-
$cacheDriver = config('locale-via-api.cache.driver', 'sync');
22+
$cacheDriver = config('locale-via-api.cache.driver', 'array');
1923
$cacheKey = config('locale-via-api.cache.prefix') . $locale;
2024
$cacheDuration = config('locale-via-api.cache.duration');
2125

@@ -40,10 +44,63 @@
4044

4145
$responseData = $response->getData(true);
4246

43-
expect($responseData)->toHaveKeys(['data', 'meta']);
44-
45-
expect($responseData['data'])->toBeArray()->toBe(['test' => ['title' => 'Test']]);
47+
expect($responseData)->toHaveKeys(['data', 'meta'])
48+
->and($responseData['data'])->toBeArray()
49+
->and($responseData['data'])->toBe(['test' => ['title' => 'Test']]);
4650

4751
$expectedHash = md5(json_encode(['test' => ['title' => 'Test']]));
52+
53+
expect($responseData['meta']['hash'])->toEqual($expectedHash);
54+
});
55+
56+
it('returns a json response with correct structure with vendor', function () {
57+
config(['locale-via-api.load_vendor_files' => true]);
58+
59+
File::put(lang_path('vendor/test-plugin/en/vendor-test.php'), "<?php return ['title' => 'Vendor Test'];");
60+
61+
$controller = new GetLocaleController;
62+
$response = $controller('en');
63+
64+
expect($response)->toBeInstanceOf(Illuminate\Http\JsonResponse::class);
65+
66+
$responseData = $response->getData(true);
67+
68+
expect($responseData)->toHaveKeys(['data', 'meta'])
69+
->and($responseData['data'])->toBeArray()
70+
->and($responseData['data'])->toHaveKey('test')
71+
->and($responseData['data'])->toHaveKey('vendor.test-plugin.vendor-test')
72+
->and($responseData['data']['test'])->toBe(['title' => 'Test'])
73+
->and($responseData['data']['vendor.test-plugin.vendor-test'])->toBe(['title' => 'Vendor Test']);
74+
75+
$expectedHash = md5(json_encode([
76+
'test' => ['title' => 'Test'],
77+
'vendor.test-plugin.vendor-test' => ['title' => 'Vendor Test'],
78+
]));
79+
80+
expect($responseData['meta']['hash'])->toEqual($expectedHash);
81+
});
82+
83+
it('returns a json response without vendor files when disabled', function () {
84+
config(['locale-via-api.load_vendor_files' => false]);
85+
86+
File::put(lang_path('vendor/test-plugin/en/vendor-test.php'), "<?php return ['title' => 'Vendor Test'];");
87+
88+
$controller = new GetLocaleController;
89+
$response = $controller('en');
90+
91+
expect($response)->toBeInstanceOf(Illuminate\Http\JsonResponse::class);
92+
93+
$responseData = $response->getData(true);
94+
95+
expect($responseData)->toHaveKeys(['data', 'meta'])
96+
->and($responseData['data'])->toBeArray()
97+
->and($responseData['data'])->toHaveKey('test')
98+
->and($responseData['data'])->not->toHaveKey('vendor.test-plugin.vendor-test')
99+
->and($responseData['data']['test'])->toBe(['title' => 'Test']);
100+
101+
$expectedHash = md5(json_encode([
102+
'test' => ['title' => 'Test'],
103+
]));
104+
48105
expect($responseData['meta']['hash'])->toEqual($expectedHash);
49106
});

0 commit comments

Comments
 (0)
Please sign in to comment.