├── README.md ├── composer.json └── src ├── Xinax └── LaravelGettext │ ├── Adapters │ ├── AdapterInterface.php │ └── LaravelAdapter.php │ ├── Commands │ ├── BaseCommand.php │ ├── GettextCreate.php │ └── GettextUpdate.php │ ├── Composers │ └── LanguageSelector.php │ ├── Config │ ├── ConfigManager.php │ └── Models │ │ └── Config.php │ ├── Exceptions │ ├── DirectoryNotFoundException.php │ ├── FileCreationException.php │ ├── LocaleFileNotFoundException.php │ ├── LocaleNotSupportedException.php │ ├── MissingPhpGettextModuleException.php │ ├── RequiredConfigurationFileException.php │ ├── RequiredConfigurationKeyException.php │ └── UndefinedDomainException.php │ ├── Facades │ └── LaravelGettext.php │ ├── FileLoader │ ├── Cache │ │ └── ApcuFileCacheLoader.php │ └── MoFileLoader.php │ ├── FileSystem.php │ ├── LaravelGettext.php │ ├── LaravelGettextServiceProvider.php │ ├── Middleware │ └── GettextMiddleware.php │ ├── Storages │ ├── MemoryStorage.php │ ├── SessionStorage.php │ └── Storage.php │ ├── Support │ └── helpers.php │ ├── Testing │ ├── Adapter │ │ └── TestAdapter.php │ └── BaseTestCase.php │ └── Translators │ ├── BaseTranslator.php │ ├── Gettext.php │ ├── Symfony.php │ └── TranslatorInterface.php └── config ├── .gitkeep └── config.php /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Gettext 2 | 3 | *Laravel Gettext* is a translation package compatible with the [Laravel Framework](https://github.com/zerospam/laravel-gettext). It provides a simple way to add localization support to Laravel applications. It is designed to work with *GNU gettext* and *Poedit*. Former versions of this package (before 4.x) works with the native php-gettext module. Current versions uses the Symfony translation package by default instead of native php extension. 4 | 5 | [![Stable build Status](https://travis-ci.org/zerospam/laravel-gettext.png?branch=8.0.4)](https://travis-ci.org/zerospam/laravel-gettext) [laravel-gettext for Laravel 8](https://github.com/zerospam/laravel-gettext/tree/8.0.4) 6 | 7 | [![Stable build Status](https://travis-ci.org/zerospam/laravel-gettext.png?branch=7.2.0)](https://travis-ci.org/zerospam/laravel-gettext) [laravel-gettext for Laravel 6](https://github.com/zerospam/laravel-gettext/tree/7.2.0) 8 | 9 | Laravel Latest Long Term Support Version is Laravel 6, we do not recommend using Laravel version < 6 10 | 11 | ### 1. Requirements 12 | 13 | - [Laravel](https://laravel.com) 14 | - [Composer](https://getcomposer.org/) 15 | 16 | #### 1.1 Optional 17 | 18 | ##### 1.1.1 APCU 19 | 20 | APCU extension installed - http://php.net/manual/en/book.apcu.php 21 | 22 | If the APCU php extension is installed, the library will use the memory to cache the loaded translation to avoid having to parse the translation file (mo/po) at each request. 23 | 24 | The cache is automatically invalidated when there is a change in the translation file. 25 | 26 | 27 | ##### 1.1.2 gettext 28 | 29 | Optional requirements if you want to use the native php-gettext extension: 30 | 31 | - php-gettext - http://www.php.net/manual/en/book.gettext.php 32 | - GNU gettext on system (and production server!) - https://www.gnu.org/software/gettext/ 33 | 34 | > You will need to update the 'handler' option to 'gettext' in order to use the native php-gettext module. 35 | 36 | ### 2. Install 37 | 38 | Once it's installed, Laravel will discover automatically the provider and load it. (Only for 5.5+) 39 | 40 | ```bash 41 | composer require zerospam/laravel-gettext 42 | ``` 43 | 44 | 45 | Now you need to publish the configuration file in order to set your own application values: 46 | 47 | ```bash 48 | php artisan vendor:publish 49 | ``` 50 | 51 | This command creates the package configuration file in: ```config/laravel-gettext.php```. 52 | 53 | You also need to register the LaravelGettext middleware in the ```app/Http/Kernel.php``` file: 54 | 55 | ```php 56 | protected $middlewareGroups = [ 57 | 'web' => [ 58 | // ... 59 | \Xinax\LaravelGettext\Middleware\GettextMiddleware::class, 60 | ], 61 | // ... 62 | ] 63 | ``` 64 | 65 | > Be sure to add the line after ```Illuminate\Session\Middleware\StartSession```, otherwise the locale won't be saved into the session. 66 | 67 | ### 3. Configuration 68 | 69 | At this time your application has full gettext support. Now you need to set some configuration values in ```config/laravel-gettext.php```. 70 | 71 | ```php 72 | /** 73 | * Default locale: this will be the default for your application all 74 | * localized strings. Is to be supposed that all strings are written 75 | * on this language. 76 | */ 77 | 'locale' => 'es_ES', 78 | ``` 79 | 80 | ```php 81 | /** 82 | * Supported locales: An array containing all allowed languages 83 | */ 84 | 'supported-locales' => array( 85 | 'es_ES', 86 | 'en_US', 87 | 'it_IT', 88 | 'es_AR', 89 | ), 90 | ``` 91 | 92 | ```php 93 | /** 94 | * Default charset encoding. 95 | */ 96 | 'encoding' => 'UTF-8', 97 | ``` 98 | 99 | Ok, now it's configured. It's time to generate the directory structure and translation files for the first time. 100 | 101 | > Make sure you have write permissions on ```resources/``` before you run this command 102 | 103 | ```bash 104 | php artisan gettext:create 105 | ``` 106 | 107 | With this command the needed directories and files are created on **resources/lang/i18n** 108 | 109 | ### 4. Workflow 110 | 111 | ##### A. Write strings :D 112 | 113 | By default *LaravelGettext* looks on app/Http/Controllers and resources/views recursively searching for translations. Translations are all texts printed with the **_i()** function. Let's look a simple view example: 114 | 115 | ```php 116 | // an example view file 117 | echo 'Non translated string'; 118 | echo _i('Translated string'); 119 | echo _i('Another translated string'); 120 | // with parameter 121 | $str = 'parameter'; 122 | $n = 2; 123 | echo _i('Translated string with %s', $str); 124 | echo _i('%dnd translated string with %s', [$n, $str]); 125 | ``` 126 | 127 | ```php 128 | // an example view in blade 129 | {{ _i('Translated string') }} 130 | ``` 131 | 132 | > Poedit doesn't "understand" blade syntax. When using blade views you must run ```php artisan gettext:update``` in order to compile all blade views to plain php before update the translations in Poedit 133 | 134 | 135 | ##### B. Plural strings 136 | 137 | The plural translations follow the same pattern above. Plural translations are all texts printed with the **_n()** function, and it follow the php ngettext. Let's look a simple view example: 138 | 139 | ```php 140 | // an example view file 141 | $n = 2; 142 | echo ($n > 1) ? 'Non translated plural string' : 'Non translated string'; 143 | echo _n('Translated string', 'Translated plural string', $n); 144 | // with parameter 145 | $str = 'parameter'; 146 | echo _n('Translated string %s', 'Translated plural string %s', 2, $str); 147 | ``` 148 | 149 | ```php 150 | // an example view in blade 151 | {{ _n('Translated string', 'Translated plural string', $n) }} 152 | ``` 153 | 154 | > The Poedit keywords are defined in configuration file with this default pattern: 155 | ```php 156 | ['_n:1,2', 'ngettext:1,2'] 157 | ``` 158 | See Plural forms used by Poedit to configure for your language. 159 | 160 | ###### With Symfony 161 | 162 | If you're using [Symfony](http://symfony.com/doc/current/translation.html) as your translation backend, you have access to their plurals syntax with the ``_s`` method. In Poedit it will be considered as a single line instead of a plural. 163 | ```php 164 | // an example view file 165 | $n = 2; 166 | echo ($n > 1) ? 'Non translated plural string' : 'Non translated string'; 167 | echo _s('Translated string|Translated plural string', $n); 168 | // with parameter 169 | $str = 'parameter'; 170 | echo _n('Translated string %s|Translated plural string %s', 2, $str); 171 | ``` 172 | With symfony complex syntax: 173 | ```php 174 | echo _s('{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', $n); 175 | // with parameter 176 | $str = 'red'; 177 | echo _s('{0} There are no %s apples|{1} There is one %s apple|]1,Inf[ There are %count% %s apples', 2, $str); 178 | ``` 179 | 180 | 181 | ##### C. Translate with Poedit 182 | 183 | Open the PO file for the language that you want to translate with Poedit. The PO files are located by default in **resources/lang/i18n/[locale]/LC_MESSAGES/[domain].po**. If you have multiple gettext domains, one file is generated by each domain. 184 | 185 | 186 | 187 | Once Poedit is loaded press the Update button to load all localized strings. You can repeat this step anytime you add a new localized string. 188 | 189 | Fill translation fields in Poedit and save the file. The first time that you do this the MO files will be generated for each locale. 190 | 191 | ##### C. Runtime methods 192 | 193 | To change configuration on runtime you have these methods: 194 | 195 | ```php 196 | /** 197 | * Sets the Current locale. 198 | * Example param value: 'es_ES' 199 | * 200 | * @param mixed $locale the locale 201 | * @return LaravelGettext 202 | */ 203 | LaravelGettext::setLocale($locale); 204 | ``` 205 | 206 | ```php 207 | /** 208 | * Gets the Current locale. 209 | * Example returned value: 'es_ES' 210 | * 211 | * @return String 212 | */ 213 | LaravelGettext::getLocale(); 214 | ``` 215 | 216 | ```php 217 | /** 218 | * Gets the language portion of the locale. 219 | * Eg from en_GB, returns en 220 | * 221 | * @return mixed 222 | */ 223 | LaravelGettext::getLocaleLanguage() 224 | ``` 225 | 226 | ```php 227 | /** 228 | * Sets the Current encoding. 229 | * Example param value: 'UTF-8' 230 | * 231 | * @param mixed $encoding the encoding 232 | * @return LaravelGettext 233 | */ 234 | LaravelGettext::setEncoding($encoding); 235 | ``` 236 | 237 | ```php 238 | /** 239 | * Gets the Current encoding. 240 | * Example returned value: 'UTF-8' 241 | * 242 | * @return String 243 | */ 244 | LaravelGettext::getEncoding(); 245 | ``` 246 | 247 | ```php 248 | /** 249 | * Sets the current domain 250 | * 251 | * @param String $domain 252 | */ 253 | LaravelGettext::setDomain($domain); 254 | ``` 255 | 256 | ```php 257 | /** 258 | * Returns the current domain 259 | * 260 | * @return String 261 | */ 262 | LaravelGettext::getDomain(); 263 | ``` 264 | 265 | ```php 266 | /** 267 | * Returns the language selector object 268 | * 269 | * @param Array $labels 270 | * @return LanguageSelector 271 | */ 272 | LaravelGettext::getSelector($labels = []); 273 | ``` 274 | 275 | 276 | ### 5. Features and examples: 277 | 278 | #### A. Route and controller implementation example: 279 | 280 | app/Http/routes.php 281 | 282 | ```php 283 | Route::get('/lang/{locale?}', [ 284 | 'as'=>'lang', 285 | 'uses'=>'HomeController@changeLang' 286 | ]); 287 | ``` 288 | 289 | app/Http/Controllers/HomeController.php 290 | 291 | ```php 292 | /** 293 | * Changes the current language and returns to previous page 294 | * @return Redirect 295 | */ 296 | public function changeLang($locale=null) 297 | { 298 | LaravelGettext::setLocale($locale); 299 | return Redirect::to(URL::previous()); 300 | } 301 | ``` 302 | 303 | #### B. A basic language selector example: 304 | 305 | ```php 306 | 311 | ``` 312 | 313 | #### C. Built-in language selector: 314 | 315 | You can use the built-in language selector in your views: 316 | 317 | ```php 318 | // Plain php: 319 | LaravelGettext::getSelector()->render(); 320 | 321 | // Blade views: 322 | {!! LaravelGettext::getSelector()->render() !!} 323 | ``` 324 | 325 | It also supports custom labels: 326 | 327 | ```php 328 | LaravelGettext::getSelector([ 329 | 'en_US' => 'English', 330 | 'es_ES' => 'Spanish', 331 | 'de_DE' => 'Deutsch', 332 | ])->render(); 333 | ``` 334 | 335 | #### D. Adding source directories and domains 336 | 337 | You can achieve this editing the **source-paths** configuration array. By default resources/views and app/Http/Controllers are set. 338 | 339 | ```php 340 | /** 341 | * Paths where Poedit will search recursively for strings to translate. 342 | * All paths are relative to app/ (don't use trailing slash). 343 | * 344 | * Remember to call artisan gettext:update after change this. 345 | */ 346 | 'source-paths' => array( 347 | 'Http/Controllers', 348 | '../resources/views', 349 | 'foo/bar', // app/foo/bar 350 | ), 351 | ``` 352 | 353 | You may want your **translations in different files**. Translations in GNUGettext are separated by domains, domains are simply context names. 354 | 355 | Laravel-Gettext set always a default domain that contains all paths that doesn't belong to any domain, its name is established by the 'domain' configuration option. 356 | 357 | To add a new domain just wrap your paths in the desired domain name, like this example: 358 | 359 | ```php 360 | 'source-paths' => array( 361 | 'frontend' => array( 362 | 'Http/Controllers', 363 | '../resources/views/frontend', 364 | ), 365 | 'backend' => array( 366 | '../resources/views/backend', 367 | ), 368 | '../resources/views/misc', 369 | ), 370 | ``` 371 | 372 | This configuration generates three translation files by each language: **messages.po**, **frontend.po** and **backend.po** 373 | 374 | To change the current domain in runtime (a route-middleware would be a nice place for do this): 375 | 376 | ```php 377 | LaravelGettext::setDomain("backend"); 378 | ``` 379 | 380 | **Remember:** *update your gettext files every time you change the 'source-paths'* option, otherwise is not necessary. 381 | 382 | ```bash 383 | php artisan gettext:update 384 | ``` 385 | 386 | This command will update your PO files and will keep the current translations intact. After this you can open Poedit and click on update button to add the new text strings in the new paths. 387 | 388 | You can update only the files of a single domain with the same command: 389 | 390 | ```bash 391 | php artisan gettext:update --domain backend 392 | ``` 393 | 394 | #### E. About gettext cache (only applies to php-gettext native module) 395 | 396 | Sometimes when you edit/add translations on PO files the changes does not appear instantly. This is because the gettext cache system holds content. The most quick fix is restart your web server. 397 | 398 | ### 6. Contributing 399 | 400 | If you want to help with the development of this package, you can: 401 | 402 | - Warn about errors that you find, in issues section 403 | - Send me a pull request with your patch 404 | - Fix my disastrous English in the documentation/comments ;-) 405 | - Make a fork and create your own version of laravel-gettext 406 | - Give a star! 407 | 408 | 409 | ### 7. Upgrade from 4.* 410 | If you're upgrading from the 4.*, the one for Laravel 5.3.*, you need to refactor your usage of the ``__`` method. 411 | 412 | Laravel now use this method for their own translation. You now need to use ``_i`` instead and add this keyword in the configuration file of Laravel-Gettext: 413 | 414 | Also, if you're using Symfony as your backend, you can add the `_s` method. It's made to use the full feature set of Symfony plurals syntax. 415 | 416 | ```php 417 | /** 418 | * The keywords list used by poedit to search the strings to be translated 419 | * 420 | * The "_", "__" and "gettext" are singular translation functions 421 | * The "_n" and "ngettext" are plural translation functions 422 | * The "dgettext" function allows a translation domain to be explicitly specified 423 | * 424 | * "__" and "_n" and "_i" and "_s" are helpers functions @see \Xinax\LaravelGettext\Support\helpers.php 425 | */ 426 | 'keywords-list' => ['_', '__', '_i', '_s', 'gettext', '_n:1,2', 'ngettext:1,2', 'dgettext:2'],; 427 | ``` 428 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zerospam/laravel-gettext", 3 | "description": "Adds localization support to laravel applications in an easy way using Poedit and GNU gettext.", 4 | "homepage": "https://github.com/zerospam/laravel-gettext", 5 | "keywords": [ 6 | "gettext", 7 | "localization", 8 | "poedit", 9 | "laravel-gettext", 10 | "laravel", "translation" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Nicolás Daniel Palumbo", 16 | "email": "n@xinax.net" 17 | }, 18 | { 19 | "name": "Antoine Aflalo", 20 | "email": "dev+laravel_gettext@zerospam.ca" 21 | } 22 | ], 23 | "support": { 24 | "issues": "https://github.com/zerospam/laravel-gettext/issues" 25 | }, 26 | "require": { 27 | "php": ">=7.2", 28 | "laravel/framework": "^6.0 || ^7.0 || ^8.0", 29 | "laravel/helpers": "^1.1" 30 | }, 31 | "require-dev": { 32 | "mockery/mockery": "dev-master", 33 | "phpunit/phpunit": "~7.0 || ^8.5 || ^9", 34 | "squizlabs/php_codesniffer" : "1.5.*", 35 | "laravel/laravel": "^6.0 || ^7.0 || ^8.0", 36 | "php-coveralls/php-coveralls": "^2.1" 37 | }, 38 | "autoload": { 39 | "psr-0": { 40 | "Xinax\\LaravelGettext\\": "src/" 41 | }, 42 | "files": [ 43 | "src/Xinax/LaravelGettext/Support/helpers.php" 44 | ] 45 | }, 46 | "minimum-stability": "stable", 47 | "extra": { 48 | "laravel": { 49 | "providers": [ 50 | "Xinax\\LaravelGettext\\LaravelGettextServiceProvider" 51 | ] 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Adapters/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | fileSystem = new FileSystem( 35 | $configManager->get(), 36 | app_path(), 37 | storage_path() 38 | ); 39 | 40 | $this->configuration = $configManager->get(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Commands/GettextCreate.php: -------------------------------------------------------------------------------- 1 | prepare(); 29 | 30 | // Directories created counter 31 | $dirCreations = 0; 32 | 33 | try { 34 | // Locales 35 | $localesGenerated = $this->fileSystem->generateLocales(); 36 | 37 | foreach ($localesGenerated as $localePath) { 38 | $this->comment(sprintf("Locale directory created (%s)", $localePath)); 39 | $dirCreations++; 40 | } 41 | 42 | $this->info("Finished"); 43 | 44 | $msg = "The directory structure is right. No directory creation were needed."; 45 | 46 | if ($dirCreations) { 47 | $msg = $dirCreations . " directories has been created."; 48 | } 49 | 50 | $this->info($msg); 51 | 52 | } catch (\Exception $e) { 53 | $this->error($e->getFile() . ":" . $e->getLine() . " - " . $e->getMessage()); 54 | } 55 | } 56 | 57 | /** 58 | * Get the console command arguments. 59 | * 60 | * @return array 61 | */ 62 | protected function getArguments() 63 | { 64 | return []; 65 | } 66 | 67 | /** 68 | * Get the console command options. 69 | * 70 | * @return array 71 | */ 72 | protected function getOptions() 73 | { 74 | return []; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Commands/GettextUpdate.php: -------------------------------------------------------------------------------- 1 | prepare(); 34 | 35 | $domainPath = $this->fileSystem->getDomainPath(); 36 | $fileSystem = $this->fileSystem; 37 | 38 | try { 39 | // Translation files base path 40 | if (!file_exists($domainPath)) { 41 | throw new DirectoryNotFoundException( 42 | "You need to call gettext:create (No locale directory)" 43 | ); 44 | } 45 | 46 | $count = [ 47 | 'added' => 0, 48 | 'updated' => 0, 49 | ]; 50 | 51 | $domains = $this->configuration->getAllDomains(); 52 | 53 | foreach ($this->configuration->getSupportedLocales() as $locale) { 54 | $localePath = $this->fileSystem->getDomainPath($locale); 55 | 56 | // Create new locale 57 | if (!file_exists($localePath)) { 58 | $this->fileSystem->addLocale($localePath, $locale); 59 | $this->comment("New locale was added: $locale ($localePath)"); 60 | 61 | $count['added']++; 62 | 63 | continue; 64 | } 65 | 66 | // Domain by command line argument 67 | if ($this->option('domain')) { 68 | $domains = [$this->option('domain')]; 69 | } 70 | 71 | // Update by domain(s) 72 | foreach ($domains as $domain) { 73 | $fileSystem->updateLocale( 74 | $localePath, 75 | $locale, 76 | $domain 77 | ); 78 | 79 | $this->comment( 80 | sprintf( 81 | "PO file for locale: %s/%s updated successfully", 82 | $locale, 83 | $domain 84 | ) 85 | ); 86 | 87 | $count['updated']++; 88 | } 89 | } 90 | 91 | $this->info("Finished"); 92 | 93 | // Number of locales created 94 | if ($count['added'] > 0) { 95 | $this->info(sprintf('%s new locales were added.', $count['added'])); 96 | } 97 | 98 | 99 | // Number of locales updated 100 | if ($count['updated'] > 0) { 101 | $this->info(sprintf('%s locales updated.', $count['updated'])); 102 | } 103 | 104 | } catch (Exception $e) { 105 | $this->error($e->getFile() . ":" . $e->getLine() . " = " . $e->getMessage()); 106 | } 107 | } 108 | 109 | /** 110 | * Get the console command arguments. 111 | * 112 | * @return array 113 | */ 114 | protected function getArguments() 115 | { 116 | return []; 117 | } 118 | 119 | /** 120 | * Get the console command options. 121 | * 122 | * @return array 123 | */ 124 | protected function getOptions() 125 | { 126 | return [ 127 | [ 128 | 'domain', 129 | '-d', 130 | InputOption::VALUE_OPTIONAL, 131 | 'Update files only for this domain', 132 | null, 133 | ] 134 | ]; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Composers/LanguageSelector.php: -------------------------------------------------------------------------------- 1 | labels = $labels; 30 | $this->gettext = $gettext; 31 | } 32 | 33 | /** 34 | * @param LaravelGettext $gettext 35 | * @param array $labels 36 | * @return LanguageSelector 37 | */ 38 | public static function create(LaravelGettext $gettext, $labels = []) 39 | { 40 | return new LanguageSelector($gettext, $labels); 41 | } 42 | 43 | /** 44 | * Renders the language selector 45 | * @return string 46 | */ 47 | public function render() 48 | { 49 | /** @var string $currentLocale */ 50 | $currentLocale = $this->gettext->getLocale(); 51 | 52 | $html = ''; 73 | 74 | return $html; 75 | } 76 | 77 | /** 78 | * Convert to string 79 | * 80 | * @return string 81 | */ 82 | public function __toString() 83 | { 84 | return $this->render(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Config/ConfigManager.php: -------------------------------------------------------------------------------- 1 | config = $this->generateFromArray($config); 33 | } else { 34 | // In Laravel 5.3 we need empty config model 35 | $this->config = new ConfigModel; 36 | } 37 | } 38 | 39 | /** 40 | * Get new instance of the ConfigManager 41 | * 42 | * @param null $config 43 | * @return static 44 | * @throws RequiredConfigurationFileException 45 | */ 46 | public static function create($config = null) 47 | { 48 | if (is_null($config)) { 49 | // Default package configuration file (published) 50 | $config = Config::get(static::DEFAULT_PACKAGE_CONFIG); 51 | } 52 | 53 | return new static($config); 54 | } 55 | 56 | /** 57 | * Get the config model 58 | * 59 | * @return ConfigModel 60 | */ 61 | public function get() 62 | { 63 | return $this->config; 64 | } 65 | 66 | /** 67 | * Creates a configuration container and checks the required fields 68 | * 69 | * @param array $config 70 | * @return ConfigModel 71 | * @throws RequiredConfigurationKeyException 72 | */ 73 | protected function generateFromArray(array $config) 74 | { 75 | $requiredKeys = [ 76 | 'locale', 77 | 'fallback-locale', 78 | 'encoding' 79 | ]; 80 | 81 | foreach ($requiredKeys as $key) { 82 | if (!array_key_exists($key, $config)) { 83 | throw new RequiredConfigurationKeyException( 84 | sprintf('Unconfigured required value: %s', $key) 85 | ); 86 | } 87 | } 88 | 89 | $container = new ConfigModel(); 90 | 91 | $id = isset($config['session-identifier']) ? $config['session-identifier'] : 'laravel-gettext-locale'; 92 | 93 | $adapter = isset($config['adapter']) ? $config['adapter'] : \Xinax\LaravelGettext\Adapters\LaravelAdapter::class; 94 | 95 | $storage = isset($config['storage']) ? $config['storage'] : SessionStorage::class; 96 | 97 | $container->setLocale($config['locale']) 98 | ->setSessionIdentifier($id) 99 | ->setEncoding($config['encoding']) 100 | ->setCategories(array_get('categories', $config, ['LC_ALL'])) 101 | ->setFallbackLocale($config['fallback-locale']) 102 | ->setSupportedLocales($config['supported-locales']) 103 | ->setDomain($config['domain']) 104 | ->setTranslationsPath($config['translations-path']) 105 | ->setProject($config['project']) 106 | ->setTranslator($config['translator']) 107 | ->setSourcePaths($config['source-paths']) 108 | ->setSyncLaravel($config['sync-laravel']) 109 | ->setAdapter($adapter) 110 | ->setStorage($storage); 111 | 112 | if (array_key_exists('relative-path', $config)) { 113 | $container->setRelativePath($config['relative-path']); 114 | } 115 | 116 | if (array_key_exists("custom-locale", $config)) { 117 | $container->setCustomLocale($config['custom-locale']); 118 | } 119 | 120 | if (array_key_exists("keywords-list", $config)) { 121 | $container->setKeywordsList($config['keywords-list']); 122 | } 123 | 124 | if (array_key_exists("handler", $config)) { 125 | $container->setHandler($config['handler']); 126 | } 127 | 128 | return $container; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Config/Models/Config.php: -------------------------------------------------------------------------------- 1 | encoding = 'UTF-8'; 136 | $this->supportedLocales = []; 137 | $this->sourcePaths = []; 138 | $this->customLocale = false; 139 | $this->relativePath = "../../../../../app"; 140 | } 141 | 142 | public function getRelativePath() 143 | { 144 | return $this->relativePath; 145 | } 146 | 147 | public function setRelativePath($path) 148 | { 149 | $this->relativePath = $path; 150 | } 151 | 152 | /** 153 | * @return string 154 | */ 155 | public function getSessionIdentifier() 156 | { 157 | return $this->sessionIdentifier; 158 | } 159 | 160 | /** 161 | * @param string $sessionIdentifier 162 | * 163 | * @return $this 164 | */ 165 | public function setSessionIdentifier($sessionIdentifier) 166 | { 167 | $this->sessionIdentifier = $sessionIdentifier; 168 | 169 | return $this; 170 | } 171 | 172 | /** 173 | * @return string 174 | */ 175 | public function getEncoding() 176 | { 177 | return $this->encoding; 178 | } 179 | 180 | /** 181 | * @param string $encoding 182 | * 183 | * @return $this 184 | */ 185 | public function setEncoding($encoding) 186 | { 187 | $this->encoding = $encoding; 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * @return string 194 | */ 195 | public function getLocale() 196 | { 197 | return $this->locale; 198 | } 199 | 200 | /** 201 | * @param string $locale 202 | * 203 | * @return $this 204 | */ 205 | public function setLocale($locale) 206 | { 207 | $this->locale = $locale; 208 | 209 | return $this; 210 | } 211 | 212 | /** 213 | * Gets categories 214 | * 215 | * @return array 216 | */ 217 | public function getCategories() 218 | { 219 | return $this->categories; 220 | } 221 | 222 | /** 223 | * Sets categories 224 | * 225 | * @param array $categories 226 | * 227 | * @return self 228 | */ 229 | public function setCategories($categories) 230 | { 231 | $this->categories = $categories; 232 | 233 | return $this; 234 | } 235 | 236 | /** 237 | * @return string 238 | */ 239 | public function getFallbackLocale() 240 | { 241 | return $this->fallbackLocale; 242 | } 243 | 244 | /** 245 | * @param string $fallbackLocale 246 | * 247 | * @return $this 248 | */ 249 | public function setFallbackLocale($fallbackLocale) 250 | { 251 | $this->fallbackLocale = $fallbackLocale; 252 | 253 | return $this; 254 | } 255 | 256 | /** 257 | * @return array 258 | */ 259 | public function getSupportedLocales() 260 | { 261 | return $this->supportedLocales; 262 | } 263 | 264 | /** 265 | * @param array $supportedLocales 266 | * 267 | * @return $this 268 | */ 269 | public function setSupportedLocales($supportedLocales) 270 | { 271 | $this->supportedLocales = $supportedLocales; 272 | 273 | return $this; 274 | } 275 | 276 | /** 277 | * @return string 278 | */ 279 | public function getDomain() 280 | { 281 | return $this->domain; 282 | } 283 | 284 | /** 285 | * @param string $domain 286 | * 287 | * @return $this 288 | */ 289 | public function setDomain($domain) 290 | { 291 | $this->domain = $domain; 292 | 293 | return $this; 294 | } 295 | 296 | /** 297 | * @return string 298 | */ 299 | public function getTranslationsPath() 300 | { 301 | return $this->translationsPath; 302 | } 303 | 304 | /** 305 | * @param string $translationsPath 306 | * 307 | * @return $this 308 | */ 309 | public function setTranslationsPath($translationsPath) 310 | { 311 | $this->translationsPath = $translationsPath; 312 | 313 | return $this; 314 | } 315 | 316 | /** 317 | * @return string 318 | */ 319 | public function getProject() 320 | { 321 | return $this->project; 322 | } 323 | 324 | /** 325 | * @param string $project 326 | * 327 | * @return $this 328 | */ 329 | public function setProject($project) 330 | { 331 | $this->project = $project; 332 | 333 | return $this; 334 | } 335 | 336 | /** 337 | * @return string 338 | */ 339 | public function getTranslator() 340 | { 341 | return $this->translator; 342 | } 343 | 344 | /** 345 | * @param string $translator 346 | * 347 | * @return $this 348 | */ 349 | public function setTranslator($translator) 350 | { 351 | $this->translator = $translator; 352 | 353 | return $this; 354 | } 355 | 356 | /** 357 | * @return array 358 | */ 359 | public function getSourcePaths() 360 | { 361 | return $this->sourcePaths; 362 | } 363 | 364 | /** 365 | * @param array $sourcePaths 366 | * 367 | * @return $this 368 | */ 369 | public function setSourcePaths($sourcePaths) 370 | { 371 | $this->sourcePaths = $sourcePaths; 372 | 373 | return $this; 374 | } 375 | 376 | /** 377 | * @return boolean 378 | */ 379 | public function isSyncLaravel() 380 | { 381 | return $this->syncLaravel; 382 | } 383 | 384 | /** 385 | * Gets the Sync with laravel locale. 386 | * 387 | * @return mixed 388 | */ 389 | public function getSyncLaravel() 390 | { 391 | return $this->syncLaravel; 392 | } 393 | 394 | /** 395 | * @param boolean $syncLaravel 396 | * 397 | * @return $this 398 | */ 399 | public function setSyncLaravel($syncLaravel) 400 | { 401 | $this->syncLaravel = $syncLaravel; 402 | 403 | return $this; 404 | } 405 | 406 | /** 407 | * Gets the adapter class. 408 | * 409 | * @return string 410 | */ 411 | public function getAdapter() 412 | { 413 | return $this->adapter; 414 | } 415 | 416 | /** 417 | * @param string $adapter 418 | * 419 | * @return $this 420 | */ 421 | public function setAdapter($adapter) 422 | { 423 | $this->adapter = $adapter; 424 | 425 | return $this; 426 | } 427 | 428 | /** 429 | * Getter for storage 430 | * 431 | * @return string 432 | */ 433 | public function getStorage() 434 | { 435 | return $this->storage; 436 | } 437 | 438 | /** 439 | * @param string $storage 440 | * 441 | * @return $this 442 | */ 443 | public function setStorage($storage) 444 | { 445 | $this->storage = $storage; 446 | 447 | return $this; 448 | } 449 | 450 | 451 | 452 | /** 453 | * Return an array with all domain names 454 | * 455 | * @return array 456 | */ 457 | public function getAllDomains() 458 | { 459 | $domains = [$this->domain]; // add the default domain 460 | 461 | foreach ($this->sourcePaths as $domain => $paths) { 462 | if (is_array($paths)) { 463 | array_push($domains, $domain); 464 | } 465 | } 466 | 467 | return array_unique($domains); 468 | } 469 | 470 | /** 471 | * Return all routes for a single domain 472 | * 473 | * @param $domain 474 | * 475 | * @return array 476 | */ 477 | public function getSourcesFromDomain($domain) 478 | { 479 | // grab any paths wrapped in $domain 480 | $explicitPaths = array_key_exists($domain, $this->sourcePaths) 481 | ? $this->sourcePaths[$domain] 482 | : []; 483 | 484 | // if we're not including the default domain, return what we have so far 485 | if ($this->domain != $domain) { 486 | return $explicitPaths; 487 | } 488 | 489 | // otherwise, grab all the default domain paths 490 | // and merge them with paths wrapped in $domain 491 | return array_reduce( 492 | $this->sourcePaths, 493 | function ($carry, $path) { 494 | if (!is_array($path)) { 495 | $carry[] = $path; 496 | } 497 | 498 | return $carry; 499 | }, 500 | $explicitPaths 501 | ); 502 | } 503 | 504 | /** 505 | * Gets C locale setting. 506 | * 507 | * @return boolean 508 | */ 509 | public function getCustomLocale() 510 | { 511 | return $this->customLocale; 512 | } 513 | 514 | /** 515 | * Sets if will use C locale structure. 516 | * 517 | * @param mixed $sourcePaths the source paths 518 | * 519 | * @return self 520 | */ 521 | public function setCustomLocale($customLocale) 522 | { 523 | $this->customLocale = $customLocale; 524 | 525 | return $this; 526 | } 527 | 528 | /** 529 | * Gets the Poedit keywords list. 530 | * 531 | * @return mixed 532 | */ 533 | public function getKeywordsList() 534 | { 535 | return !empty($this->keywordsList) 536 | ? $this->keywordsList 537 | : ['_']; 538 | } 539 | 540 | /** 541 | * Sets the Poedit keywords list. 542 | * 543 | * @param mixed $keywordsList the keywords list 544 | * 545 | * @return self 546 | */ 547 | public function setKeywordsList($keywordsList) 548 | { 549 | $this->keywordsList = $keywordsList; 550 | 551 | return $this; 552 | } 553 | 554 | /** 555 | * Sets the handler type. Also check for valid handler name 556 | * 557 | * @param $handler 558 | * 559 | * @return $this 560 | * 561 | * @throws \Exception 562 | */ 563 | public function setHandler($handler) 564 | { 565 | if (!in_array($handler, [ 566 | 'symfony', 567 | 'gettext', 568 | ]) 569 | ) { 570 | throw new \Exception("Handler '$handler' is not supported'"); 571 | }; 572 | 573 | $this->handler = $handler; 574 | 575 | return $this; 576 | } 577 | 578 | /** 579 | * Returns the handler name 580 | * 581 | * @return mixed 582 | */ 583 | public function getHandler() 584 | { 585 | return !empty($this->handler) 586 | ? $this->handler 587 | : 'symfony'; 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Exceptions/DirectoryNotFoundException.php: -------------------------------------------------------------------------------- 1 | underlyingFileLoader = $underlyingFileLoader; 30 | } 31 | 32 | 33 | /** 34 | * @param string $resource 35 | * 36 | * @return array 37 | * 38 | * @throws InvalidResourceException if stream content has an invalid format 39 | */ 40 | protected function loadResource(string $resource): array 41 | { 42 | if (!extension_loaded('apcu')) { 43 | return $this->underlyingFileLoader->loadResource($resource); 44 | } 45 | 46 | return $this->cachedMessages($resource); 47 | } 48 | 49 | /** 50 | * Calculate the checksum for the file 51 | * 52 | * @param $resource 53 | * 54 | * @return string 55 | */ 56 | private function checksum($resource) 57 | { 58 | return filemtime($resource) . '-' . filesize($resource); 59 | } 60 | 61 | /** 62 | * Checksum saved in cache 63 | * 64 | * @param $resource 65 | * 66 | * @return string 67 | */ 68 | private function cacheChecksum($resource) 69 | { 70 | return apcu_fetch($resource . '-checksum'); 71 | } 72 | 73 | /** 74 | * Set the cache checksum 75 | * 76 | * @param $resource 77 | * @param $checksum 78 | * 79 | * @return array|bool 80 | */ 81 | private function setCacheChecksum($resource, $checksum) 82 | { 83 | return apcu_store($resource . '-checksum', $checksum); 84 | } 85 | 86 | /** 87 | * Return the cached messages 88 | * 89 | * @param $ressource 90 | * 91 | * @return array 92 | */ 93 | private function cachedMessages($ressource) 94 | { 95 | if ($this->cacheChecksum($ressource) == ($currentChecksum = $this->checksum($ressource))) { 96 | return apcu_fetch($ressource . '-messages'); 97 | } 98 | 99 | $messages = $this->underlyingFileLoader->loadResource($ressource); 100 | 101 | apcu_store($ressource . '-messages', $messages); 102 | $this->setCacheChecksum($ressource, $currentChecksum); 103 | 104 | return $messages; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/FileLoader/MoFileLoader.php: -------------------------------------------------------------------------------- 1 | readLong($stream, $isBigEndian); 65 | $count = $this->readLong($stream, $isBigEndian); 66 | $offsetId = $this->readLong($stream, $isBigEndian); 67 | $offsetTranslated = $this->readLong($stream, $isBigEndian); 68 | // sizeHashes 69 | $this->readLong($stream, $isBigEndian); 70 | // offsetHashes 71 | $this->readLong($stream, $isBigEndian); 72 | 73 | $messages = array(); 74 | 75 | for ($i = 0; $i < $count; ++$i) { 76 | $pluralId = null; 77 | $translated = null; 78 | 79 | fseek($stream, $offsetId + $i * 8); 80 | 81 | $length = $this->readLong($stream, $isBigEndian); 82 | $offset = $this->readLong($stream, $isBigEndian); 83 | 84 | if ($length < 1) { 85 | continue; 86 | } 87 | 88 | fseek($stream, $offset); 89 | $singularId = fread($stream, $length); 90 | 91 | if (strpos($singularId, "\000") !== false) { 92 | list($singularId, $pluralId) = explode("\000", $singularId); 93 | } 94 | 95 | fseek($stream, $offsetTranslated + $i * 8); 96 | $length = $this->readLong($stream, $isBigEndian); 97 | $offset = $this->readLong($stream, $isBigEndian); 98 | 99 | if ($length < 1) { 100 | continue; 101 | } 102 | 103 | fseek($stream, $offset); 104 | $translated = fread($stream, $length); 105 | 106 | if (strpos($translated, "\000") !== false) { 107 | $translated = explode("\000", $translated); 108 | } 109 | 110 | $ids = array('singular' => $singularId, 'plural' => $pluralId); 111 | $item = compact('ids', 'translated'); 112 | 113 | if (is_array($item['translated'])) { 114 | $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); 115 | if (isset($item['ids']['plural'])) { 116 | $messages[$item['ids']['plural']] = stripcslashes(implode('|', $item['translated'])); 117 | } 118 | } elseif (!empty($item['ids']['singular'])) { 119 | $messages[$item['ids']['singular']] = stripcslashes($item['translated']); 120 | } 121 | } 122 | 123 | fclose($stream); 124 | 125 | return array_filter($messages); 126 | } 127 | 128 | /** 129 | * Reads an unsigned long from stream respecting endianess. 130 | * 131 | * @param resource $stream 132 | * @param bool $isBigEndian 133 | * 134 | * @return int 135 | */ 136 | private function readLong($stream, $isBigEndian) 137 | { 138 | $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); 139 | $result = current($result); 140 | 141 | return (int) substr($result, -8); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/FileSystem.php: -------------------------------------------------------------------------------- 1 | configuration = $config; 56 | $this->basePath = $basePath; 57 | 58 | $this->storagePath = $storagePath; 59 | $this->storageContainer = "framework"; 60 | $this->folderName = 'i18n'; 61 | } 62 | 63 | /** 64 | * Build views in order to parse php files 65 | * 66 | * @param Array $viewPaths 67 | * @param String $domain 68 | * 69 | * @return Boolean status 70 | */ 71 | /** 72 | * Build views in order to parse php files 73 | * 74 | * @param array $viewPaths 75 | * @param string $domain 76 | * @return bool 77 | * @throws FileCreationException 78 | */ 79 | public function compileViews(array $viewPaths, $domain) 80 | { 81 | // Check the output directory 82 | $targetDir = $this->storagePath . DIRECTORY_SEPARATOR . $this->storageContainer; 83 | 84 | if (!file_exists($targetDir)) { 85 | $this->createDirectory($targetDir); 86 | } 87 | 88 | // Domain separation 89 | $domainDir = $targetDir . DIRECTORY_SEPARATOR . $domain; 90 | $this->clearDirectory($domainDir); 91 | $this->createDirectory($domainDir); 92 | 93 | foreach ($viewPaths as $path) { 94 | $path = $this->basePath . DIRECTORY_SEPARATOR . $path; 95 | 96 | if (!$realPath = realPath($path)) { 97 | throw new Exceptions\DirectoryNotFoundException("Failed to resolve $path, please check that it exists"); 98 | } 99 | 100 | $fs = new \Illuminate\Filesystem\Filesystem($path); 101 | $files = $fs->allFiles($realPath); 102 | 103 | $compiler = new \Illuminate\View\Compilers\BladeCompiler($fs, $domainDir); 104 | 105 | foreach ($files as $file) { 106 | $filePath = $file->getRealPath(); 107 | $compiler->setPath($filePath); 108 | 109 | $contents = $compiler->compileString($fs->get($filePath)); 110 | 111 | $compiledPath = $compiler->getCompiledPath($compiler->getPath()); 112 | 113 | $fs->put( 114 | $compiledPath . '.php', 115 | $contents 116 | ); 117 | } 118 | } 119 | 120 | return true; 121 | } 122 | 123 | /** 124 | * Constructs and returns the full path to the translation files 125 | * 126 | * @param null $append 127 | * @return string 128 | */ 129 | public function getDomainPath($append = null) 130 | { 131 | $path = [ 132 | $this->basePath, 133 | $this->configuration->getTranslationsPath(), 134 | $this->folderName, 135 | ]; 136 | 137 | if (!is_null($append)) { 138 | array_push($path, $append); 139 | } 140 | 141 | return implode(DIRECTORY_SEPARATOR, $path); 142 | } 143 | 144 | /** 145 | * Creates a configured .po file on $path 146 | * If PHP are not able to create the file the content will be returned instead 147 | * 148 | * @param string $path 149 | * @param string $locale 150 | * @param string $domain 151 | * @param bool|true $write 152 | * @return int|string 153 | */ 154 | public function createPOFile($path, $locale, $domain, $write = true) 155 | { 156 | $project = $this->configuration->getProject(); 157 | $timestamp = date("Y-m-d H:iO"); 158 | $translator = $this->configuration->getTranslator(); 159 | $encoding = $this->configuration->getEncoding(); 160 | 161 | $relativePath = $this->configuration->getRelativePath(); 162 | 163 | $keywords = implode(';', $this->configuration->getKeywordsList()); 164 | 165 | $template = 'msgid ""' . "\n"; 166 | $template .= 'msgstr ""' . "\n"; 167 | $template .= '"Project-Id-Version: ' . $project . '\n' . "\"\n"; 168 | $template .= '"POT-Creation-Date: ' . $timestamp . '\n' . "\"\n"; 169 | $template .= '"PO-Revision-Date: ' . $timestamp . '\n' . "\"\n"; 170 | $template .= '"Last-Translator: ' . $translator . '\n' . "\"\n"; 171 | $template .= '"Language-Team: ' . $translator . '\n' . "\"\n"; 172 | $template .= '"Language: ' . $locale . '\n' . "\"\n"; 173 | $template .= '"MIME-Version: 1.0' . '\n' . "\"\n"; 174 | $template .= '"Content-Type: text/plain; charset=' . $encoding . '\n' . "\"\n"; 175 | $template .= '"Content-Transfer-Encoding: 8bit' . '\n' . "\"\n"; 176 | $template .= '"X-Generator: Poedit 1.5.4' . '\n' . "\"\n"; 177 | $template .= '"X-Poedit-KeywordsList: ' . $keywords . '\n' . "\"\n"; 178 | $template .= '"X-Poedit-Basepath: ' . $relativePath . '\n' . "\"\n"; 179 | $template .= '"X-Poedit-SourceCharset: ' . $encoding . '\n' . "\"\n"; 180 | 181 | // Source paths 182 | $sourcePaths = $this->configuration->getSourcesFromDomain($domain); 183 | 184 | // Compiled views on paths 185 | if (count($sourcePaths)) { 186 | 187 | // View compilation 188 | $this->compileViews($sourcePaths, $domain); 189 | array_push($sourcePaths, $this->getStorageForDomain($domain)); 190 | 191 | $i = 0; 192 | 193 | foreach ($sourcePaths as $sourcePath) { 194 | $template .= '"X-Poedit-SearchPath-' . $i . ': ' . $sourcePath . '\n' . "\"\n"; 195 | $i++; 196 | } 197 | 198 | } 199 | 200 | if (!$write) { 201 | return $template . "\n"; 202 | } 203 | 204 | // File creation 205 | $file = fopen($path, "w"); 206 | $result = fwrite($file, $template); 207 | fclose($file); 208 | 209 | return $result; 210 | } 211 | 212 | /** 213 | * Validate if the directory can be created 214 | * 215 | * @param $path 216 | * @throws FileCreationException 217 | */ 218 | protected function createDirectory($path) 219 | { 220 | if (!file_exists($path) && !mkdir($path)) { 221 | throw new FileCreationException( 222 | sprintf('Can\'t create the directory: %s', $path) 223 | ); 224 | } 225 | } 226 | 227 | /** 228 | * Adds a new locale directory + .po file 229 | * 230 | * @param String $localePath 231 | * @param String $locale 232 | * @throws FileCreationException 233 | */ 234 | public function addLocale($localePath, $locale) 235 | { 236 | $data = array( 237 | $localePath, 238 | "LC_MESSAGES" 239 | ); 240 | 241 | if (!file_exists($localePath)) { 242 | $this->createDirectory($localePath); 243 | } 244 | 245 | if ($this->configuration->getCustomLocale()) { 246 | $data[1] = 'C'; 247 | 248 | $gettextPath = implode(DIRECTORY_SEPARATOR, $data); 249 | if (!file_exists($gettextPath)) { 250 | $this->createDirectory($gettextPath); 251 | } 252 | 253 | $data[2] = 'LC_MESSAGES'; 254 | } 255 | 256 | $gettextPath = implode(DIRECTORY_SEPARATOR, $data); 257 | if (!file_exists($gettextPath)) { 258 | $this->createDirectory($gettextPath); 259 | } 260 | 261 | 262 | // File generation for each domain 263 | foreach ($this->configuration->getAllDomains() as $domain) { 264 | $data[3] = $domain . ".po"; 265 | 266 | $localePOPath = implode(DIRECTORY_SEPARATOR, $data); 267 | 268 | if (!$this->createPOFile($localePOPath, $locale, $domain)) { 269 | throw new FileCreationException( 270 | sprintf('Can\'t create the file: %s', $localePOPath) 271 | ); 272 | } 273 | 274 | } 275 | 276 | } 277 | 278 | /** 279 | * Update the .po file headers by domain 280 | * (mainly source-file paths) 281 | * 282 | * @param $localePath 283 | * @param $locale 284 | * @param $domain 285 | * @return bool 286 | * @throws LocaleFileNotFoundException 287 | */ 288 | public function updateLocale($localePath, $locale, $domain) 289 | { 290 | $data = [ 291 | $localePath, 292 | "LC_MESSAGES", 293 | $domain . ".po", 294 | ]; 295 | 296 | if ($this->configuration->getCustomLocale()) { 297 | $customLocale = array('C'); 298 | array_splice($data, 1, 0, $customLocale); 299 | } 300 | 301 | $localePOPath = implode(DIRECTORY_SEPARATOR, $data); 302 | 303 | if (!file_exists($localePOPath) || !$localeContents = file_get_contents($localePOPath)) { 304 | throw new LocaleFileNotFoundException( 305 | sprintf('Can\'t read %s verify your locale structure', $localePOPath) 306 | ); 307 | } 308 | 309 | $newHeader = $this->createPOFile( 310 | $localePOPath, 311 | $locale, 312 | $domain, 313 | false 314 | ); 315 | 316 | // Header replacement 317 | $localeContents = preg_replace('/^([^#])+:?/', $newHeader, $localeContents); 318 | 319 | if (!file_put_contents($localePOPath, $localeContents)) { 320 | throw new LocaleFileNotFoundException( 321 | sprintf('Can\'t write on %s', $localePOPath) 322 | ); 323 | } 324 | 325 | return true; 326 | } 327 | 328 | /** 329 | * Return the relative path from a file or directory to anothe 330 | * 331 | * @param string $from 332 | * @param string $to 333 | * @return string 334 | * @author Laurent Goussard 335 | */ 336 | public function getRelativePath($from, $to) 337 | { 338 | // Compatibility fixes for Windows paths 339 | $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; 340 | $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; 341 | $from = str_replace('\\', '/', $from); 342 | $to = str_replace('\\', '/', $to); 343 | 344 | $from = explode('/', $from); 345 | $to = explode('/', $to); 346 | $relPath = $to; 347 | 348 | foreach ($from as $depth => $dir) { 349 | if ($dir !== $to[$depth]) { 350 | // Number of remaining directories 351 | $remaining = count($from) - $depth; 352 | 353 | if ($remaining > 1) { 354 | // Add traversals up to first matching directory 355 | $padLength = (count($relPath) + $remaining - 1) * -1; 356 | 357 | $relPath = array_pad( 358 | $relPath, 359 | $padLength, 360 | '..' 361 | ); 362 | 363 | break; 364 | } 365 | 366 | $relPath[0] = './' . $relPath[0]; 367 | } 368 | 369 | array_shift($relPath); 370 | } 371 | 372 | return implode('/', $relPath); 373 | 374 | } 375 | 376 | /** 377 | * Checks the required directory 378 | * Optionally checks each local directory, if $checkLocales is true 379 | * 380 | * @param bool|false $checkLocales 381 | * @return bool 382 | * @throws DirectoryNotFoundException 383 | */ 384 | public function checkDirectoryStructure($checkLocales = false) 385 | { 386 | // Application base path 387 | if (!file_exists($this->basePath)) { 388 | throw new Exceptions\DirectoryNotFoundException( 389 | sprintf( 390 | 'Missing root path directory: %s, check the \'base-path\' key in your configuration.', 391 | $this->basePath 392 | ) 393 | ); 394 | } 395 | 396 | // Domain path 397 | $domainPath = $this->getDomainPath(); 398 | 399 | // Translation files domain path 400 | if (!file_exists($domainPath)) { 401 | throw new Exceptions\DirectoryNotFoundException( 402 | sprintf( 403 | 'Missing base required directory: %s, remember to run \'artisan gettext:create\' the first time', 404 | $domainPath 405 | ) 406 | ); 407 | } 408 | 409 | if (!$checkLocales) { 410 | return true; 411 | } 412 | 413 | foreach ($this->configuration->getSupportedLocales() as $locale) { 414 | // Default locale is not needed 415 | if ($locale == $this->configuration->getLocale()) { 416 | continue; 417 | } 418 | 419 | $localePath = $this->getDomainPath($locale); 420 | 421 | if (!file_exists($localePath)) { 422 | throw new Exceptions\DirectoryNotFoundException( 423 | sprintf( 424 | 'Missing locale required directory: %s, maybe you forgot to run \'artisan gettext:update\'', 425 | $locale 426 | ) 427 | ); 428 | } 429 | } 430 | 431 | return true; 432 | } 433 | 434 | /** 435 | * Creates the localization directories and files by domain 436 | * 437 | * @return array 438 | * @throws FileCreationException 439 | */ 440 | public function generateLocales() 441 | { 442 | // Application base path 443 | if (!file_exists($this->getDomainPath())) { 444 | $this->createDirectory($this->getDomainPath()); 445 | } 446 | $localePaths = []; 447 | 448 | // Locale directories 449 | foreach ($this->configuration->getSupportedLocales() as $locale) { 450 | $localePath = $this->getDomainPath($locale); 451 | 452 | if (!file_exists($localePath)) { 453 | // Locale directory is created 454 | $this->addLocale($localePath, $locale); 455 | 456 | array_push($localePaths, $localePath); 457 | 458 | } 459 | } 460 | 461 | return $localePaths; 462 | } 463 | 464 | 465 | /** 466 | * Gets the package configuration model. 467 | * 468 | * @return Config 469 | */ 470 | public function getConfiguration() 471 | { 472 | return $this->configuration; 473 | } 474 | 475 | /** 476 | * Set the package configuration model 477 | * 478 | * @param Config $configuration 479 | * @return $this 480 | */ 481 | public function setConfiguration(Config $configuration) 482 | { 483 | $this->configuration = $configuration; 484 | return $this; 485 | } 486 | 487 | /** 488 | * Get the filesystem base path 489 | * 490 | * @return string 491 | */ 492 | public function getBasePath() 493 | { 494 | return $this->basePath; 495 | } 496 | 497 | /** 498 | * Set the filesystem base path 499 | * 500 | * @param $basePath 501 | * @return $this 502 | */ 503 | public function setBasePath($basePath) 504 | { 505 | $this->basePath = $basePath; 506 | return $this; 507 | } 508 | 509 | /** 510 | * Get the storage path 511 | * 512 | * @return string 513 | */ 514 | public function getStoragePath() 515 | { 516 | return $this->storagePath; 517 | } 518 | 519 | /** 520 | * Set the storage path 521 | * 522 | * @param $storagePath 523 | * @return $this 524 | */ 525 | public function setStoragePath($storagePath) 526 | { 527 | $this->storagePath = $storagePath; 528 | return $this; 529 | } 530 | 531 | /** 532 | * Get the full path for domain storage directory 533 | * 534 | * @param $domain 535 | * @return String 536 | */ 537 | public function getStorageForDomain($domain) 538 | { 539 | $domainPath = $this->storagePath . 540 | DIRECTORY_SEPARATOR . 541 | $this->storageContainer . 542 | DIRECTORY_SEPARATOR . 543 | $domain; 544 | 545 | return $this->getRelativePath($this->basePath, $domainPath); 546 | } 547 | 548 | /** 549 | * Removes the directory contents recursively 550 | * 551 | * @param string $path 552 | * @return null|boolean 553 | */ 554 | public static function clearDirectory($path) 555 | { 556 | if (!file_exists($path)) { 557 | return null; 558 | } 559 | 560 | $files = new RecursiveIteratorIterator( 561 | new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), 562 | RecursiveIteratorIterator::CHILD_FIRST 563 | ); 564 | 565 | foreach ($files as $fileinfo) { 566 | // if the file isn't a .gitignore file we should remove it. 567 | if ($fileinfo->getFilename() !== '.gitignore') { 568 | $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink'); 569 | $todo($fileinfo->getRealPath()); 570 | } 571 | } 572 | 573 | // since the folder now contains a .gitignore we can't remove it 574 | //rmdir($path); 575 | return true; 576 | } 577 | 578 | /** 579 | * Get the folder name 580 | * 581 | * @return string 582 | */ 583 | public function getFolderName() 584 | { 585 | return $this->folderName; 586 | } 587 | 588 | /** 589 | * Set the folder name 590 | * 591 | * @param $folderName 592 | */ 593 | public function setFolderName($folderName) 594 | { 595 | $this->folderName = $folderName; 596 | } 597 | 598 | /** 599 | * Returns the full path for a .po/.mo file from its domain and locale 600 | * 601 | * @param $locale 602 | * @param $domain 603 | * 604 | * @param string $type 605 | * 606 | * @return string 607 | */ 608 | public function makeFilePath($locale, $domain, $type = 'po') 609 | { 610 | $filePath = implode( 611 | DIRECTORY_SEPARATOR, [ 612 | $locale, 613 | 'LC_MESSAGES', 614 | $domain . "." . $type 615 | ] 616 | ); 617 | 618 | return $this->getDomainPath($filePath); 619 | } 620 | 621 | } 622 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/LaravelGettext.php: -------------------------------------------------------------------------------- 1 | translator = $gettext; 24 | } 25 | 26 | /** 27 | * Get the current encoding 28 | * 29 | * @return string 30 | */ 31 | public function getEncoding() 32 | { 33 | return $this->translator->getEncoding(); 34 | } 35 | 36 | /** 37 | * Set the current encoding 38 | * 39 | * @param string $encoding 40 | * @return $this 41 | */ 42 | public function setEncoding($encoding) 43 | { 44 | $this->encoding = $encoding; 45 | return $this; 46 | } 47 | 48 | /** 49 | * Gets the Current locale. 50 | * 51 | * @return string 52 | */ 53 | public function getLocale() 54 | { 55 | return $this->translator->getLocale(); 56 | } 57 | 58 | /** 59 | * Set current locale 60 | * 61 | * @param string $locale 62 | * @return $this 63 | * @throws Exceptions\LocaleNotSupportedException 64 | * @throws \Exception 65 | */ 66 | public function setLocale($locale) 67 | { 68 | if ($locale != $this->getLocale()) { 69 | $this->translator->setLocale($locale); 70 | } 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Get the language portion of the locale 77 | * (ex. en_GB returns en) 78 | * 79 | * @param string|null $locale 80 | * @return string|null 81 | */ 82 | public function getLocaleLanguage($locale = null) 83 | { 84 | if (is_null($locale)) { 85 | $locale = $this->getLocale(); 86 | } 87 | 88 | $localeArray = explode('_', $locale); 89 | 90 | if (!isset($localeArray[0])) { 91 | return null; 92 | } 93 | 94 | return $localeArray[0]; 95 | } 96 | 97 | /** 98 | * Get the language selector object 99 | * 100 | * @param array $labels 101 | * @return LanguageSelector 102 | */ 103 | public function getSelector($labels = []) 104 | { 105 | return LanguageSelector::create($this, $labels); 106 | } 107 | 108 | /** 109 | * Sets the current domain 110 | * 111 | * @param string $domain 112 | * @return $this 113 | */ 114 | public function setDomain($domain) 115 | { 116 | $this->translator->setDomain($domain); 117 | return $this; 118 | } 119 | 120 | /** 121 | * Returns the current domain 122 | * 123 | * @return string 124 | */ 125 | public function getDomain() 126 | { 127 | return $this->translator->getDomain(); 128 | } 129 | 130 | /** 131 | * Translates a message with the current handler 132 | * 133 | * @param $message 134 | * @return string 135 | */ 136 | public function translate($message) 137 | { 138 | return $this->translator->translate($message); 139 | } 140 | 141 | /** 142 | * Translates a plural string with the current handler 143 | * 144 | * @param $singular 145 | * @param $plural 146 | * @param $count 147 | * @return string 148 | */ 149 | public function translatePlural($singular, $plural, $count) 150 | { 151 | return $this->translator->translatePlural($singular, $plural, $count); 152 | } 153 | 154 | /** 155 | * Returns the translator. 156 | * 157 | * @return TranslatorInterface 158 | */ 159 | public function getTranslator() 160 | { 161 | return $this->translator; 162 | } 163 | 164 | /** 165 | * Sets the translator 166 | * 167 | * @param TranslatorInterface $translator 168 | * @return $this 169 | */ 170 | public function setTranslator(TranslatorInterface $translator) 171 | { 172 | $this->translator = $translator; 173 | return $this; 174 | } 175 | 176 | /** 177 | * Returns supported locales 178 | * 179 | * @return array 180 | */ 181 | public function getSupportedLocales() 182 | { 183 | return $this->translator->supportedLocales(); 184 | } 185 | 186 | /** 187 | * Indicates if given locale is supported 188 | * 189 | * @return bool 190 | */ 191 | public function isLocaleSupported($locale) 192 | { 193 | return $this->translator->isLocaleSupported($locale); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/LaravelGettextServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 36 | __DIR__ . '/../../config/config.php' => config_path('laravel-gettext.php') 37 | ], 'config'); 38 | 39 | } 40 | 41 | /** 42 | * Register the service provider. 43 | * 44 | * @return mixed 45 | */ 46 | public function register() 47 | { 48 | $configuration = ConfigManager::create(); 49 | 50 | $this->app->bind( 51 | AdapterInterface::class, 52 | $configuration->get()->getAdapter() 53 | ); 54 | 55 | $this->app->singleton(Config::class, function($app) use ($configuration){ 56 | return $configuration->get(); 57 | }); 58 | 59 | // Main class register 60 | $this->app->singleton(LaravelGettext::class, function (Application $app) use ($configuration) { 61 | 62 | $fileSystem = new FileSystem($configuration->get(), app_path(), storage_path()); 63 | $storage = $app->make($configuration->get()->getStorage()); 64 | 65 | if ('symfony' == $configuration->get()->getHandler()) { 66 | // symfony translator implementation 67 | $translator = new Translators\Symfony( 68 | $configuration->get(), 69 | $this->app->make(AdapterInterface::class), 70 | $fileSystem, 71 | $storage 72 | ); 73 | } else { 74 | // GNU/Gettext php extension 75 | $translator = new Translators\Gettext( 76 | $configuration->get(), 77 | $this->app->make(AdapterInterface::class), 78 | $fileSystem, 79 | $storage 80 | ); 81 | } 82 | 83 | return new LaravelGettext($translator); 84 | 85 | }); 86 | $this->app->alias(LaravelGettext::class, 'laravel-gettext'); 87 | 88 | // Alias 89 | $this->app->booting(function () { 90 | $aliasLoader = AliasLoader::getInstance(); 91 | $aliasLoader->alias('LaravelGettext', \Xinax\LaravelGettext\Facades\LaravelGettext::class); 92 | }); 93 | 94 | $this->registerCommands(); 95 | } 96 | 97 | /** 98 | * Register commands 99 | */ 100 | protected function registerCommands() 101 | { 102 | // Package commands 103 | $this->app->bind('xinax::gettext.create', function ($app) { 104 | return new Commands\GettextCreate(); 105 | }); 106 | 107 | $this->app->bind('xinax::gettext.update', function ($app) { 108 | return new Commands\GettextUpdate(); 109 | }); 110 | 111 | $this->commands([ 112 | 'xinax::gettext.create', 113 | 'xinax::gettext.update', 114 | ]); 115 | } 116 | 117 | /** 118 | * Get the services 119 | * 120 | * @return array 121 | */ 122 | public function provides() 123 | { 124 | return [ 125 | 'laravel-gettext' 126 | ]; 127 | } 128 | } -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Middleware/GettextMiddleware.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 31 | } 32 | 33 | 34 | /** 35 | * @var String 36 | */ 37 | protected $domain; 38 | 39 | /** 40 | * Current locale 41 | * @type String 42 | */ 43 | protected $locale; 44 | 45 | /** 46 | * Current encoding 47 | * @type String 48 | */ 49 | protected $encoding; 50 | 51 | 52 | /** 53 | * Getter for domain 54 | * 55 | * @return String 56 | */ 57 | public function getDomain() 58 | { 59 | return $this->domain ?: $this->configuration->getDomain(); 60 | } 61 | 62 | /** 63 | * @param String $domain 64 | * 65 | * @return $this 66 | */ 67 | public function setDomain($domain) 68 | { 69 | $this->domain = $domain; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Getter for locale 76 | * 77 | * @return String 78 | */ 79 | public function getLocale() 80 | { 81 | return $this->locale ?: $this->configuration->getLocale(); 82 | } 83 | 84 | /** 85 | * @param String $locale 86 | * 87 | * @return $this 88 | */ 89 | public function setLocale($locale) 90 | { 91 | $this->locale = $locale; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Getter for configuration 98 | * 99 | * @return \Xinax\LaravelGettext\Config\Models\Config 100 | */ 101 | public function getConfiguration() 102 | { 103 | return $this->configuration; 104 | } 105 | 106 | /** 107 | * Getter for encoding 108 | * 109 | * @return String 110 | */ 111 | public function getEncoding() 112 | { 113 | return $this->encoding ?: $this->configuration->getEncoding(); 114 | } 115 | 116 | /** 117 | * @param String $encoding 118 | * 119 | * @return $this 120 | */ 121 | public function setEncoding($encoding) 122 | { 123 | $this->encoding = $encoding; 124 | 125 | return $this; 126 | } 127 | 128 | 129 | 130 | 131 | 132 | } -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Storages/SessionStorage.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 30 | } 31 | 32 | 33 | /** 34 | * Getter for domain 35 | * 36 | * @return String 37 | */ 38 | public function getDomain() 39 | { 40 | return $this->sessionGet('domain', $this->configuration->getDomain()); 41 | } 42 | 43 | /** 44 | * @param String $domain 45 | * 46 | * @return $this 47 | */ 48 | public function setDomain($domain) 49 | { 50 | $this->sessionSet('domain', $domain); 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Getter for locale 57 | * 58 | * @return String 59 | */ 60 | public function getLocale() 61 | { 62 | return $this->sessionGet('locale', $this->configuration->getLocale()); 63 | } 64 | 65 | /** 66 | * @param String $locale 67 | * 68 | * @return $this 69 | */ 70 | public function setLocale($locale) 71 | { 72 | $this->sessionSet('locale', $locale); 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Getter for configuration 79 | * 80 | * @return \Xinax\LaravelGettext\Config\Models\Config 81 | */ 82 | public function getConfiguration() 83 | { 84 | return $this->configuration; 85 | } 86 | 87 | 88 | /** 89 | * Return a value from session with an optional default 90 | * 91 | * @param $key 92 | * @param null $default 93 | * 94 | * @return mixed 95 | */ 96 | protected function sessionGet($key, $default = null) 97 | { 98 | $token = $this->configuration->getSessionIdentifier() . "-" . $key; 99 | 100 | return Session::get($token, $default); 101 | } 102 | 103 | /** 104 | * Sets a value in session session 105 | * 106 | * @param $key 107 | * @param $value 108 | * 109 | * @return mixed 110 | */ 111 | protected function sessionSet($key, $value) 112 | { 113 | $token = $this->configuration->getSessionIdentifier() . "-" . $key; 114 | Session::put($token, $value); 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * Getter for locale 121 | * 122 | * @return String 123 | */ 124 | public function getEncoding() 125 | { 126 | return $this->sessionGet('encoding', $this->configuration->getEncoding()); 127 | } 128 | 129 | /** 130 | * @param string $encoding 131 | * 132 | * @return $this 133 | */ 134 | public function setEncoding($encoding) 135 | { 136 | $this->sessionSet('encoding', $encoding); 137 | 138 | return $this; 139 | } 140 | } -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Storages/Storage.php: -------------------------------------------------------------------------------- 1 | translate($message); 20 | 21 | if (strlen($translation)) { 22 | if (isset($args)) { 23 | if (!is_array($args)) { 24 | $args = array_slice(func_get_args(), 1); 25 | } 26 | $translation = vsprintf($translation, $args); 27 | } 28 | 29 | return $translation; 30 | } 31 | 32 | /** 33 | * If translations are missing returns 34 | * the original message. 35 | * 36 | * @see https://github.com/symfony/symfony/issues/13483 37 | */ 38 | return $message; 39 | } 40 | } 41 | 42 | if (!function_exists('__')) { 43 | /** 44 | * Translate a formatted string based on printf formats 45 | * Can be use an array on args or use the number of the arguments 46 | * 47 | * @param string $message the message to translate 48 | * @param array|mixed $args the tokens values used inside the $message 49 | * 50 | * @return string the message translated and formatted 51 | */ 52 | function __($message, $args = null) 53 | { 54 | return _i($message, $args); 55 | } 56 | } 57 | 58 | if (!function_exists('_')) { 59 | /** 60 | * Generic translation function 61 | * 62 | * @param $message 63 | * 64 | * @return mixed 65 | */ 66 | function _($message, $args = null) 67 | { 68 | return _i($message, $args); 69 | } 70 | } 71 | 72 | if (!function_exists('_n')) { 73 | /** 74 | * Translate a formatted pluralized string based on printf formats 75 | * Can be use an array on args or use the number of the arguments 76 | * 77 | * @param string $singular the singular message to be translated 78 | * @param string $plural the plural message to be translated if the $count > 1 79 | * @param int $count the number of occurrence to be used to pluralize the $singular 80 | * @param array|mixed $args the tokens values used inside $singular or $plural 81 | * 82 | * @return string the message translated, pluralized and formatted 83 | */ 84 | function _n($singular, $plural, $count, $args = null) 85 | { 86 | $translator = app(LaravelGettext::class); 87 | $message = $translator->translatePlural($singular, $plural, $count); 88 | 89 | if (isset($args) && !is_array($args)) { 90 | $args = array_slice(func_get_args(), 3); 91 | } 92 | $message = vsprintf($message, $args); 93 | 94 | return $message; 95 | } 96 | } 97 | 98 | if (!function_exists('_s')) { 99 | /** 100 | * Translate a formatted pluralized string based on printf formats mixed with the Symfony format 101 | * Can be use an array on args or use the number of the arguments 102 | * 103 | * Only works if Symfony is the used backend 104 | * 105 | * @param string $message The one line message containing the different pluralization separated by pipes 106 | * See Symfony translation documentation 107 | * @param int $count the number of occurrence to be used to pluralize the $singular 108 | * @param array|mixed $args the tokens values used inside $singular or $plural 109 | * 110 | * @return string the message translated, pluralized and formatted 111 | */ 112 | function _s($message, $count, $args = null) 113 | { 114 | $translator = app(LaravelGettext::class); 115 | $message = $translator->getTranslator()->translatePluralInline($message, $count); 116 | 117 | if (isset($args) && !is_array($args)) { 118 | $args = array_slice(func_get_args(), 3); 119 | } 120 | $message = vsprintf($message, $args); 121 | 122 | return $message; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Testing/Adapter/TestAdapter.php: -------------------------------------------------------------------------------- 1 | locale; 28 | } 29 | 30 | /** 31 | * Sets the locale on the adapter 32 | * 33 | * @param string $locale 34 | * 35 | * @return boolean 36 | */ 37 | public function setLocale($locale) 38 | { 39 | $this->locale = $locale; 40 | 41 | return true; 42 | } 43 | 44 | /** 45 | * Get the application path 46 | * 47 | * @return string 48 | */ 49 | public function getApplicationPath() 50 | { 51 | return app_path(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Testing/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | appPath) { 30 | return null; 31 | } 32 | 33 | $app = require $this->appPath; 34 | $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 35 | 36 | $app->register(LaravelGettextServiceProvider::class); 37 | 38 | return $app; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Translators/BaseTranslator.php: -------------------------------------------------------------------------------- 1 | configuration = $config; 52 | $this->adapter = $adapter; 53 | $this->fileSystem = $fileSystem; 54 | $this->storage = $storage; 55 | } 56 | 57 | /** 58 | * Returns the current locale string identifier 59 | * 60 | * @return String 61 | */ 62 | public function getLocale() 63 | { 64 | return $this->storage->getLocale(); 65 | } 66 | 67 | /** 68 | * Sets and stores on session the current locale code 69 | * 70 | * @param $locale 71 | * 72 | * @return BaseTranslator 73 | */ 74 | public function setLocale($locale) 75 | { 76 | if ($locale == $this->storage->getLocale()) { 77 | return $this; 78 | } 79 | 80 | $this->storage->setLocale($locale); 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Returns a boolean that indicates if $locale 87 | * is supported by configuration 88 | * 89 | * @param $locale 90 | * 91 | * @return bool 92 | */ 93 | public function isLocaleSupported($locale) 94 | { 95 | if ($locale) { 96 | return in_array($locale, $this->configuration->getSupportedLocales()); 97 | } 98 | 99 | return false; 100 | } 101 | 102 | /** 103 | * Return the current locale 104 | * 105 | * @return mixed 106 | */ 107 | public function __toString() 108 | { 109 | return $this->getLocale(); 110 | } 111 | 112 | /** 113 | * Gets the Current encoding. 114 | * 115 | * @return mixed 116 | */ 117 | public function getEncoding() 118 | { 119 | return $this->storage->getEncoding(); 120 | } 121 | 122 | /** 123 | * Sets the Current encoding. 124 | * 125 | * @param mixed $encoding the encoding 126 | * 127 | * @return self 128 | */ 129 | public function setEncoding($encoding) 130 | { 131 | $this->storage->setEncoding($encoding); 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Sets the current domain and updates gettext domain application 138 | * 139 | * @param String $domain 140 | * 141 | * @throws UndefinedDomainException If domain is not defined 142 | * @return self 143 | */ 144 | public function setDomain($domain) 145 | { 146 | if (!in_array($domain, $this->configuration->getAllDomains())) { 147 | throw new UndefinedDomainException("Domain '$domain' is not registered."); 148 | } 149 | 150 | $this->storage->setDomain($domain); 151 | 152 | return $this; 153 | } 154 | 155 | /** 156 | * Returns the current domain 157 | * 158 | * @return String 159 | */ 160 | public function getDomain() 161 | { 162 | return $this->storage->getDomain(); 163 | } 164 | 165 | 166 | /** 167 | * Returns supported locales 168 | * 169 | * @return array 170 | */ 171 | public function supportedLocales() 172 | { 173 | return $this->configuration->getSupportedLocales(); 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Translators/Gettext.php: -------------------------------------------------------------------------------- 1 | domain = $this->storage->getDomain(); 69 | 70 | // Encoding is set from configuration 71 | $this->encoding = $this->storage->getEncoding(); 72 | 73 | // Categories are set from configuration 74 | $this->categories = $this->configuration->getCategories(); 75 | 76 | // Sets defaults for boot 77 | $locale = $this->storage->getLocale(); 78 | 79 | $this->setLocale($locale); 80 | } 81 | 82 | 83 | /** 84 | * Sets the current locale code 85 | */ 86 | public function setLocale($locale) 87 | { 88 | if (!$this->isLocaleSupported($locale)) { 89 | throw new LocaleNotSupportedException( 90 | sprintf('Locale %s is not supported', $locale) 91 | ); 92 | } 93 | 94 | try { 95 | $customLocale = $this->configuration->getCustomLocale() ? "C." : $locale . "."; 96 | $gettextLocale = $customLocale . $this->getEncoding(); 97 | 98 | // Update all categories set in config 99 | foreach($this->categories as $category) { 100 | putenv("$category=$gettextLocale"); 101 | setlocale(constant($category), $gettextLocale); 102 | } 103 | 104 | parent::setLocale($locale); 105 | 106 | // Laravel built-in locale 107 | if ($this->configuration->isSyncLaravel()) { 108 | $this->adapter->setLocale($locale); 109 | } 110 | 111 | return $this->getLocale(); 112 | } catch (\Exception $e) { 113 | $this->locale = $this->configuration->getFallbackLocale(); 114 | $exceptionPosition = $e->getFile() . ":" . $e->getLine(); 115 | throw new \Exception($exceptionPosition . $e->getMessage()); 116 | 117 | } 118 | } 119 | 120 | /** 121 | * Returns a boolean that indicates if $locale 122 | * is supported by configuration 123 | * 124 | * @return boolean 125 | */ 126 | public function isLocaleSupported($locale) 127 | { 128 | if ($locale) { 129 | return in_array($locale, $this->supportedLocales()); 130 | } 131 | 132 | return false; 133 | } 134 | 135 | /** 136 | * Return the current locale 137 | * 138 | * @return mixed 139 | */ 140 | public function __toString() 141 | { 142 | return $this->getLocale(); 143 | } 144 | 145 | 146 | /** 147 | * Gets the Current encoding. 148 | * 149 | * @return mixed 150 | */ 151 | public function getEncoding() 152 | { 153 | return $this->encoding; 154 | } 155 | 156 | /** 157 | * Sets the Current encoding. 158 | * 159 | * @param mixed $encoding the encoding 160 | * @return self 161 | */ 162 | public function setEncoding($encoding) 163 | { 164 | $this->encoding = $encoding; 165 | return $this; 166 | } 167 | 168 | /** 169 | * Sets the current domain and updates gettext domain application 170 | * 171 | * @param String $domain 172 | * @throws UndefinedDomainException If domain is not defined 173 | * @return self 174 | */ 175 | public function setDomain($domain) 176 | { 177 | parent::setDomain($domain); 178 | 179 | $customLocale = $this->configuration->getCustomLocale() ? "/" . $this->getLocale() : ""; 180 | 181 | bindtextdomain($domain, $this->fileSystem->getDomainPath() . $customLocale); 182 | bind_textdomain_codeset($domain, $this->getEncoding()); 183 | 184 | $this->domain = textdomain($domain); 185 | 186 | 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * Translates a message with gettext 193 | * 194 | * @param $message 195 | */ 196 | public function translate($message) 197 | { 198 | return gettext($message); 199 | } 200 | 201 | /** 202 | * Translates a plural message with gettext 203 | * 204 | * @param $singular 205 | * @param $plural 206 | * @param $count 207 | * 208 | * @return string 209 | */ 210 | public function translatePlural($singular, $plural, $count) 211 | { 212 | return ngettext($singular, $plural, $count); 213 | } 214 | 215 | public function translatePluralInline($message, $amount) 216 | { 217 | throw new \RuntimeException('Not supported by gettext, please use Symfony'); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Translators/Symfony.php: -------------------------------------------------------------------------------- 1 | setLocale($this->storage->getLocale()); 36 | $this->loadLocaleFile(); 37 | } 38 | 39 | 40 | /** 41 | * Translates a message using the Symfony translation component 42 | * 43 | * @param $message 44 | * 45 | * @return string 46 | */ 47 | public function translate($message) 48 | { 49 | return $this->symfonyTranslator->trans($message, [], $this->getDomain(), $this->getLocale()); 50 | } 51 | 52 | /** 53 | * Returns the translator instance 54 | * 55 | * @return SymfonyTranslator 56 | */ 57 | protected function getTranslator() 58 | { 59 | if (isset($this->symfonyTranslator)) { 60 | return $this->symfonyTranslator; 61 | } 62 | 63 | return $this->symfonyTranslator = $this->createTranslator(); 64 | } 65 | 66 | /** 67 | * Set locale overload. 68 | * Needed to re-build the catalogue when locale changes. 69 | * 70 | * @param $locale 71 | * 72 | * @return $this 73 | */ 74 | public function setLocale($locale) 75 | { 76 | parent::setLocale($locale); 77 | $this->getTranslator()->setLocale($locale); 78 | $this->loadLocaleFile(); 79 | 80 | if ($locale != $this->adapter->getLocale()) { 81 | $this->adapter->setLocale($locale); 82 | } 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Set domain overload. 89 | * Needed to re-build the catalogue when domain changes. 90 | * 91 | * 92 | * @param String $domain 93 | * 94 | * @return $this 95 | */ 96 | public function setDomain($domain) 97 | { 98 | parent::setDomain($domain); 99 | 100 | $this->loadLocaleFile(); 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Creates a new translator instance 107 | * 108 | * @return SymfonyTranslator 109 | */ 110 | protected function createTranslator() 111 | { 112 | $translator = new SymfonyTranslator($this->configuration->getLocale()); 113 | $translator->setFallbackLocales([$this->configuration->getFallbackLocale()]); 114 | $translator->addLoader('mo', new ApcuFileCacheLoader(new MoFileLoader())); 115 | $translator->addLoader('po', new ApcuFileCacheLoader(new PoFileLoader())); 116 | 117 | return $translator; 118 | } 119 | 120 | /** 121 | * Translates a plural string 122 | * 123 | * @param $singular 124 | * @param $plural 125 | * @param $amount 126 | * 127 | * @return string 128 | */ 129 | public function translatePlural($singular, $plural, $amount) 130 | { 131 | return $this->symfonyTranslator->trans( 132 | $amount > 1 133 | ? $plural 134 | : $singular, 135 | ['%count%' => $amount], 136 | $this->getDomain(), 137 | $this->getLocale() 138 | ); 139 | } 140 | 141 | /** 142 | * Translate a plural string that is only on one line separated with pipes 143 | * 144 | * @param $message 145 | * @param $amount 146 | * 147 | * @return string 148 | */ 149 | public function translatePluralInline($message, $amount) 150 | { 151 | return $this->symfonyTranslator->trans( 152 | $message, 153 | [ 154 | '%count%' => $amount 155 | ], 156 | $this->getDomain(), 157 | $this->getLocale() 158 | ); 159 | } 160 | 161 | /** 162 | * @internal param $translator 163 | */ 164 | protected function loadLocaleFile() 165 | { 166 | if (isset($this->loadedResources[$this->getDomain()]) 167 | && isset($this->loadedResources[$this->getDomain()][$this->getLocale()]) 168 | ) { 169 | return; 170 | } 171 | $translator = $this->getTranslator(); 172 | 173 | $fileMo = $this->fileSystem->makeFilePath($this->getLocale(), $this->getDomain(), 'mo'); 174 | if (file_exists($fileMo)) { 175 | $translator->addResource('mo', $fileMo, $this->getLocale(), $this->getDomain()); 176 | } else { 177 | $file = $this->fileSystem->makeFilePath($this->getLocale(), $this->getDomain()); 178 | $translator->addResource('po', $file, $this->getLocale(), $this->getDomain()); 179 | } 180 | 181 | $this->loadedResources[$this->getDomain()][$this->getLocale()] = true; 182 | } 183 | 184 | /** 185 | * Returns a boolean that indicates if $locale 186 | * is supported by configuration 187 | * 188 | * @param $locale 189 | * 190 | * @return bool 191 | */ 192 | public function isLocaleSupported($locale) 193 | { 194 | if ($locale) { 195 | return in_array($locale, $this->configuration->getSupportedLocales()); 196 | } 197 | 198 | return false; 199 | } 200 | 201 | /** 202 | * Return the current locale 203 | * 204 | * @return mixed 205 | */ 206 | public function __toString() 207 | { 208 | return $this->getLocale(); 209 | } 210 | 211 | /** 212 | * Returns supported locales 213 | * 214 | * @return array 215 | */ 216 | public function supportedLocales() 217 | { 218 | return $this->configuration->getSupportedLocales(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/Xinax/LaravelGettext/Translators/TranslatorInterface.php: -------------------------------------------------------------------------------- 1 | 'symfony', 15 | 16 | /** 17 | * Session identifier: Key under which the current locale will be stored. 18 | */ 19 | 'session-identifier' => 'laravel-gettext-locale', 20 | 21 | /** 22 | * Default locale: this will be the default for your application. 23 | * Is to be supposed that all strings are written in this language. 24 | */ 25 | 'locale' => 'en_US', 26 | 27 | /** 28 | * Supported locales: An array containing all allowed languages 29 | */ 30 | 'supported-locales' => [ 31 | 'en_US', 32 | ], 33 | 34 | /** 35 | * Default charset encoding. 36 | */ 37 | 'encoding' => 'UTF-8', 38 | 39 | /** 40 | * ----------------------------------------------------------------------- 41 | * All standard configuration ends here. The following values 42 | * are only for special cases. 43 | * ----------------------------------------------------------------------- 44 | **/ 45 | 46 | /** 47 | * Locale categories to set 48 | */ 49 | 'categories' => [ 50 | 'LC_ALL', 51 | ], 52 | 53 | /** 54 | * Base translation directory path (don't use trailing slash) 55 | */ 56 | 'translations-path' => '../resources/lang', 57 | 58 | /** 59 | * Relative path to the app folder: is used on .po header files 60 | */ 61 | 'relative-path' => '../../../../../app', 62 | 63 | /** 64 | * Fallback locale: When default locale is not available 65 | */ 66 | 'fallback-locale' => 'en_US', 67 | 68 | /** 69 | * Default domain used for translations: It is the file name for .po and .mo files 70 | */ 71 | 'domain' => 'messages', 72 | 73 | /** 74 | * Project name: is used on .po header files 75 | */ 76 | 'project' => 'MultilanguageLaravelApplication', 77 | 78 | /** 79 | * Translator contact data (used on .po headers too) 80 | */ 81 | 'translator' => 'James Translator ', 82 | 83 | /** 84 | * Paths where Poedit will search recursively for strings to translate. 85 | * All paths are relative to app/ (don't use trailing slash). 86 | * 87 | * Remember to call artisan gettext:update after change this. 88 | */ 89 | 'source-paths' => [ 90 | 'Http', 91 | '../resources/views', 92 | 'Console', 93 | ], 94 | 95 | /** 96 | * Multi-domain directory paths. If you want the translations in 97 | * different files, just wrap your paths into a domain name. 98 | * for example: 99 | */ 100 | /* 101 | 'source-paths' => [ 102 | 103 | // 'frontend' domain 104 | 'frontend' => [ 105 | 'controllers', 106 | 'views/frontend', 107 | ], 108 | 109 | // 'backend' domain 110 | 'backend' => [ 111 | 'views/backend', 112 | ], 113 | 114 | // 'messages' domain (matches default domain) 115 | 'storage/views', 116 | ], 117 | */ 118 | 119 | /** 120 | * Sync laravel: A flag that determines if the laravel built-in locale must 121 | * be changed when you call LaravelGettext::setLocale. 122 | */ 123 | 'sync-laravel' => true, 124 | 125 | /** 126 | * The adapter used to sync the laravel built-in locale 127 | */ 128 | 'adapter' => \Xinax\LaravelGettext\Adapters\LaravelAdapter::class, 129 | 130 | /** 131 | * Where to store the current locale/domain 132 | * 133 | * By default, in the session. 134 | * Can be changed for only memory or your own storage mechanism 135 | * 136 | * @see \Xinax\LaravelGettext\Storages\Storage 137 | */ 138 | 'storage' => \Xinax\LaravelGettext\Storages\SessionStorage::class, 139 | 140 | /** 141 | * Use custom locale that is not supported by the system 142 | */ 143 | 'custom-locale' => false, 144 | 145 | /** 146 | * The keywords list used by poedit to search the strings to be translated 147 | * 148 | * The "_", "__" and "gettext" are singular translation functions 149 | * The "_n" and "ngettext" are plural translation functions 150 | * The "dgettext" function allows a translation domain to be explicitly specified 151 | * 152 | * "__" and "_n" and "_i" and "_s" are helpers functions @see \Xinax\LaravelGettext\Support\helpers.php 153 | */ 154 | 'keywords-list' => ['_', '__', '_i', '_s', 'gettext', '_n:1,2', 'ngettext:1,2', 'dgettext:2'], 155 | ]; 156 | --------------------------------------------------------------------------------