├── .codeclimate.yml ├── .gitignore ├── .php_cs ├── .styleci.yml ├── .travis.yml ├── composer.json ├── config └── lang-detector.php ├── phpunit.xml ├── readme.md ├── src ├── Contracts │ ├── DetectorDriverInterface.php │ ├── LanguageDetectorInterface.php │ └── ShouldPrefixRoutesInterface.php ├── Drivers │ ├── AbstractDetector.php │ ├── BrowserDetectorDriver.php │ ├── SubdomainDetectorDriver.php │ └── UriDetectorDriver.php ├── Facades │ └── LanguageDetector.php ├── LanguageDetector.php ├── Providers │ └── LanguageDetectorServiceProvider.php └── Support │ └── helpers.php └── tests ├── Drivers ├── AbstractDriversTestCase.php ├── BrowserDetectorDriverTest.php ├── SubdomainDetectorDriverTest.php └── UriDetectorDriverTest.php └── Providers └── LanguageDetectorServiceProviderTest.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | # Save as .codeclimate.yml (note leading .) in project root directory 2 | languages: 3 | PHP: true 4 | 5 | exclude_paths: 6 | # - "tests/" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | .php_cs.cache 5 | .phpunit.result.cache 6 | tmp[a-zA-Z0-9]{6,99} 7 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | files() 5 | ->in(__DIR__) 6 | ->exclude('vendor') 7 | ->notName("*.md") 8 | ->notName("*.xml") 9 | ->notName("*.txt") 10 | ->notName("*.phar") 11 | ->ignoreDotFiles(true) 12 | ->ignoreVCS(true); 13 | 14 | $fixers = [ 15 | 'blankline_after_open_tag', 16 | 'braces', 17 | 'concat_without_spaces', 18 | 'double_arrow_multiline_whitespaces', 19 | 'duplicate_semicolon', 20 | 'elseif', 21 | 'empty_return', 22 | 'encoding', 23 | 'eof_ending', 24 | 'extra_empty_lines', 25 | 'function_call_space', 26 | 'function_declaration', 27 | 'include', 28 | 'indentation', 29 | 'join_function', 30 | 'line_after_namespace', 31 | 'linefeed', 32 | 'list_commas', 33 | 'logical_not_operators_with_successor_space', 34 | 'lowercase_constants', 35 | 'lowercase_keywords', 36 | 'method_argument_space', 37 | 'multiline_array_trailing_comma', 38 | 'multiline_spaces_before_semicolon', 39 | 'multiple_use', 40 | 'namespace_no_leading_whitespace', 41 | 'no_blank_lines_after_class_opening', 42 | 'no_empty_lines_after_phpdocs', 43 | 'object_operator', 44 | 'operators_spaces', 45 | 'parenthesis', 46 | 'phpdoc_indent', 47 | 'phpdoc_inline_tag', 48 | 'phpdoc_no_access', 49 | 'phpdoc_no_package', 50 | 'phpdoc_scalar', 51 | 'phpdoc_short_description', 52 | 'phpdoc_to_comment', 53 | 'phpdoc_trim', 54 | 'phpdoc_type_to_var', 55 | 'phpdoc_var_without_name', 56 | 'remove_leading_slash_use', 57 | 'remove_lines_between_uses', 58 | 'return', 59 | 'self_accessor', 60 | 'short_array_syntax', 61 | 'short_echo_tag', 62 | 'short_tag', 63 | 'single_array_no_trailing_comma', 64 | 'single_blank_line_before_namespace', 65 | 'single_line_after_imports', 66 | 'single_quote', 67 | 'spaces_before_semicolon', 68 | 'spaces_cast', 69 | 'standardize_not_equal', 70 | 'ternary_spaces', 71 | 'trailing_spaces', 72 | 'trim_array_spaces', 73 | 'unalign_equals', 74 | 'unary_operators_spaces', 75 | 'unused_use', 76 | 'visibility', 77 | 'whitespacy_lines', 78 | ]; 79 | 80 | 81 | return Symfony\CS\Config\Config::create() 82 | ->level(Symfony\CS\FixerInterface::NONE_LEVEL) 83 | ->fixers($fixers) 84 | ->finder($finder) 85 | ->setUsingCache(true); 86 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | - 8.0 9 | - hhvm 10 | 11 | before_script: 12 | - travis_retry composer self-update 13 | - travis_retry composer install --prefer-source --no-interaction --dev 14 | 15 | script: phpunit 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vluzrmos/language-detector", 3 | "description": "Detect the language for your application using browser preferences, subdomains or route prefixes.", 4 | "keywords": [ 5 | "Laravel", 6 | "Lumen", 7 | "Locale", 8 | "Language", 9 | "i18n" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Vagner do Carmo", 15 | "email": "vluzrmos@gmail.com" 16 | } 17 | ], 18 | "type": "package", 19 | "require": { 20 | "php": "^7.2 || ^8.0", 21 | "illuminate/support": "~6.0 || ~7.0 || ~8.0 || ~9.0 || ~10.0 || ^11.0 || ^12.0" 22 | }, 23 | "require-dev": { 24 | "friendsofphp/php-cs-fixer": "^2.16 || ^3.51", 25 | "orchestra/testbench": "^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0", 26 | "phpunit/phpunit": "^8.5 || ^9.0 || ^10.5 || ^11.5.3" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Vluzrmos\\LanguageDetector\\": "src/" 31 | }, 32 | "files": [ 33 | "src/Support/helpers.php" 34 | ] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Vluzrmos\\LanguageDetector\\Testing\\": "tests/" 39 | } 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Vluzrmos\\LanguageDetector\\Providers\\LanguageDetectorServiceProvider" 45 | ] 46 | } 47 | }, 48 | "config": { 49 | "preferred-install": "dist" 50 | }, 51 | "minimum-stability": "dev", 52 | "prefer-stable": true 53 | } 54 | -------------------------------------------------------------------------------- /config/lang-detector.php: -------------------------------------------------------------------------------- 1 | env('LANG_DETECTOR_AUTODETECT', true), 8 | 9 | /* 10 | * Default driver to use to detect the request language. 11 | * 12 | * Available: browser, subdomain, uri. 13 | */ 14 | 'driver' => env('LANG_DETECTOR_DRIVER', 'browser'), 15 | 16 | /* 17 | * Used on subdomain and uri drivers. That indicates which segment should be used 18 | * to verify the language. 19 | */ 20 | 'segment' => env('LANG_DETECTOR_SEGMENT', 0), 21 | 22 | /* 23 | * Languages available on the application. 24 | * 25 | * You could use parse_langs_to_array to use the string syntax 26 | * or just use the array of languages with its aliases. 27 | */ 28 | 'languages' => parse_langs_to_array( 29 | env('LANG_DETECTOR_LANGUAGES', ['en']) 30 | ), 31 | 32 | /* 33 | * Indicates if should store detected locale on cookies 34 | */ 35 | 'cookie' => (bool) env('LANG_DETECTOR_COOKIE', true), 36 | 37 | /* 38 | * Indicates if should encrypt cookie 39 | */ 40 | 'cookie_encrypt' => (bool) env('LANG_DETECTOR_COOKIE_ENCRYPT', false), 41 | 42 | /* 43 | * Cookie name 44 | */ 45 | 'cookie_name' => env('LANG_DETECTOR_COOKIE', 'locale'), 46 | ]; 47 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Laravel Language Detector 2 | 3 | [![Join the chat at https://gitter.im/vluzrmos/laravel-language-detector](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vluzrmos/laravel-language-detector?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | [![Latest Stable Version](https://poser.pugx.org/vluzrmos/language-detector/v/stable)](https://packagist.org/packages/vluzrmos/language-detector) 6 | [![Total Downloads](https://poser.pugx.org/vluzrmos/language-detector/downloads)](https://packagist.org/packages/vluzrmos/language-detector) 7 | [![License](https://poser.pugx.org/vluzrmos/language-detector/license)](https://packagist.org/packages/vluzrmos/language-detector) 8 | 9 | [![Build Status](https://travis-ci.org/vluzrmos/laravel-language-detector.svg)](https://travis-ci.org/vluzrmos/laravel-language-detector) 10 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/vluzrmos/laravel-language-detector/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/vluzrmos/laravel-language-detector/?branch=master) 11 | [![Code Climate](https://codeclimate.com/github/vluzrmos/laravel-language-detector/badges/gpa.svg)](https://codeclimate.com/github/vluzrmos/laravel-language-detector) 12 | [![Codacy Badge](https://www.codacy.com/project/badge/f024fb197e1c4a298a537794cb077901)](https://www.codacy.com/app/vluzrmos/laravel-language-detector) 13 | [![StyleCI](https://styleci.io/repos/38231293/shield)](https://styleci.io/repos/38231293) 14 | 15 | This package provides an easy way to detect and apply the language for your application 16 | using [browser preferences](#browser-preferences), [subdomains](#subdomains) or [route prefixes](#route-prefixes). 17 | 18 | # Installation 19 | 20 | Require the package using composer: 21 | 22 | `composer require vluzrmos/language-detector` 23 | 24 | Add the service provider as follows: 25 | 26 | ## Laravel 27 | 28 | This is not needed for Laravel 5.5 and upper version. 29 | 30 | Edit your `config/app.php`: 31 | 32 | Insert this line of code **above** the listed RouteServiceProvider, ex: 33 | 34 | ``` 35 | Vluzrmos\LanguageDetector\Providers\LanguageDetectorServiceProvider::class, 36 | App\Providers\RouteServiceProvider::class, 37 | ``` 38 | > ::class notation is optional. 39 | 40 | ## Lumen 41 | 42 | Edit the `bootstrap/app.php`: 43 | 44 | ```php 45 | $app->register(Vluzrmos\LanguageDetector\Providers\LanguageDetectorServiceProvider::class); 46 | ``` 47 | > ::class notation is optional. 48 | 49 | # Configuration 50 | 51 | Two options for Laravel, either publish the package configuration using: 52 | 53 | php artisan vendor:publish --provider="Vluzrmos\LanguageDetector\Providers\LanguageDetectorServiceProvider" 54 | 55 | then edit the new `config/lang-detector.php` file or add the following lines to your .env file: 56 | 57 | ```bash 58 | #Indicates whether the language should be autodetected (can be removed) 59 | LANG_DETECTOR_AUTODETECT=true 60 | 61 | #Driver to use, the default is browser 62 | LANG_DETECTOR_DRIVER="browser" 63 | 64 | #Segment to use in the uri or subdomain driver, default 0 (can be removed) 65 | LANG_DETECTOR_SEGMENT=0 66 | 67 | #Name of cookie to cache the detected language (use false|null to disable) 68 | LANG_DETECTOR_COOKIE=locale 69 | 70 | #Comma-separated list of languages provided on the app 71 | LANG_DETECTOR_LANGUAGES="en,fr,pt_BR" 72 | 73 | #To alias the language use the notation ":", "=", ":=" or "=>" to separate the alias and its value. 74 | # LANG_DETECTOR_LANGUAGES="en, en-us:en, pt-br:pt_BR" 75 | ``` 76 | 77 | If you not want to use that, just publish the configurations of the package with 78 | `php artisan vendor:publish` and edit on `config/lang-detector.php` generated. 79 | 80 | **For Lumen**, consider to copy `vendor/vluzrmos/language-detector/config/lang-detector.php` 81 | to your configs dir and use `$app->configure('lang-detector')` before register the 82 | `LanguageDetectorServiceProvider`. 83 | 84 | # Detector Drivers 85 | 86 | There are a few drivers that you might to use, choose one which matches with your application design: 87 | 88 | ## Browser Preferences 89 | The driver `browser` will try to detect the language of the application based on the request languages (browser preferences). This driver doesn't need any other configuration, just configure the available languages. 90 | 91 | ## Subdomains 92 | The driver `subdomain` will try to detect the language of the application which matches with subdomain of the hostname. 93 | eg.: 94 | 95 | http://fr.site.domain 96 | 97 | The `subdomain` driver will detect language `fr` and set the application to `fr` if it is one of the available languages in the `lang-detector` config file. 98 | 99 | > Note: subdomain and uri drivers require [aliases](#aliasing-language-locales) of the the language-locales in the lang-detector config file. 100 | 101 | ## Route Prefixes 102 | The driver `uri` will try to detect the language based on the route prefix: 103 | 104 | http://site.domain/en-us/home 105 | 106 | That driver will detect en-us and set it to the application. 107 | (Note: Consider to [aliase](#aliasing-language-locales) that locale) 108 | 109 | And don't worry, if the url is like: 110 | 111 | http://site.domain/home 112 | 113 | The language will not be changed, the application will use your default language configured on your `config/app.php` file. 114 | 115 | With `uri` driver, your route group needs be like this: 116 | 117 | ```php 118 | //That would be nice if you put (edit) it on your App\Providers\RouteServiceProvider. 119 | 120 | // Laravel 121 | Route::group(['prefix' => app('language.routePrefix'), 'namespace' => 'App\Http\Controllers'], function ($router) { 122 | require __DIR__.'/../Http/routes.php'; 123 | }); 124 | 125 | //For lumen, it should be on bootstrap/app.php. 126 | 127 | // Lumen 128 | $app->group(['prefix' => app('language.routePrefix'), 'namespace' => 'App\Http\Controllers'], function ($app) { 129 | require __DIR__.'/../app/Http/routes.php'; 130 | } 131 | ``` 132 | 133 | **Issue**: Lumen 5.0 doesn't support route prefix with empty strings, you should use 134 | that script: 135 | 136 | ```php 137 | $prefix = app('language.routePrefix'); 138 | 139 | $options = []; 140 | 141 | if (!empty($prefix) && $prefix!="/") { 142 | $options['prefix'] = $prefix; 143 | } 144 | 145 | // any other options here 146 | $options['namespace'] = 'App\Http\Controllers'; 147 | 148 | $app->group($options, function () use($app) { 149 | // ... 150 | }); 151 | ``` 152 | 153 | > Note: That is only for Lumen 5.0, the newest version (5.1) already fixes it. 154 | 155 | # Aliasing language locales 156 | 157 | You might to use the style `lang_LOCALE` or just `lang` on your `resources/lang` dir. 158 | The language detector driver you have chosen will try to detect the language 159 | which matches with `lang` or `lang_LOCALE` available on your `config/lang-detector.php`. 160 | 161 | ```php 162 | 'languages' => ['en', 'pt_BR' ...] 163 | ``` 164 | 165 | example: 166 | 167 | ``` 168 | ├── lang 169 | │ ├── en 170 | │ │ ├── messages.php 171 | │ │ └── validation.php 172 | │ └── pt_BR 173 | │ ├── messages.php 174 | │ └── validation.php 175 | ``` 176 | 177 | If you are not following that style of languages names or if you are using 178 | the `subdomain` or `uri` drivers, just configure it on `config/lang-detector.php` file: 179 | 180 | ```php 181 | 'languages' => [ 182 | 'pt_BR' => 'pt-BR', //will detect pt_BR language, and set pt-BR to the application, 183 | 'pt' => 'pt-BR', //aliasing, will detect pt and set pt-BR to the application 184 | 'pt-br' => "pt-BR", //aliasing, will detect pt-br and set pt-BR to the application (you will need it with subdomain driver) 185 | 'en', //will detect 'en' language 186 | ] 187 | ``` 188 | 189 | or if you are using `.env` instead of config file: 190 | 191 | ``` 192 | #Just put the languages in a comma-separated string. 193 | #To aliase the language use the notation ":", "=", ":=" or "=>" to separate the alias and its value. 194 | LANG_DETECTOR_LANGUAGES="pt_BR:pt-BR, pt:pt-BR, pt-br:pt-BR, en" 195 | ``` 196 | 197 | # Suggestions 198 | 199 | The default Laravel language lines are available translated into 51 languages here: 200 | 201 | - [caouecs/laravel-lang](https://github.com/caouecs/Laravel-lang) 202 | 203 | If you want to translate your models you can use this package: 204 | 205 | - [dimsav/laravel-translatable](https://github.com/dimsav/laravel-translatable) 206 | 207 | # License 208 | 209 | MIT. 210 | -------------------------------------------------------------------------------- /src/Contracts/DetectorDriverInterface.php: -------------------------------------------------------------------------------- 1 | setRequest($request); 43 | $this->setLanguages($languages); 44 | } 45 | 46 | /** 47 | * Getter to the request. 48 | * 49 | * @return Request 50 | */ 51 | public function getRequest() 52 | { 53 | return $this->request; 54 | } 55 | 56 | /** 57 | * Setter to the request. 58 | * 59 | * @param Request $request 60 | */ 61 | public function setRequest(Request $request) 62 | { 63 | $this->request = $request; 64 | } 65 | 66 | /** 67 | * Get the languages available for the application. 68 | * 69 | * @return array 70 | */ 71 | public function getLanguages() 72 | { 73 | $languages = []; 74 | 75 | foreach ($this->languages as $key => $value) { 76 | $languages[] = $this->keyOrValue($key, $value); 77 | } 78 | 79 | return $languages; 80 | } 81 | 82 | /** 83 | * Set languages available to the application. 84 | * 85 | * @param array $languages 86 | */ 87 | public function setLanguages(array $languages) 88 | { 89 | $this->languages = $languages; 90 | } 91 | 92 | /** 93 | * Set default segment value. 94 | * 95 | * @param int $segment 96 | */ 97 | public function setDefaultSegment($segment = 0) 98 | { 99 | $this->segment = (int) $segment; 100 | } 101 | 102 | /** 103 | * Get default segment value. 104 | * 105 | * @return int 106 | */ 107 | public function getDefaultSegment() 108 | { 109 | return $this->segment; 110 | } 111 | 112 | /** 113 | * Return the real locale based on available languages. 114 | * 115 | * @param string $locale 116 | * @return string|null 117 | */ 118 | public function getAliasedLocale($locale) 119 | { 120 | if (isset($this->languages[$locale])) { 121 | return $this->languages[$locale]; 122 | } 123 | 124 | return in_array($locale, $this->languages) ? $locale : null; 125 | } 126 | 127 | /** 128 | * Get the $value if key is numeric or null, otherwise will return the key. 129 | * 130 | * @param string|int $key 131 | * @param mixed $value 132 | * 133 | * @return mixed 134 | */ 135 | protected function keyOrValue($key, $value) 136 | { 137 | if (is_numeric($key) || empty($key)) { 138 | return $value; 139 | } 140 | 141 | return $key; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Drivers/BrowserDetectorDriver.php: -------------------------------------------------------------------------------- 1 | chooseBestLanguage(); 18 | 19 | return $this->getAliasedLocale($bestLanguage); 20 | } 21 | 22 | /** 23 | * Get the best language between the browser and the application. 24 | * 25 | * @return string 26 | */ 27 | public function chooseBestLanguage() 28 | { 29 | return $this->request->getPreferredLanguage($this->getLanguages()); 30 | } 31 | 32 | /** 33 | * Get accept languages. 34 | * 35 | * @deprecated 36 | * 37 | * @return array 38 | */ 39 | public function browserLanguages() 40 | { 41 | return $this->request->getLanguages(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Drivers/SubdomainDetectorDriver.php: -------------------------------------------------------------------------------- 1 | getSegments(); 25 | 26 | $locale = null; 27 | 28 | if ($this->isPartsValid($parts)) { 29 | $locale = $parts[$this->getDefaultSegment()]; 30 | } 31 | 32 | return $locale ? $this->getAliasedLocale($locale) : null; 33 | } 34 | 35 | /** 36 | * Get parts of the subdomain. 37 | * 38 | * @return array 39 | */ 40 | public function getSegments() 41 | { 42 | return preg_split('/\./', $this->request->getHost()); 43 | } 44 | 45 | /** 46 | * Check if parts of the url are valid. 47 | * 48 | * @param array $parts 49 | * @return bool 50 | */ 51 | public function isPartsValid(array $parts) 52 | { 53 | return count($parts) >= $this->minParts; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Drivers/UriDetectorDriver.php: -------------------------------------------------------------------------------- 1 | getSegments(); 28 | 29 | if ($this->isPartsValid($parts) && 30 | $parts[$this->getDefaultSegment()] == $locale 31 | ) { 32 | return $locale; 33 | } 34 | 35 | $aliases = $this->getAliasesToLocale($locale); 36 | 37 | return $aliases ? array_shift($aliases) : ''; 38 | } 39 | 40 | /** 41 | * Get parts of the url. 42 | * 43 | * @return array 44 | */ 45 | public function getSegments() 46 | { 47 | return array_filter(preg_split('/\//', $this->request->path())); 48 | } 49 | 50 | /** 51 | * Array of aliases to a given locale. 52 | * 53 | * @param $locale 54 | * @return array 55 | */ 56 | protected function getAliasesToLocale($locale) 57 | { 58 | $aliases = array_keys($this->languages, $locale); 59 | 60 | $parts = $this->getSegments(); 61 | 62 | if ($this->isPartsValid($parts)) { 63 | $segment = $parts[$this->getDefaultSegment()]; 64 | 65 | return array_filter( 66 | $aliases, 67 | function ($item) use ($segment) { 68 | return $segment == $item; 69 | } 70 | ); 71 | } 72 | 73 | return []; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Facades/LanguageDetector.php: -------------------------------------------------------------------------------- 1 | translator = $translator; 51 | $this->driver = $driver; 52 | } 53 | 54 | /** 55 | * Detect and apply the detected language. 56 | * 57 | * @return string|null Returns the detected locale or null. 58 | */ 59 | public function detectAndApply() 60 | { 61 | $language = $this->detect(); 62 | 63 | if ($language) { 64 | $this->apply($language); 65 | } 66 | 67 | return $language; 68 | } 69 | 70 | /** 71 | * Detect the language. 72 | * 73 | * @return string 74 | */ 75 | public function detect() 76 | { 77 | return $this->getLanguageFromCookie() ?: $this->getDriver()->detect(); 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getLanguageFromCookie() 84 | { 85 | if ($this->cookie) { 86 | /** @var \Illuminate\Http\Request $request */ 87 | $request = $this->getDriver()->getRequest(); 88 | 89 | return $request->cookie($this->cookie); 90 | } 91 | 92 | return false; 93 | } 94 | 95 | /** 96 | * Add cookie with locale to queue. 97 | * 98 | * @param string $locale 99 | */ 100 | public function addCookieToQueue($locale) 101 | { 102 | if ($this->cookie) { 103 | /** @var \Illuminate\Cookie\CookieJar $cookieJar */ 104 | $cookieJar = cookie(); 105 | 106 | $cookieJar->queue($cookieJar->forever($this->cookie, $locale)); 107 | } 108 | } 109 | 110 | /** 111 | * Determine if should use cookies. 112 | * false or null will disable feature, string will set cookie name. 113 | * 114 | * @param string|bool|null $cookieName 115 | */ 116 | public function useCookies($cookieName = 'locale') 117 | { 118 | $this->cookie = empty($cookieName) ? false : $cookieName; 119 | } 120 | 121 | /** 122 | * Get the driver. 123 | * 124 | * @return Driver 125 | */ 126 | public function getDriver() 127 | { 128 | return $this->driver; 129 | } 130 | 131 | /** 132 | * Set driver to detect language. 133 | * 134 | * @param Driver $driver 135 | */ 136 | public function setDriver(Driver $driver) 137 | { 138 | $this->driver = $driver; 139 | } 140 | 141 | /** 142 | * Set locale to the application. 143 | * 144 | * @param string $locale 145 | */ 146 | public function apply($locale) 147 | { 148 | $this->translator->setLocale($locale); 149 | 150 | if (!$this->getLanguageFromCookie()) { 151 | $this->addCookieToQueue($locale); 152 | } 153 | 154 | $this->applyCallbacks($locale); 155 | } 156 | 157 | /** 158 | * Add a callback to call after applying the detected locale. 159 | * @param Closure $callback 160 | */ 161 | public function addCallback(Closure $callback) 162 | { 163 | $this->callbacks[] = $callback; 164 | } 165 | 166 | /** 167 | * Call all registered callbacks. 168 | * 169 | * @param string $language 170 | */ 171 | protected function applyCallbacks($language) 172 | { 173 | foreach ($this->callbacks as $callback) { 174 | call_user_func($callback, $language, $this); 175 | } 176 | } 177 | 178 | /** 179 | * Get the route prefix. 180 | * 181 | * @return string 182 | */ 183 | public function routePrefix() 184 | { 185 | $driver = $this->getDriver(); 186 | 187 | if ($driver instanceof ShouldPrefixRoute) { 188 | return $driver->routePrefix($this->translator->getLocale()); 189 | } 190 | 191 | return ''; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Providers/LanguageDetectorServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'Vluzrmos\LanguageDetector\Drivers\BrowserDetectorDriver', 41 | 'subdomain' => 'Vluzrmos\LanguageDetector\Drivers\SubdomainDetectorDriver', 42 | 'uri' => 'Vluzrmos\LanguageDetector\Drivers\UriDetectorDriver', 43 | ]; 44 | 45 | /** 46 | * Bootstrap the application. 47 | * 48 | * @return void 49 | */ 50 | public function boot() 51 | { 52 | $this->translator = $this->app['translator']; 53 | $this->config = $this->app['config']; 54 | $this->request = $this->app['request']; 55 | 56 | $this->registerAndPublishConfigurations(); 57 | $this->registerAllDrivers(); 58 | $this->registerLanguageDetector(); 59 | $this->detectAndApplyLanguage(); 60 | $this->registerRoutePrefix(); 61 | } 62 | 63 | /** 64 | * Register the package. 65 | */ 66 | public function register() 67 | { 68 | $this->registerEncryptCookies(); 69 | } 70 | 71 | /** 72 | * The services that package provides. 73 | * 74 | * @return array 75 | */ 76 | public function provides() 77 | { 78 | return [ 79 | 'language.detector', 80 | 'language.driver.browser', 81 | 'language.driver.subdomain', 82 | 'language.driver.uri', 83 | ]; 84 | } 85 | 86 | /** 87 | * Disable cookie encryption for language cookie name. 88 | */ 89 | protected function registerEncryptCookies() 90 | { 91 | $this->app->resolving('Illuminate\Cookie\Middleware\EncryptCookies', function ($middleware) { 92 | if ($this->config('cookie', true) && ! $this->config('cookie_encrypt', false)) { 93 | $middleware->disableFor($this->config('cookie_name', 'locale')); 94 | } 95 | }); 96 | } 97 | 98 | /** 99 | * Register and publish configuration files. 100 | * 101 | * @return void 102 | */ 103 | protected function registerAndPublishConfigurations() 104 | { 105 | $configFile = __DIR__.'/../../config/lang-detector.php'; 106 | 107 | $this->publishes([$configFile => base_path('config/lang-detector.php')]); 108 | 109 | $this->mergeConfigFrom($configFile, 'lang-detector'); 110 | } 111 | 112 | /** 113 | * Register All drivers available. 114 | * 115 | * @return void 116 | */ 117 | protected function registerAllDrivers() 118 | { 119 | $languages = $this->config('languages', []); 120 | 121 | if (empty($languages) || in_array('auto', $languages)) { 122 | $languages = $this->getSupportedLanguages(); 123 | 124 | $this->app['config']->set('lang-detector.languages', $languages); 125 | } 126 | 127 | $segment = $this->config('segment', 0); 128 | 129 | foreach ($this->drivers as $short => $driver) { 130 | $this->app->singleton( 131 | 'language.driver.'.$short, 132 | function () use ($driver, $languages, $segment) { 133 | /** @var AbstractDetector $instance */ 134 | $instance = new $driver($this->request, $languages); 135 | $instance->setDefaultSegment($segment); 136 | 137 | return $instance; 138 | } 139 | ); 140 | } 141 | } 142 | 143 | /** 144 | * Get a config value. 145 | * @param string $key 146 | * @param mixed $default 147 | * @return mixed 148 | */ 149 | protected function config($key, $default = null) 150 | { 151 | return $this->config->get('lang-detector.'.$key, $default); 152 | } 153 | 154 | /** 155 | * Register the detector instance. 156 | * 157 | * @return void 158 | */ 159 | protected function registerLanguageDetector() 160 | { 161 | $contract = 'Vluzrmos\LanguageDetector\Contracts\LanguageDetectorInterface'; 162 | 163 | $cookie = $this->config('cookie', true) ? $this->config('cookie_name', 'locale') : null; 164 | 165 | $driver = $this->config('driver', 'browser'); 166 | 167 | $this->app->singleton( 168 | $contract, 169 | function () use ($driver, $cookie) { 170 | $detector = new LanguageDetector( 171 | $this->translator, 172 | $this->app['language.driver.'.$driver] 173 | ); 174 | 175 | if ($cookie) { 176 | $detector->useCookies($cookie); 177 | } 178 | 179 | if (method_exists($this->app, 'setLocale')) { 180 | $detector->addCallback(function ($locale) { 181 | $this->app->setLocale($locale); 182 | }); 183 | } 184 | 185 | return $detector; 186 | } 187 | ); 188 | 189 | $this->app->alias($contract, 'language.detector'); 190 | } 191 | 192 | /** 193 | * Detect and apply language for the application. 194 | */ 195 | protected function detectAndApplyLanguage() 196 | { 197 | if ($this->config('autodetect', true)) { 198 | $this->getLanguageDetector()->detectAndApply(); 199 | } 200 | } 201 | 202 | /** 203 | * Get language.detector from container. 204 | * 205 | * @return LanguageDetector 206 | */ 207 | protected function getLanguageDetector() 208 | { 209 | return $this->app['language.detector']; 210 | } 211 | 212 | /** 213 | * Regiter in container the routePrefix. 214 | * 215 | * @return void 216 | */ 217 | protected function registerRoutePrefix() 218 | { 219 | $this->app->bind( 220 | 'language.routePrefix', 221 | function () { 222 | return $this->getLanguageDetector()->routePrefix(); 223 | } 224 | ); 225 | } 226 | 227 | /** 228 | * Get a list of supported locales. 229 | */ 230 | protected function getSupportedLanguages() 231 | { 232 | /** @var \Illuminate\Cache\Repository $cache */ 233 | $cache = $this->app['cache']; 234 | 235 | return $cache->rememberForever('lang-detector.supported-languages', function () { 236 | $iterator = \Symfony\Component\Finder\Finder::create() 237 | ->directories() 238 | ->in($this->app->langPath()) 239 | ->depth(0); 240 | 241 | $langs = []; 242 | 243 | foreach ($iterator as $dir) { 244 | $langs[] = $dir->getBasename(); 245 | } 246 | 247 | return parse_langs_to_array($langs); 248 | }); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/Support/helpers.php: -------------------------------------------------------------------------------- 1 | $langLocale) { 23 | if (is_numeric($alias) && preg_match('/(-|_)/', $langLocale)) { 24 | list($lang, $locale) = preg_split('/(-|_)/', $langLocale); 25 | 26 | $newAlias = strtolower($lang).'_'.strtoupper($locale); 27 | 28 | if (! isset($languages[$newAlias])) { 29 | $languages[$newAlias] = $langLocale; 30 | } 31 | } 32 | } 33 | 34 | return $languages; 35 | } 36 | } 37 | 38 | if (! function_exists('split_str_to_simple_array')) { 39 | /** 40 | * Parse a simple string comma-separated to array. It also allow to use simple 41 | * indexes, like: "something:awesome, locale:pt-br, country:brazil". 42 | * 43 | * @param string $str 44 | * 45 | * @return array 46 | */ 47 | function split_str_to_simple_array($str) 48 | { 49 | $array = []; 50 | 51 | /* split pairs of comma-separated values into items */ 52 | $items = preg_split('/\s*,\s*/', $str); 53 | 54 | foreach ($items as $item) { 55 | /* split index:value of each pair of items*/ 56 | $pairs = preg_split('/\s*(:=|:|=>|=)\s*/', $item); 57 | 58 | if (count($pairs) == 2) { 59 | $array[$pairs[0]] = $pairs[1]; 60 | } else { 61 | $array[] = $pairs[0]; 62 | } 63 | } 64 | 65 | return $array; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Drivers/AbstractDriversTestCase.php: -------------------------------------------------------------------------------- 1 | translator = $this->app['translator']; 35 | 36 | $this->detector = new LanguageDetector($this->translator); 37 | 38 | $this->translator->setLocale('fr'); 39 | } 40 | 41 | /** 42 | * Create a new request instance. 43 | * 44 | * @param string $uri 45 | * @param string $method 46 | * @param string $acceptedLanguages 47 | * 48 | * @return \Illuminate\Http\Request 49 | */ 50 | public function createRequest($uri = 'http://localhost:8000', $method = 'GET', $acceptedLanguages = 'en-us,en;q=0.8') 51 | { 52 | $request = Request::create($uri, $method); 53 | 54 | $request->headers->set('accept_language', $acceptedLanguages); 55 | 56 | return $request; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Drivers/BrowserDetectorDriverTest.php: -------------------------------------------------------------------------------- 1 | createRequest(); 18 | 19 | $driver = new BrowserDetectorDriver($request, ['en']); 20 | 21 | $locale = $driver->detect(); 22 | 23 | $this->assertEquals('en', $locale); 24 | 25 | $this->assertNotEquals('en', $this->translator->getLocale()); 26 | 27 | $this->detector->setDriver($driver); 28 | 29 | $this->detector->detectAndApply(); 30 | 31 | $this->assertEquals('en', $this->translator->getLocale()); 32 | } 33 | 34 | /** 35 | * Testing detection of default browser preferences and aliases. (default is en, en_US). 36 | */ 37 | public function testDetectWithAliases() 38 | { 39 | $request = $this->createRequest(); 40 | 41 | $driver = new BrowserDetectorDriver($request, ['en' => 'en_US', 'pt-br']); 42 | 43 | $locale = $driver->detect(); 44 | 45 | $this->assertEquals('en_US', $locale); 46 | 47 | $this->assertNotEquals('en_US', $this->translator->getLocale()); 48 | 49 | $this->detector->setDriver($driver); 50 | 51 | $this->detector->detectAndApply(); 52 | 53 | $this->assertEquals('en_US', $this->translator->getLocale()); 54 | } 55 | 56 | /** 57 | * Assert detection with content negotiation. 58 | */ 59 | public function testDetectWithMultiSubLocalesAndContentNegotiation() 60 | { 61 | $request = $this->createRequest('http://localhost:8000', 'GET', 'zh-CN; q=0.8,en; q=0.5'); 62 | 63 | $driver = new BrowserDetectorDriver($request, ['en', 'zh_CN' => 'zh-CN']); 64 | 65 | $locale = $driver->detect(); 66 | 67 | $this->assertEquals('zh-CN', $locale); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Drivers/SubdomainDetectorDriverTest.php: -------------------------------------------------------------------------------- 1 | createSubdomainDetectorForRequest('http://example.com', ['en']); 18 | 19 | $locale = $subdomain->detect(); 20 | 21 | $this->assertEmpty($locale); 22 | 23 | $this->detector->setDriver($subdomain); 24 | 25 | $this->detector->detectAndApply(); 26 | 27 | $this->assertEquals('fr', $this->translator->getLocale()); 28 | } 29 | 30 | /** 31 | * Testing should change the locale matching if available with subdomain. 32 | */ 33 | public function testShouldMatchesWithTheSubdomain() 34 | { 35 | $subdomain = $this->createSubdomainDetectorForRequest('http://en.example.com', ['en']); 36 | 37 | $locale = $subdomain->detect(); 38 | 39 | $this->assertEquals('en', $locale); 40 | 41 | $this->detector->setDriver($subdomain); 42 | 43 | $this->detector->detectAndApply(); 44 | 45 | $this->assertEquals('en', $this->translator->getLocale()); 46 | } 47 | 48 | /** 49 | * Testing should alises the subdomain. 50 | */ 51 | public function testShouldMatchesWithTheSubdomainAndAliases() 52 | { 53 | $subdomain = $this->createSubdomainDetectorForRequest('http://en-us.example.com', ['en', 'en-us' => 'en_US']); 54 | 55 | $locale = $subdomain->detect(); 56 | 57 | $this->assertEquals('en_US', $locale); 58 | 59 | $this->detector->setDriver($subdomain); 60 | 61 | $this->detector->detectAndApply(); 62 | 63 | $this->assertEquals('en_US', $this->translator->getLocale()); 64 | } 65 | 66 | /** 67 | * Create an instance of SubdomainDetectorDriver for a given Request Uri. 68 | * 69 | * @param string $requestUri Url of the Request. 70 | * @param array $languages Languages available on the application. 71 | * @param string $method Requested Method. 72 | * 73 | * @return SubdomainDetectorDriver 74 | */ 75 | protected function createSubdomainDetectorForRequest($requestUri, array $languages = [], $method = 'GET') 76 | { 77 | $request = $this->createRequest($requestUri, $method); 78 | 79 | return new SubdomainDetectorDriver($request, $languages); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Drivers/UriDetectorDriverTest.php: -------------------------------------------------------------------------------- 1 | createUriDetectorForRequest('http://example.com', ['en']); 18 | 19 | $locale = $uri->detect(); 20 | 21 | $this->assertEmpty($locale); 22 | 23 | $this->detector->setDriver($uri); 24 | 25 | $this->detector->detectAndApply(); 26 | 27 | $this->assertEquals('fr', $this->translator->getLocale()); 28 | 29 | $prefix = $uri->routePrefix($this->translator->getLocale()); 30 | 31 | $this->assertEmpty($prefix); 32 | } 33 | 34 | /** 35 | * Testing should change the locale matching if available with uri. 36 | */ 37 | public function testShouldMatchesWithTheUri() 38 | { 39 | $uri = $this->createUriDetectorForRequest('http://example.com/en', ['en']); 40 | 41 | $locale = $uri->detect(); 42 | 43 | $this->assertEquals('en', $locale); 44 | 45 | $this->detector->setDriver($uri); 46 | 47 | $this->detector->detectAndApply(); 48 | 49 | $this->assertEquals('en', $this->translator->getLocale()); 50 | 51 | $prefix = $uri->routePrefix($this->translator->getLocale()); 52 | 53 | $this->assertEquals('en', $prefix); 54 | } 55 | 56 | /** 57 | * Testing should alises the subdomain. 58 | */ 59 | public function testShouldMatchesWithTheSubdomainAndAliases() 60 | { 61 | $uri = $this->createUriDetectorForRequest('http://example.com/en-us', ['en', 'en-us' => 'en_US']); 62 | 63 | $locale = $uri->detect(); 64 | 65 | $this->assertEquals('en_US', $locale); 66 | 67 | $this->detector->setDriver($uri); 68 | 69 | $this->detector->detectAndApply(); 70 | 71 | $this->assertEquals('en_US', $this->translator->getLocale()); 72 | 73 | $prefix = $uri->routePrefix($this->translator->getLocale()); 74 | 75 | $this->assertEquals('en-us', $prefix); 76 | } 77 | 78 | /** 79 | * Create an instance of UriDetectorDriver for a given Request Uri. 80 | * 81 | * @param string $requestUri Url of the request. 82 | * @param array $languages Languages available on the application. 83 | * @param string $method Requested method. 84 | * 85 | * @return UriDetectorDriver 86 | */ 87 | protected function createUriDetectorForRequest($requestUri, array $languages = [], $method = 'GET') 88 | { 89 | $request = $this->createRequest($requestUri, $method); 90 | 91 | return new UriDetectorDriver($request, $languages); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Providers/LanguageDetectorServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 22 | 'Vluzrmos\LanguageDetector\Contracts\DetectorDriverInterface', 23 | $this->app['language.driver.'.$shortcut] 24 | ); 25 | } 26 | 27 | $this->assertInstanceOf( 28 | 'Vluzrmos\LanguageDetector\Contracts\LanguageDetectorInterface', 29 | $this->app['language.detector'] 30 | ); 31 | 32 | $this->assertInstanceOf( 33 | 'Vluzrmos\LanguageDetector\Contracts\LanguageDetectorInterface', 34 | $this->app['Vluzrmos\LanguageDetector\Contracts\LanguageDetectorInterface'] 35 | ); 36 | 37 | $this->app['translator']->setLocale('fr'); 38 | 39 | $this->app['language.detector']->detectAndApply(); 40 | 41 | $this->assertEquals('en', $this->app['translator']->getLocale()); 42 | } 43 | 44 | /** 45 | * @param Application $app 46 | * @return array 47 | */ 48 | public function getPackageProviders($app) 49 | { 50 | $app['config']->set( 51 | 'language.provider', 52 | 'Vluzrmos\LanguageDetector\Providers\LanguageDetectorServiceProvider' 53 | ); 54 | 55 | return [$app['config']->get('language.provider')]; 56 | } 57 | } 58 | --------------------------------------------------------------------------------