From 279fe2eca9d970b1f6774a0705035ea71bc3b41a Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Wed, 2 Mar 2022 12:19:52 +0100 Subject: [PATCH] Correzioni minori e aggiornamento a Laravel 9 --- .../Controllers/RequirementsController.php | 7 +- app/Http/Kernel.php | 2 +- app/Http/Middleware/EnsureConfiguration.php | 3 +- app/Http/Middleware/Language.php | 2 +- app/Http/Middleware/TrustProxies.php | 8 +- app/Http/Middleware/VerifyCsrfToken.php | 1 - .../Adapters/AdapterInterface.php | 28 + .../Adapters/LaravelAdapter.php | 40 ++ app/LaravelGettext/Commands/BaseCommand.php | 42 ++ app/LaravelGettext/Commands/GettextCreate.php | 76 +++ app/LaravelGettext/Commands/GettextUpdate.php | 136 ++++ .../Composers/LanguageSelector.php | 86 +++ app/LaravelGettext/Config/ConfigManager.php | 131 ++++ app/LaravelGettext/Config/Models/Config.php | 589 +++++++++++++++++ .../Exceptions/DirectoryNotFoundException.php | 10 + .../Exceptions/FileCreationException.php | 10 + .../LocaleFileNotFoundException.php | 10 + .../LocaleNotSupportedException.php | 10 + .../MissingPhpGettextModuleException.php | 10 + .../RequiredConfigurationFileException.php | 10 + .../RequiredConfigurationKeyException.php | 10 + .../Exceptions/UndefinedDomainException.php | 10 + app/LaravelGettext/Facades/LaravelGettext.php | 18 + .../FileLoader/Cache/ApcuFileCacheLoader.php | 106 +++ .../FileLoader/MoFileLoader.php | 143 ++++ app/LaravelGettext/FileSystem.php | 621 ++++++++++++++++++ app/LaravelGettext/LaravelGettext.php | 195 ++++++ .../LaravelGettextServiceProvider.php | 128 ++++ .../Middleware/GettextMiddleware.php | 29 + app/LaravelGettext/Storages/MemoryStorage.php | 132 ++++ .../Storages/SessionStorage.php | 140 ++++ app/LaravelGettext/Storages/Storage.php | 60 ++ app/LaravelGettext/Support/helpers.php | 124 ++++ .../Testing/Adapter/TestAdapter.php | 53 ++ app/LaravelGettext/Testing/BaseTestCase.php | 40 ++ .../Translators/BaseTranslator.php | 176 +++++ app/LaravelGettext/Translators/Gettext.php | 219 ++++++ app/LaravelGettext/Translators/Symfony.php | 220 +++++++ .../Translators/TranslatorInterface.php | 119 ++++ app/View/Components/Inputs/Number.php | 4 +- composer.json | 37 +- config/app.php | 1 + config/laravel-gettext.php | 8 +- config/trustedproxy.php | 48 -- logs/.htaccess | 1 - .../devcode-it/aggiornamenti/composer.json | 2 +- .../causali-trasporto/composer.json | 2 +- 47 files changed, 3774 insertions(+), 83 deletions(-) create mode 100644 app/LaravelGettext/Adapters/AdapterInterface.php create mode 100644 app/LaravelGettext/Adapters/LaravelAdapter.php create mode 100644 app/LaravelGettext/Commands/BaseCommand.php create mode 100644 app/LaravelGettext/Commands/GettextCreate.php create mode 100644 app/LaravelGettext/Commands/GettextUpdate.php create mode 100644 app/LaravelGettext/Composers/LanguageSelector.php create mode 100644 app/LaravelGettext/Config/ConfigManager.php create mode 100644 app/LaravelGettext/Config/Models/Config.php create mode 100644 app/LaravelGettext/Exceptions/DirectoryNotFoundException.php create mode 100644 app/LaravelGettext/Exceptions/FileCreationException.php create mode 100644 app/LaravelGettext/Exceptions/LocaleFileNotFoundException.php create mode 100644 app/LaravelGettext/Exceptions/LocaleNotSupportedException.php create mode 100644 app/LaravelGettext/Exceptions/MissingPhpGettextModuleException.php create mode 100644 app/LaravelGettext/Exceptions/RequiredConfigurationFileException.php create mode 100644 app/LaravelGettext/Exceptions/RequiredConfigurationKeyException.php create mode 100644 app/LaravelGettext/Exceptions/UndefinedDomainException.php create mode 100644 app/LaravelGettext/Facades/LaravelGettext.php create mode 100644 app/LaravelGettext/FileLoader/Cache/ApcuFileCacheLoader.php create mode 100644 app/LaravelGettext/FileLoader/MoFileLoader.php create mode 100644 app/LaravelGettext/FileSystem.php create mode 100644 app/LaravelGettext/LaravelGettext.php create mode 100644 app/LaravelGettext/LaravelGettextServiceProvider.php create mode 100644 app/LaravelGettext/Middleware/GettextMiddleware.php create mode 100644 app/LaravelGettext/Storages/MemoryStorage.php create mode 100644 app/LaravelGettext/Storages/SessionStorage.php create mode 100644 app/LaravelGettext/Storages/Storage.php create mode 100644 app/LaravelGettext/Support/helpers.php create mode 100644 app/LaravelGettext/Testing/Adapter/TestAdapter.php create mode 100644 app/LaravelGettext/Testing/BaseTestCase.php create mode 100644 app/LaravelGettext/Translators/BaseTranslator.php create mode 100644 app/LaravelGettext/Translators/Gettext.php create mode 100644 app/LaravelGettext/Translators/Symfony.php create mode 100644 app/LaravelGettext/Translators/TranslatorInterface.php delete mode 100644 config/trustedproxy.php delete mode 100755 logs/.htaccess diff --git a/app/Http/Controllers/RequirementsController.php b/app/Http/Controllers/RequirementsController.php index 521b1b475..e6f14effe 100644 --- a/app/Http/Controllers/RequirementsController.php +++ b/app/Http/Controllers/RequirementsController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\Str; use Util\FileSystem; class RequirementsController extends Controller @@ -42,7 +43,7 @@ class RequirementsController extends Controller $list = config('requirements'); if (!empty($file)) { $file = realpath($file); - if (string_starts_with($file)) { + if (Str::startsWith($file)) { $list = include $file; } } @@ -89,9 +90,9 @@ class RequirementsController extends Controller $ini = FileSystem::convertBytes($value); $real = FileSystem::convertBytes($suggested); - if (starts_with($values['suggested'], '>')) { + if (Str::startsWith($values['suggested'], '>')) { $status = $ini >= substr($real, 1); - } elseif (starts_with($values['suggested'], '<')) { + } elseif (Str::startsWith($values['suggested'], '<')) { $status = $ini <= substr($real, 1); } else { $status = ($real == $ini); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 0fd745516..e3b6c28fa 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -21,7 +21,7 @@ class Kernel extends HttpKernel protected $middleware = [ \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, - \Fruitcake\Cors\HandleCors::class, + \Illuminate\Http\Middleware\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, diff --git a/app/Http/Middleware/EnsureConfiguration.php b/app/Http/Middleware/EnsureConfiguration.php index 49548d270..caf9b8b75 100644 --- a/app/Http/Middleware/EnsureConfiguration.php +++ b/app/Http/Middleware/EnsureConfiguration.php @@ -9,6 +9,7 @@ use App\Http\Controllers\UpdateController; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Str; class EnsureConfiguration { @@ -33,7 +34,7 @@ class EnsureConfiguration public function handle(Request $request, Closure $next) { $route = $request->route(); - if (starts_with($route->parameter('path'), 'assets')) { + if (Str::startsWith($route->parameter('path'), 'assets')) { return $next($request); } diff --git a/app/Http/Middleware/Language.php b/app/Http/Middleware/Language.php index c5cf50781..2f503134e 100644 --- a/app/Http/Middleware/Language.php +++ b/app/Http/Middleware/Language.php @@ -4,7 +4,7 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; -use Xinax\LaravelGettext\Facades\LaravelGettext; +use App\LaravelGettext\Facades\LaravelGettext; class Language { diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 14befceb0..9f962fde6 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -2,7 +2,7 @@ namespace App\Http\Middleware; -use Fideloper\Proxy\TrustProxies as Middleware; +use Illuminate\Http\Middleware\TrustProxies as Middleware; use Illuminate\Http\Request; class TrustProxies extends Middleware @@ -19,5 +19,9 @@ class TrustProxies extends Middleware * * @var int */ - protected $headers = Request::HEADER_X_FORWARDED_ALL; + protected $headers = Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB; } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 173185d6b..4c029cf39 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,5 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - 'legacy', ]; } diff --git a/app/LaravelGettext/Adapters/AdapterInterface.php b/app/LaravelGettext/Adapters/AdapterInterface.php new file mode 100644 index 000000000..4802299e6 --- /dev/null +++ b/app/LaravelGettext/Adapters/AdapterInterface.php @@ -0,0 +1,28 @@ +fileSystem = new FileSystem( + $configManager->get(), + app_path(), + storage_path() + ); + + $this->configuration = $configManager->get(); + } +} diff --git a/app/LaravelGettext/Commands/GettextCreate.php b/app/LaravelGettext/Commands/GettextCreate.php new file mode 100644 index 000000000..f7b0006d0 --- /dev/null +++ b/app/LaravelGettext/Commands/GettextCreate.php @@ -0,0 +1,76 @@ +prepare(); + + // Directories created counter + $dirCreations = 0; + + try { + // Locales + $localesGenerated = $this->fileSystem->generateLocales(); + + foreach ($localesGenerated as $localePath) { + $this->comment(sprintf("Locale directory created (%s)", $localePath)); + $dirCreations++; + } + + $this->info("Finished"); + + $msg = "The directory structure is right. No directory creation were needed."; + + if ($dirCreations) { + $msg = $dirCreations . " directories has been created."; + } + + $this->info($msg); + + } catch (\Exception $e) { + $this->error($e->getFile() . ":" . $e->getLine() . " - " . $e->getMessage()); + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return []; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return []; + } +} diff --git a/app/LaravelGettext/Commands/GettextUpdate.php b/app/LaravelGettext/Commands/GettextUpdate.php new file mode 100644 index 000000000..9fd0420db --- /dev/null +++ b/app/LaravelGettext/Commands/GettextUpdate.php @@ -0,0 +1,136 @@ +prepare(); + + $domainPath = $this->fileSystem->getDomainPath(); + $fileSystem = $this->fileSystem; + + try { + // Translation files base path + if (!file_exists($domainPath)) { + throw new DirectoryNotFoundException( + "You need to call gettext:create (No locale directory)" + ); + } + + $count = [ + 'added' => 0, + 'updated' => 0, + ]; + + $domains = $this->configuration->getAllDomains(); + + foreach ($this->configuration->getSupportedLocales() as $locale) { + $localePath = $this->fileSystem->getDomainPath($locale); + + // Create new locale + if (!file_exists($localePath)) { + $this->fileSystem->addLocale($localePath, $locale); + $this->comment("New locale was added: $locale ($localePath)"); + + $count['added']++; + + continue; + } + + // Domain by command line argument + if ($this->option('domain')) { + $domains = [$this->option('domain')]; + } + + // Update by domain(s) + foreach ($domains as $domain) { + $fileSystem->updateLocale( + $localePath, + $locale, + $domain + ); + + $this->comment( + sprintf( + "PO file for locale: %s/%s updated successfully", + $locale, + $domain + ) + ); + + $count['updated']++; + } + } + + $this->info("Finished"); + + // Number of locales created + if ($count['added'] > 0) { + $this->info(sprintf('%s new locales were added.', $count['added'])); + } + + + // Number of locales updated + if ($count['updated'] > 0) { + $this->info(sprintf('%s locales updated.', $count['updated'])); + } + + } catch (Exception $e) { + $this->error($e->getFile() . ":" . $e->getLine() . " = " . $e->getMessage()); + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return []; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + [ + 'domain', + '-d', + InputOption::VALUE_OPTIONAL, + 'Update files only for this domain', + null, + ] + ]; + } +} diff --git a/app/LaravelGettext/Composers/LanguageSelector.php b/app/LaravelGettext/Composers/LanguageSelector.php new file mode 100644 index 000000000..dd7724d5a --- /dev/null +++ b/app/LaravelGettext/Composers/LanguageSelector.php @@ -0,0 +1,86 @@ +labels = $labels; + $this->gettext = $gettext; + } + + /** + * @param LaravelGettext $gettext + * @param array $labels + * @return LanguageSelector + */ + public static function create(LaravelGettext $gettext, $labels = []) + { + return new LanguageSelector($gettext, $labels); + } + + /** + * Renders the language selector + * @return string + */ + public function render() + { + /** @var string $currentLocale */ + $currentLocale = $this->gettext->getLocale(); + + $html = ''; + + return $html; + } + + /** + * Convert to string + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/app/LaravelGettext/Config/ConfigManager.php b/app/LaravelGettext/Config/ConfigManager.php new file mode 100644 index 000000000..57f274f95 --- /dev/null +++ b/app/LaravelGettext/Config/ConfigManager.php @@ -0,0 +1,131 @@ +config = $this->generateFromArray($config); + } else { + // In Laravel 5.3 we need empty config model + $this->config = new ConfigModel; + } + } + + /** + * Get new instance of the ConfigManager + * + * @param null $config + * @return static + * @throws RequiredConfigurationFileException + */ + public static function create($config = null) + { + if (is_null($config)) { + // Default package configuration file (published) + $config = Config::get(static::DEFAULT_PACKAGE_CONFIG); + } + + return new static($config); + } + + /** + * Get the config model + * + * @return ConfigModel + */ + public function get() + { + return $this->config; + } + + /** + * Creates a configuration container and checks the required fields + * + * @param array $config + * @return ConfigModel + * @throws RequiredConfigurationKeyException + */ + protected function generateFromArray(array $config) + { + $requiredKeys = [ + 'locale', + 'fallback-locale', + 'encoding' + ]; + + foreach ($requiredKeys as $key) { + if (!array_key_exists($key, $config)) { + throw new RequiredConfigurationKeyException( + sprintf('Unconfigured required value: %s', $key) + ); + } + } + + $container = new ConfigModel(); + + $id = isset($config['session-identifier']) ? $config['session-identifier'] : 'laravel-gettext-locale'; + + $adapter = isset($config['adapter']) ? $config['adapter'] : \App\LaravelGettext\Adapters\LaravelAdapter::class; + + $storage = isset($config['storage']) ? $config['storage'] : SessionStorage::class; + + $container->setLocale($config['locale']) + ->setSessionIdentifier($id) + ->setEncoding($config['encoding']) + ->setCategories(Arr::get('categories', $config, ['LC_ALL'])) + ->setFallbackLocale($config['fallback-locale']) + ->setSupportedLocales($config['supported-locales']) + ->setDomain($config['domain']) + ->setTranslationsPath($config['translations-path']) + ->setProject($config['project']) + ->setTranslator($config['translator']) + ->setSourcePaths($config['source-paths']) + ->setSyncLaravel($config['sync-laravel']) + ->setAdapter($adapter) + ->setStorage($storage); + + if (array_key_exists('relative-path', $config)) { + $container->setRelativePath($config['relative-path']); + } + + if (array_key_exists("custom-locale", $config)) { + $container->setCustomLocale($config['custom-locale']); + } + + if (array_key_exists("keywords-list", $config)) { + $container->setKeywordsList($config['keywords-list']); + } + + if (array_key_exists("handler", $config)) { + $container->setHandler($config['handler']); + } + + return $container; + } +} diff --git a/app/LaravelGettext/Config/Models/Config.php b/app/LaravelGettext/Config/Models/Config.php new file mode 100644 index 000000000..1c3b3edf1 --- /dev/null +++ b/app/LaravelGettext/Config/Models/Config.php @@ -0,0 +1,589 @@ +encoding = 'UTF-8'; + $this->supportedLocales = []; + $this->sourcePaths = []; + $this->customLocale = false; + $this->relativePath = "../../../../../app"; + } + + public function getRelativePath() + { + return $this->relativePath; + } + + public function setRelativePath($path) + { + $this->relativePath = $path; + } + + /** + * @return string + */ + public function getSessionIdentifier() + { + return $this->sessionIdentifier; + } + + /** + * @param string $sessionIdentifier + * + * @return $this + */ + public function setSessionIdentifier($sessionIdentifier) + { + $this->sessionIdentifier = $sessionIdentifier; + + return $this; + } + + /** + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $encoding + * + * @return $this + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + + return $this; + } + + /** + * @return string + */ + public function getLocale() + { + return $this->locale; + } + + /** + * @param string $locale + * + * @return $this + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * Gets categories + * + * @return array + */ + public function getCategories() + { + return $this->categories; + } + + /** + * Sets categories + * + * @param array $categories + * + * @return self + */ + public function setCategories($categories) + { + $this->categories = $categories; + + return $this; + } + + /** + * @return string + */ + public function getFallbackLocale() + { + return $this->fallbackLocale; + } + + /** + * @param string $fallbackLocale + * + * @return $this + */ + public function setFallbackLocale($fallbackLocale) + { + $this->fallbackLocale = $fallbackLocale; + + return $this; + } + + /** + * @return array + */ + public function getSupportedLocales() + { + return $this->supportedLocales; + } + + /** + * @param array $supportedLocales + * + * @return $this + */ + public function setSupportedLocales($supportedLocales) + { + $this->supportedLocales = $supportedLocales; + + return $this; + } + + /** + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * @param string $domain + * + * @return $this + */ + public function setDomain($domain) + { + $this->domain = $domain; + + return $this; + } + + /** + * @return string + */ + public function getTranslationsPath() + { + return $this->translationsPath; + } + + /** + * @param string $translationsPath + * + * @return $this + */ + public function setTranslationsPath($translationsPath) + { + $this->translationsPath = $translationsPath; + + return $this; + } + + /** + * @return string + */ + public function getProject() + { + return $this->project; + } + + /** + * @param string $project + * + * @return $this + */ + public function setProject($project) + { + $this->project = $project; + + return $this; + } + + /** + * @return string + */ + public function getTranslator() + { + return $this->translator; + } + + /** + * @param string $translator + * + * @return $this + */ + public function setTranslator($translator) + { + $this->translator = $translator; + + return $this; + } + + /** + * @return array + */ + public function getSourcePaths() + { + return $this->sourcePaths; + } + + /** + * @param array $sourcePaths + * + * @return $this + */ + public function setSourcePaths($sourcePaths) + { + $this->sourcePaths = $sourcePaths; + + return $this; + } + + /** + * @return boolean + */ + public function isSyncLaravel() + { + return $this->syncLaravel; + } + + /** + * Gets the Sync with laravel locale. + * + * @return mixed + */ + public function getSyncLaravel() + { + return $this->syncLaravel; + } + + /** + * @param boolean $syncLaravel + * + * @return $this + */ + public function setSyncLaravel($syncLaravel) + { + $this->syncLaravel = $syncLaravel; + + return $this; + } + + /** + * Gets the adapter class. + * + * @return string + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * @param string $adapter + * + * @return $this + */ + public function setAdapter($adapter) + { + $this->adapter = $adapter; + + return $this; + } + + /** + * Getter for storage + * + * @return string + */ + public function getStorage() + { + return $this->storage; + } + + /** + * @param string $storage + * + * @return $this + */ + public function setStorage($storage) + { + $this->storage = $storage; + + return $this; + } + + + + /** + * Return an array with all domain names + * + * @return array + */ + public function getAllDomains() + { + $domains = [$this->domain]; // add the default domain + + foreach ($this->sourcePaths as $domain => $paths) { + if (is_array($paths)) { + array_push($domains, $domain); + } + } + + return array_unique($domains); + } + + /** + * Return all routes for a single domain + * + * @param $domain + * + * @return array + */ + public function getSourcesFromDomain($domain) + { + // grab any paths wrapped in $domain + $explicitPaths = array_key_exists($domain, $this->sourcePaths) + ? $this->sourcePaths[$domain] + : []; + + // if we're not including the default domain, return what we have so far + if ($this->domain != $domain) { + return $explicitPaths; + } + + // otherwise, grab all the default domain paths + // and merge them with paths wrapped in $domain + return array_reduce( + $this->sourcePaths, + function ($carry, $path) { + if (!is_array($path)) { + $carry[] = $path; + } + + return $carry; + }, + $explicitPaths + ); + } + + /** + * Gets C locale setting. + * + * @return boolean + */ + public function getCustomLocale() + { + return $this->customLocale; + } + + /** + * Sets if will use C locale structure. + * + * @param mixed $sourcePaths the source paths + * + * @return self + */ + public function setCustomLocale($customLocale) + { + $this->customLocale = $customLocale; + + return $this; + } + + /** + * Gets the Poedit keywords list. + * + * @return mixed + */ + public function getKeywordsList() + { + return !empty($this->keywordsList) + ? $this->keywordsList + : ['_']; + } + + /** + * Sets the Poedit keywords list. + * + * @param mixed $keywordsList the keywords list + * + * @return self + */ + public function setKeywordsList($keywordsList) + { + $this->keywordsList = $keywordsList; + + return $this; + } + + /** + * Sets the handler type. Also check for valid handler name + * + * @param $handler + * + * @return $this + * + * @throws \Exception + */ + public function setHandler($handler) + { + if (!in_array($handler, [ + 'symfony', + 'gettext', + ]) + ) { + throw new \Exception("Handler '$handler' is not supported'"); + }; + + $this->handler = $handler; + + return $this; + } + + /** + * Returns the handler name + * + * @return mixed + */ + public function getHandler() + { + return !empty($this->handler) + ? $this->handler + : 'symfony'; + } +} diff --git a/app/LaravelGettext/Exceptions/DirectoryNotFoundException.php b/app/LaravelGettext/Exceptions/DirectoryNotFoundException.php new file mode 100644 index 000000000..ebb72cdf3 --- /dev/null +++ b/app/LaravelGettext/Exceptions/DirectoryNotFoundException.php @@ -0,0 +1,10 @@ +underlyingFileLoader = $underlyingFileLoader; + } + + + /** + * @param string $resource + * + * @return array + * + * @throws InvalidResourceException if stream content has an invalid format + */ + protected function loadResource(string $resource): array + { + if (!extension_loaded('apcu')) { + return $this->underlyingFileLoader->loadResource($resource); + } + + return $this->cachedMessages($resource); + } + + /** + * Calculate the checksum for the file + * + * @param $resource + * + * @return string + */ + private function checksum($resource) + { + return filemtime($resource) . '-' . filesize($resource); + } + + /** + * Checksum saved in cache + * + * @param $resource + * + * @return string + */ + private function cacheChecksum($resource) + { + return apcu_fetch($resource . '-checksum'); + } + + /** + * Set the cache checksum + * + * @param $resource + * @param $checksum + * + * @return array|bool + */ + private function setCacheChecksum($resource, $checksum) + { + return apcu_store($resource . '-checksum', $checksum); + } + + /** + * Return the cached messages + * + * @param $ressource + * + * @return array + */ + private function cachedMessages($ressource) + { + if ($this->cacheChecksum($ressource) == ($currentChecksum = $this->checksum($ressource))) { + return apcu_fetch($ressource . '-messages'); + } + + $messages = $this->underlyingFileLoader->loadResource($ressource); + + apcu_store($ressource . '-messages', $messages); + $this->setCacheChecksum($ressource, $currentChecksum); + + return $messages; + } +} diff --git a/app/LaravelGettext/FileLoader/MoFileLoader.php b/app/LaravelGettext/FileLoader/MoFileLoader.php new file mode 100644 index 000000000..f330a7cb4 --- /dev/null +++ b/app/LaravelGettext/FileLoader/MoFileLoader.php @@ -0,0 +1,143 @@ +readLong($stream, $isBigEndian); + $count = $this->readLong($stream, $isBigEndian); + $offsetId = $this->readLong($stream, $isBigEndian); + $offsetTranslated = $this->readLong($stream, $isBigEndian); + // sizeHashes + $this->readLong($stream, $isBigEndian); + // offsetHashes + $this->readLong($stream, $isBigEndian); + + $messages = array(); + + for ($i = 0; $i < $count; ++$i) { + $pluralId = null; + $translated = null; + + fseek($stream, $offsetId + $i * 8); + + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $singularId = fread($stream, $length); + + if (strpos($singularId, "\000") !== false) { + list($singularId, $pluralId) = explode("\000", $singularId); + } + + fseek($stream, $offsetTranslated + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $translated = fread($stream, $length); + + if (strpos($translated, "\000") !== false) { + $translated = explode("\000", $translated); + } + + $ids = array('singular' => $singularId, 'plural' => $pluralId); + $item = compact('ids', 'translated'); + + if (is_array($item['translated'])) { + $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); + if (isset($item['ids']['plural'])) { + $messages[$item['ids']['plural']] = stripcslashes(implode('|', $item['translated'])); + } + } elseif (!empty($item['ids']['singular'])) { + $messages[$item['ids']['singular']] = stripcslashes($item['translated']); + } + } + + fclose($stream); + + return array_filter($messages); + } + + /** + * Reads an unsigned long from stream respecting endianess. + * + * @param resource $stream + * @param bool $isBigEndian + * + * @return int + */ + private function readLong($stream, $isBigEndian) + { + $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); + $result = current($result); + + return (int) substr($result, -8); + } +} diff --git a/app/LaravelGettext/FileSystem.php b/app/LaravelGettext/FileSystem.php new file mode 100644 index 000000000..2bbb6eb57 --- /dev/null +++ b/app/LaravelGettext/FileSystem.php @@ -0,0 +1,621 @@ +configuration = $config; + $this->basePath = $basePath; + + $this->storagePath = $storagePath; + $this->storageContainer = "framework"; + $this->folderName = 'i18n'; + } + + /** + * Build views in order to parse php files + * + * @param Array $viewPaths + * @param String $domain + * + * @return Boolean status + */ + /** + * Build views in order to parse php files + * + * @param array $viewPaths + * @param string $domain + * @return bool + * @throws FileCreationException + */ + public function compileViews(array $viewPaths, $domain) + { + // Check the output directory + $targetDir = $this->storagePath . DIRECTORY_SEPARATOR . $this->storageContainer; + + if (!file_exists($targetDir)) { + $this->createDirectory($targetDir); + } + + // Domain separation + $domainDir = $targetDir . DIRECTORY_SEPARATOR . $domain; + $this->clearDirectory($domainDir); + $this->createDirectory($domainDir); + + foreach ($viewPaths as $path) { + $path = $this->basePath . DIRECTORY_SEPARATOR . $path; + + if (!$realPath = realPath($path)) { + throw new Exceptions\DirectoryNotFoundException("Failed to resolve $path, please check that it exists"); + } + + $fs = new \Illuminate\Filesystem\Filesystem($path); + $files = $fs->allFiles($realPath); + + $compiler = new \Illuminate\View\Compilers\BladeCompiler($fs, $domainDir); + + foreach ($files as $file) { + $filePath = $file->getRealPath(); + $compiler->setPath($filePath); + + $contents = $compiler->compileString($fs->get($filePath)); + + $compiledPath = $compiler->getCompiledPath($compiler->getPath()); + + $fs->put( + $compiledPath . '.php', + $contents + ); + } + } + + return true; + } + + /** + * Constructs and returns the full path to the translation files + * + * @param null $append + * @return string + */ + public function getDomainPath($append = null) + { + $path = [ + $this->basePath, + $this->configuration->getTranslationsPath(), + $this->folderName, + ]; + + if (!is_null($append)) { + array_push($path, $append); + } + + return implode(DIRECTORY_SEPARATOR, $path); + } + + /** + * Creates a configured .po file on $path + * If PHP are not able to create the file the content will be returned instead + * + * @param string $path + * @param string $locale + * @param string $domain + * @param bool|true $write + * @return int|string + */ + public function createPOFile($path, $locale, $domain, $write = true) + { + $project = $this->configuration->getProject(); + $timestamp = date("Y-m-d H:iO"); + $translator = $this->configuration->getTranslator(); + $encoding = $this->configuration->getEncoding(); + + $relativePath = $this->configuration->getRelativePath(); + + $keywords = implode(';', $this->configuration->getKeywordsList()); + + $template = 'msgid ""' . "\n"; + $template .= 'msgstr ""' . "\n"; + $template .= '"Project-Id-Version: ' . $project . '\n' . "\"\n"; + $template .= '"POT-Creation-Date: ' . $timestamp . '\n' . "\"\n"; + $template .= '"PO-Revision-Date: ' . $timestamp . '\n' . "\"\n"; + $template .= '"Last-Translator: ' . $translator . '\n' . "\"\n"; + $template .= '"Language-Team: ' . $translator . '\n' . "\"\n"; + $template .= '"Language: ' . $locale . '\n' . "\"\n"; + $template .= '"MIME-Version: 1.0' . '\n' . "\"\n"; + $template .= '"Content-Type: text/plain; charset=' . $encoding . '\n' . "\"\n"; + $template .= '"Content-Transfer-Encoding: 8bit' . '\n' . "\"\n"; + $template .= '"X-Generator: Poedit 1.5.4' . '\n' . "\"\n"; + $template .= '"X-Poedit-KeywordsList: ' . $keywords . '\n' . "\"\n"; + $template .= '"X-Poedit-Basepath: ' . $relativePath . '\n' . "\"\n"; + $template .= '"X-Poedit-SourceCharset: ' . $encoding . '\n' . "\"\n"; + + // Source paths + $sourcePaths = $this->configuration->getSourcesFromDomain($domain); + + // Compiled views on paths + if (count($sourcePaths)) { + + // View compilation + $this->compileViews($sourcePaths, $domain); + array_push($sourcePaths, $this->getStorageForDomain($domain)); + + $i = 0; + + foreach ($sourcePaths as $sourcePath) { + $template .= '"X-Poedit-SearchPath-' . $i . ': ' . $sourcePath . '\n' . "\"\n"; + $i++; + } + + } + + if (!$write) { + return $template . "\n"; + } + + // File creation + $file = fopen($path, "w"); + $result = fwrite($file, $template); + fclose($file); + + return $result; + } + + /** + * Validate if the directory can be created + * + * @param $path + * @throws FileCreationException + */ + protected function createDirectory($path) + { + if (!file_exists($path) && !mkdir($path)) { + throw new FileCreationException( + sprintf('Can\'t create the directory: %s', $path) + ); + } + } + + /** + * Adds a new locale directory + .po file + * + * @param String $localePath + * @param String $locale + * @throws FileCreationException + */ + public function addLocale($localePath, $locale) + { + $data = array( + $localePath, + "LC_MESSAGES" + ); + + if (!file_exists($localePath)) { + $this->createDirectory($localePath); + } + + if ($this->configuration->getCustomLocale()) { + $data[1] = 'C'; + + $gettextPath = implode(DIRECTORY_SEPARATOR, $data); + if (!file_exists($gettextPath)) { + $this->createDirectory($gettextPath); + } + + $data[2] = 'LC_MESSAGES'; + } + + $gettextPath = implode(DIRECTORY_SEPARATOR, $data); + if (!file_exists($gettextPath)) { + $this->createDirectory($gettextPath); + } + + + // File generation for each domain + foreach ($this->configuration->getAllDomains() as $domain) { + $data[3] = $domain . ".po"; + + $localePOPath = implode(DIRECTORY_SEPARATOR, $data); + + if (!$this->createPOFile($localePOPath, $locale, $domain)) { + throw new FileCreationException( + sprintf('Can\'t create the file: %s', $localePOPath) + ); + } + + } + + } + + /** + * Update the .po file headers by domain + * (mainly source-file paths) + * + * @param $localePath + * @param $locale + * @param $domain + * @return bool + * @throws LocaleFileNotFoundException + */ + public function updateLocale($localePath, $locale, $domain) + { + $data = [ + $localePath, + "LC_MESSAGES", + $domain . ".po", + ]; + + if ($this->configuration->getCustomLocale()) { + $customLocale = array('C'); + array_splice($data, 1, 0, $customLocale); + } + + $localePOPath = implode(DIRECTORY_SEPARATOR, $data); + + if (!file_exists($localePOPath) || !$localeContents = file_get_contents($localePOPath)) { + throw new LocaleFileNotFoundException( + sprintf('Can\'t read %s verify your locale structure', $localePOPath) + ); + } + + $newHeader = $this->createPOFile( + $localePOPath, + $locale, + $domain, + false + ); + + // Header replacement + $localeContents = preg_replace('/^([^#])+:?/', $newHeader, $localeContents); + + if (!file_put_contents($localePOPath, $localeContents)) { + throw new LocaleFileNotFoundException( + sprintf('Can\'t write on %s', $localePOPath) + ); + } + + return true; + } + + /** + * Return the relative path from a file or directory to anothe + * + * @param string $from + * @param string $to + * @return string + * @author Laurent Goussard + */ + public function getRelativePath($from, $to) + { + // Compatibility fixes for Windows paths + $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; + $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; + $from = str_replace('\\', '/', $from); + $to = str_replace('\\', '/', $to); + + $from = explode('/', $from); + $to = explode('/', $to); + $relPath = $to; + + foreach ($from as $depth => $dir) { + if ($dir !== $to[$depth]) { + // Number of remaining directories + $remaining = count($from) - $depth; + + if ($remaining > 1) { + // Add traversals up to first matching directory + $padLength = (count($relPath) + $remaining - 1) * -1; + + $relPath = array_pad( + $relPath, + $padLength, + '..' + ); + + break; + } + + $relPath[0] = './' . $relPath[0]; + } + + array_shift($relPath); + } + + return implode('/', $relPath); + + } + + /** + * Checks the required directory + * Optionally checks each local directory, if $checkLocales is true + * + * @param bool|false $checkLocales + * @return bool + * @throws DirectoryNotFoundException + */ + public function checkDirectoryStructure($checkLocales = false) + { + // Application base path + if (!file_exists($this->basePath)) { + throw new Exceptions\DirectoryNotFoundException( + sprintf( + 'Missing root path directory: %s, check the \'base-path\' key in your configuration.', + $this->basePath + ) + ); + } + + // Domain path + $domainPath = $this->getDomainPath(); + + // Translation files domain path + if (!file_exists($domainPath)) { + throw new Exceptions\DirectoryNotFoundException( + sprintf( + 'Missing base required directory: %s, remember to run \'artisan gettext:create\' the first time', + $domainPath + ) + ); + } + + if (!$checkLocales) { + return true; + } + + foreach ($this->configuration->getSupportedLocales() as $locale) { + // Default locale is not needed + if ($locale == $this->configuration->getLocale()) { + continue; + } + + $localePath = $this->getDomainPath($locale); + + if (!file_exists($localePath)) { + throw new Exceptions\DirectoryNotFoundException( + sprintf( + 'Missing locale required directory: %s, maybe you forgot to run \'artisan gettext:update\'', + $locale + ) + ); + } + } + + return true; + } + + /** + * Creates the localization directories and files by domain + * + * @return array + * @throws FileCreationException + */ + public function generateLocales() + { + // Application base path + if (!file_exists($this->getDomainPath())) { + $this->createDirectory($this->getDomainPath()); + } + $localePaths = []; + + // Locale directories + foreach ($this->configuration->getSupportedLocales() as $locale) { + $localePath = $this->getDomainPath($locale); + + if (!file_exists($localePath)) { + // Locale directory is created + $this->addLocale($localePath, $locale); + + array_push($localePaths, $localePath); + + } + } + + return $localePaths; + } + + + /** + * Gets the package configuration model. + * + * @return Config + */ + public function getConfiguration() + { + return $this->configuration; + } + + /** + * Set the package configuration model + * + * @param Config $configuration + * @return $this + */ + public function setConfiguration(Config $configuration) + { + $this->configuration = $configuration; + return $this; + } + + /** + * Get the filesystem base path + * + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * Set the filesystem base path + * + * @param $basePath + * @return $this + */ + public function setBasePath($basePath) + { + $this->basePath = $basePath; + return $this; + } + + /** + * Get the storage path + * + * @return string + */ + public function getStoragePath() + { + return $this->storagePath; + } + + /** + * Set the storage path + * + * @param $storagePath + * @return $this + */ + public function setStoragePath($storagePath) + { + $this->storagePath = $storagePath; + return $this; + } + + /** + * Get the full path for domain storage directory + * + * @param $domain + * @return String + */ + public function getStorageForDomain($domain) + { + $domainPath = $this->storagePath . + DIRECTORY_SEPARATOR . + $this->storageContainer . + DIRECTORY_SEPARATOR . + $domain; + + return $this->getRelativePath($this->basePath, $domainPath); + } + + /** + * Removes the directory contents recursively + * + * @param string $path + * @return null|boolean + */ + public static function clearDirectory($path) + { + if (!file_exists($path)) { + return null; + } + + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileinfo) { + // if the file isn't a .gitignore file we should remove it. + if ($fileinfo->getFilename() !== '.gitignore') { + $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink'); + $todo($fileinfo->getRealPath()); + } + } + + // since the folder now contains a .gitignore we can't remove it + //rmdir($path); + return true; + } + + /** + * Get the folder name + * + * @return string + */ + public function getFolderName() + { + return $this->folderName; + } + + /** + * Set the folder name + * + * @param $folderName + */ + public function setFolderName($folderName) + { + $this->folderName = $folderName; + } + + /** + * Returns the full path for a .po/.mo file from its domain and locale + * + * @param $locale + * @param $domain + * + * @param string $type + * + * @return string + */ + public function makeFilePath($locale, $domain, $type = 'po') + { + $filePath = implode( + DIRECTORY_SEPARATOR, [ + $locale, + 'LC_MESSAGES', + $domain . "." . $type + ] + ); + + return $this->getDomainPath($filePath); + } + +} diff --git a/app/LaravelGettext/LaravelGettext.php b/app/LaravelGettext/LaravelGettext.php new file mode 100644 index 000000000..51ab3eea8 --- /dev/null +++ b/app/LaravelGettext/LaravelGettext.php @@ -0,0 +1,195 @@ +translator = $gettext; + } + + /** + * Get the current encoding + * + * @return string + */ + public function getEncoding() + { + return $this->translator->getEncoding(); + } + + /** + * Set the current encoding + * + * @param string $encoding + * @return $this + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Gets the Current locale. + * + * @return string + */ + public function getLocale() + { + return $this->translator->getLocale(); + } + + /** + * Set current locale + * + * @param string $locale + * @return $this + * @throws Exceptions\LocaleNotSupportedException + * @throws \Exception + */ + public function setLocale($locale) + { + if ($locale != $this->getLocale()) { + $this->translator->setLocale($locale); + } + + return $this; + } + + /** + * Get the language portion of the locale + * (ex. en_GB returns en) + * + * @param string|null $locale + * @return string|null + */ + public function getLocaleLanguage($locale = null) + { + if (is_null($locale)) { + $locale = $this->getLocale(); + } + + $localeArray = explode('_', $locale); + + if (!isset($localeArray[0])) { + return null; + } + + return $localeArray[0]; + } + + /** + * Get the language selector object + * + * @param array $labels + * @return LanguageSelector + */ + public function getSelector($labels = []) + { + return LanguageSelector::create($this, $labels); + } + + /** + * Sets the current domain + * + * @param string $domain + * @return $this + */ + public function setDomain($domain) + { + $this->translator->setDomain($domain); + return $this; + } + + /** + * Returns the current domain + * + * @return string + */ + public function getDomain() + { + return $this->translator->getDomain(); + } + + /** + * Translates a message with the current handler + * + * @param $message + * @return string + */ + public function translate($message) + { + return $this->translator->translate($message); + } + + /** + * Translates a plural string with the current handler + * + * @param $singular + * @param $plural + * @param $count + * @return string + */ + public function translatePlural($singular, $plural, $count) + { + return $this->translator->translatePlural($singular, $plural, $count); + } + + /** + * Returns the translator. + * + * @return TranslatorInterface + */ + public function getTranslator() + { + return $this->translator; + } + + /** + * Sets the translator + * + * @param TranslatorInterface $translator + * @return $this + */ + public function setTranslator(TranslatorInterface $translator) + { + $this->translator = $translator; + return $this; + } + + /** + * Returns supported locales + * + * @return array + */ + public function getSupportedLocales() + { + return $this->translator->supportedLocales(); + } + + /** + * Indicates if given locale is supported + * + * @return bool + */ + public function isLocaleSupported($locale) + { + return $this->translator->isLocaleSupported($locale); + } +} diff --git a/app/LaravelGettext/LaravelGettextServiceProvider.php b/app/LaravelGettext/LaravelGettextServiceProvider.php new file mode 100644 index 000000000..a02dc90b1 --- /dev/null +++ b/app/LaravelGettext/LaravelGettextServiceProvider.php @@ -0,0 +1,128 @@ +publishes([ + __DIR__ . '/../../config/config.php' => config_path('laravel-gettext.php') + ], 'config'); + + } + + /** + * Register the service provider. + * + * @return mixed + */ + public function register() + { + $configuration = ConfigManager::create(); + + $this->app->bind( + AdapterInterface::class, + $configuration->get()->getAdapter() + ); + + $this->app->singleton(Config::class, function($app) use ($configuration){ + return $configuration->get(); + }); + + // Main class register + $this->app->singleton(LaravelGettext::class, function (Application $app) use ($configuration) { + + $fileSystem = new FileSystem($configuration->get(), app_path(), storage_path()); + $storage = $app->make($configuration->get()->getStorage()); + + if ('symfony' == $configuration->get()->getHandler()) { + // symfony translator implementation + $translator = new Translators\Symfony( + $configuration->get(), + $this->app->make(AdapterInterface::class), + $fileSystem, + $storage + ); + } else { + // GNU/Gettext php extension + $translator = new Translators\Gettext( + $configuration->get(), + $this->app->make(AdapterInterface::class), + $fileSystem, + $storage + ); + } + + return new LaravelGettext($translator); + + }); + $this->app->alias(LaravelGettext::class, 'laravel-gettext'); + + // Alias + $this->app->booting(function () { + $aliasLoader = AliasLoader::getInstance(); + $aliasLoader->alias('LaravelGettext', \App\LaravelGettext\Facades\LaravelGettext::class); + }); + + $this->registerCommands(); + } + + /** + * Register commands + */ + protected function registerCommands() + { + // Package commands + $this->app->bind('xinax::gettext.create', function ($app) { + return new Commands\GettextCreate(); + }); + + $this->app->bind('xinax::gettext.update', function ($app) { + return new Commands\GettextUpdate(); + }); + + $this->commands([ + 'xinax::gettext.create', + 'xinax::gettext.update', + ]); + } + + /** + * Get the services + * + * @return array + */ + public function provides() + { + return [ + 'laravel-gettext' + ]; + } +} \ No newline at end of file diff --git a/app/LaravelGettext/Middleware/GettextMiddleware.php b/app/LaravelGettext/Middleware/GettextMiddleware.php new file mode 100644 index 000000000..7d6122ac6 --- /dev/null +++ b/app/LaravelGettext/Middleware/GettextMiddleware.php @@ -0,0 +1,29 @@ +configuration = $configuration; + } + + + /** + * @var String + */ + protected $domain; + + /** + * Current locale + * @type String + */ + protected $locale; + + /** + * Current encoding + * @type String + */ + protected $encoding; + + + /** + * Getter for domain + * + * @return String + */ + public function getDomain() + { + return $this->domain ?: $this->configuration->getDomain(); + } + + /** + * @param String $domain + * + * @return $this + */ + public function setDomain($domain) + { + $this->domain = $domain; + + return $this; + } + + /** + * Getter for locale + * + * @return String + */ + public function getLocale() + { + return $this->locale ?: $this->configuration->getLocale(); + } + + /** + * @param String $locale + * + * @return $this + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * Getter for configuration + * + * @return \App\LaravelGettext\Config\Models\Config + */ + public function getConfiguration() + { + return $this->configuration; + } + + /** + * Getter for encoding + * + * @return String + */ + public function getEncoding() + { + return $this->encoding ?: $this->configuration->getEncoding(); + } + + /** + * @param String $encoding + * + * @return $this + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + + return $this; + } + + + + + +} \ No newline at end of file diff --git a/app/LaravelGettext/Storages/SessionStorage.php b/app/LaravelGettext/Storages/SessionStorage.php new file mode 100644 index 000000000..b6493b0e3 --- /dev/null +++ b/app/LaravelGettext/Storages/SessionStorage.php @@ -0,0 +1,140 @@ +configuration = $configuration; + } + + + /** + * Getter for domain + * + * @return String + */ + public function getDomain() + { + return $this->sessionGet('domain', $this->configuration->getDomain()); + } + + /** + * @param String $domain + * + * @return $this + */ + public function setDomain($domain) + { + $this->sessionSet('domain', $domain); + + return $this; + } + + /** + * Getter for locale + * + * @return String + */ + public function getLocale() + { + return $this->sessionGet('locale', $this->configuration->getLocale()); + } + + /** + * @param String $locale + * + * @return $this + */ + public function setLocale($locale) + { + $this->sessionSet('locale', $locale); + + return $this; + } + + /** + * Getter for configuration + * + * @return \App\LaravelGettext\Config\Models\Config + */ + public function getConfiguration() + { + return $this->configuration; + } + + + /** + * Return a value from session with an optional default + * + * @param $key + * @param null $default + * + * @return mixed + */ + protected function sessionGet($key, $default = null) + { + $token = $this->configuration->getSessionIdentifier() . "-" . $key; + + return Session::get($token, $default); + } + + /** + * Sets a value in session session + * + * @param $key + * @param $value + * + * @return mixed + */ + protected function sessionSet($key, $value) + { + $token = $this->configuration->getSessionIdentifier() . "-" . $key; + Session::put($token, $value); + + return $this; + } + + /** + * Getter for locale + * + * @return String + */ + public function getEncoding() + { + return $this->sessionGet('encoding', $this->configuration->getEncoding()); + } + + /** + * @param string $encoding + * + * @return $this + */ + public function setEncoding($encoding) + { + $this->sessionSet('encoding', $encoding); + + return $this; + } +} \ No newline at end of file diff --git a/app/LaravelGettext/Storages/Storage.php b/app/LaravelGettext/Storages/Storage.php new file mode 100644 index 000000000..145b1e421 --- /dev/null +++ b/app/LaravelGettext/Storages/Storage.php @@ -0,0 +1,60 @@ +translate($message); + + if (strlen($translation)) { + if (isset($args)) { + if (!is_array($args)) { + $args = array_slice(func_get_args(), 1); + } + $translation = vsprintf($translation, $args); + } + + return $translation; + } + + /** + * If translations are missing returns + * the original message. + * + * @see https://github.com/symfony/symfony/issues/13483 + */ + return $message; + } +} + +if (!function_exists('__')) { + /** + * Translate a formatted string based on printf formats + * Can be use an array on args or use the number of the arguments + * + * @param string $message the message to translate + * @param array|mixed $args the tokens values used inside the $message + * + * @return string the message translated and formatted + */ + function __($message, $args = null) + { + return _i($message, $args); + } +} + +if (!function_exists('_')) { + /** + * Generic translation function + * + * @param $message + * + * @return mixed + */ + function _($message, $args = null) + { + return _i($message, $args); + } +} + +if (!function_exists('_n')) { + /** + * Translate a formatted pluralized string based on printf formats + * Can be use an array on args or use the number of the arguments + * + * @param string $singular the singular message to be translated + * @param string $plural the plural message to be translated if the $count > 1 + * @param int $count the number of occurrence to be used to pluralize the $singular + * @param array|mixed $args the tokens values used inside $singular or $plural + * + * @return string the message translated, pluralized and formatted + */ + function _n($singular, $plural, $count, $args = null) + { + $translator = app(LaravelGettext::class); + $message = $translator->translatePlural($singular, $plural, $count); + + if (isset($args) && !is_array($args)) { + $args = array_slice(func_get_args(), 3); + } + $message = vsprintf($message, $args); + + return $message; + } +} + +if (!function_exists('_s')) { + /** + * Translate a formatted pluralized string based on printf formats mixed with the Symfony format + * Can be use an array on args or use the number of the arguments + * + * Only works if Symfony is the used backend + * + * @param string $message The one line message containing the different pluralization separated by pipes + * See Symfony translation documentation + * @param int $count the number of occurrence to be used to pluralize the $singular + * @param array|mixed $args the tokens values used inside $singular or $plural + * + * @return string the message translated, pluralized and formatted + */ + function _s($message, $count, $args = null) + { + $translator = app(LaravelGettext::class); + $message = $translator->getTranslator()->translatePluralInline($message, $count); + + if (isset($args) && !is_array($args)) { + $args = array_slice(func_get_args(), 3); + } + $message = vsprintf($message, $args); + + return $message; + } +} diff --git a/app/LaravelGettext/Testing/Adapter/TestAdapter.php b/app/LaravelGettext/Testing/Adapter/TestAdapter.php new file mode 100644 index 000000000..70f37af0e --- /dev/null +++ b/app/LaravelGettext/Testing/Adapter/TestAdapter.php @@ -0,0 +1,53 @@ +locale; + } + + /** + * Sets the locale on the adapter + * + * @param string $locale + * + * @return boolean + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return true; + } + + /** + * Get the application path + * + * @return string + */ + public function getApplicationPath() + { + return app_path(); + } +} diff --git a/app/LaravelGettext/Testing/BaseTestCase.php b/app/LaravelGettext/Testing/BaseTestCase.php new file mode 100644 index 000000000..ad64b365d --- /dev/null +++ b/app/LaravelGettext/Testing/BaseTestCase.php @@ -0,0 +1,40 @@ +appPath) { + return null; + } + + $app = require $this->appPath; + $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap(); + + $app->register(LaravelGettextServiceProvider::class); + + return $app; + } +} \ No newline at end of file diff --git a/app/LaravelGettext/Translators/BaseTranslator.php b/app/LaravelGettext/Translators/BaseTranslator.php new file mode 100644 index 000000000..1080b0cfd --- /dev/null +++ b/app/LaravelGettext/Translators/BaseTranslator.php @@ -0,0 +1,176 @@ +configuration = $config; + $this->adapter = $adapter; + $this->fileSystem = $fileSystem; + $this->storage = $storage; + } + + /** + * Returns the current locale string identifier + * + * @return String + */ + public function getLocale() + { + return $this->storage->getLocale(); + } + + /** + * Sets and stores on session the current locale code + * + * @param $locale + * + * @return BaseTranslator + */ + public function setLocale($locale) + { + if ($locale == $this->storage->getLocale()) { + return $this; + } + + $this->storage->setLocale($locale); + + return $this; + } + + /** + * Returns a boolean that indicates if $locale + * is supported by configuration + * + * @param $locale + * + * @return bool + */ + public function isLocaleSupported($locale) + { + if ($locale) { + return in_array($locale, $this->configuration->getSupportedLocales()); + } + + return false; + } + + /** + * Return the current locale + * + * @return mixed + */ + public function __toString() + { + return $this->getLocale(); + } + + /** + * Gets the Current encoding. + * + * @return mixed + */ + public function getEncoding() + { + return $this->storage->getEncoding(); + } + + /** + * Sets the Current encoding. + * + * @param mixed $encoding the encoding + * + * @return self + */ + public function setEncoding($encoding) + { + $this->storage->setEncoding($encoding); + + return $this; + } + + /** + * Sets the current domain and updates gettext domain application + * + * @param String $domain + * + * @throws UndefinedDomainException If domain is not defined + * @return self + */ + public function setDomain($domain) + { + if (!in_array($domain, $this->configuration->getAllDomains())) { + throw new UndefinedDomainException("Domain '$domain' is not registered."); + } + + $this->storage->setDomain($domain); + + return $this; + } + + /** + * Returns the current domain + * + * @return String + */ + public function getDomain() + { + return $this->storage->getDomain(); + } + + + /** + * Returns supported locales + * + * @return array + */ + public function supportedLocales() + { + return $this->configuration->getSupportedLocales(); + } + +} \ No newline at end of file diff --git a/app/LaravelGettext/Translators/Gettext.php b/app/LaravelGettext/Translators/Gettext.php new file mode 100644 index 000000000..0afa4927a --- /dev/null +++ b/app/LaravelGettext/Translators/Gettext.php @@ -0,0 +1,219 @@ +domain = $this->storage->getDomain(); + + // Encoding is set from configuration + $this->encoding = $this->storage->getEncoding(); + + // Categories are set from configuration + $this->categories = $this->configuration->getCategories(); + + // Sets defaults for boot + $locale = $this->storage->getLocale(); + + $this->setLocale($locale); + } + + + /** + * Sets the current locale code + */ + public function setLocale($locale) + { + if (!$this->isLocaleSupported($locale)) { + throw new LocaleNotSupportedException( + sprintf('Locale %s is not supported', $locale) + ); + } + + try { + $customLocale = $this->configuration->getCustomLocale() ? "C." : $locale . "."; + $gettextLocale = $customLocale . $this->getEncoding(); + + // Update all categories set in config + foreach($this->categories as $category) { + putenv("$category=$gettextLocale"); + setlocale(constant($category), $gettextLocale); + } + + parent::setLocale($locale); + + // Laravel built-in locale + if ($this->configuration->isSyncLaravel()) { + $this->adapter->setLocale($locale); + } + + return $this->getLocale(); + } catch (\Exception $e) { + $this->locale = $this->configuration->getFallbackLocale(); + $exceptionPosition = $e->getFile() . ":" . $e->getLine(); + throw new \Exception($exceptionPosition . $e->getMessage()); + + } + } + + /** + * Returns a boolean that indicates if $locale + * is supported by configuration + * + * @return boolean + */ + public function isLocaleSupported($locale) + { + if ($locale) { + return in_array($locale, $this->supportedLocales()); + } + + return false; + } + + /** + * Return the current locale + * + * @return mixed + */ + public function __toString() + { + return $this->getLocale(); + } + + + /** + * Gets the Current encoding. + * + * @return mixed + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Sets the Current encoding. + * + * @param mixed $encoding the encoding + * @return self + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Sets the current domain and updates gettext domain application + * + * @param String $domain + * @throws UndefinedDomainException If domain is not defined + * @return self + */ + public function setDomain($domain) + { + parent::setDomain($domain); + + $customLocale = $this->configuration->getCustomLocale() ? "/" . $this->getLocale() : ""; + + bindtextdomain($domain, $this->fileSystem->getDomainPath() . $customLocale); + bind_textdomain_codeset($domain, $this->getEncoding()); + + $this->domain = textdomain($domain); + + + + return $this; + } + + /** + * Translates a message with gettext + * + * @param $message + */ + public function translate($message) + { + return gettext($message); + } + + /** + * Translates a plural message with gettext + * + * @param $singular + * @param $plural + * @param $count + * + * @return string + */ + public function translatePlural($singular, $plural, $count) + { + return ngettext($singular, $plural, $count); + } + + public function translatePluralInline($message, $amount) + { + throw new \RuntimeException('Not supported by gettext, please use Symfony'); + } +} diff --git a/app/LaravelGettext/Translators/Symfony.php b/app/LaravelGettext/Translators/Symfony.php new file mode 100644 index 000000000..e874f90da --- /dev/null +++ b/app/LaravelGettext/Translators/Symfony.php @@ -0,0 +1,220 @@ +setLocale($this->storage->getLocale()); + $this->loadLocaleFile(); + } + + + /** + * Translates a message using the Symfony translation component + * + * @param $message + * + * @return string + */ + public function translate($message) + { + return $this->symfonyTranslator->trans($message, [], $this->getDomain(), $this->getLocale()); + } + + /** + * Returns the translator instance + * + * @return SymfonyTranslator + */ + protected function getTranslator() + { + if (isset($this->symfonyTranslator)) { + return $this->symfonyTranslator; + } + + return $this->symfonyTranslator = $this->createTranslator(); + } + + /** + * Set locale overload. + * Needed to re-build the catalogue when locale changes. + * + * @param $locale + * + * @return $this + */ + public function setLocale($locale) + { + parent::setLocale($locale); + $this->getTranslator()->setLocale($locale); + $this->loadLocaleFile(); + + if ($locale != $this->adapter->getLocale()) { + $this->adapter->setLocale($locale); + } + + return $this; + } + + /** + * Set domain overload. + * Needed to re-build the catalogue when domain changes. + * + * + * @param String $domain + * + * @return $this + */ + public function setDomain($domain) + { + parent::setDomain($domain); + + $this->loadLocaleFile(); + + return $this; + } + + /** + * Creates a new translator instance + * + * @return SymfonyTranslator + */ + protected function createTranslator() + { + $translator = new SymfonyTranslator($this->configuration->getLocale()); + $translator->setFallbackLocales([$this->configuration->getFallbackLocale()]); + $translator->addLoader('mo', new ApcuFileCacheLoader(new MoFileLoader())); + $translator->addLoader('po', new ApcuFileCacheLoader(new PoFileLoader())); + + return $translator; + } + + /** + * Translates a plural string + * + * @param $singular + * @param $plural + * @param $amount + * + * @return string + */ + public function translatePlural($singular, $plural, $amount) + { + return $this->symfonyTranslator->trans( + $amount > 1 + ? $plural + : $singular, + ['%count%' => $amount], + $this->getDomain(), + $this->getLocale() + ); + } + + /** + * Translate a plural string that is only on one line separated with pipes + * + * @param $message + * @param $amount + * + * @return string + */ + public function translatePluralInline($message, $amount) + { + return $this->symfonyTranslator->trans( + $message, + [ + '%count%' => $amount + ], + $this->getDomain(), + $this->getLocale() + ); + } + + /** + * @internal param $translator + */ + protected function loadLocaleFile() + { + if (isset($this->loadedResources[$this->getDomain()]) + && isset($this->loadedResources[$this->getDomain()][$this->getLocale()]) + ) { + return; + } + $translator = $this->getTranslator(); + + $fileMo = $this->fileSystem->makeFilePath($this->getLocale(), $this->getDomain(), 'mo'); + if (file_exists($fileMo)) { + $translator->addResource('mo', $fileMo, $this->getLocale(), $this->getDomain()); + } else { + $file = $this->fileSystem->makeFilePath($this->getLocale(), $this->getDomain()); + $translator->addResource('po', $file, $this->getLocale(), $this->getDomain()); + } + + $this->loadedResources[$this->getDomain()][$this->getLocale()] = true; + } + + /** + * Returns a boolean that indicates if $locale + * is supported by configuration + * + * @param $locale + * + * @return bool + */ + public function isLocaleSupported($locale) + { + if ($locale) { + return in_array($locale, $this->configuration->getSupportedLocales()); + } + + return false; + } + + /** + * Return the current locale + * + * @return mixed + */ + public function __toString() + { + return $this->getLocale(); + } + + /** + * Returns supported locales + * + * @return array + */ + public function supportedLocales() + { + return $this->configuration->getSupportedLocales(); + } +} diff --git a/app/LaravelGettext/Translators/TranslatorInterface.php b/app/LaravelGettext/Translators/TranslatorInterface.php new file mode 100644 index 000000000..fd6b78f14 --- /dev/null +++ b/app/LaravelGettext/Translators/TranslatorInterface.php @@ -0,0 +1,119 @@ +get('decimals'); - if (string_starts_with($decimals, 'qta')) { + if (Str::startsWith($decimals, 'qta')) { $decimals = setting('Cifre decimali per quantità'); // Se non è previsto un valore minimo, lo imposta a 1 diff --git a/composer.json b/composer.json index 07a9c6fd4..b091006d2 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ }], "type": "project", "require": { - "php": "^7.3|^8.0", + "php": "^8.0.2", "ext-curl": "*", "ext-dom": "*", "ext-fileinfo": "*", @@ -32,26 +32,21 @@ "danielstjules/stringy": "^3.1", "devcode-it/aggiornamenti": "@dev", "devcode-it/causali-trasporto": "@dev", - "devcode-it/legacy": "@dev", - "fideloper/proxy": "^4.4", - "filp/whoops": "^2.1", - "fruitcake/laravel-cors": "^2.0", + "devcode-it/legacy": "dev-legacy", "guzzlehttp/guzzle": "^7.0.1", "intervention/image": "^2.3", - "laravel/framework": "^8.12", + "laravel/framework": "^9.0", "laravel/tinker": "^2.5", + "spatie/laravel-ignition": "^1.0", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php70": "^1.8", - "zerospam/laravel-gettext": "^7.2" + "symfony/polyfill-php70": "^1.8" }, "require-dev": { - "facade/ignition": "^2.5", "fakerphp/faker": "^1.9.1", "friendsofphp/php-cs-fixer": "^3.0", "jeroen-g/laravel-packager": "^2.5", - "laravel/homestead": "^12.2", "mockery/mockery": "^1.4.2", - "nunomaduro/collision": "^5.0", + "nunomaduro/collision": "^6.1", "phpunit/phpunit": "^9.3.3" }, "config": { @@ -66,7 +61,8 @@ "Database\\Seeders\\": "database/seeders/" }, "files": [ - "app/helpers.php" + "app/helpers.php", + "app/LaravelGettext/Support/helpers.php" ] }, "autoload-dev": { @@ -95,27 +91,34 @@ ] } }, - "repositories": { - "devcode-it/aggiornamenti": { + "repositories": [ + { + "url": "https://github.com/wdog/sdd_ita.git", + "type": "git" + }, + { + "name": "devcode-it/aggiornamenti", "type": "path", "url": "./packages/devcode-it/aggiornamenti", "options": { "symlink": true } }, - "devcode-it/causali-trasporto": { + { + "name": "devcode-it/causali-trasporto", "type": "path", "url": "./packages/devcode-it/causali-trasporto", "options": { "symlink": true } }, - "devcode-it/legacy": { + { + "name": "devcode-it/legacy", "type": "path", "url": "../legacy", "options": { "symlink": true } } - } + ] } diff --git a/config/app.php b/config/app.php index bb3c738cb..100fdf96c 100644 --- a/config/app.php +++ b/config/app.php @@ -172,6 +172,7 @@ return [ // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\LaravelGettext\LaravelGettextServiceProvider::class, ], /* diff --git a/config/laravel-gettext.php b/config/laravel-gettext.php index 919fb79b3..9afd68375 100644 --- a/config/laravel-gettext.php +++ b/config/laravel-gettext.php @@ -126,7 +126,7 @@ return [ /* * The adapter used to sync the laravel built-in locale */ - 'adapter' => \Xinax\LaravelGettext\Adapters\LaravelAdapter::class, + 'adapter' => \App\LaravelGettext\Adapters\LaravelAdapter::class, /* * Where to store the current locale/domain @@ -134,9 +134,9 @@ return [ * By default, in the session. * Can be changed for only memory or your own storage mechanism * - * @see \Xinax\LaravelGettext\Storages\Storage + * @see \App\LaravelGettext\Storages\Storage */ - 'storage' => \Xinax\LaravelGettext\Storages\SessionStorage::class, + 'storage' => \App\LaravelGettext\Storages\SessionStorage::class, /* * Use custom locale that is not supported by the system @@ -150,7 +150,7 @@ return [ * The "_n" and "ngettext" are plural translation functions * The "dgettext" function allows a translation domain to be explicitly specified * - * "__" and "_n" and "_i" and "_s" are helpers functions @see \Xinax\LaravelGettext\Support\helpers.php + * "__" and "_n" and "_i" and "_s" are helpers functions @see \App\LaravelGettext\Support\helpers.php */ 'keywords-list' => ['_', '__', '_i', '_s', 'gettext', '_n:1,2', 'ngettext:1,2', 'dgettext:2', 'tr'], ]; diff --git a/config/trustedproxy.php b/config/trustedproxy.php deleted file mode 100644 index 019784326..000000000 --- a/config/trustedproxy.php +++ /dev/null @@ -1,48 +0,0 @@ - null, // [,], '*', ',' - - /* - * To trust one or more specific proxies that connect - * directly to your server, use an array or a string separated by comma of IP addresses: - */ - // 'proxies' => ['192.168.1.1'], - // 'proxies' => '192.168.1.1, 192.168.1.2', - - /* - * Or, to trust all proxies that connect - * directly to your server, use a "*" - */ - // 'proxies' => '*', - - /* - * Which headers to use to detect proxy related data (For, Host, Proto, Port) - * - * Options include: - * - * - Illuminate\Http\Request::HEADER_X_FORWARDED_ALL (use all x-forwarded-* headers to establish trust) - * - Illuminate\Http\Request::HEADER_FORWARDED (use the FORWARDED header to establish trust) - * - Illuminate\Http\Request::HEADER_X_FORWARDED_AWS_ELB (If you are using AWS Elastic Load Balancer) - * - * - 'HEADER_X_FORWARDED_ALL' (use all x-forwarded-* headers to establish trust) - * - 'HEADER_FORWARDED' (use the FORWARDED header to establish trust) - * - 'HEADER_X_FORWARDED_AWS_ELB' (If you are using AWS Elastic Load Balancer) - * - * @link https://symfony.com/doc/current/deployment/proxies.html - */ - 'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, -]; diff --git a/logs/.htaccess b/logs/.htaccess deleted file mode 100755 index 3a4288278..000000000 --- a/logs/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Deny from all diff --git a/packages/devcode-it/aggiornamenti/composer.json b/packages/devcode-it/aggiornamenti/composer.json index df34311fe..799081ac8 100644 --- a/packages/devcode-it/aggiornamenti/composer.json +++ b/packages/devcode-it/aggiornamenti/composer.json @@ -13,7 +13,7 @@ "keywords": ["Laravel", "Aggiornamenti"], "require": { "erusev/parsedown": "^1.7", - "illuminate/support": "~7|~8" + "illuminate/support": "~9" }, "require-dev": { "phpunit/phpunit": "~9.0", diff --git a/packages/devcode-it/causali-trasporto/composer.json b/packages/devcode-it/causali-trasporto/composer.json index 8ce134570..c0b030cef 100644 --- a/packages/devcode-it/causali-trasporto/composer.json +++ b/packages/devcode-it/causali-trasporto/composer.json @@ -12,7 +12,7 @@ "homepage": "https://github.com/devcode-it/causali-trasporto", "keywords": ["Laravel", "CausaliTrasporto"], "require": { - "illuminate/support": "~7|~8" + "illuminate/support": "~9" }, "require-dev": { "phpunit/phpunit": "~9.0",