├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── localizator.php └── src ├── Collections ├── DefaultKeyCollection.php └── JsonKeyCollection.php ├── Commands └── LocalizeCommand.php ├── Contracts ├── Collectable.php ├── Translatable.php └── Writable.php ├── Facades └── Localizator.php ├── ServiceProvider.php ├── Services ├── Collectors │ ├── DefaultKeyCollector.php │ └── JsonKeyCollector.php ├── FileFinder.php ├── Localizator.php ├── Parser.php └── Writers │ ├── DefaultWriter.php │ └── JsonWriter.php └── helpers.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `amirami/localizator` will be documented in this file. 4 | 5 | ## 0.14.0 - 2025-03-06 6 | 7 | ### What's Changed 8 | 9 | * Adds Laravel 12 support by @greatislander in https://github.com/amiranagram/localizator/pull/65 10 | 11 | ### New Contributors 12 | 13 | * @greatislander made their first contribution in https://github.com/amiranagram/localizator/pull/65 14 | 15 | **Full Changelog**: https://github.com/amiranagram/localizator/compare/v0.13.0-alpha...v0.14.0-alpha 16 | 17 | ## 0.13.0 - 2024-03-27 18 | 19 | ### What's Changed 20 | 21 | * Support Laravel 11 by @asbiin in https://github.com/amiranagram/localizator/pull/60 22 | 23 | ## 0.12.1 - 2023-04-24 24 | 25 | ### What's Changed 26 | 27 | - Added --force option to the localize command by @filip-nifti in https://github.com/amiranagram/localizator/pull/56 28 | 29 | ## 0.12.0 - 2023-04-14 30 | 31 | ### What's Changed 32 | 33 | - Add Laravel 10 compatibility by @musmanikram in https://github.com/amiranagram/localizator/pull/55 34 | 35 | ## 0.11.1 - 2023-01-16 36 | 37 | ### Fixed 38 | 39 | - `lang_path` for Laravel 9 by @amiranagram in https://github.com/amiranagram/localizator/pull/49 40 | 41 | ## 0.11.0 - 2022-11-14 42 | 43 | ### Changed 44 | 45 | - Exclude subdirectories by @amiranagram in https://github.com/amiranagram/localizator/pull/45 46 | 47 | ## 0.10.1 - 2022-10-19 48 | 49 | ### Added 50 | 51 | - Added an option to remove missing keys by @filip-nifti in https://github.com/amiranagram/localizator/pull/42 52 | 53 | ## 0.9.1 - 2022-04-11 54 | 55 | ## Changed 56 | 57 | - Keep numbered keys as strings in PHP lang files by @amiranagram in https://github.com/amiranagram/localizator/pull/37 58 | 59 | ## 0.9.0 - 2022-03-12 60 | 61 | ## Changed 62 | 63 | - Add support for newlines and spaces by @rexlManu in https://github.com/amiranagram/localizator/pull/35 64 | 65 | ## 0.8.1 - 2022-02-23 66 | 67 | ## Changed 68 | 69 | - New lang path for Laravel 9.x by @amiranagram in https://github.com/amiranagram/localizator/pull/33 70 | 71 | ## 0.8.0 - 2022-02-15 72 | 73 | ## Added 74 | 75 | - Added support for Laravel 9 by @musmanikram in https://github.com/amiranagram/localizator/pull/32 76 | 77 | ## v0.7.1-alpha 78 | 79 | ### Added 80 | 81 | - Confirm usage of localize command in production. 82 | 83 | ### Changed 84 | 85 | - Use `testbench:^4.0` for Laravel 6.* LTS. 86 | 87 | ## v0.7.0-alpha 88 | 89 | ### Changed 90 | 91 | - Update dependencies, PHP 7.2.5 is now the minimum version. 92 | 93 | ## v0.6.1-alpha 94 | 95 | ### Fixed 96 | 97 | - Remove space after php opening tag for default locale export. 98 | 99 | ## v0.6.0-alpha 100 | 101 | ### Added 102 | 103 | - Added PHP 8 Support. 104 | 105 | ## v0.5.1-alpha 106 | 107 | ### Fixed 108 | 109 | - Strip slashes from translation keys. 110 | 111 | ## v0.5.0-alpha 112 | 113 | ### Fixed 114 | 115 | - Update PHP CS Fixer. 116 | - Natural case-insensitive sort. 117 | - Use `assertSame` instead of `assertEquals` in tests to compare arrays. 118 | 119 | ## v0.4.0-alpha 120 | 121 | ### Added 122 | 123 | - Default export to php lang files. 124 | 125 | ## v0.3.0-alpha 126 | 127 | ### Added 128 | 129 | - Laravel 6 LTS support. 130 | 131 | ## v0.2.0-alpha 132 | 133 | ### Added 134 | 135 | - Basic tests. 136 | 137 | ### Changed 138 | 139 | - Small improvements using paths. 140 | - Documentation. 141 | - Dependencies. 142 | 143 | ## v0.1.0-alpha 144 | 145 | - Initial release. 146 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) Amir Rami 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Localizator 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/amirami/localizator.svg?style=flat-square)](https://packagist.org/packages/amirami/localizator) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/amirami/localizator.svg?style=flat-square)](https://packagist.org/packages/amirami/localizator) 5 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/amiranagram/localizator/tests.yml?style=flat-square)](https://github.com/amiranagram/localizator/actions?query=workflow%3Atests+branch%3Amaster) 6 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/amiranagram/localizator/php-cs-fixer.yml?label=code%20style&style=flat-square)](https://github.com/amiranagram/localizator/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amaster) 7 | 8 | Localizator is a small tool for Laravel that gives you the ability to extract untranslated string from your project files with one command. 9 | 10 | ## Support 11 | 12 | Buy Me A Coffee 13 | 14 | [![DigitalOcean Referral Badge](https://web-platforms.sfo2.digitaloceanspaces.com/WWW/Badge%203.svg)](https://www.digitalocean.com/?refcode=f38828dd20f8&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) 15 | 16 | ## Compatibility 17 | 18 | | Laravel | Localizator | 19 | |---------|-------------| 20 | | 6.x LTS | 0.3-0.12 | 21 | | 8.x | * | 22 | | 9.x | ^0.8 | 23 | | 10.x | ^0.12 | 24 | | 11.x | ^0.13 | 25 | | 12.x | ^0.14 | 26 | 27 | ## Installation 28 | 29 | You can install the package via composer: 30 | 31 | ```bash 32 | composer require --dev amirami/localizator 33 | ``` 34 | 35 | This package makes use of [Laravels package auto-discovery mechanism](https://medium.com/@taylorotwell/package-auto-discovery-in-laravel-5-5-ea9e3ab20518), which means if you don't install dev dependencies in production, it also won't be loaded. 36 | 37 | If for some reason you want manually control this: 38 | - add the package to the `extra.laravel.dont-discover` key in `composer.json`, e.g. 39 | ```json 40 | "extra": { 41 | "laravel": { 42 | "dont-discover": [ 43 | "amirami/localizator" 44 | ] 45 | } 46 | } 47 | ``` 48 | - Add the following class to the `providers` array in `config/app.php`: 49 | ```php 50 | Amirami\Localizator\ServiceProvider::class, 51 | ``` 52 | If you want to manually load it only in non-production environments, instead you can add this to your `AppServiceProvider` with the `register()` method: 53 | ```php 54 | public function register() 55 | { 56 | if ($this->app->isLocal()) { 57 | $this->app->register(\Amirami\Localizator\ServiceProvider::class); 58 | } 59 | // ... 60 | } 61 | ``` 62 | 63 | > Note: Avoid caching the configuration in your development environment, it may cause issues after installing this package; respectively clear the cache beforehand via `php artisan cache:clear` if you encounter problems when running the commands 64 | 65 | You can publish the config file with: 66 | ```bash 67 | php artisan vendor:publish --provider="Amirami\Localizator\ServiceProvider" --tag="config" 68 | ``` 69 | 70 | This is the contents of the published config file: 71 | 72 | ```php 73 | [ 81 | /** 82 | * Short keys. This is the default for Laravel. 83 | * They are stored in PHP files inside folders name by their locale code. 84 | * Laravel comes with default: auth.php, pagination.php, passwords.php and validation.php 85 | */ 86 | 'default' => true, 87 | 88 | /** 89 | * Translations strings as key. 90 | * They are stored in JSON file for each locale. 91 | */ 92 | 'json' => true, 93 | ], 94 | 95 | /** 96 | * Search criteria for files. 97 | */ 98 | 'search' => [ 99 | /** 100 | * Directories which should be looked inside. 101 | */ 102 | 'dirs' => ['resources/views'], 103 | 104 | /** 105 | * Subdirectories which will be excluded. 106 | * The values must be relative to the included directory paths. 107 | */ 108 | 'exclude' => [ 109 | // 110 | ], 111 | 112 | /** 113 | * Patterns by which files should be queried. 114 | * The values can be a regular expression, glob, or just a string. 115 | */ 116 | 'patterns' => ['*.php'], 117 | 118 | /** 119 | * Functions that the strings will be extracted from. 120 | * Add here any custom defined functions. 121 | * NOTE: The translation string should always be the first argument. 122 | */ 123 | 'functions' => ['__', 'trans', '@lang'] 124 | ], 125 | 126 | /** 127 | * Should the localize command sort extracted strings alphabetically? 128 | */ 129 | 'sort' => true, 130 | 131 | ]; 132 | 133 | ``` 134 | 135 | ## Usage 136 | 137 | To extract all the strings, it's as simple as running: 138 | 139 | ``` bash 140 | php artisan localize de,fr 141 | ``` 142 | 143 | This command will create (if don't exist) `de.json` and `fr.json` files inside the `resources/lang` directory. 144 | If you have short keys enabled and used in your files (e.g. `pagination.next`) the localize command will create folders `de` and `fr` inside `resources/lang` directory and PHP files inside by the short key's prefix (e.g. `pagination.php`). 145 | 146 | You can also run the artisan command without the country code arguments. 147 | 148 | ``` bash 149 | php artisan localize 150 | ``` 151 | 152 | In this case translation strings will be generated for the language specified in `app.locale` config. 153 | 154 | > Note: Strings you have already translated will not be overwritten. 155 | 156 | ### Remove Missing Keys 157 | 158 | By default, the strings inside the locale files will be preserved even if they are not present the next time you run the localize command. 159 | If you want to remove those keys that are not present in your files anymore you can append the --remove-missing option to the localize command. 160 | ``` bash 161 | php artisan localize --remove-missing 162 | ``` 163 | ### Key Sorting 164 | 165 | By default, the strings generated inside those JSON files will be sorted alphabetically by their keys. 166 | If you wanna turn off this feature just set `sort => false` in the config file. 167 | 168 | ### Searching 169 | 170 | The way the strings are being extracted is simple. 171 | 172 | We are looking inside the directories defined in `search.dirs` config, we match the files using patterns defined in `search.patterns`, and finally we look to extract strings 173 | which are the first argument of the functions defined in `search.functions`. 174 | 175 | You are free to change any of these values inside the config file to suit you own needs. 176 | 177 | ## Testing 178 | 179 | ``` bash 180 | composer test 181 | ``` 182 | 183 | ## Changelog 184 | 185 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 186 | 187 | ## Contributing 188 | 189 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 190 | 191 | ## Security Vulnerabilities 192 | 193 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 194 | 195 | ## Credits 196 | 197 | - [Amir Rami](https://github.com/amiranagram) 198 | - [All Contributors](../../contributors) 199 | 200 | ## License 201 | 202 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 203 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amirami/localizator", 3 | "description": "Localizator is a small tool for Laravel that gives you the ability to extract untranslated strings from project files. It works using the artisan command line and the provided localize command.", 4 | "homepage": "https://github.com/amiranagram/localizator", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Amir Rami", 9 | "email": "me@amirrami.com", 10 | "homepage": "https://amirrami.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": "^7.2.5|^8.0", 16 | "ext-json": "*", 17 | "illuminate/config": "^8.0|^9.0|^10.0|^11.0|^12.0", 18 | "illuminate/console": "^8.0|^9.0|^10.0|^11.0|^12.0", 19 | "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", 20 | "illuminate/filesystem": "^8.0|^9.0|^10.0|^11.0|^12.0", 21 | "symfony/finder": "^5.1|^6.0|^7.0" 22 | }, 23 | "require-dev": { 24 | "mockery/mockery": "^1.3.3", 25 | "orchestra/testbench": "^6.6|^7.0|^8.0|^9.0|^10.0", 26 | "phpunit/phpunit": "^9.5|^10.0|^11.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Amirami\\Localizator\\": "src" 31 | }, 32 | "files": [ 33 | "src/helpers.php" 34 | ] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Amirami\\Localizator\\Tests\\": "tests" 39 | } 40 | }, 41 | "scripts": { 42 | "test": "vendor/bin/phpunit --colors=always", 43 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 44 | "act": "act -P ubuntu-latest=shivammathur/node:latest -j" 45 | }, 46 | "config": { 47 | "sort-packages": true 48 | }, 49 | "extra": { 50 | "laravel": { 51 | "providers": [ 52 | "Amirami\\Localizator\\ServiceProvider" 53 | ] 54 | } 55 | }, 56 | "minimum-stability": "dev", 57 | "prefer-stable": true 58 | } 59 | -------------------------------------------------------------------------------- /config/localizator.php: -------------------------------------------------------------------------------- 1 | [ 9 | /** 10 | * Short keys. This is the default for Laravel. 11 | * They are stored in PHP files inside folders name by their locale code. 12 | * Laravel comes with default: auth.php, pagination.php, passwords.php and validation.php 13 | */ 14 | 'default' => true, 15 | 16 | /** 17 | * Translations strings as key. 18 | * They are stored in JSON file for each locale. 19 | */ 20 | 'json' => true, 21 | ], 22 | 23 | /** 24 | * Search criteria for files. 25 | */ 26 | 'search' => [ 27 | /** 28 | * Directories which should be looked inside. 29 | */ 30 | 'dirs' => ['resources/views'], 31 | 32 | /** 33 | * Subdirectories which will be excluded. 34 | * The values must be relative to the included directory paths. 35 | */ 36 | 'exclude' => [ 37 | // 38 | ], 39 | 40 | /** 41 | * Patterns by which files should be queried. 42 | * The values can be a regular expression, glob, or just a string. 43 | */ 44 | 'patterns' => ['*.php'], 45 | 46 | /** 47 | * Functions that the strings will be extracted from. 48 | * Add here any custom defined functions. 49 | * NOTE: The translation string should always be the first argument. 50 | */ 51 | 'functions' => ['__', 'trans', '@lang'] 52 | ], 53 | 54 | /** 55 | * Should the localize command sort extracted strings alphabetically? 56 | */ 57 | 'sort' => true, 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /src/Collections/DefaultKeyCollection.php: -------------------------------------------------------------------------------- 1 | sortKeys(SORT_NATURAL | SORT_FLAG_CASE); 17 | } 18 | 19 | /** 20 | * @param mixed $items 21 | * @return static 22 | */ 23 | public function merge($items): self 24 | { 25 | return parent::merge(Arr::dot($items)); 26 | } 27 | 28 | /** 29 | * @param mixed $items 30 | * @return static 31 | */ 32 | public function intersectByKeys($items): self 33 | { 34 | return new self(collect(Arr::dot($this))->intersectByKeys($items)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Collections/JsonKeyCollection.php: -------------------------------------------------------------------------------- 1 | sortKeys(SORT_NATURAL | SORT_FLAG_CASE); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Commands/LocalizeCommand.php: -------------------------------------------------------------------------------- 1 | confirmToProceed()) { 38 | return 1; 39 | } 40 | 41 | $locales = $this->getLocales(); 42 | $progressBar = $this->output->createProgressBar(count($locales)); 43 | 44 | $this->info('Localizing: '.implode(', ', $locales)); 45 | 46 | $parser->parseKeys(); 47 | 48 | $progressBar->setFormat('%current%/%max% [%bar%] %percent:3s%% %message%'); 49 | $progressBar->setMessage('Localizing...'); 50 | $progressBar->start(); 51 | 52 | foreach ($locales as $locale) { 53 | $progressBar->setMessage("Localizing {$locale}..."); 54 | 55 | foreach ($this->getTypes() as $type) { 56 | $localizator->localize( 57 | $parser->getKeys($locale, $type), 58 | $type, 59 | $locale, 60 | $this->option('remove-missing') 61 | ); 62 | } 63 | 64 | $progressBar->advance(); 65 | } 66 | 67 | $progressBar->finish(); 68 | 69 | $this->info( 70 | "\nTranslatable strings have been generated for locale(s): ".implode(', ', $locales) 71 | ); 72 | 73 | return 0; 74 | } 75 | 76 | /** 77 | * @return array 78 | */ 79 | protected function getLocales(): array 80 | { 81 | return $this->argument('lang') 82 | ? explode(',', $this->argument('lang')) 83 | : [config('app.locale')]; 84 | } 85 | 86 | /** 87 | * @return array 88 | */ 89 | protected function getTypes(): array 90 | { 91 | return array_keys(array_filter(config('localizator.localize'))); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Contracts/Collectable.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 21 | $this->publishes([ 22 | __DIR__.'/../config/localizator.php' => config_path('localizator.php'), 23 | ], 'config'); 24 | 25 | $this->commands(LocalizeCommand::class); 26 | } 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function register(): void 33 | { 34 | $this->mergeConfigFrom(__DIR__.'/../config/localizator.php', 'localizator'); 35 | 36 | $this->registerContainerClasses(); 37 | } 38 | 39 | /** 40 | * @return void 41 | */ 42 | private function registerContainerClasses(): void 43 | { 44 | $this->app->singleton('localizator', Localizator::class); 45 | 46 | $this->app->bind('localizator.writers.default', DefaultWriter::class); 47 | $this->app->bind('localizator.writers.json', JsonWriter::class); 48 | 49 | $this->app->bind('localizator.collector.default', DefaultKeyCollector::class); 50 | $this->app->bind('localizator.collector.json', JsonKeyCollector::class); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Services/Collectors/DefaultKeyCollector.php: -------------------------------------------------------------------------------- 1 | getFiles($locale) 23 | ->each(function (SplFileInfo $fileInfo) use ($locale, $translated) { 24 | $translated->put( 25 | $fileInfo->getFilenameWithoutExtension(), 26 | $this->requireFile($locale, $fileInfo) 27 | ); 28 | }); 29 | 30 | return $translated; 31 | } 32 | 33 | /** 34 | * @param string $locale 35 | * @return Collection 36 | */ 37 | protected function getFiles(string $locale): Collection 38 | { 39 | $dir = lang_path($locale); 40 | 41 | if (! file_exists($dir)) { 42 | if (! mkdir($dir, 0755) && ! is_dir($dir)) { 43 | throw new RuntimeException(sprintf('Directory "%s" was not created', $dir)); 44 | } 45 | 46 | return new Collection; 47 | } 48 | 49 | return new Collection( 50 | (new Finder)->in($dir)->name('*.php')->files() 51 | ); 52 | } 53 | 54 | /** 55 | * @param string $locale 56 | * @param SplFileInfo $fileInfo 57 | * @return array 58 | * @noinspection PhpIncludeInspection 59 | */ 60 | protected function requireFile(string $locale, SplFileInfo $fileInfo): array 61 | { 62 | return require lang_path( 63 | $locale.DIRECTORY_SEPARATOR.$fileInfo->getRelativePathname() 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Services/Collectors/JsonKeyCollector.php: -------------------------------------------------------------------------------- 1 | config = $config->get('localizator'); 24 | } 25 | 26 | /** 27 | * @return Collection 28 | */ 29 | public function getFiles(): Collection 30 | { 31 | $directories = array_map(static function ($dir) { 32 | return base_path($dir); 33 | }, $this->config['search']['dirs']); 34 | 35 | return new Collection( 36 | (new Finder)->in($directories) 37 | ->notPath($this->config['search']['exclude']) 38 | ->name($this->config['search']['patterns']) 39 | ->files() 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Services/Localizator.php: -------------------------------------------------------------------------------- 1 | getWriter($type)->put($locale, $this->collect($keys, $type, $locale, $removeMissing)); 20 | } 21 | 22 | /** 23 | * @param Translatable $keys 24 | * @param string $type 25 | * @param string $locale 26 | * @return Translatable 27 | */ 28 | protected function collect(Translatable $keys, string $type, string $locale, bool $removeMissing): Translatable 29 | { 30 | return $keys 31 | ->merge($this->getCollector($type)->getTranslated($locale) 32 | ->when($removeMissing, function (Translatable $keyCollection) use ($keys) { 33 | return $keyCollection->intersectByKeys($keys); 34 | }))->when(config('localizator.sort'), function (Translatable $keyCollection) { 35 | return $keyCollection->sortAlphabetically(); 36 | }); 37 | } 38 | 39 | /** 40 | * @param string $type 41 | * @return Writable 42 | */ 43 | protected function getWriter(string $type): Writable 44 | { 45 | return app("localizator.writers.$type"); 46 | } 47 | 48 | /** 49 | * @param string $type 50 | * @return Collectable 51 | */ 52 | protected function getCollector(string $type): Collectable 53 | { 54 | return app("localizator.collector.$type"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Services/Parser.php: -------------------------------------------------------------------------------- 1 | config = $config->get('localizator'); 44 | $this->finder = $finder; 45 | $this->defaultKeys = new DefaultKeyCollection; 46 | $this->jsonKeys = new JsonKeyCollection; 47 | } 48 | 49 | /** 50 | * @return void 51 | */ 52 | public function parseKeys(): void 53 | { 54 | $this->finder 55 | ->getFiles() 56 | ->map(function (SplFileInfo $file) { 57 | return $this->getStrings($file); 58 | }) 59 | ->flatten() 60 | ->map(function (string $string) { 61 | return stripslashes($string); 62 | }) 63 | ->each(function (string $string) { 64 | if ($this->isDotKey($string)) { 65 | $this->defaultKeys->push($string); 66 | } else { 67 | $this->jsonKeys->push($string); 68 | } 69 | }); 70 | } 71 | 72 | /** 73 | * @param $key 74 | * @return bool 75 | */ 76 | protected function isDotKey($key): bool 77 | { 78 | return (bool) preg_match('/^[^.\s]\S*\.\S*[^.\s]$/', $key); 79 | } 80 | 81 | /** 82 | * @param SplFileInfo $file 83 | * @return Collection 84 | */ 85 | protected function getStrings(SplFileInfo $file): Collection 86 | { 87 | $keys = new Collection; 88 | 89 | foreach ($this->config['search']['functions'] as $function) { 90 | if (preg_match_all($this->searchPattern($function), $file->getContents(), $matches)) { 91 | $keys->push($matches[2]); 92 | } 93 | } 94 | 95 | return $keys->count() ? $keys->flatten()->unique() : $keys; 96 | } 97 | 98 | /** 99 | * @param string $function 100 | * @return string 101 | */ 102 | protected function searchPattern(string $function): string 103 | { 104 | return '/('.$function.')\([\r\n\s]{0,}\h*[\'"](.+)[\'"]\h*[\r\n\s]{0,}[),]/U'; 105 | } 106 | 107 | /** 108 | * @param string $locale 109 | * @param string $type 110 | * @return Translatable 111 | */ 112 | public function getKeys(string $locale, string $type): Translatable 113 | { 114 | switch ($type) { 115 | case 'default': 116 | return $this->defaultKeys->combine( 117 | $this->combineValues($locale, $type, $this->defaultKeys) 118 | ); 119 | case 'json': 120 | return $this->jsonKeys->combine( 121 | $this->combineValues($locale, $type, $this->jsonKeys) 122 | ); 123 | } 124 | 125 | throw new RuntimeException('Export type not recognized! Only recognized types are "default" and "json".'); 126 | } 127 | 128 | /** 129 | * @param string $locale 130 | * @param string $type 131 | * @param Collection $values 132 | * @return Collection 133 | */ 134 | protected function combineValues(string $locale, string $type, Collection $values): Collection 135 | { 136 | if ($type === 'default' || $locale !== config('app.locale')) { 137 | return (new Collection)->pad($values->count(), ''); 138 | } 139 | 140 | return $values; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Services/Writers/DefaultWriter.php: -------------------------------------------------------------------------------- 1 | tempUuid = Str::uuid(); 22 | } 23 | 24 | /** 25 | * @param string $locale 26 | * @param Translatable $keys 27 | */ 28 | public function put(string $locale, Translatable $keys): void 29 | { 30 | $this->elevate($keys) 31 | ->each(function ($contents, $fileName) use ($locale) { 32 | $file = $this->getFile($locale, $fileName); 33 | 34 | (new Filesystem)->put( 35 | $file, 36 | $this->exportArray($contents) 37 | ); 38 | }); 39 | } 40 | 41 | /** 42 | * @param Translatable $keys 43 | * @return DefaultKeyCollection 44 | */ 45 | protected function elevate(Translatable $keys): DefaultKeyCollection 46 | { 47 | $elevated = []; 48 | 49 | $keys->each(function ($value, $key) use (&$elevated) { 50 | Arr::set($elevated, $key, $value); 51 | }); 52 | 53 | return new DefaultKeyCollection($elevated); 54 | } 55 | 56 | /** 57 | * @param array $contents 58 | * @return string 59 | */ 60 | public function exportArray(array $contents): string 61 | { 62 | $export = var_export($this->temporarilyModifyIntKeys($contents), true); 63 | 64 | $patterns = [ 65 | "/array \(/" => '[', 66 | "/^([ ]*)\)(,?)$/m" => '$1$1]$2', 67 | "/=>[ ]?\n[ ]+\[/" => '=> [', 68 | "/([ ]*)(\'[^\']+\') => ([\[\'])/" => '$1$1$2 => $3', 69 | ]; 70 | 71 | $export = preg_replace( 72 | array_keys($patterns), 73 | array_values($patterns), 74 | $export 75 | ); 76 | 77 | return sprintf("tempUuid", '', $export)); 78 | } 79 | 80 | /** 81 | * @param string $locale 82 | * @param string $fileName 83 | * @return string 84 | */ 85 | protected function getFile(string $locale, string $fileName): string 86 | { 87 | return lang_path($locale.DIRECTORY_SEPARATOR.$fileName.'.php'); 88 | } 89 | 90 | /** 91 | * @param array $contents 92 | * @return array 93 | */ 94 | public function temporarilyModifyIntKeys(array $contents): array 95 | { 96 | $collection = collect($contents) 97 | ->mapWithKeys(function ($value, $key) { 98 | if (is_int($key)) { 99 | $key .= '_'.$this->tempUuid; 100 | } 101 | 102 | if (is_array($value)) { 103 | $value = $this->temporarilyModifyIntKeys($value); 104 | } 105 | 106 | return [$key => $value]; 107 | }); 108 | 109 | return $collection->toArray(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Services/Writers/JsonWriter.php: -------------------------------------------------------------------------------- 1 | put( 20 | $file, 21 | $keys->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | version(), '9.0', '>=') 11 | ? app()->langPath($path) 12 | : resource_path('lang'.($path !== '' ? DIRECTORY_SEPARATOR.$path : '')); 13 | } 14 | } 15 | --------------------------------------------------------------------------------