Skip to content

Commit

Permalink
feat!: read vendor files (#10)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
marcoraddatz and marcoraddatz authored May 16, 2024
1 parent 2fd555b commit 9ce4481
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 33 deletions.
12 changes: 10 additions & 2 deletions config/locale-via-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* We cache the locale for a certain amount of time.
* To disable caching, set driver to 'array'.
*/
'cache' => [
'cache' => [
/**
* The cache driver to use.
* You can use any driver supported by Laravel.
Expand All @@ -26,10 +26,18 @@
'prefix' => 'locale-via-api:',
],

/**
* Load vendor files.
* If set to true, the package will load the vendor files.
* If set to false, you have to load the vendor files yourself.
* DEFAULT: true
*/
'load_vendor_files' => true,

/**
* Add your supported locales here.
*/
'locales' => [
'locales' => [
'en',
'de',
],
Expand Down
139 changes: 115 additions & 24 deletions src/Controllers/GetLocaleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,145 @@

class GetLocaleController extends Controller
{
private const LOCALE_NOT_FOUND = 'Locale not found.';

/**
* Handle the incoming request.
*
* @throws \Throwable
*/
public function __invoke(string $locale): JsonResponse
{
// Doesn't match our whitelist
abort_unless(in_array($locale, config('locale-via-api.locales'), true), 404);

// Might be on whitelist, but doesn't exist
abort_unless(File::exists(lang_path($locale)), 404);
// Ensure locale is valid and exists
$this->ensureLocaleIsValid($locale);
$this->ensureLocaleExists($locale);

$data = Cache::driver(config('locale-via-api.cache.driver', 'sync'))->remember(
config('locale-via-api.cache.prefix', 'locale-via-api:') . $locale,
// Get cached data or generate it if not present
$data = Cache::driver(config('locale-via-api.cache.driver', 'array'))->remember(
$this->getCacheKey($locale),
config('locale-via-api.cache.duration', 3600),
function () use ($locale) {
return $this->getLocaleData($locale);
});
return $this->getMergedLocaleData($locale);
}
);

return response()->json([
'data' => $data,
'meta' => [
'hash' => md5(json_encode($data)),
],
]);
// Return JSON response
return $this->createJsonResponse($data);
}

/**
* Ensure the locale is valid.
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
private function ensureLocaleIsValid(string $locale): void
{
abort_unless(in_array($locale, config('locale-via-api.locales'), true), 404, self::LOCALE_NOT_FOUND);
}

/**
* Ensure the locale directory exists.
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
private function ensureLocaleExists(string $locale): void
{
abort_unless(File::exists(lang_path($locale)), 404, self::LOCALE_NOT_FOUND);
}

/**
* Get the cache key for the given locale.
*/
private function getCacheKey(string $locale): string
{
return sprintf('%s%s', config('locale-via-api.cache.prefix', 'locale-via-api:'), $locale);
}

/**
* Get merged locale data.
*/
private function getMergedLocaleData(string $locale): array
{
$data = $this->getLocaleData($locale);

if (config('locale-via-api.load_vendor_files', true)) {
// Get vendor directories
$vendorLocales = File::directories(lang_path('vendor'));

foreach ($vendorLocales as $vendorLocale) {
$vendorName = basename($vendorLocale);
$data = array_merge_recursive(
$data,
$this->getVendorLocaleData(sprintf('vendor/%s/%s', $vendorName, $locale), $vendorName)
);
}
}

ksort($data);

return $data;
}

/**
* Get locale data from files.
*/
protected function getLocaleData(string $locale): array
{
return $this->loadLocaleFiles(lang_path($locale));
}

/**
* Get vendor locale data from files.
*/
protected function getVendorLocaleData(string $path, string $vendorName): array
{
return $this->loadLocaleFiles(lang_path($path), sprintf('vendor.%s', $vendorName));
}

/**
* Load locale files from a given path.
*/
protected function loadLocaleFiles(string $directory, string $prefix = ''): array
{
$data = [];
$files = File::allFiles(lang_path($locale));

foreach ($files as $file) {
$fileName = Str::before($file->getFilename(), '.');
if (! File::exists($directory)) {
return $data;
}

$files = File::allFiles($directory);

// No support for JSON files right now
foreach ($files as $file) {
if ($file->getExtension() !== 'php') {
continue;
}

// This is a directory
if (! Str::is($locale, $file->getRelativePath())) {
$fileName = Str::replace('/', '.', Str::before($file->getRelativePathname(), '.'));
$fileName = Str::replace($locale . '.', '', $fileName);
$relativePath = Str::replaceFirst($directory . DIRECTORY_SEPARATOR, '', $file->getPathname());
$fileName = Str::before($relativePath, '.');

// Convert the relative path to a dot notation key
$key = Str::replace(DIRECTORY_SEPARATOR, '.', $fileName);

if ($prefix) {
$key = sprintf('%s.%s', $prefix, $key);
}

$data[$fileName] = File::getRequire($file);
$data[$key] = File::getRequire($file);
}

return $data;
}

/**
* Create a JSON response.
*/
private function createJsonResponse(array $data): JsonResponse
{
return response()->json([
'data' => $data,
'meta' => [
'hash' => md5(json_encode($data)),
],
]);
}
}
71 changes: 64 additions & 7 deletions tests/Feature/GetLocaleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

use Empuxa\LocaleViaApi\Controllers\GetLocaleController;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;

beforeEach(function () {
File::deleteDirectory(lang_path() . '/en');
File::deleteDirectory(lang_path('en'));
File::deleteDirectory(lang_path('vendor/test-plugin'));

if (! File::exists(lang_path('en'))) {
File::makeDirectory(lang_path() . '/en', 0755, true);
File::put(lang_path() . '/en/test.php', "<?php return ['title' => 'Test'];");
File::makeDirectory(lang_path('en'), 0755, true);
File::makeDirectory(lang_path('vendor/test-plugin/en'), 0755, true);

File::put(lang_path('en/test.php'), "<?php return ['title' => 'Test'];");
}
});

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

$cacheDriver = config('locale-via-api.cache.driver', 'sync');
$cacheDriver = config('locale-via-api.cache.driver', 'array');
$cacheKey = config('locale-via-api.cache.prefix') . $locale;
$cacheDuration = config('locale-via-api.cache.duration');

Expand All @@ -40,10 +44,63 @@

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

expect($responseData)->toHaveKeys(['data', 'meta']);

expect($responseData['data'])->toBeArray()->toBe(['test' => ['title' => 'Test']]);
expect($responseData)->toHaveKeys(['data', 'meta'])
->and($responseData['data'])->toBeArray()
->and($responseData['data'])->toBe(['test' => ['title' => 'Test']]);

$expectedHash = md5(json_encode(['test' => ['title' => 'Test']]));

expect($responseData['meta']['hash'])->toEqual($expectedHash);
});

it('returns a json response with correct structure with vendor', function () {
config(['locale-via-api.load_vendor_files' => true]);

File::put(lang_path('vendor/test-plugin/en/vendor-test.php'), "<?php return ['title' => 'Vendor Test'];");

$controller = new GetLocaleController;
$response = $controller('en');

expect($response)->toBeInstanceOf(Illuminate\Http\JsonResponse::class);

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

expect($responseData)->toHaveKeys(['data', 'meta'])
->and($responseData['data'])->toBeArray()
->and($responseData['data'])->toHaveKey('test')
->and($responseData['data'])->toHaveKey('vendor.test-plugin.vendor-test')
->and($responseData['data']['test'])->toBe(['title' => 'Test'])
->and($responseData['data']['vendor.test-plugin.vendor-test'])->toBe(['title' => 'Vendor Test']);

$expectedHash = md5(json_encode([
'test' => ['title' => 'Test'],
'vendor.test-plugin.vendor-test' => ['title' => 'Vendor Test'],
]));

expect($responseData['meta']['hash'])->toEqual($expectedHash);
});

it('returns a json response without vendor files when disabled', function () {
config(['locale-via-api.load_vendor_files' => false]);

File::put(lang_path('vendor/test-plugin/en/vendor-test.php'), "<?php return ['title' => 'Vendor Test'];");

$controller = new GetLocaleController;
$response = $controller('en');

expect($response)->toBeInstanceOf(Illuminate\Http\JsonResponse::class);

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

expect($responseData)->toHaveKeys(['data', 'meta'])
->and($responseData['data'])->toBeArray()
->and($responseData['data'])->toHaveKey('test')
->and($responseData['data'])->not->toHaveKey('vendor.test-plugin.vendor-test')
->and($responseData['data']['test'])->toBe(['title' => 'Test']);

$expectedHash = md5(json_encode([
'test' => ['title' => 'Test'],
]));

expect($responseData['meta']['hash'])->toEqual($expectedHash);
});

0 comments on commit 9ce4481

Please sign in to comment.