├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── run-tests.yml
├── .gitignore
├── .scrutinizer.yml
├── ADDITIONS.md
├── CACHING.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── UPGRADING.md
├── composer.json
├── phpunit.xml.dist
├── src
├── Mcamara
│ └── LaravelLocalization
│ │ ├── Commands
│ │ ├── RouteTranslationsCacheCommand.php
│ │ ├── RouteTranslationsClearCommand.php
│ │ └── RouteTranslationsListCommand.php
│ │ ├── Exceptions
│ │ ├── SupportedLocalesNotDefined.php
│ │ └── UnsupportedLocaleException.php
│ │ ├── Facades
│ │ └── LaravelLocalization.php
│ │ ├── Interfaces
│ │ └── LocalizedUrlRoutable.php
│ │ ├── LanguageNegotiator.php
│ │ ├── LaravelLocalization.php
│ │ ├── LaravelLocalizationServiceProvider.php
│ │ ├── Middleware
│ │ ├── LaravelLocalizationMiddlewareBase.php
│ │ ├── LaravelLocalizationRedirectFilter.php
│ │ ├── LaravelLocalizationRoutes.php
│ │ ├── LaravelLocalizationViewPath.php
│ │ ├── LocaleCookieRedirect.php
│ │ └── LocaleSessionRedirect.php
│ │ └── Traits
│ │ ├── LoadsTranslatedCachedRoutes.php
│ │ └── TranslatedRouteCommandContext.php
├── config
│ └── config.php
└── stubs
│ └── routes.stub
└── tests
├── CustomTranslatorTest.php
├── LaravelLocalizationTest.php
├── ModelWithTranslatableRoutes.php
├── TestCase.php
├── full-config
└── config.php
└── lang
├── en
└── routes.php
└── es
└── routes.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [mcamara, iwasherefirst2]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **More info:**
24 | - Version of Laravel
25 | - Version of the Laravel-localization package
26 | - Which middleware is used in `Route::groups`
27 | - Copy of the config file ( or at least setting of `supportedLocales`, `useAcceptLanguageHeader` and `hideDefaultLocaleInURL`).
28 | - Minimal steps to reproduce on a clean Laravel installation.
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | test:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | os: [ubuntu-latest]
16 | php: [8.4, 8.3, 8.2]
17 | laravel: [12.*, 11.*, 10.*]
18 | stability: [prefer-lowest, prefer-stable]
19 | include:
20 | - laravel: 12.*
21 | testbench: 10.*
22 | - laravel: 11.*
23 | testbench: 9.*
24 | - laravel: 10.*
25 | testbench: 8.*
26 |
27 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
28 |
29 | steps:
30 | - name: Checkout code
31 | uses: actions/checkout@v4
32 |
33 | - name: Setup PHP
34 | uses: shivammathur/setup-php@v2
35 | with:
36 | php-version: ${{ matrix.php }}
37 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
38 | coverage: none
39 |
40 | - name: Setup problem matchers
41 | run: |
42 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
43 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
44 |
45 | - name: Install dependencies
46 | run: |
47 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench-browser-kit:${{ matrix.testbench }}" --no-interaction --no-update
48 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
49 |
50 | - name: Execute tests
51 | run: vendor/bin/phpunit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.buildpath
2 | /.project
3 | /.settings
4 | /vendor
5 | composer.lock
6 | composer.phar
7 | Thumbs.db
8 | phpunit.xml
9 | .phpunit.cache
10 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | checks:
2 | php:
3 | code_rating: true
4 | duplication: true
5 | filter:
6 | excluded_paths:
7 | - tests/*
8 | - src/config/*
--------------------------------------------------------------------------------
/ADDITIONS.md:
--------------------------------------------------------------------------------
1 | # Additional information
2 |
3 | ## Installation
4 |
5 | ### For Laravel 5.4 and below:
6 |
7 | For older versions of the framework, follow the steps below:
8 |
9 | Register the service provider in `config/app.php`
10 |
11 | ```php
12 | 'providers' => [
13 | // [...]
14 | Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider::class,
15 | ],
16 | ```
17 |
18 | You may also register the `LaravelLocalization` facade:
19 |
20 | ```php
21 | 'aliases' => [
22 | // [...]
23 | 'LaravelLocalization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class,
24 | ],
25 | ```
26 |
27 | ## Config
28 |
29 | ### Service Providers
30 |
31 | Otherwise, you can use `ConfigServiceProviders` (check this file for more info).
32 |
33 | For example, editing the default config service provider that Laravel loads when it's installed. This file is placed in `app/providers/ConfigServicePovider.php` and would look like this:
34 |
35 | ```php
36 | [
45 | 'ace' => array( 'name' => 'Achinese', 'script' => 'Latn', 'native' => 'Aceh' ),
46 | 'ca' => array( 'name' => 'Catalan', 'script' => 'Latn', 'native' => 'català' ),
47 | 'en' => array( 'name' => 'English', 'script' => 'Latn', 'native' => 'English' ),
48 | ],
49 |
50 | 'laravellocalization.useAcceptLanguageHeader' => true,
51 |
52 | 'laravellocalization.hideDefaultLocaleInURL' => true
53 | ]);
54 | }
55 |
56 | }
57 | ```
58 |
59 | This config would add Catalan and Achinese as languages and override any other previous supported locales and all the other options in the package.
60 |
61 | You can create your own config providers and add them on your application config file (check the providers array in `config/app.php`).
62 |
--------------------------------------------------------------------------------
/CACHING.md:
--------------------------------------------------------------------------------
1 | # Laravel Localization: Caching Routes
2 |
3 | If you want to cache the routes in all languages, you will need to use special Artisan commands. **Using `artisan route:cache`** will not work correctly!
4 |
5 | ## Setup
6 |
7 | For the route caching solution to work, it is required to make a minor adjustment to your application route provision.
8 |
9 |
10 | **before laravel 11**
11 | In your App's `RouteServiceProvider`, use the `LoadsTranslatedCachedRoutes` trait:
12 |
13 | ```php
14 | $this->loadCachedRoutes());
33 | ...
34 | }
35 | ```
36 |
37 | ## Usage
38 |
39 | To cache your routes, use:
40 |
41 | ``` bash
42 | php artisan route:trans:cache
43 | ```
44 |
45 | ... instead of the normal `route:cache` command.
46 |
47 | To list the routes for a given locale, use
48 |
49 | ``` bash
50 | php artisan route:trans:list {locale}
51 |
52 | # for instance:
53 | php artisan route:trans:list en
54 | ```
55 |
56 | To clear cached routes for all locales, use
57 |
58 | ``` bash
59 | php artisan route:trans:clear
60 | ```
61 |
62 | ### Note
63 |
64 | Using `route:clear` will also effectively unset the cache (at the minor cost of leaving some clutter in your bootstrap/cache directory).
65 |
66 |
67 | ## History
68 |
69 | Caching routes, before version 1.3, was done using a separate package,
70 | [https://github.com/czim/laravel-localization-route-cache](https://github.com/czim/laravel-localization-route-cache).
71 | That separate package is no longer required, and should be removed when upgrading to 1.3 or newer.
72 |
73 |
74 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 2.0.0
2 | - Support [contextual binding](https://laravel.com/docs/container#contextual-binding) ([#879](https://github.com/mcamara/laravel-localization/pull/879))
3 |
4 | For guidance on the upgrade process, please refer to the [UPGRADING.md](/UPGRADING.md) file.
5 |
6 | ### 1.4.0
7 | - Added compatibility with Laravel 6
8 |
9 | ### 1.3.20
10 | - Respect locales mapping ([#631](https://github.com/mcamara/laravel-localization/pull/631))
11 |
12 | ### 1.3.11
13 | - Merged in solution for caching translated and localized routes (originally in separate package [czim/laravel-localization-route-cache](https://github.com/czim/laravel-localization-route-cache)) by [CZim](https://github.com/czim).
14 | If you used this package, be sure to remove it when upgrading to this version.
15 | - Added `'utf8suffix' => env('LARAVELLOCALIZATION_UTF8SUFFIX', '.UTF-8')` to config file.
16 |
17 | ### 1.2.3
18 | - Added `getLocalesOrder()` function to the package
19 |
20 | ### 1.2
21 | - Added compatibility with Laravel 5.4
22 |
23 | ### 1.1
24 | - Added compatibility with Laravel 5.2
25 |
26 | ### 1.0.12
27 | - Added regional for date localization
28 |
29 | ### 1.0.7
30 | - Added Session and Cookie Middleware
31 | - Deleted useSessionLocale and useCookieLocale from config file
32 |
33 | ### 1.0
34 | - Laravel 5 supported
35 | - Added Middleware
36 | - Removed deprecated functions
37 |
38 | ### 0.15.0
39 | - Added tests from scratch
40 | - Refactored multiple functions
41 | - getLocalizedURL now accepts attributes for the url (if needed)
42 | - $routeName is always a string, no need to be an array if it just have the translation key for the current url
43 |
44 | ### 0.14.0
45 | - Laravel 4.2 compatibility
46 | - Removed Laravel 4.0 compatibility
47 |
48 | ### 0.13.5
49 | - Fixes issue with grouped routes
50 |
51 | ### 0.13.4
52 | - Fixes issue localizing a url when segment starts with a locale
53 |
54 | ### 0.13.3
55 | - Allow no url to be passed in localizeURL
56 |
57 | ### 0.13.2
58 | - Fixes issue with double slashes in localized urls
59 | - Strip trailing slashes from all localized urls
60 |
61 | ### 0.13.1
62 | - Fixes URL localization issue when the base path is not / (a.k.a, Laravel is not installed in the web root).
63 |
64 | ### 0.13.0
65 | - Deprecated "getLanguageBar"
66 |
67 | ### 0.12.1
68 | - Throws exception if Larvel's default locale is not in the array of supported locales.
69 |
70 | ### 0.12.0
71 | - Changes 302 redirect in to 307 to prevent POST values from being consumed.
72 | - Added localizeURL function
73 |
74 | ### 0.11.0
75 | - Deprecated "getCurrentLanguageDirection", "getCurrentLanguageScript"
76 | - Added "getCurrentLocaleDirection", "getCurrentLocaleScript", "getCurrentLocaleName"
77 |
78 | ### 0.10.1
79 | - Fixes to maintain compatibility with older config and languagebar.blade.php templates
80 | - Fixed backward compatibility of getLanguageBar
81 | - getLocalizedURL now returns URL paths in the same format as parameter inputted; trailing and leading slashes or lack of are respected.
82 | - getLocalizedURL now compatible with querystrings
83 | - merged getNonLocalizedURL and getLocalizedURL
84 | - getNonLocalizedURL($url = null) is now a wrapper for getLocalizedURL(false, $url = null)
85 |
86 | ### 0.10
87 | - Standardizing function names and variables using locale
88 | - Deprecated getCleanRoute
89 | - Deprecated useBrowserLanguage
90 | - Changed config useBrowserLanguage to useAcceptLanguageHeader
91 | - Deprecated useSessionLanguage
92 | - Changed config useSessionLanguage to useSessionLocale
93 | - Deprecated useCookieLanguagee
94 | - Changed config useCookieLanguage to useCookieLocale
95 |
96 | ### 0.9
97 | - Fixes issue #47
98 | - Fixes issue where getCleanRoute would only clean out the currently set locale.
99 | - getLocalizedURL now throws an UnsupportedLocaleException if the requested locale is not in the list of supported locales.
100 |
101 | ### 0.8
102 | - Changed getLanguageBar to just return view. All other code has been moved to languagebar view.
103 | - Deprecated getPrintCurrentLanguage
104 | - Deprecated getLanguageBarClassName
105 |
106 | ### 0.7
107 | - Merged languagesAllowed & supportedLanguages
108 | - Added native for language names
109 | - Added new function getSupportedLocales
110 | - Deprecated getAllowedLanguages use getSupportedLocales instead
111 | - Deprecated getSupportedLanguages use getSupportedLocales instead
112 |
113 | ### 0.6
114 | - Added support for language script and direction
115 |
116 | ### 0.5
117 | - Added multi-language routes
118 | - Function `getCurrentLanguage` is not static
119 |
120 | ### 0.4
121 | - Added the ability to edit the language bar code
122 |
123 | ### 0.3
124 | - Added 'LaravelLocalizationRedirectFilter' filter
125 |
126 | ### 0.2
127 | - Added `getURLLanguage` method.
128 | - Added `getLanguageBar` method.
129 | - Added `getURLLanguage` method.
130 | - Added config file
131 | - Added `useBrowserLanguage` config value
132 | - Added README
133 |
134 | ### 0.1
135 | - Initial release.
136 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 mcamara
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Localization
2 |
3 | [](https://packagist.org/packages/mcamara/laravel-localization)
4 | [](https://packagist.org/packages/mcamara/laravel-localization)
5 | 
6 | [](https://www.codetriage.com/mcamara/laravel-localization)
7 | [](https://houndci.com)
8 |
9 | Easy i18n localization for Laravel, an useful tool to combine with Laravel localization classes.
10 |
11 | The package offers the following:
12 |
13 | - Detect language from browser
14 | - Smart redirects (Save locale in session/cookie)
15 | - Smart routing (Define your routes only once, no matter how many languages you use)
16 | - Translatable Routes
17 | - Supports caching & testing
18 | - Option to hide default locale in url
19 | - Many snippets and helpers (like language selector)
20 |
21 | ## Table of Contents
22 |
23 | - Installation
24 | - Usage
25 | - Redirect Middleware
26 | - Helpers
27 | - Translated Routes
28 | - Caching routes
29 | - Testing
30 | - Common Issues
31 | - POST is not working
32 | - MethodNotAllowedHttpException
33 | - Validation message is always in default locale
34 | - Collaborators
35 | - Changelog
36 | - License
37 |
38 | ## Laravel compatibility
39 |
40 | Laravel | laravel-localization
41 | :-------------|:----------
42 | 4.0.x | 0.13.x
43 | 4.1.x | 0.13.x
44 | 4.2.x | 0.15.x
45 | 5.0.x/5.1.x | 1.0.x
46 | 5.2.x-5.4.x (PHP 7 not required) | 1.2.
47 | 5.2.0-6.x (PHP version >= 7 required) | 1.4.x
48 | 6.x-10.x (PHP version >= 7 required) | 1.8.x
49 | 10.x-12.x (PHP version >= 8.2 required) | 2.0.x
50 |
51 | ## Installation
52 |
53 | Install the package via composer: `composer require mcamara/laravel-localization`
54 |
55 | For Laravel 5.4 and below it necessary to [register the service provider](/ADDITIONS.md#for-laravel-5.4-and-below).
56 |
57 | ### Config Files
58 |
59 | In order to edit the default configuration you may execute:
60 |
61 | ```
62 | php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider"
63 | ```
64 |
65 | After that, `config/laravellocalization.php` will be created.
66 |
67 | The configuration options are:
68 |
69 | - **supportedLocales** Languages of your app (Default: English & Spanish).
70 | - **useAcceptLanguageHeader** If true, then automatically detect language from browser.
71 | - **hideDefaultLocaleInURL** If true, then do not show default locale in url.
72 | - **localesOrder** Sort languages in custom order.
73 | - **localesMapping** Rename url locales.
74 | - **utf8suffix** Allow changing utf8suffix for CentOS etc.
75 | - **urlsIgnored** Ignore specific urls.
76 |
77 | ### Register Middleware
78 |
79 | You may register the package middleware in the `app/Http/Kernel.php` file:
80 |
81 | ```php
82 | \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
95 | 'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
96 | 'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
97 | 'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
98 | 'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class
99 | ];
100 | }
101 | ```
102 |
103 | If you are using Laravel 11, you may register in `bootstrap/app.php` file in closure `withMiddleware`:
104 |
105 | ```php
106 | return Application::configure(basePath: dirname(__DIR__))
107 | // Other application configurations
108 | ->withMiddleware(function (Middleware $middleware) {
109 | $middleware->alias([
110 | /**** OTHER MIDDLEWARE ALIASES ****/
111 | 'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
112 | 'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
113 | 'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
114 | 'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
115 | 'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class,
116 | ]);
117 | })
118 | ```
119 |
120 | ## Usage
121 |
122 | Add the following to your routes file:
123 |
124 | ```php
125 | // routes/web.php
126 |
127 | Route::group(['prefix' => LaravelLocalization::setLocale()], function()
128 | {
129 | /** ADD ALL LOCALIZED ROUTES INSIDE THIS GROUP **/
130 | Route::get('/', function()
131 | {
132 | return View::make('hello');
133 | });
134 |
135 | Route::get('test',function(){
136 | return View::make('test');
137 | });
138 | });
139 |
140 | /** OTHER PAGES THAT SHOULD NOT BE LOCALIZED **/
141 |
142 | ```
143 |
144 | Once this route group is added to the routes file, a user can access all locales added into `supportedLocales` (`en` and `es` by default).
145 | For example, the above route file creates the following addresses:
146 |
147 | ```
148 | // Set application language to English
149 | http://url-to-laravel/en
150 | http://url-to-laravel/en/test
151 |
152 | // Set application language to Spanish
153 | http://url-to-laravel/es
154 | http://url-to-laravel/es/test
155 |
156 | // Set application language to English or Spanish (depending on browsers default locales)
157 | // if nothing found set to default locale
158 | http://url-to-laravel
159 | http://url-to-laravel/test
160 | ```
161 | The package sets your application locale `App::getLocale()` according to your url. The locale may then be used for [Laravel's localization features](http://laravel.com/docs/localization).
162 |
163 | You may add middleware to your group like this:
164 |
165 | ```php
166 | Route::group(
167 | [
168 | 'prefix' => LaravelLocalization::setLocale(),
169 | 'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]
170 | ], function(){ //...
171 | });
172 | ```
173 |
174 | ### Recommendations
175 |
176 | ***1.***: It is **strongly** recommended to use a [redirecting middleware](#redirect-middleware).
177 | Urls without locale should only be used to determine browser/default locale and to redirect to the [localized url](#localized-urls).
178 | Otherwise, when search engine robots crawl for example `http://url-to-laravel/test` they may get different language content for each visit.
179 | Also having multiple urls for the same content creates a SEO duplicate-content issue.
180 |
181 | ***2.***: It is **strongly** recommended to [localize your links](#localized-urls), even if you use a redirect middleware.
182 | Otherwise, you will cause at least one redirect each time a user clicks on a link.
183 | Also, any action url from a post form must be localized, to prevent that it gets redirected to a get request.
184 |
185 |
186 | ## Redirect Middleware
187 |
188 | The following redirection middleware depends on the settings of `hideDefaultLocaleInURL`
189 | and `useAcceptLanguageHeader` in `config/laravellocalization.php`:
190 |
191 | ### LocaleSessionRedirect
192 |
193 | Whenever a locale is present in the url, it will be stored in the session by this middleware.
194 |
195 | If there is no locale present in the url, then this middleware will check the following
196 |
197 | - If no locale is saved in session and `useAcceptLanguageHeader` is set to true, compute locale from browser and redirect to url with locale.
198 | - If a locale is saved in session redirect to url with locale, unless its the default locale and `hideDefaultLocaleInURL` is set to true.
199 |
200 | For example, if a user navigates to http://url-to-laravel/test and `en` is the current locale, it would redirect him automatically to http://url-to-laravel/en/test.
201 |
202 | ### LocaleCookieRedirect
203 |
204 | Similar to LocaleSessionRedirect, but it stores value in a cookie instead of a session.
205 |
206 | Whenever a locale is present in the url, it will be stored in the cookie by this middleware.
207 |
208 | In there is no locale present in the url, then this middleware will check the following
209 |
210 | - If no locale is saved in cookie and `useAcceptLanguageHeader` is set to true, compute locale from browser and redirect to url with locale.
211 | - If a locale is saved in cookie redirect to url with locale, unless its the default locale and `hideDefaultLocaleInURL` is set to true.
212 |
213 | For example, if a user navigates to http://url-to-laravel/test and `de` is the current locale, it would redirect him automatically to http://url-to-laravel/de/test.
214 |
215 |
216 | ### LaravelLocalizationRedirectFilter
217 |
218 | When the default locale is present in the url and `hideDefaultLocaleInURL` is set to true, then the middleware redirects to the url without locale.
219 |
220 | For example, if `es` is the default locale, then http://url-to-laravel/es/test would be redirected to http://url-to-laravel/test and the`App::getLocale()` would be
221 | set to `es`.
222 |
223 |
224 | ## Helpers
225 |
226 | This package comes with a bunch of helpers.
227 |
228 | ### Localized URLs
229 | Localized URLS taken into account [route model binding]([https://laravel.com/docs/master/routing#route-model-binding]) when generating the localized route,
230 | aswell as the `hideDefaultLocaleInURL` and [Translated Routes](#translated-routes) settings.
231 |
232 |
233 | #### Get localized URL
234 |
235 | ```php
236 | // If current locale is Spanish, it returns `/es/test`
237 | @lang('Follow this link')
238 | ```
239 |
240 | #### Get localized URL for an specific locale
241 | Get current URL in specific locale:
242 |
243 | ```php
244 | // Returns current url with English locale.
245 | {{ LaravelLocalization::getLocalizedURL('en') }}
246 | ```
247 |
248 | ### Get Clean routes
249 |
250 | Returns a URL clean of any localization.
251 |
252 | ```php
253 | // Returns /about
254 | {{ LaravelLocalization::getNonLocalizedURL('/es/about') }}
255 | ```
256 |
257 | ### Get URL for an specific translation key
258 |
259 | Returns a route, [localized](#translated-routes) to the desired locale. If the translation key does not exist in the locale given, this function will return false.
260 |
261 |
262 | ```php
263 | // Returns /es/acerca
264 | {{ LaravelLocalization::getURLFromRouteNameTranslated('es', 'routes.about') }}
265 | ```
266 | **Example of a localized link using routes with attributes**
267 |
268 | ```php
269 | // An array of attributes can be provided.
270 | // Returns /en/archive/ghosts, /fr/archive/fantômes, /pt/arquivo/fantasmas, etc.
271 | Ghost Stories
272 | ```
273 |
274 |
275 | ### Get Supported Locales
276 |
277 | Return all supported locales and their properties as an array.
278 |
279 | ```php
280 | {{ LaravelLocalization::getSupportedLocales() }}
281 | ```
282 |
283 |
284 |
285 | ### Get Supported Locales Custom Order
286 |
287 | Return all supported locales but in the order specified in the configuration file. You can use this function to print locales in the language selector.
288 |
289 | ```php
290 | {{ LaravelLocalization::getLocalesOrder() }}
291 | ```
292 |
293 | ### Get Supported Locales Keys
294 |
295 | Return an array with all the keys for the supported locales.
296 |
297 | ```php
298 | {{ LaravelLocalization::getSupportedLanguagesKeys() }}
299 | ```
300 |
301 | ### Get Current Locale
302 |
303 | Return the key of the current locale.
304 |
305 | ```php
306 | {{ LaravelLocalization::getCurrentLocale() }}
307 | ```
308 |
309 | ### Get Current Locale Name
310 | Return current locale's name as string (English/Spanish/Arabic/ ..etc).
311 |
312 | ```php
313 | {{ LaravelLocalization::getCurrentLocaleName() }}
314 | ```
315 |
316 | ### Get Current Locale Native Name
317 | Return current locale's native name as string (English/Español/عربى/ ..etc).
318 |
319 | ```php
320 | {{ LaravelLocalization::getCurrentLocaleNative() }}
321 | ```
322 |
323 | ### Get Current Locale Regional Name
324 | Return current locale's regional name as string (en_GB/en_US/fr_FR/ ..etc).
325 |
326 | ```php
327 | {{ LaravelLocalization::getCurrentLocaleRegional() }}
328 | ```
329 |
330 | ### Get Current Locale Direction
331 |
332 | Return current locale's direction as string (ltr/rtl).
333 |
334 |
335 | ```php
336 | {{ LaravelLocalization::getCurrentLocaleDirection() }}
337 | ```
338 |
339 |
340 |
341 | ### Get Current Locale Script
342 | Return the [ISO 15924](http://www.unicode.org/iso15924) code for the current locale script as a string; "Latn", "Cyrl", "Arab", etc.
343 |
344 | ```php
345 | {{ LaravelLocalization::getCurrentLocaleScript() }}
346 | ```
347 |
348 | ### Set view-base-path to current locale
349 |
350 | Register the middleware `LaravelLocalizationViewPath` to set current locale as view-base-path.
351 |
352 | Now you can wrap your views in language-based folders like the translation files.
353 |
354 | `resources/views/en/`, `resources/views/fr`, ...
355 |
356 |
357 | ### Map your own custom lang url segments
358 |
359 | As you can modify the supportedLocales even by renaming their keys, it is possible to use the string ```uk``` instead of ```en-GB``` to provide custom lang url segments. Of course, you need to prevent any collisions with already existing keys and should stick to the convention as long as possible. But if you are using such a custom key, you have to store your mapping to the ```localesMapping``` array. This ```
360 | localesMapping``` is needed to enable the LanguageNegotiator to correctly assign the desired locales based on HTTP Accept Language Header. Here is a quick example how to map HTTP Accept Language Header 'en-GB' to url segment 'uk':
361 |
362 | ```php
363 | // config/laravellocalization.php
364 |
365 | 'localesMapping' => [
366 | 'en-GB' => 'uk'
367 | ],
368 | ```
369 |
370 | After that ```http://url-to-laravel/en-GB/a/b/c``` becomes ```http://url-to-laravel/uk/a/b/c```.
371 |
372 | ```php
373 | LaravelLocalization::getLocalizedURL('en-GB', 'a/b/c'); // http://url-to-laravel/uk/a/b/c
374 | LaravelLocalization::getLocalizedURL('uk', 'a/b/c'); // http://url-to-laravel/uk/a/b/c
375 | ```
376 |
377 | ## Creating a language selector
378 |
379 | If you're supporting multiple locales in your project you will probably want to provide the users with a way to change language. Below is a simple example of blade template code you can use to create your own language selector.
380 |
381 | ```blade
382 |
391 | ```
392 | Here default language will be forced in getLocalizedURL() to be present in the URL even `hideDefaultLocaleInURL = true`.
393 |
394 | Note that Route Model Binding is supported.
395 |
396 | ## Translated Routes
397 |
398 | You may translate your routes. For example, http://url/en/about and http://url/es/acerca (acerca is about in spanish)
399 | or http://url/en/article/important-article and http://url/es/articulo/important-article (article is articulo in spanish) would be redirected to the same controller/view as follows:
400 |
401 | It is necessary that at least the `localize` middleware in loaded in your `Route::group` middleware (See [installation instruction](#installation)).
402 |
403 | For each language, add a `routes.php` into `resources/lang/**/routes.php` folder.
404 | The file contains an array with all translatable routes. For example, like this:
405 |
406 | > Keep in mind: starting from [Laravel 9](https://laravel.com/docs/9.x/upgrade#the-lang-directory), the `resources/lang` folder is now located in the root project folder (`lang`).
407 | > If your project has `lang` folder in the root, you must add a `routes.php` into `lang/**/routes.php` folder.
408 |
409 | ```php
410 | "about",
414 | "article" => "article/{article}",
415 | ];
416 | ```
417 | ```php
418 | "acerca",
422 | "article" => "articulo/{article}",
423 | ];
424 | ```
425 |
426 | You may add the routes in `routes/web.php` like this:
427 |
428 | ```php
429 | Route::group(['prefix' => LaravelLocalization::setLocale(),
430 | 'middleware' => [ 'localize' ]], function () {
431 |
432 | Route::get(LaravelLocalization::transRoute('routes.about'), function () {
433 | return view('about');
434 | });
435 |
436 | Route::get(LaravelLocalization::transRoute('routes.article'), function (\App\Article $article) {
437 | return $article;
438 | });
439 |
440 | //,...
441 | });
442 | ```
443 |
444 | Once files are saved, you can access http://url/en/about , http://url/es/acerca , http://url/en/article/important-article and http://url/es/articulo/important-article without any problem.
445 |
446 | ### Translatable route parameters
447 |
448 | Maybe you noticed in the previous example the English slug in the Spanish url:
449 |
450 | http://url/es/articulo/important-article
451 |
452 | It is possible to have translated slugs, for example like this:
453 |
454 | http://url/en/article/important-change
455 | http://url/es/articulo/cambio-importante
456 |
457 | However, in order to do this, each article must have many slugs (one for each locale).
458 | Its up to you how you want to implement this relation. The only requirement for translatable route parameters is, that the relevant model implements the interface `LocalizedUrlRoutable`.
459 |
460 | #### Implementing LocalizedUrlRoutable
461 |
462 | To implement `\Mcamara\LaravelLocalization\Interfaces\LocalizedUrlRoutable`,
463 | one has to create the function `getLocalizedRouteKey($locale)`, which must return for a given locale the translated slug. In the above example, inside the model article, `getLocalizedRouteKey('en')` should return `important-change` and `getLocalizedRouteKey('es')` should return `cambio-importante`.
464 |
465 | #### Route Model Binding
466 |
467 | To use [route-model-binding](https://laravel.com/docs/routing#route-model-binding), one should overwrite the function `resolveRouteBinding($slug)`
468 | in the model. The function should return the model that belongs to the translated slug `$slug`.
469 | For example:
470 |
471 | ```php
472 | public function resolveRouteBinding($slug)
473 | {
474 | return static::findByLocalizedSlug($slug)->first() ?? abort(404);
475 | }
476 | ```
477 |
478 | #### Tutorial Video
479 |
480 | You may want to checkout this [video](https://youtu.be/B1AUqCdizgc) which demonstrates how one may set up translatable route parameters.
481 |
482 | ## Events
483 |
484 | You can capture the URL parameters during translation if you wish to translate them too. To do so, just create an event listener for the `routes.translation` event like so:
485 |
486 | ```php
487 | Event::listen('routes.translation', function($locale, $attributes)
488 | {
489 | // Do your magic
490 |
491 | return $attributes;
492 | });
493 | ```
494 |
495 | Be sure to pass the locale and the attributes as parameters to the closure. You may also use Event Subscribers, see: [http://laravel.com/docs/events#event-subscribers](http://laravel.com/docs/events#event-subscribers)
496 |
497 | ## Caching routes
498 |
499 | To cache your routes, use:
500 |
501 | ``` bash
502 | php artisan route:trans:cache
503 | ```
504 |
505 | ... instead of the normal `route:cache` command. Using `artisan route:cache` will **not** work correctly!
506 |
507 | For the route caching solution to work, it is required to make a minor adjustment to your application route provision.
508 |
509 | **before laravel 11**
510 |
511 | In your App's `RouteServiceProvider`, use the `LoadsTranslatedCachedRoutes` trait:
512 |
513 | ```php
514 | $this->loadCachedRoutes());
535 | ...
536 | }
537 | ```
538 |
539 |
540 |
541 | For more details see [here](CACHING.md).
542 |
543 | ## Common Issues
544 |
545 | ### POST is not working
546 |
547 | This may happen if you do not localize your action route that is inside your `Routes::group`.
548 | This may cause a redirect, which then changes the post request into a get request.
549 | To prevent that, simply use the [localize helper](#get-localized-url).
550 |
551 | For example, if you use `Auth::routes()` and put them into your `Route::group` Then
552 |
553 | ```
554 |
557 | ```
558 |
559 | will not work. Instead, one has to use
560 |
561 | ```php
562 |
565 | ```
566 |
567 |
568 | Another way to solve this is to put http method to config to 'laravellocalization.httpMethodsIgnored'
569 | to prevent of processing this type of requests
570 |
571 | ### MethodNotAllowedHttpException
572 |
573 | If you do not localize your post url and use a redirect middleware,
574 | then the post request gets redirected as a get request.
575 | If you have not defined such a get route, you will cause this exception.
576 |
577 | To localize your post url see the example in [POST is not working](#post-is-not-working).
578 |
579 | ### Validation message is only in default locale
580 |
581 | This also happens if you did not localize your post url.
582 | If you don't localize your post url, the default locale is set while validating,
583 | and when returning to `back()` it shows the validation message in default locale.
584 |
585 | To localize your post url see the example in [POST is not working](#post-is-not-working).
586 |
587 | ## Testing
588 |
589 | During the test setup, the called route is not yet known. This means no language can be set.
590 | When a request is made during a test, this results in a 404 - without the prefix set the localized route does not seem to exist.
591 |
592 | To fix this, you can use this function to manually set the language prefix:
593 | ```php
594 | // TestCase.php
595 | protected function refreshApplicationWithLocale($locale)
596 | {
597 | self::tearDown();
598 | putenv(LaravelLocalization::ENV_ROUTE_KEY . '=' . $locale);
599 | self::setUp();
600 | }
601 |
602 | protected function tearDown(): void
603 | {
604 | putenv(LaravelLocalization::ENV_ROUTE_KEY);
605 | parent::tearDown();
606 | }
607 |
608 | // YourTest.php
609 | public function testBasicTest()
610 | {
611 | $this->refreshApplicationWithLocale('en');
612 | // Testing code
613 | }
614 | ```
615 |
616 | ## Collaborators
617 | - [Adam Nielsen (iwasherefirst2)](https://github.com/iwasherefirst2)
618 |
619 | Ask [mcamara](https://github.com/mcamara) if you want to be one of them!
620 |
621 | ## Changelog
622 |
623 | View changelog here -> [changelog](CHANGELOG.md)
624 |
625 | ## License
626 |
627 | Laravel Localization is an open-sourced laravel package licensed under the MIT license
628 |
--------------------------------------------------------------------------------
/UPGRADING.md:
--------------------------------------------------------------------------------
1 | ## From v1 to v2
2 | This package now uses [dependency injection](https://laravel.com/docs/container#introduction) to retrieve dependencies from the container.
3 |
4 | This modification is a breaking change, especially if you had made extensions to the `__construct` method within the `Mcamara\LaravelLocalization\LaravelLocalization` class.
5 | You may now use depdency injection in your own implementation and forward the dependencies to the parent constructor.
6 | ```php
7 | use Mcamara\LaravelLocalization\LaravelLocalization;
8 | use Illuminate\Contracts\Config\Repository as ConfigRepository;
9 | use Illuminate\Contracts\Foundation\Application;
10 | use Illuminate\Contracts\Routing\UrlGenerator;
11 | use Illuminate\Contracts\Translation\Translator;
12 | use Illuminate\Http\Request;
13 | use Illuminate\Routing\Router;
14 |
15 | class MyLaravelLocalization extends LaravelLocalization
16 | {
17 | public function __construct(
18 | mixed $myCustomVariable,
19 | Application $app,
20 | ConfigRepository $configRepository,
21 | Translator $translator,
22 | Router $router,
23 | Request $request,
24 | UrlGenerator $url
25 | ) {
26 | parent::__construct($app, $configRepository, $translator, $router, $request, $url);
27 | }
28 | }
29 | ```
30 |
31 | If your previous approach involved overriding the `LaravelLocalization` singleton in the container and generating a new instance of your custom implementation, there's now a more straightforward method for binding. This will automatically inject the correct dependencies for you.
32 | ```diff
33 | use Mcamara\LaravelLocalization\LaravelLocalization;
34 |
35 | -$this->app->singleton(LaravelLocalization::class, function () {
36 | - return new MyLaravelLocalization();
37 | -});
38 | +$this->app->singleton(LaravelLocalization::class, MyLaravelLocalization::class);
39 | ```
40 |
41 | For more information, please see the following PR [#879](https://github.com/mcamara/laravel-localization/pull/879/files)
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mcamara/laravel-localization",
3 | "description": "Easy localization for Laravel",
4 | "keywords": [
5 | "localization",
6 | "laravel",
7 | "php"
8 | ],
9 | "homepage": "https://github.com/mcamara/laravel-localization",
10 | "license": "MIT",
11 | "authors": [
12 | {
13 | "name": "Marc Cámara",
14 | "email": "mcamara88@gmail.com",
15 | "role": "Developer"
16 | }
17 | ],
18 | "require": {
19 | "php": "^8.2",
20 | "laravel/framework": "^10.0|^11.0|^12.0"
21 | },
22 | "require-dev": {
23 | "orchestra/testbench-browser-kit": "^8.5|^9.0|^10.0",
24 | "phpunit/phpunit": "^10.1|^11.0"
25 | },
26 | "suggest": {
27 | "ext-intl": "*"
28 | },
29 | "scripts": {
30 | "test": "vendor/bin/phpunit"
31 | },
32 | "autoload": {
33 | "classmap": [],
34 | "psr-0": {
35 | "Mcamara\\LaravelLocalization": "src/"
36 | }
37 | },
38 | "autoload-dev": {
39 | "psr-4": {
40 | "Mcamara\\LaravelLocalization\\Tests\\": "tests"
41 | }
42 | },
43 | "extra": {
44 | "laravel": {
45 | "providers": [
46 | "Mcamara\\LaravelLocalization\\LaravelLocalizationServiceProvider"
47 | ],
48 | "aliases": {
49 | "LaravelLocalization": "Mcamara\\LaravelLocalization\\Facades\\LaravelLocalization"
50 | }
51 | }
52 | },
53 | "minimum-stability": "dev",
54 | "prefer-stable": true
55 | }
56 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
20 | ./src/Mcamara
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Commands/RouteTranslationsCacheCommand.php:
--------------------------------------------------------------------------------
1 | call('route:trans:clear');
31 |
32 | $this->cacheRoutesPerLocale();
33 |
34 | $this->info('Routes cached successfully for all locales!');
35 | }
36 |
37 | /**
38 | * Cache the routes separately for each locale.
39 | */
40 | protected function cacheRoutesPerLocale()
41 | {
42 | // Store the default routes cache,
43 | // this way the Application will detect that routes are cached.
44 | $allLocales = $this->getSupportedLocales();
45 |
46 | array_push($allLocales, null);
47 |
48 | foreach ($allLocales as $locale) {
49 |
50 | $routes = $this->getFreshApplicationRoutesForLocale($locale);
51 |
52 | if (count($routes) == 0) {
53 | $this->error("Your application doesn't have any routes.");
54 | return;
55 | }
56 |
57 | foreach ($routes as $route) {
58 | $route->prepareForSerialization();
59 | }
60 |
61 | $this->files->put(
62 | $this->makeLocaleRoutesPath($locale), $this->buildRouteCacheFile($routes)
63 | );
64 | }
65 | }
66 |
67 | /**
68 | * Boot a fresh copy of the application and get the routes for a given locale.
69 | *
70 | * @param string|null $locale
71 | * @return \Illuminate\Routing\RouteCollection
72 | */
73 | protected function getFreshApplicationRoutesForLocale($locale = null)
74 | {
75 | if ($locale === null) {
76 | return $this->getFreshApplicationRoutes();
77 | }
78 |
79 |
80 | $key = LaravelLocalization::ENV_ROUTE_KEY;
81 |
82 | putenv("{$key}={$locale}");
83 |
84 | $routes = $this->getFreshApplicationRoutes();
85 |
86 | putenv("{$key}=");
87 |
88 | return $routes;
89 | }
90 |
91 | /**
92 | * Build the route cache file.
93 | *
94 | * @param \Illuminate\Routing\RouteCollection $routes
95 | * @return string
96 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
97 | */
98 | protected function buildRouteCacheFile(RouteCollection $routes)
99 | {
100 | $stub = $this->files->get(
101 | realpath(
102 | __DIR__
103 | . DIRECTORY_SEPARATOR . '..'
104 | . DIRECTORY_SEPARATOR . '..'
105 | . DIRECTORY_SEPARATOR . '..'
106 | . DIRECTORY_SEPARATOR . 'stubs'
107 | . DIRECTORY_SEPARATOR . 'routes.stub'
108 | )
109 | );
110 |
111 | return str_replace(
112 | [
113 | '{{routes}}',
114 | '{{translatedRoutes}}',
115 | ],
116 | [
117 | base64_encode(serialize($routes)),
118 | $this->getLaravelLocalization()->getSerializedTranslatedRoutes(),
119 | ],
120 | $stub
121 | );
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Commands/RouteTranslationsClearCommand.php:
--------------------------------------------------------------------------------
1 | files = $files;
44 | }
45 |
46 | /**
47 | * Execute the console command.
48 | *
49 | * @return void
50 | */
51 | public function handle()
52 | {
53 | foreach ($this->getSupportedLocales() as $locale) {
54 |
55 | $path = $this->makeLocaleRoutesPath($locale);
56 |
57 | if ($this->files->exists($path)) {
58 | $this->files->delete($path);
59 | }
60 | }
61 |
62 | $path = $this->laravel->getCachedRoutesPath();
63 |
64 | if ($this->files->exists($path)) {
65 | $this->files->delete($path);
66 | }
67 |
68 | $this->info('Route caches for locales cleared!');
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Commands/RouteTranslationsListCommand.php:
--------------------------------------------------------------------------------
1 | argument('locale');
32 |
33 | if ( ! $this->isSupportedLocale($locale)) {
34 | $this->error("Unsupported locale: '{$locale}'.");
35 | return;
36 | }
37 |
38 | $this->loadFreshApplicationRoutes($locale);
39 |
40 | parent::handle();
41 | }
42 |
43 | /**
44 | * Boot a fresh copy of the application and replace the router/routes.
45 | *
46 | * @param string $locale
47 | * @return void
48 | */
49 | protected function loadFreshApplicationRoutes($locale)
50 | {
51 | $app = require $this->getBootstrapPath() . '/app.php';
52 |
53 | $key = LaravelLocalization::ENV_ROUTE_KEY;
54 |
55 | putenv("{$key}={$locale}");
56 |
57 | $app->make(Kernel::class)->bootstrap();
58 |
59 | putenv("{$key}=");
60 |
61 | $this->router = $app['router'];
62 | }
63 |
64 | /**
65 | * Get the console command arguments.
66 | *
67 | * @return array
68 | */
69 | protected function getArguments()
70 | {
71 | return [
72 | ['locale', InputArgument::REQUIRED, 'The locale to list routes for.'],
73 | ];
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Exceptions/SupportedLocalesNotDefined.php:
--------------------------------------------------------------------------------
1 | app = app();
55 |
56 | $this->configRepository = $this->app['config'];
57 |
58 | $this->defaultLocale = $defaultLocale;
59 |
60 | if (class_exists('Locale')) {
61 | $this->use_intl = true;
62 |
63 | foreach ($supportedLanguages as $key => $supportedLanguage) {
64 | if ( ! isset($supportedLanguage['lang'])) {
65 | $supportedLanguage['lang'] = Locale::canonicalize($key);
66 | } else {
67 | $supportedLanguage['lang'] = Locale::canonicalize($supportedLanguage['lang']);
68 | }
69 | if (isset($supportedLanguage['regional'])) {
70 | $supportedLanguage['regional'] = Locale::canonicalize($supportedLanguage['regional']);
71 | }
72 |
73 | $this->supportedLanguages[$key] = $supportedLanguage;
74 | }
75 | } else {
76 | $this->supportedLanguages = $supportedLanguages;
77 | }
78 |
79 | $this->request = $request;
80 | }
81 |
82 | /**
83 | * Negotiates language with the user's browser through the Accept-Language
84 | * HTTP header or the user's host address. Language codes are generally in
85 | * the form "ll" for a language spoken in only one country, or "ll-CC" for a
86 | * language spoken in a particular country. For example, U.S. English is
87 | * "en-US", while British English is "en-UK". Portuguese as spoken in
88 | * Portugal is "pt-PT", while Brazilian Portuguese is "pt-BR".
89 | *
90 | * This function is based on negotiateLanguage from Pear HTTP2
91 | * http://pear.php.net/package/HTTP2/
92 | *
93 | * Quality factors in the Accept-Language: header are supported, e.g.:
94 | * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
95 | *
96 | * @return string The negotiated language result or app.locale.
97 | */
98 | public function negotiateLanguage()
99 | {
100 | $matches = $this->getMatchesFromAcceptedLanguages();
101 | foreach ($matches as $key => $q) {
102 |
103 | $key = ($this->configRepository->get('laravellocalization.localesMapping')[$key]) ?? $key;
104 |
105 | if (!empty($this->supportedLanguages[$key])) {
106 | return $key;
107 | }
108 |
109 | if ($this->use_intl) {
110 | $key = Locale::canonicalize($key);
111 | }
112 |
113 | // Search for acceptable locale by 'regional' => 'af_ZA' or 'lang' => 'af-ZA' match.
114 | foreach ( $this->supportedLanguages as $key_supported => $locale ) {
115 | if ( (isset($locale['regional']) && $locale['regional'] == $key) || (isset($locale['lang']) && $locale['lang'] == $key) ) {
116 | return $key_supported;
117 | }
118 | }
119 | }
120 | // If any (i.e. "*") is acceptable, return the first supported format
121 | if (isset($matches['*'])) {
122 | reset($this->supportedLanguages);
123 |
124 | return key($this->supportedLanguages);
125 | }
126 |
127 | if ($this->use_intl && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
128 | $http_accept_language = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
129 |
130 | if (!empty($this->supportedLanguages[$http_accept_language])) {
131 | return $http_accept_language;
132 | }
133 | }
134 |
135 | if ($this->request->server('REMOTE_HOST')) {
136 | $remote_host = explode('.', $this->request->server('REMOTE_HOST'));
137 | $lang = strtolower(end($remote_host));
138 |
139 | if (!empty($this->supportedLanguages[$lang])) {
140 | return $lang;
141 | }
142 | }
143 |
144 | return $this->defaultLocale;
145 | }
146 |
147 | /**
148 | * Return all the accepted languages from the browser.
149 | *
150 | * @return array Matches from the header field Accept-Languages
151 | */
152 | private function getMatchesFromAcceptedLanguages()
153 | {
154 | $matches = [];
155 |
156 | if ($acceptLanguages = $this->request->header('Accept-Language')) {
157 | $acceptLanguages = explode(',', $acceptLanguages);
158 |
159 | $generic_matches = [];
160 | foreach ($acceptLanguages as $option) {
161 | $option = array_map('trim', explode(';', $option));
162 | $l = $option[0];
163 | if (isset($option[1])) {
164 | $q = (float) str_replace('q=', '', $option[1]);
165 | } else {
166 | $q = null;
167 | // Assign default low weight for generic values
168 | if ($l == '*/*') {
169 | $q = 0.01;
170 | } elseif (substr($l, -1) == '*') {
171 | $q = 0.02;
172 | }
173 | }
174 | // Unweighted values, get high weight by their position in the
175 | // list
176 | $q = $q ?? 1000 - \count($matches);
177 | $matches[$l] = $q;
178 |
179 | //If for some reason the Accept-Language header only sends language with country
180 | //we should make the language without country an accepted option, with a value
181 | //less than it's parent.
182 | $l_ops = explode('-', $l);
183 | array_pop($l_ops);
184 | while (!empty($l_ops)) {
185 | //The new generic option needs to be slightly less important than it's base
186 | $q -= 0.001;
187 | $op = implode('-', $l_ops);
188 | if (empty($generic_matches[$op]) || $generic_matches[$op] > $q) {
189 | $generic_matches[$op] = $q;
190 | }
191 | array_pop($l_ops);
192 | }
193 | }
194 | $matches = array_merge($generic_matches, $matches);
195 |
196 | arsort($matches, SORT_NUMERIC);
197 | }
198 |
199 | return $matches;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/LaravelLocalization.php:
--------------------------------------------------------------------------------
1 | app = $app;
136 | $this->configRepository = $configRepository;
137 | $this->translator = $translator;
138 | $this->router = $router;
139 | $this->request = $request;
140 | $this->url = $url;
141 |
142 | // set default locale
143 | $this->defaultLocale = $this->configRepository->get('app.locale');
144 | $supportedLocales = $this->getSupportedLocales();
145 |
146 | if (empty($supportedLocales[$this->defaultLocale])) {
147 | throw new UnsupportedLocaleException('Laravel default locale is not in the supportedLocales array.');
148 | }
149 | }
150 |
151 | /**
152 | * Set and return current locale.
153 | *
154 | * @param string $locale Locale to set the App to (optional)
155 | *
156 | * @return string Returns locale (if route has any) or null (if route does not have a locale)
157 | */
158 | public function setLocale($locale = null)
159 | {
160 | if (empty($locale) || !\is_string($locale)) {
161 | // If the locale has not been passed through the function
162 | // it tries to get it from the first segment of the url
163 | $locale = $this->request->segment(1);
164 |
165 | // If the locale is determined by env, use that
166 | // Note that this is how per-locale route caching is performed.
167 | if ( ! $locale) {
168 | $locale = $this->getForcedLocale();
169 | }
170 | }
171 |
172 | $locale = $this->getInversedLocaleFromMapping($locale);
173 |
174 | if (!empty($this->supportedLocales[$locale])) {
175 | $this->currentLocale = $locale;
176 | } else {
177 | // if the first segment/locale passed is not valid
178 | // the system would ask which locale have to take
179 | // it could be taken by the browser
180 | // depending on your configuration
181 |
182 | $locale = null;
183 |
184 | // if we reached this point and hideDefaultLocaleInURL is true
185 | // we have to assume we are routing to a defaultLocale route.
186 | if ($this->hideDefaultLocaleInURL()) {
187 | $this->currentLocale = $this->defaultLocale;
188 | }
189 | // but if hideDefaultLocaleInURL is false, we have
190 | // to retrieve it from the browser...
191 | else {
192 | $this->currentLocale = $this->getCurrentLocale();
193 | }
194 | }
195 |
196 | $this->app->setLocale($this->currentLocale);
197 | $this->translator->setLocale($this->currentLocale);
198 |
199 | // Regional locale such as de_DE, so formatLocalized works in Carbon
200 | $regional = $this->getCurrentLocaleRegional();
201 | $suffix = $this->configRepository->get('laravellocalization.utf8suffix');
202 | if ($regional) {
203 | setlocale(LC_TIME, $regional . $suffix);
204 | setlocale(LC_MONETARY, $regional . $suffix);
205 | }
206 |
207 | return $this->getLocaleFromMapping($locale);
208 | }
209 |
210 | /**
211 | * Check if $locale is default locale and supposed to be hidden in url
212 | *
213 | * @param string $locale Locale to be checked
214 | *
215 | * @return boolean Returns true if above requirement are met, otherwise false
216 | */
217 |
218 | public function isHiddenDefault($locale)
219 | {
220 | return ($this->getDefaultLocale() === $locale && $this->hideDefaultLocaleInURL());
221 | }
222 |
223 | /**
224 | * Set and return supported locales.
225 | *
226 | * @param array $locales Locales that the App supports
227 | */
228 | public function setSupportedLocales($locales)
229 | {
230 | $this->supportedLocales = $locales;
231 | }
232 |
233 | /**
234 | * Returns an URL adapted to $locale or current locale.
235 | *
236 | * @param string $url URL to adapt. If not passed, the current url would be taken.
237 | * @param string|bool $locale Locale to adapt, false to remove locale
238 | *
239 | * @throws UnsupportedLocaleException
240 | *
241 | * @return string URL translated
242 | */
243 | public function localizeURL($url = null, $locale = null)
244 | {
245 | return $this->getLocalizedURL($locale, $url);
246 | }
247 |
248 | /**
249 | * Returns an URL adapted to $locale.
250 | *
251 | *
252 | * @param string|bool $locale Locale to adapt, false to remove locale
253 | * @param string|false $url URL to adapt in the current language. If not passed, the current url would be taken.
254 | * @param array $attributes Attributes to add to the route, if empty, the system would try to extract them from the url.
255 | * @param bool $forceDefaultLocation Force to show default location even hideDefaultLocaleInURL set as TRUE
256 | *
257 | * @throws SupportedLocalesNotDefined
258 | * @throws UnsupportedLocaleException
259 | *
260 | * @return string|false URL translated, False if url does not exist
261 | */
262 | public function getLocalizedURL($locale = null, $url = null, $attributes = [], $forceDefaultLocation = false)
263 | {
264 | if ($locale === null) {
265 | $locale = $this->getCurrentLocale();
266 | }
267 |
268 | if (!$this->checkLocaleInSupportedLocales($locale)) {
269 | throw new UnsupportedLocaleException('Locale \''.$locale.'\' is not in the list of supported locales.');
270 | }
271 |
272 | if (empty($attributes)) {
273 | $attributes = $this->extractAttributes($url, $locale);
274 | }
275 |
276 | if (empty($url)) {
277 | $url = $this->request->fullUrl();
278 | $urlQuery = parse_url($url, PHP_URL_QUERY);
279 | $urlQuery = $urlQuery ? '?'.$urlQuery : '';
280 |
281 | if (!empty($this->routeName)) {
282 | return $this->getURLFromRouteNameTranslated($locale, $this->routeName, $attributes, $forceDefaultLocation) . $urlQuery;
283 | }
284 | } else {
285 | $urlQuery = parse_url($url, PHP_URL_QUERY);
286 | $urlQuery = $urlQuery ? '?'.$urlQuery : '';
287 |
288 | $url = $this->url->to($url);
289 | }
290 |
291 | $url = preg_replace('/'. preg_quote($urlQuery, '/') . '$/', '', $url);
292 |
293 | if ($locale && $translatedRoute = $this->findTranslatedRouteByUrl($url, $attributes, $this->currentLocale)) {
294 | return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes, $forceDefaultLocation).$urlQuery;
295 | }
296 |
297 | $base_path = $this->request->getBaseUrl();
298 | $parsed_url = parse_url($url);
299 | $url_locale = $this->getDefaultLocale();
300 |
301 | if (!$parsed_url || empty($parsed_url['path'])) {
302 | $path = $parsed_url['path'] = '';
303 | } else {
304 | $parsed_url['path'] = str_replace($base_path, '', '/'.ltrim($parsed_url['path'], '/'));
305 | $path = $parsed_url['path'];
306 | foreach ($this->getSupportedLocales() as $localeCode => $lang) {
307 | $localeCode = $this->getLocaleFromMapping($localeCode);
308 |
309 | $parsed_url['path'] = preg_replace('%^/?'.$localeCode.'/%', '$1', $parsed_url['path']);
310 | if ($parsed_url['path'] !== $path) {
311 | $url_locale = $localeCode;
312 | break;
313 | }
314 |
315 | $parsed_url['path'] = preg_replace('%^/?'.$localeCode.'$%', '$1', $parsed_url['path']);
316 | if ($parsed_url['path'] !== $path) {
317 | $url_locale = $localeCode;
318 | break;
319 | }
320 | }
321 | }
322 |
323 | $parsed_url['path'] = ltrim($parsed_url['path'], '/');
324 |
325 | if ($translatedRoute = $this->findTranslatedRouteByPath($parsed_url['path'], $url_locale)) {
326 | return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes, $forceDefaultLocation).$urlQuery;
327 | }
328 |
329 | $locale = $this->getLocaleFromMapping($locale);
330 |
331 | if (!empty($locale)) {
332 | if ($forceDefaultLocation || $locale != $this->getDefaultLocale() || !$this->hideDefaultLocaleInURL()) {
333 | $parsed_url['path'] = $locale.'/'.ltrim($parsed_url['path'], '/');
334 | }
335 | }
336 | $parsed_url['path'] = ltrim(ltrim($base_path, '/').'/'.$parsed_url['path'], '/');
337 |
338 | //Make sure that the pass path is returned with a leading slash only if it come in with one.
339 | if (Str::startsWith($path, '/') === true) {
340 | $parsed_url['path'] = '/'.$parsed_url['path'];
341 | }
342 | $parsed_url['path'] = rtrim($parsed_url['path'], '/');
343 |
344 | $url = $this->unparseUrl($parsed_url);
345 |
346 | if ($this->checkUrl($url)) {
347 | return $url.$urlQuery;
348 | }
349 |
350 | return $this->createUrlFromUri($url).$urlQuery;
351 | }
352 |
353 | /**
354 | * Returns an URL adapted to the route name and the locale given.
355 | *
356 | *
357 | * @param string|bool $locale Locale to adapt
358 | * @param string $transKeyName Translation key name of the url to adapt
359 | * @param array $attributes Attributes for the route (only needed if transKeyName needs them)
360 | * @param bool $forceDefaultLocation Force to show default location even hideDefaultLocaleInURL set as TRUE
361 | *
362 | * @throws SupportedLocalesNotDefined
363 | * @throws UnsupportedLocaleException
364 | *
365 | * @return string|false URL translated
366 | */
367 | public function getURLFromRouteNameTranslated($locale, $transKeyName, $attributes = [], $forceDefaultLocation = false)
368 | {
369 | if (!$this->checkLocaleInSupportedLocales($locale)) {
370 | throw new UnsupportedLocaleException('Locale \''.$locale.'\' is not in the list of supported locales.');
371 | }
372 |
373 | if (!\is_string($locale)) {
374 | $locale = $this->getDefaultLocale();
375 | }
376 |
377 | $route = '';
378 |
379 | if ($forceDefaultLocation || !($locale === $this->defaultLocale && $this->hideDefaultLocaleInURL())) {
380 | $route = '/'.$locale;
381 | }
382 | if (\is_string($locale) && $this->translator->has($transKeyName, $locale)) {
383 | $translation = $this->translator->get($transKeyName, [], $locale);
384 | $route .= '/'.$translation;
385 |
386 | $route = $this->substituteAttributesInRoute($attributes, $route, $locale);
387 | }
388 |
389 | if (empty($route)) {
390 | // This locale does not have any key for this route name
391 | return false;
392 | }
393 |
394 | return rtrim($this->createUrlFromUri($route), '/');
395 | }
396 |
397 | /**
398 | * It returns an URL without locale (if it has it)
399 | * Convenience function wrapping getLocalizedURL(false).
400 | *
401 | * @param string|false $url URL to clean, if false, current url would be taken
402 | *
403 | * @return string URL with no locale in path
404 | */
405 | public function getNonLocalizedURL($url = null)
406 | {
407 | return $this->getLocalizedURL(false, $url);
408 | }
409 |
410 | /**
411 | * Returns default locale.
412 | *
413 | * @return string
414 | */
415 | public function getDefaultLocale()
416 | {
417 | return $this->defaultLocale;
418 | }
419 |
420 | /**
421 | * Return locales mapping.
422 | *
423 | * @return array
424 | */
425 | public function getLocalesMapping()
426 | {
427 | if (empty($this->localesMapping)) {
428 | $this->localesMapping = $this->configRepository->get('laravellocalization.localesMapping');
429 | }
430 |
431 | return $this->localesMapping;
432 | }
433 |
434 | /**
435 | * Returns a locale from the mapping.
436 | *
437 | * @param string|null $locale
438 | *
439 | * @return string|null
440 | */
441 | public function getLocaleFromMapping($locale)
442 | {
443 | return $this->getLocalesMapping()[$locale] ?? $locale;
444 | }
445 |
446 | /**
447 | * Returns inversed locale from the mapping.
448 | *
449 | * @param string|null $locale
450 | *
451 | * @return string|null
452 | */
453 | public function getInversedLocaleFromMapping($locale)
454 | {
455 | return \array_flip($this->getLocalesMapping())[$locale] ?? $locale;
456 | }
457 |
458 | /**
459 | * Return an array of all supported Locales.
460 | *
461 | * @throws SupportedLocalesNotDefined
462 | *
463 | * @return array
464 | */
465 | public function getSupportedLocales()
466 | {
467 | if (!empty($this->supportedLocales)) {
468 | return $this->supportedLocales;
469 | }
470 |
471 | $locales = $this->configRepository->get('laravellocalization.supportedLocales');
472 |
473 | if (empty($locales) || !\is_array($locales)) {
474 | throw new SupportedLocalesNotDefined();
475 | }
476 |
477 | $this->supportedLocales = $locales;
478 |
479 | return $locales;
480 | }
481 |
482 | /**
483 | * Return an array of all supported Locales but in the order the user
484 | * has specified in the config file. Useful for the language selector.
485 | *
486 | * @return array
487 | */
488 | public function getLocalesOrder()
489 | {
490 | $locales = $this->getSupportedLocales();
491 |
492 | $order = $this->configRepository->get('laravellocalization.localesOrder');
493 |
494 | uksort($locales, function ($a, $b) use ($order) {
495 | $pos_a = array_search($a, $order);
496 | $pos_b = array_search($b, $order);
497 | return $pos_a - $pos_b;
498 | });
499 |
500 | return $locales;
501 | }
502 |
503 | /**
504 | * Returns current locale name.
505 | *
506 | * @return string current locale name
507 | */
508 | public function getCurrentLocaleName()
509 | {
510 | return $this->supportedLocales[$this->getCurrentLocale()]['name'];
511 | }
512 |
513 | /**
514 | * Returns current locale native name.
515 | *
516 | * @return string current locale native name
517 | */
518 | public function getCurrentLocaleNative()
519 | {
520 | return $this->supportedLocales[$this->getCurrentLocale()]['native'];
521 | }
522 |
523 | /**
524 | * Returns current locale direction.
525 | *
526 | * @return string current locale direction
527 | */
528 | public function getCurrentLocaleDirection()
529 | {
530 | if (!empty($this->supportedLocales[$this->getCurrentLocale()]['dir'])) {
531 | return $this->supportedLocales[$this->getCurrentLocale()]['dir'];
532 | }
533 |
534 | switch ($this->getCurrentLocaleScript()) {
535 | // Other (historic) RTL scripts exist, but this list contains the only ones in current use.
536 | case 'Arab':
537 | case 'Hebr':
538 | case 'Mong':
539 | case 'Tfng':
540 | case 'Thaa':
541 | return 'rtl';
542 | default:
543 | return 'ltr';
544 | }
545 | }
546 |
547 | /**
548 | * Returns current locale script.
549 | *
550 | * @return string current locale script
551 | */
552 | public function getCurrentLocaleScript()
553 | {
554 | return $this->supportedLocales[$this->getCurrentLocale()]['script'];
555 | }
556 |
557 | /**
558 | * Returns current language's native reading.
559 | *
560 | * @return string current language's native reading
561 | */
562 | public function getCurrentLocaleNativeReading()
563 | {
564 | return $this->supportedLocales[$this->getCurrentLocale()]['native'];
565 | }
566 |
567 | /**
568 | * Returns current language.
569 | *
570 | * @return string current language
571 | */
572 | public function getCurrentLocale()
573 | {
574 | if ($this->currentLocale) {
575 | return $this->currentLocale;
576 | }
577 |
578 | if ($this->useAcceptLanguageHeader() && !$this->app->runningInConsole()) {
579 | $negotiator = new LanguageNegotiator($this->defaultLocale, $this->getSupportedLocales(), $this->request);
580 |
581 | return $negotiator->negotiateLanguage();
582 | }
583 |
584 | // or get application default language
585 | return $this->configRepository->get('app.locale');
586 | }
587 |
588 | /**
589 | * Returns current regional.
590 | *
591 | * @return string current regional
592 | */
593 | public function getCurrentLocaleRegional()
594 | {
595 | // need to check if it exists, since 'regional' has been added
596 | // after version 1.0.11 and existing users will not have it
597 | if (isset($this->supportedLocales[$this->getCurrentLocale()]['regional'])) {
598 | return $this->supportedLocales[$this->getCurrentLocale()]['regional'];
599 | } else {
600 | return;
601 | }
602 | }
603 |
604 | /**
605 | * Returns supported languages language key.
606 | *
607 | * @return array keys of supported languages
608 | */
609 | public function getSupportedLanguagesKeys()
610 | {
611 | return array_keys($this->supportedLocales);
612 | }
613 |
614 | /**
615 | * Check if Locale exists on the supported locales array.
616 | *
617 | * @param string|bool $locale string|bool Locale to be checked
618 | *
619 | * @throws SupportedLocalesNotDefined
620 | *
621 | * @return bool is the locale supported?
622 | */
623 | public function checkLocaleInSupportedLocales($locale)
624 | {
625 | $inversedLocale = $this->getInversedLocaleFromMapping($locale);
626 | $locales = $this->getSupportedLocales();
627 | if ($locale !== false && empty($locales[$locale]) && empty($locales[$inversedLocale])) {
628 | return false;
629 | }
630 |
631 | return true;
632 | }
633 |
634 | /**
635 | * Change route attributes for the ones in the $attributes array.
636 | *
637 | * @param $attributes array Array of attributes
638 | * @param string $route string route to substitute
639 | *
640 | * @return string route with attributes changed
641 | */
642 | protected function substituteAttributesInRoute($attributes, $route, $locale = null)
643 | {
644 | foreach ($attributes as $key => $value) {
645 | if ($value instanceOf Interfaces\LocalizedUrlRoutable) {
646 | $value = $value->getLocalizedRouteKey($locale);
647 | }
648 | elseif ($value instanceOf UrlRoutable) {
649 | $value = $value->getRouteKey();
650 | }
651 | $route = str_replace(array('{'.$key.'}', '{'.$key.'?}'), $value, $route);
652 | }
653 |
654 | // delete empty optional arguments that are not in the $attributes array
655 | $route = preg_replace('/\/{[^)]+\?}/', '', $route);
656 |
657 | return $route;
658 | }
659 |
660 | /**
661 | * Returns translated routes.
662 | *
663 | * @return array translated routes
664 | */
665 | protected function getTranslatedRoutes()
666 | {
667 | return $this->translatedRoutes;
668 | }
669 |
670 | /**
671 | * Set current route name.
672 | *
673 | * @param string $routeName current route name
674 | */
675 | public function setRouteName($routeName)
676 | {
677 | $this->routeName = $routeName;
678 | }
679 |
680 | /**
681 | * Translate routes and save them to the translated routes array (used in the localize route filter).
682 | *
683 | * @param string $routeName Key of the translated string
684 | *
685 | * @return string Translated string
686 | */
687 | public function transRoute($routeName)
688 | {
689 | if (!\in_array($routeName, $this->translatedRoutes)) {
690 | $this->translatedRoutes[] = $routeName;
691 | }
692 |
693 | return $this->translator->get($routeName);
694 | }
695 |
696 | /**
697 | * Returns the translation key for a given path.
698 | *
699 | * @param string $path Path to get the key translated
700 | *
701 | * @return string|false Key for translation, false if not exist
702 | */
703 | public function getRouteNameFromAPath($path)
704 | {
705 | $attributes = $this->extractAttributes($path);
706 |
707 | $path = parse_url($path)['path'];
708 | $path = trim(str_replace('/'.$this->currentLocale.'/', '', $path), "/");
709 |
710 | foreach ($this->translatedRoutes as $route) {
711 | if (trim($this->substituteAttributesInRoute($attributes, $this->translator->get($route), $this->currentLocale), '/') === $path) {
712 | return $route;
713 | }
714 | }
715 |
716 | return false;
717 | }
718 |
719 | /**
720 | * Returns the translated route for the path and the url given.
721 | *
722 | * @param string $path Path to check if it is a translated route
723 | * @param string $url_locale Language to check if the path exists
724 | *
725 | * @return string|false Key for translation, false if not exist
726 | */
727 | protected function findTranslatedRouteByPath($path, $url_locale)
728 | {
729 | // check if this url is a translated url
730 | foreach ($this->translatedRoutes as $translatedRoute) {
731 | if ($this->translator->get($translatedRoute, [], $url_locale) == rawurldecode($path)) {
732 | return $translatedRoute;
733 | }
734 | }
735 |
736 | return false;
737 | }
738 |
739 | /**
740 | * Returns the translated route for an url and the attributes given and a locale.
741 | *
742 | *
743 | * @param string|false|null $url Url to check if it is a translated route
744 | * @param array $attributes Attributes to check if the url exists in the translated routes array
745 | * @param string $locale Language to check if the url exists
746 | *
747 | * @throws SupportedLocalesNotDefined
748 | * @throws UnsupportedLocaleException
749 | *
750 | * @return string|false Key for translation, false if not exist
751 | */
752 | protected function findTranslatedRouteByUrl($url, $attributes, $locale)
753 | {
754 | if (empty($url)) {
755 | return false;
756 | }
757 |
758 | if (isset($this->cachedTranslatedRoutesByUrl[$locale][$url])) {
759 | return $this->cachedTranslatedRoutesByUrl[$locale][$url];
760 | }
761 |
762 | // check if this url is a translated url
763 | foreach ($this->translatedRoutes as $translatedRoute) {
764 | $routeName = $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes);
765 |
766 | // We can ignore extra url parts and compare only their url_path (ignore arguments that are not attributes)
767 | if (parse_url($this->getNonLocalizedURL($routeName), PHP_URL_PATH) == parse_url($this->getNonLocalizedURL(urldecode($url)), PHP_URL_PATH)) {
768 | $this->cachedTranslatedRoutesByUrl[$locale][$url] = $translatedRoute;
769 |
770 | return $translatedRoute;
771 | }
772 | }
773 |
774 | return false;
775 | }
776 |
777 | /**
778 | * Returns true if the string given is a valid url.
779 | *
780 | * @param string $url String to check if it is a valid url
781 | *
782 | * @return bool Is the string given a valid url?
783 | */
784 | protected function checkUrl($url)
785 | {
786 | return filter_var($url, FILTER_VALIDATE_URL);
787 | }
788 |
789 | /**
790 | * Returns the config repository for this instance.
791 | *
792 | * @return \Illuminate\Contracts\Config\Repository Configuration repository
793 | */
794 | public function getConfigRepository()
795 | {
796 | return $this->configRepository;
797 | }
798 |
799 | /**
800 | * Returns the translation key for a given path.
801 | *
802 | * @return bool Returns value of useAcceptLanguageHeader in config.
803 | */
804 | protected function useAcceptLanguageHeader()
805 | {
806 | return $this->configRepository->get('laravellocalization.useAcceptLanguageHeader');
807 | }
808 |
809 | public function hideUrlAndAcceptHeader()
810 | {
811 | return $this->hideDefaultLocaleInURL() && $this->useAcceptLanguageHeader();
812 | }
813 |
814 | /**
815 | * Returns the translation key for a given path.
816 | *
817 | * @return bool Returns value of hideDefaultLocaleInURL in config.
818 | */
819 | public function hideDefaultLocaleInURL()
820 | {
821 | return $this->configRepository->get('laravellocalization.hideDefaultLocaleInURL');
822 | }
823 |
824 | /**
825 | * Create an url from the uri.
826 | *
827 | * @param string $uri Uri
828 | *
829 | * @return string Url for the given uri
830 | */
831 | public function createUrlFromUri($uri)
832 | {
833 | $uri = ltrim($uri, '/');
834 |
835 | if (empty($this->baseUrl)) {
836 | return app('url')->to($uri);
837 | }
838 |
839 | return $this->baseUrl.$uri;
840 | }
841 |
842 | /**
843 | * Sets the base url for the site.
844 | *
845 | * @param string $url Base url for the site
846 | */
847 | public function setBaseUrl($url)
848 | {
849 | if (substr($url, -1) != '/') {
850 | $url .= '/';
851 | }
852 |
853 | $this->baseUrl = $url;
854 | }
855 |
856 | /**
857 | * Returns serialized translated routes for caching purposes.
858 | *
859 | * @return string
860 | */
861 | public function getSerializedTranslatedRoutes()
862 | {
863 | return base64_encode(serialize($this->translatedRoutes));
864 | }
865 |
866 | /**
867 | * Sets the translated routes list.
868 | * Only useful from a cached routes context.
869 | *
870 | * @param string $serializedRoutes
871 | */
872 | public function setSerializedTranslatedRoutes($serializedRoutes)
873 | {
874 | if ( ! $serializedRoutes) {
875 | return;
876 | }
877 |
878 | $this->translatedRoutes = unserialize(base64_decode($serializedRoutes));
879 | }
880 |
881 | /**
882 | * Extract attributes for current url.
883 | *
884 | * @param bool|false|null|string $url to extract attributes, if not present, the system will look for attributes in the current call
885 | * @param string $locale
886 | *
887 | * @return array Array with attributes
888 | */
889 | protected function extractAttributes($url = false, $locale = '')
890 | {
891 | if (!empty($url)) {
892 | $attributes = [];
893 | $parse = parse_url($url);
894 | if (isset($parse['path'])) {
895 | $parse['path'] = trim(str_replace('/'.$this->currentLocale.'/', '', $parse['path']), "/");
896 | $url = explode('/', trim($parse['path'], '/'));
897 | } else {
898 | $url = [];
899 | }
900 |
901 | foreach ($this->router->getRoutes() as $route) {
902 | $attributes = [];
903 | $path = method_exists($route, 'uri') ? $route->uri() : $route->getUri();
904 |
905 | if (!preg_match("/{[\w]+\??}/", $path)) {
906 | continue;
907 | }
908 |
909 | $path = explode('/', $path);
910 | $i = 0;
911 |
912 | // The system's route can't be smaller
913 | // only the $url can be missing segments (optional parameters)
914 | // We can assume it's the wrong route
915 | if (count($path) < count($url)) {
916 | continue;
917 | }
918 |
919 | $match = true;
920 | foreach ($path as $j => $segment) {
921 | if (isset($url[$i])) {
922 | if ($segment === $url[$i]) {
923 | $i++;
924 | continue;
925 | } elseif (preg_match("/{[\w]+}/", $segment)) {
926 | // must-have parameters
927 | $attribute_name = preg_replace(['/}/', '/{/', "/\?/"], '', $segment);
928 | $attributes[$attribute_name] = $url[$i];
929 | $i++;
930 | continue;
931 | } elseif (preg_match("/{[\w]+\?}/", $segment)) {
932 | // optional parameters
933 | if (!isset($path[$j + 1]) || $path[$j + 1] !== $url[$i]) {
934 | // optional parameter taken
935 | $attribute_name = preg_replace(['/}/', '/{/', "/\?/"], '', $segment);
936 | $attributes[$attribute_name] = $url[$i];
937 | $i++;
938 | continue;
939 | } else {
940 | $match = false;
941 | break;
942 | }
943 | } else {
944 | // As soon as one segment doesn't match, then we have the wrong route
945 | $match = false;
946 | break;
947 | }
948 | } elseif (preg_match("/{[\w]+\?}/", $segment)) {
949 | $attribute_name = preg_replace(['/}/', '/{/', "/\?/"], '', $segment);
950 | $attributes[$attribute_name] = null;
951 | $i++;
952 | } else {
953 | // no optional parameters but no more $url given
954 | // this route does not match the url
955 | $match = false;
956 | break;
957 | }
958 | }
959 |
960 | if (isset($url[$i + 1])) {
961 | $match = false;
962 | }
963 |
964 | if ($match) {
965 | return $attributes;
966 | }
967 | }
968 | } else {
969 | if (!$this->router->current()) {
970 | return [];
971 | }
972 |
973 | $attributes = $this->normalizeAttributes($this->router->current()->parameters());
974 | $response = event('routes.translation', [$locale, $attributes]);
975 |
976 | if (!empty($response)) {
977 | $response = array_shift($response);
978 | }
979 |
980 | if (\is_array($response)) {
981 | $attributes = array_merge($attributes, $response);
982 | }
983 | }
984 |
985 | return $attributes;
986 | }
987 |
988 | /**
989 | * Build URL using array data from parse_url.
990 | *
991 | * @param array|false $parsed_url Array of data from parse_url function
992 | *
993 | * @return string Returns URL as string.
994 | */
995 | protected function unparseUrl($parsed_url)
996 | {
997 | if (empty($parsed_url)) {
998 | return '';
999 | }
1000 |
1001 | $url = '';
1002 | $url .= isset($parsed_url['scheme']) ? $parsed_url['scheme'].'://' : '';
1003 | $url .= $parsed_url['host'] ?? '';
1004 | $url .= isset($parsed_url['port']) ? ':'.$parsed_url['port'] : '';
1005 | $user = $parsed_url['user'] ?? '';
1006 | $pass = isset($parsed_url['pass']) ? ':'.$parsed_url['pass'] : '';
1007 | $url .= $user.(($user || $pass) ? "$pass@" : '');
1008 |
1009 | if (!empty($url)) {
1010 | $url .= isset($parsed_url['path']) ? '/'.ltrim($parsed_url['path'], '/') : '';
1011 | } else {
1012 | $url .= $parsed_url['path'] ?? '';
1013 | }
1014 |
1015 | $url .= isset($parsed_url['query']) ? '?'.$parsed_url['query'] : '';
1016 | $url .= isset($parsed_url['fragment']) ? '#'.$parsed_url['fragment'] : '';
1017 |
1018 | return $url;
1019 | }
1020 |
1021 | /**
1022 | * Normalize attributes gotten from request parameters.
1023 | *
1024 | * @param array $attributes The attributes
1025 | * @return array The normalized attributes
1026 | */
1027 | protected function normalizeAttributes($attributes)
1028 | {
1029 | if (array_key_exists('data', $attributes) && \is_array($attributes['data']) && ! \count($attributes['data'])) {
1030 | $attributes['data'] = null;
1031 | return $attributes;
1032 | }
1033 | return $attributes;
1034 | }
1035 |
1036 | /**
1037 | * Returns the forced environment set route locale.
1038 | *
1039 | * @return string|null
1040 | */
1041 | protected function getForcedLocale()
1042 | {
1043 | if (version_compare($this->app->version(), '6') >= 0) {
1044 | return Env::get(static::ENV_ROUTE_KEY, function () {
1045 | $value = getenv(static::ENV_ROUTE_KEY);
1046 |
1047 | if ($value !== false) {
1048 | return $value;
1049 | }
1050 | });
1051 | } else {
1052 | return env(static::ENV_ROUTE_KEY, function () {
1053 | $value = getenv(static::ENV_ROUTE_KEY);
1054 |
1055 | if ($value !== false) {
1056 | return $value;
1057 | }
1058 | });
1059 | }
1060 | }
1061 | }
1062 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/LaravelLocalizationServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
17 | __DIR__.'/../../config/config.php' => config_path('laravellocalization.php'),
18 | ], 'config');
19 | }
20 |
21 | /**
22 | * Get the services provided by the provider.
23 | *
24 | * @return array
25 | */
26 | public function provides()
27 | {
28 | return ['modules.handler', 'modules'];
29 | }
30 |
31 | /**
32 | * Register the service provider.
33 | *
34 | * @return void
35 | */
36 | public function register()
37 | {
38 | $packageConfigFile = __DIR__.'/../../config/config.php';
39 |
40 | $this->mergeConfigFrom(
41 | $packageConfigFile, 'laravellocalization'
42 | );
43 |
44 | $this->registerBindings();
45 |
46 | $this->registerCommands();
47 | }
48 |
49 | /**
50 | * Registers app bindings and aliases.
51 | */
52 | protected function registerBindings()
53 | {
54 | $this->app->singleton(LaravelLocalization::class);
55 |
56 | $this->app->alias(LaravelLocalization::class, 'laravellocalization');
57 | }
58 |
59 | /**
60 | * Registers route caching commands.
61 | */
62 | protected function registerCommands()
63 | {
64 | $this->app->singleton('laravellocalizationroutecache.cache', Commands\RouteTranslationsCacheCommand::class);
65 | $this->app->singleton('laravellocalizationroutecache.clear', Commands\RouteTranslationsClearCommand::class);
66 | $this->app->singleton('laravellocalizationroutecache.list', Commands\RouteTranslationsListCommand::class);
67 |
68 | $this->commands([
69 | 'laravellocalizationroutecache.cache',
70 | 'laravellocalizationroutecache.clear',
71 | 'laravellocalizationroutecache.list',
72 | ]);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationMiddlewareBase.php:
--------------------------------------------------------------------------------
1 | method(), config('laravellocalization.httpMethodsIgnored'))) {
24 | return true;
25 | }
26 | $this->except = $this->except ?? config('laravellocalization.urlsIgnored', []);
27 | foreach ($this->except as $except) {
28 | if ($except !== '/') {
29 | $except = trim($except, '/');
30 | }
31 |
32 | if ($request->is($except)) {
33 | return true;
34 | }
35 | }
36 |
37 | return false;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationRedirectFilter.php:
--------------------------------------------------------------------------------
1 | shouldIgnore($request)) {
22 | return $next($request);
23 | }
24 |
25 | $params = explode('/', $request->getPathInfo());
26 |
27 | // Dump the first element (empty string) as getPathInfo() always returns a leading slash
28 | array_shift($params);
29 |
30 | if (\count($params) > 0) {
31 | $locale = $params[0];
32 |
33 | if (app('laravellocalization')->checkLocaleInSupportedLocales($locale)) {
34 | if (app('laravellocalization')->isHiddenDefault($locale)) {
35 | $redirection = app('laravellocalization')->getNonLocalizedURL();
36 |
37 | // Save any flashed data for redirect
38 | app('session')->reflash();
39 |
40 | return new RedirectResponse($redirection, 302, ['Vary' => 'Accept-Language']);
41 | }
42 | }
43 | }
44 |
45 | return $next($request);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationRoutes.php:
--------------------------------------------------------------------------------
1 | shouldIgnore($request)) {
21 | return $next($request);
22 | }
23 |
24 | $app = app();
25 |
26 | $routeName = $app['laravellocalization']->getRouteNameFromAPath($request->getUri());
27 |
28 | $app['laravellocalization']->setRouteName($routeName);
29 |
30 | return $next($request);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationViewPath.php:
--------------------------------------------------------------------------------
1 | shouldIgnore($request)) {
22 | return $next($request);
23 | }
24 |
25 | $app = app();
26 |
27 | $currentLocale = app('laravellocalization')->getCurrentLocale();
28 | $viewPath = resource_path('views/' . $currentLocale);
29 |
30 | // Add current locale-code to view-paths
31 | View::addLocation($viewPath);
32 |
33 | return $next($request);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Middleware/LocaleCookieRedirect.php:
--------------------------------------------------------------------------------
1 | shouldIgnore($request)) {
23 | return $next($request);
24 | }
25 |
26 | $params = explode('/', $request->path());
27 | $locale = $request->cookie('locale', false);
28 |
29 | if (\count($params) > 0 && app('laravellocalization')->checkLocaleInSupportedLocales($params[0])) {
30 | return $next($request)->withCookie(cookie()->forever('locale', $params[0]));
31 | }
32 |
33 | if (empty($locale) && app('laravellocalization')->hideUrlAndAcceptHeader()){
34 | // When default locale is hidden and accept language header is true,
35 | // then compute browser language when no session has been set.
36 | // Once the session has been set, there is no need
37 | // to negotiate language from browser again.
38 | $negotiator = new LanguageNegotiator(
39 | app('laravellocalization')->getDefaultLocale(),
40 | app('laravellocalization')->getSupportedLocales(),
41 | $request
42 | );
43 | $locale = $negotiator->negotiateLanguage();
44 | Cookie::queue(Cookie::forever('locale', $locale));
45 | }
46 |
47 | if ($locale === false){
48 | $locale = app('laravellocalization')->getCurrentLocale();
49 | }
50 |
51 | if (
52 | $locale &&
53 | app('laravellocalization')->checkLocaleInSupportedLocales($locale) &&
54 | !(app('laravellocalization')->isHiddenDefault($locale))
55 | ) {
56 | $redirection = app('laravellocalization')->getLocalizedURL($locale);
57 | $redirectResponse = new RedirectResponse($redirection, 302, ['Vary' => 'Accept-Language']);
58 |
59 | return $redirectResponse->withCookie(cookie()->forever('locale', $locale));
60 | }
61 |
62 | return $next($request);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Middleware/LocaleSessionRedirect.php:
--------------------------------------------------------------------------------
1 | shouldIgnore($request)) {
23 | return $next($request);
24 | }
25 |
26 | $params = explode('/', $request->path());
27 | $locale = session('locale', false);
28 |
29 | if (\count($params) > 0 && app('laravellocalization')->checkLocaleInSupportedLocales($params[0])) {
30 | session(['locale' => $params[0]]);
31 |
32 | return $next($request);
33 | }
34 |
35 | if (empty($locale) && app('laravellocalization')->hideUrlAndAcceptHeader()){
36 | // When default locale is hidden and accept language header is true,
37 | // then compute browser language when no session has been set.
38 | // Once the session has been set, there is no need
39 | // to negotiate language from browser again.
40 | $negotiator = new LanguageNegotiator(
41 | app('laravellocalization')->getDefaultLocale(),
42 | app('laravellocalization')->getSupportedLocales(),
43 | $request
44 | );
45 | $locale = $negotiator->negotiateLanguage();
46 | session(['locale' => $locale]);
47 | }
48 |
49 | if ($locale === false){
50 | $locale = app('laravellocalization')->getCurrentLocale();
51 | }
52 |
53 | if (
54 | $locale &&
55 | app('laravellocalization')->checkLocaleInSupportedLocales($locale) &&
56 | !(app('laravellocalization')->isHiddenDefault($locale))
57 | ) {
58 | app('session')->reflash();
59 | $redirection = app('laravellocalization')->getLocalizedURL($locale);
60 |
61 | return new RedirectResponse($redirection, 302, ['Vary' => 'Accept-Language']);
62 | }
63 |
64 | return $next($request);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Traits/LoadsTranslatedCachedRoutes.php:
--------------------------------------------------------------------------------
1 | getLaravelLocalization();
24 |
25 | // compute $locale from url.
26 | // It is null if url does not contain locale.
27 | $locale = $localization->getInversedLocaleFromMapping(
28 | $localization->setLocale()
29 | );
30 |
31 | $localeKeys = $localization->getSupportedLanguagesKeys();
32 |
33 | // First, try to load the routes specifically cached for this locale
34 | // if they do not exist, write a warning to the log and load the default
35 | // routes instead. Note that this is guaranteed to exist, because the
36 | // 'cached routes' check in the Application checks its existence.
37 |
38 | $path = $this->makeLocaleRoutesPath($locale, $localeKeys);
39 |
40 | if ( ! file_exists($path)) {
41 |
42 | Log::warning("Routes cached, but no cached routes found for locale '{$locale}'!");
43 |
44 | $path = $this->getDefaultCachedRoutePath();
45 | }
46 |
47 | $this->app->booted(function () use ($path) {
48 | require $path;
49 | });
50 | }
51 |
52 | /**
53 | * Returns the path to the cached routes file for a given locale.
54 | *
55 | * @param string $locale
56 | * @param string[] $localeKeys
57 | * @return string
58 | */
59 | protected function makeLocaleRoutesPath($locale, $localeKeys)
60 | {
61 | $path = $this->getDefaultCachedRoutePath();
62 |
63 | if ( ! $locale || ! in_array($locale, $localeKeys)) {
64 | return $path;
65 | }
66 |
67 | return substr($path, 0, -4) . '_' . $locale . '.php';
68 | }
69 |
70 | /**
71 | * Returns the path to the standard cached routes file.
72 | *
73 | * @return string
74 | */
75 | protected function getDefaultCachedRoutePath()
76 | {
77 | return $this->app->getCachedRoutesPath();
78 | }
79 |
80 | /**
81 | * @return string|null
82 | */
83 | protected function getLocaleFromRequest()
84 | {
85 | return request()->segment(1);
86 | }
87 |
88 | /**
89 | * @return \Mcamara\LaravelLocalization\LaravelLocalization
90 | */
91 | protected function getLaravelLocalization()
92 | {
93 | return app('laravellocalization');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Mcamara/LaravelLocalization/Traits/TranslatedRouteCommandContext.php:
--------------------------------------------------------------------------------
1 | getSupportedLocales());
16 | }
17 |
18 | /**
19 | * @return string[]
20 | */
21 | protected function getSupportedLocales()
22 | {
23 | return $this->getLaravelLocalization()->getSupportedLanguagesKeys();
24 | }
25 |
26 | /**
27 | * @return \Mcamara\LaravelLocalization\LaravelLocalization
28 | */
29 | protected function getLaravelLocalization()
30 | {
31 | return $this->laravel->make('laravellocalization');
32 | }
33 |
34 | /**
35 | * @return string
36 | */
37 | protected function getBootstrapPath()
38 | {
39 | if (method_exists($this->laravel, 'bootstrapPath')) {
40 | return $this->laravel->bootstrapPath();
41 | }
42 |
43 | return $this->laravel->basePath() . DIRECTORY_SEPARATOR . 'bootstrap';
44 | }
45 |
46 | /**
47 | * @param string $locale
48 | * @return string
49 | */
50 | protected function makeLocaleRoutesPath($locale = '')
51 | {
52 | $path = $this->laravel->getCachedRoutesPath();
53 |
54 | if ( ! $locale ) {
55 | return $path;
56 | }
57 |
58 | return substr($path, 0, -4) . '_' . $locale . '.php';
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/stubs/routes.stub:
--------------------------------------------------------------------------------
1 | setRoutes(
17 | unserialize(base64_decode('{{routes}}'))
18 | );
19 |
20 | app('laravellocalization')->setSerializedTranslatedRoutes('{{translatedRoutes}}');
21 |
--------------------------------------------------------------------------------
/tests/CustomTranslatorTest.php:
--------------------------------------------------------------------------------
1 | app,
22 | $this->app['config'],
23 | $translator,
24 | $this->app['router'],
25 | $this->app['request'],
26 | $this->app['url']
27 | );
28 |
29 | $localization->setLocale('es');
30 |
31 | $this->assertEquals('es', $translator->getLocale());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/LaravelLocalizationTest.php:
--------------------------------------------------------------------------------
1 | setLocale($locale);
26 | }
27 |
28 | app('router')->group([
29 | 'prefix' => app('laravellocalization')->setLocale(),
30 | 'middleware' => [
31 | 'Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes',
32 | 'Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter',
33 | ],
34 | ], function () {
35 | app('router')->get('/', ['as'=> 'index', function () {
36 | return app('translator')->get('LaravelLocalization::routes.hello');
37 | }, ]);
38 |
39 | app('router')->get('test', ['as'=> 'test', function () {
40 | return app('translator')->get('LaravelLocalization::routes.test_text');
41 | }, ]);
42 |
43 | app('router')->get(app('laravellocalization')->transRoute('LaravelLocalization::routes.about'), ['as'=> 'about', function () {
44 | return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available';
45 | }, ]);
46 |
47 | app('router')->get(app('laravellocalization')->transRoute('LaravelLocalization::routes.view'), ['as'=> 'view', function () {
48 | return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available';
49 | }, ]);
50 |
51 | app('router')->get(app('laravellocalization')->transRoute('LaravelLocalization::routes.view_project'), ['as'=> 'view_project', function () {
52 | return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available';
53 | }, ]);
54 |
55 | app('router')->get(app('laravellocalization')->transRoute('LaravelLocalization::routes.manage'), ['as'=> 'manage', function () {
56 | return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available';
57 | }, ]);
58 | });
59 |
60 | app('router')->get('/skipped', ['as'=> 'skipped', function () {
61 | return Request::url();
62 | }, ]);
63 | }
64 |
65 | /**
66 | * Refresh routes and refresh application.
67 | *
68 | * @param bool|string $locale
69 | */
70 | protected function refreshApplication($locale = false)
71 | {
72 | parent::refreshApplication();
73 | $this->setRoutes($locale);
74 | }
75 |
76 | /**
77 | * Create fake request
78 | * @param [type] $method [description]
79 | * @param [type] $content [description]
80 | * @param string $uri [description]
81 | * @param array $server [description]
82 | * @param array $parameters [description]
83 | * @param array $cookies [description]
84 | * @param array $files [description]
85 | * @return [type] [description]
86 | */
87 | protected function createRequest(
88 | $uri = '/test',
89 | $method = 'GET',
90 | $parameters = [],
91 | $cookies = [],
92 | $files = [],
93 | $server = ['CONTENT_TYPE' => 'application/json'],
94 | $content = null
95 | )
96 | {
97 | $request = new \Illuminate\Http\Request;
98 | return $request->createFromBase(
99 | \Symfony\Component\HttpFoundation\Request::create(
100 | $uri,
101 | 'GET',
102 | [],
103 | [],
104 | [],
105 | $server,
106 | $content
107 | )
108 | );
109 | }
110 |
111 | /**
112 | * Define environment setup.
113 | *
114 | * @param Illuminate\Foundation\Application $app
115 | *
116 | * @return void
117 | */
118 | protected function getEnvironmentSetUp($app)
119 | {
120 | app('config')->set('app.url', self::TEST_URL);
121 |
122 | app('config')->set('app.locale', $this->defaultLocale);
123 |
124 | $packageConfigFile = __DIR__.'/../src/config/config.php';
125 | $config = app('files')->getRequire($packageConfigFile);
126 |
127 | app('config')->set('laravellocalization', $config);
128 |
129 | $this->supportedLocales = app('config')->get('laravellocalization.supportedLocales');
130 |
131 | app('translator')->getLoader()->addNamespace('LaravelLocalization', realpath(dirname(__FILE__)).'/lang');
132 |
133 | app('translator')->load('LaravelLocalization', 'routes', 'es');
134 | app('translator')->load('LaravelLocalization', 'routes', 'en');
135 |
136 | app('laravellocalization')->setBaseUrl(self::TEST_URL);
137 |
138 | $this->setRoutes();
139 | }
140 |
141 | public function testSetLocale(): void
142 | {
143 | $this->assertEquals(route('about'), 'http://localhost/about');
144 |
145 | $this->refreshApplication('es');
146 | $this->assertEquals('es', app('laravellocalization')->setLocale('es'));
147 | $this->assertEquals('es', app('laravellocalization')->getCurrentLocale());
148 | $this->assertEquals(route('about'), 'http://localhost/acerca');
149 |
150 | $this->refreshApplication();
151 |
152 | $this->assertEquals('en', app('laravellocalization')->setLocale('en'));
153 |
154 | $this->assertEquals(route('about'), 'http://localhost/about');
155 |
156 | $this->assertNull(app('laravellocalization')->setLocale('de'));
157 | $this->assertEquals('en', app('laravellocalization')->getCurrentLocale());
158 | }
159 |
160 | public function testLocalizeURL(): void
161 | {
162 | $this->assertEquals(
163 | self::TEST_URL.app('laravellocalization')->getCurrentLocale(),
164 | app('laravellocalization')->localizeURL()
165 | );
166 |
167 | // Missing trailing slash in a URL
168 | $this->assertEquals(
169 | self::TEST_URL.app('laravellocalization')->getCurrentLocale(),
170 | app('laravellocalization')->localizeURL()
171 | );
172 |
173 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', true);
174 |
175 | // testing hide default locale option
176 | $this->assertNotEquals(
177 | self::TEST_URL.app('laravellocalization')->getDefaultLocale(),
178 | app('laravellocalization')->localizeURL()
179 | );
180 |
181 | $this->assertEquals(
182 | self::TEST_URL,
183 | app('laravellocalization')->localizeURL()
184 | );
185 |
186 | app('laravellocalization')->setLocale('es');
187 |
188 | $this->assertEquals(
189 | self::TEST_URL.'es',
190 | app('laravellocalization')->localizeURL()
191 | );
192 |
193 | $this->assertEquals(
194 | self::TEST_URL.'about',
195 | app('laravellocalization')->localizeURL(self::TEST_URL.'about', 'en')
196 | );
197 |
198 | $this->assertNotEquals(
199 | self::TEST_URL.'en/about',
200 | app('laravellocalization')->localizeURL(self::TEST_URL.'about', 'en')
201 | );
202 |
203 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', false);
204 |
205 | $this->assertEquals(
206 | self::TEST_URL.'en/about',
207 | app('laravellocalization')->localizeURL(self::TEST_URL.'about', 'en')
208 | );
209 |
210 | $this->assertNotEquals(
211 | self::TEST_URL.'about',
212 | app('laravellocalization')->localizeURL(self::TEST_URL.'about', 'en')
213 | );
214 | }
215 |
216 | public function testGetLocalizedURL(): void
217 | {
218 | $this->assertEquals(
219 | self::TEST_URL.app('laravellocalization')->getCurrentLocale(),
220 | app('laravellocalization')->getLocalizedURL()
221 | );
222 |
223 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', true);
224 | // testing default language hidden
225 |
226 | $this->assertNotEquals(
227 | self::TEST_URL.app('laravellocalization')->getDefaultLocale(),
228 | app('laravellocalization')->getLocalizedURL()
229 | );
230 |
231 | app('laravellocalization')->setLocale('es');
232 |
233 | $this->assertNotEquals(
234 | self::TEST_URL,
235 | app('laravellocalization')->getLocalizedURL()
236 | );
237 |
238 | $this->assertNotEquals(
239 | self::TEST_URL.app('laravellocalization')->getDefaultLocale(),
240 | app('laravellocalization')->getLocalizedURL()
241 | );
242 |
243 | $this->assertEquals(
244 | self::TEST_URL.app('laravellocalization')->getCurrentLocale(),
245 | app('laravellocalization')->getLocalizedURL()
246 | );
247 |
248 | $this->assertEquals(
249 | self::TEST_URL.'es/acerca',
250 | app('laravellocalization')->getLocalizedURL('es', self::TEST_URL.'about')
251 | );
252 |
253 | app('laravellocalization')->setLocale('en');
254 |
255 | $crawler = $this->call(
256 | 'GET',
257 | self::TEST_URL.'about',
258 | [],
259 | [],
260 | [],
261 | ['HTTP_ACCEPT_LANGUAGE' => 'en,es']
262 | );
263 |
264 | $this->assertResponseOk();
265 | $this->assertEquals(
266 | self::TEST_URL.'es/acerca',
267 | $crawler->getContent()
268 | );
269 |
270 | $this->refreshApplication();
271 |
272 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', true);
273 |
274 | $this->assertEquals(
275 | self::TEST_URL.'test',
276 | app('laravellocalization')->getLocalizedURL('en', self::TEST_URL.'test')
277 | );
278 |
279 | $this->assertEquals(
280 | self::TEST_URL.'test?a=1',
281 | app('laravellocalization')->getLocalizedURL('en', self::TEST_URL.'test?a=1')
282 | );
283 |
284 | $crawler = $this->call(
285 | 'GET',
286 | app('laravellocalization')->getLocalizedURL('en', self::TEST_URL.'test'),
287 | [],
288 | [],
289 | [],
290 | ['HTTP_ACCEPT_LANGUAGE' => 'en,es']
291 | );
292 |
293 | $this->assertResponseOk();
294 | $this->assertEquals(
295 | 'Test text',
296 | $crawler->getContent()
297 | );
298 |
299 | $this->refreshApplication('es');
300 |
301 | $this->assertEquals(
302 | self::TEST_URL.'es/test',
303 | app('laravellocalization')->getLocalizedURL('es', self::TEST_URL.'test')
304 | );
305 |
306 | $this->assertEquals(
307 | self::TEST_URL.'es/test?a=1',
308 | app('laravellocalization')->getLocalizedURL('es', self::TEST_URL.'test?a=1')
309 | );
310 | }
311 |
312 | public function testGetLocalizedURLWithQueryStringAndhideDefaultLocaleInURL(): void
313 | {
314 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', true);
315 | $request = $this->createRequest(
316 | $uri = 'en/about?q=2'
317 | );
318 | $laravelLocalization = app(LaravelLocalization::class, ['request' => $request]);
319 | $laravelLocalization->transRoute('LaravelLocalization::routes.about');
320 |
321 | $this->assertEquals(
322 | self::TEST_URL . 'about?q=2',
323 | $laravelLocalization->getLocalizedURL()
324 | );
325 | }
326 |
327 | public function testGetLocalizedURLWithQueryStringAndNotTranslatedRoute(): void
328 | {
329 | $request = $this->createRequest(
330 | $uri = 'en/about?q=2'
331 | );
332 | $laravelLocalization = app(LaravelLocalization::class, ['request' => $request]);
333 |
334 | $this->assertEquals(
335 | self::TEST_URL . 'en/about?q=2',
336 | $laravelLocalization->getLocalizedURL()
337 | );
338 | }
339 |
340 | /**
341 | * @param string $path
342 | * @param string|bool $expectedRouteName
343 | */
344 | #[DataProvider('getRouteNameFromAPathDataProvider')]
345 | public function testGetRouteNameFromAPath($path, $expectedRouteName): void
346 | {
347 | $this->assertEquals(
348 | $expectedRouteName,
349 | app('laravellocalization')->getRouteNameFromAPath($path)
350 | );
351 | }
352 |
353 | public static function getRouteNameFromAPathDataProvider(): array
354 | {
355 | return [
356 | [self::TEST_URL, false],
357 | [self::TEST_URL.'es', false],
358 | [self::TEST_URL.'en/about', 'LaravelLocalization::routes.about'],
359 | [self::TEST_URL.'ver/1', false],
360 | [self::TEST_URL.'view/1', 'LaravelLocalization::routes.view'],
361 | [self::TEST_URL.'view/1/project', 'LaravelLocalization::routes.view_project'],
362 | [self::TEST_URL.'view/1/project/1', 'LaravelLocalization::routes.view_project'],
363 | [self::TEST_URL.'en/view/1/project/1', 'LaravelLocalization::routes.view_project'],
364 | [self::TEST_URL.'manage/1', 'LaravelLocalization::routes.manage'],
365 | [self::TEST_URL.'manage', 'LaravelLocalization::routes.manage'],
366 | [self::TEST_URL.'manage/', 'LaravelLocalization::routes.manage'],
367 | [self::TEST_URL.'manage/0', 'LaravelLocalization::routes.manage'],
368 | [self::TEST_URL.'manage/0?ex=2&ex2=a', 'LaravelLocalization::routes.manage'],
369 | ];
370 | }
371 |
372 | public function testGetLocalizedUrlForIgnoredUrls(): void {
373 | $crawler = $this->call(
374 | 'GET',
375 | self::TEST_URL.'skipped',
376 | [],
377 | [],
378 | [],
379 | ['HTTP_ACCEPT_LANGUAGE' => 'en,es']
380 | );
381 |
382 | $this->assertResponseOk();
383 | $this->assertEquals(
384 | self::TEST_URL.'skipped',
385 | $crawler->getContent()
386 | );
387 | }
388 |
389 | /**
390 | * @param bool $hideDefaultLocaleInURL
391 | * @param bool $forceDefault
392 | * @param string $locale
393 | * @param string $path
394 | * @param string $expectedURL
395 | */
396 | #[DataProvider('getLocalizedURLDataProvider')]
397 | public function testGetLocalizedURLFormat($hideDefaultLocaleInURL, $forceDefault, $locale, $path, $expectedURL): void
398 | {
399 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', $hideDefaultLocaleInURL);
400 | $this->assertEquals(
401 | $expectedURL,
402 | app('laravellocalization')->getLocalizedURL($locale, $path, [], $forceDefault)
403 | );
404 |
405 | }
406 |
407 | public static function getLocalizedURLDataProvider(): array
408 | {
409 | return [
410 | // Do not hide default
411 | [false, false, 'es', self::TEST_URL, self::TEST_URL.'es'],
412 | [false, false, 'es', self::TEST_URL.'es', self::TEST_URL.'es'],
413 | [false, false, 'es', self::TEST_URL.'en/about', self::TEST_URL.'es/acerca'],
414 | [false, false, 'es', self::TEST_URL.'ver/1', self::TEST_URL.'es/ver/1'],
415 | [false, false, 'es', self::TEST_URL.'view/1/project', self::TEST_URL.'es/ver/1/proyecto'],
416 | [false, false, 'es', self::TEST_URL.'view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
417 | [false, false, 'es', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
418 | [false, false, 'es', self::TEST_URL.'manage/1', self::TEST_URL.'es/administrar/1'],
419 | [false, false, 'es', self::TEST_URL.'manage', self::TEST_URL.'es/administrar'],
420 | [false, false, 'es', self::TEST_URL.'manage/', self::TEST_URL.'es/administrar'],
421 | [false, false, 'es', self::TEST_URL.'manage/0', self::TEST_URL.'es/administrar/0'],
422 | [false, false, 'es', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'es/administrar/0?ex=2&ex2=a'],
423 |
424 | // Do not hide default
425 | [false, false, 'en', self::TEST_URL.'en', self::TEST_URL.'en'],
426 | [false, false, 'en', self::TEST_URL.'about', self::TEST_URL.'en/about'],
427 | [false, false, 'en', self::TEST_URL.'ver/1', self::TEST_URL.'en/ver/1'],
428 | [false, false, 'en', self::TEST_URL.'view/1/project', self::TEST_URL.'en/view/1/project'],
429 | [false, false, 'en', self::TEST_URL.'view/1/project/1', self::TEST_URL.'en/view/1/project/1'],
430 | [false, false, 'en', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'en/view/1/project/1'],
431 | [false, false, 'en', self::TEST_URL.'manage/1', self::TEST_URL.'en/manage/1'],
432 | [false, false, 'en', self::TEST_URL.'manage', self::TEST_URL.'en/manage'],
433 | [false, false, 'en', self::TEST_URL.'manage/', self::TEST_URL.'en/manage'],
434 | [false, false, 'en', self::TEST_URL.'manage/0', self::TEST_URL.'en/manage/0'],
435 | [false, false, 'en', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'en/manage/0?ex=2&ex2=a'],
436 |
437 | // Hide default
438 | [true, false, 'es', self::TEST_URL, self::TEST_URL.'es'],
439 | [true, false, 'es', self::TEST_URL.'es', self::TEST_URL.'es'],
440 | [true, false, 'es', self::TEST_URL.'en/about', self::TEST_URL.'es/acerca'],
441 | [true, false, 'es', self::TEST_URL.'ver/1', self::TEST_URL.'es/ver/1'],
442 | [true, false, 'es', self::TEST_URL.'view/1/project', self::TEST_URL.'es/ver/1/proyecto'],
443 | [true, false, 'es', self::TEST_URL.'view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
444 | [true, false, 'es', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
445 | [true, false, 'es', self::TEST_URL.'manage/1', self::TEST_URL.'es/administrar/1'],
446 | [true, false, 'es', self::TEST_URL.'manage', self::TEST_URL.'es/administrar'],
447 | [true, false, 'es', self::TEST_URL.'manage/', self::TEST_URL.'es/administrar'],
448 | [true, false, 'es', self::TEST_URL.'manage/0', self::TEST_URL.'es/administrar/0'],
449 | [true, false, 'es', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'es/administrar/0?ex=2&ex2=a'],
450 |
451 | // Hide default
452 | [true, false, 'en', self::TEST_URL.'en', self::TEST_URL.''],
453 | [true, false, 'en', self::TEST_URL.'about', self::TEST_URL.'about'],
454 | [true, false, 'en', self::TEST_URL.'ver/1', self::TEST_URL.'ver/1'],
455 | [true, false, 'en', self::TEST_URL.'view/1/project', self::TEST_URL.'view/1/project'],
456 | [true, false, 'en', self::TEST_URL.'view/1/project/1', self::TEST_URL.'view/1/project/1'],
457 | [true, false, 'en', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'view/1/project/1'],
458 | [true, false, 'en', self::TEST_URL.'manage/1', self::TEST_URL.'manage/1'],
459 | [true, false, 'en', self::TEST_URL.'manage', self::TEST_URL.'manage'],
460 | [true, false, 'en', self::TEST_URL.'manage/', self::TEST_URL.'manage'],
461 | [true, false, 'en', self::TEST_URL.'manage/0', self::TEST_URL.'manage/0'],
462 | [true, false, 'en', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'manage/0?ex=2&ex2=a'],
463 |
464 | // Do not hide default FORCE SHOWING
465 | [false, true, 'es', self::TEST_URL, self::TEST_URL.'es'],
466 | [false, true, 'es', self::TEST_URL.'es', self::TEST_URL.'es'],
467 | [false, true, 'es', self::TEST_URL.'en/about', self::TEST_URL.'es/acerca'],
468 | [false, true, 'es', self::TEST_URL.'ver/1', self::TEST_URL.'es/ver/1'],
469 | [false, true, 'es', self::TEST_URL.'view/1/project', self::TEST_URL.'es/ver/1/proyecto'],
470 | [false, true, 'es', self::TEST_URL.'view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
471 | [false, true, 'es', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
472 | [false, true, 'es', self::TEST_URL.'manage/1', self::TEST_URL.'es/administrar/1'],
473 | [false, true, 'es', self::TEST_URL.'manage', self::TEST_URL.'es/administrar'],
474 | [false, true, 'es', self::TEST_URL.'manage/', self::TEST_URL.'es/administrar'],
475 | [false, true, 'es', self::TEST_URL.'manage/0', self::TEST_URL.'es/administrar/0'],
476 | [false, true, 'es', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'es/administrar/0?ex=2&ex2=a'],
477 |
478 | // Do not hide default FORCE SHOWING
479 | [false, true, 'en', self::TEST_URL.'en', self::TEST_URL.'en'],
480 | [false, true, 'en', self::TEST_URL.'about', self::TEST_URL.'en/about'],
481 | [false, true, 'en', self::TEST_URL.'ver/1', self::TEST_URL.'en/ver/1'],
482 | [false, true, 'en', self::TEST_URL.'view/1/project', self::TEST_URL.'en/view/1/project'],
483 | [false, true, 'en', self::TEST_URL.'view/1/project/1', self::TEST_URL.'en/view/1/project/1'],
484 | [false, true, 'en', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'en/view/1/project/1'],
485 | [false, true, 'en', self::TEST_URL.'manage/1', self::TEST_URL.'en/manage/1'],
486 | [false, true, 'en', self::TEST_URL.'manage', self::TEST_URL.'en/manage'],
487 | [false, true, 'en', self::TEST_URL.'manage/', self::TEST_URL.'en/manage'],
488 | [false, true, 'en', self::TEST_URL.'manage/0', self::TEST_URL.'en/manage/0'],
489 | [false, true, 'en', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'en/manage/0?ex=2&ex2=a'],
490 |
491 | // Hide default FORCE SHOWING
492 | [true, true, 'es', self::TEST_URL, self::TEST_URL.'es'],
493 | [true, true, 'es', self::TEST_URL.'es', self::TEST_URL.'es'],
494 | [true, true, 'es', self::TEST_URL.'en/about', self::TEST_URL.'es/acerca'],
495 | [true, true, 'es', self::TEST_URL.'ver/1', self::TEST_URL.'es/ver/1'],
496 | [true, true, 'es', self::TEST_URL.'view/1/project', self::TEST_URL.'es/ver/1/proyecto'],
497 | [true, true, 'es', self::TEST_URL.'view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
498 | [true, true, 'es', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'es/ver/1/proyecto/1'],
499 | [true, true, 'es', self::TEST_URL.'manage/1', self::TEST_URL.'es/administrar/1'],
500 | [true, true, 'es', self::TEST_URL.'manage', self::TEST_URL.'es/administrar'],
501 | [true, true, 'es', self::TEST_URL.'manage/', self::TEST_URL.'es/administrar'],
502 | [true, true, 'es', self::TEST_URL.'manage/0', self::TEST_URL.'es/administrar/0'],
503 | [true, true, 'es', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'es/administrar/0?ex=2&ex2=a'],
504 |
505 | // Hide default FORCE SHOWING
506 | [true, true, 'en', self::TEST_URL.'en', self::TEST_URL.'en'],
507 | [true, true, 'en', self::TEST_URL.'about', self::TEST_URL.'en/about'],
508 | [true, true, 'en', self::TEST_URL.'ver/1', self::TEST_URL.'en/ver/1'],
509 | [true, true, 'en', self::TEST_URL.'view/1/project', self::TEST_URL.'en/view/1/project'],
510 | [true, true, 'en', self::TEST_URL.'view/1/project/1', self::TEST_URL.'en/view/1/project/1'],
511 | [true, true, 'en', self::TEST_URL.'en/view/1/project/1', self::TEST_URL.'en/view/1/project/1'],
512 | [true, true, 'en', self::TEST_URL.'manage/1', self::TEST_URL.'en/manage/1'],
513 | [true, true, 'en', self::TEST_URL.'manage', self::TEST_URL.'en/manage'],
514 | [true, true, 'en', self::TEST_URL.'manage/', self::TEST_URL.'en/manage'],
515 | [true, true, 'en', self::TEST_URL.'manage/0', self::TEST_URL.'en/manage/0'],
516 | [true, true, 'en', self::TEST_URL.'manage/0?ex=2&ex2=a', self::TEST_URL.'en/manage/0?ex=2&ex2=a'],
517 | ];
518 | }
519 |
520 | public function testGetURLFromRouteNameTranslated(): void
521 | {
522 | $this->assertEquals(
523 | self::TEST_URL.'es/acerca',
524 | app('laravellocalization')->getURLFromRouteNameTranslated('es', 'LaravelLocalization::routes.about')
525 | );
526 |
527 | $this->assertEquals(
528 | self::TEST_URL.'en/about',
529 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.about')
530 | );
531 |
532 | $this->assertEquals(
533 | self::TEST_URL.'en/view/1',
534 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.view', ['id' => 1])
535 | );
536 |
537 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', true);
538 |
539 | $this->assertEquals(
540 | self::TEST_URL.'about',
541 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.about')
542 | );
543 |
544 | $this->assertEquals(
545 | self::TEST_URL.'es/acerca',
546 | app('laravellocalization')->getURLFromRouteNameTranslated('es', 'LaravelLocalization::routes.about')
547 | );
548 |
549 | $this->assertEquals(
550 | self::TEST_URL.'es/ver/1',
551 | app('laravellocalization')->getURLFromRouteNameTranslated('es', 'LaravelLocalization::routes.view', ['id' => 1])
552 | );
553 |
554 | $this->assertEquals(
555 | self::TEST_URL.'view/1',
556 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.view', ['id' => 1])
557 | );
558 |
559 | $this->assertNotEquals(
560 | self::TEST_URL.'en/view/1',
561 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.view', ['id' => 1])
562 | );
563 |
564 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', false);
565 |
566 | $this->assertNotEquals(
567 | self::TEST_URL.'view/1',
568 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.view', ['id' => 1])
569 | );
570 |
571 | $this->assertEquals(
572 | self::TEST_URL.'en/view/1',
573 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.view', ['id' => 1])
574 | );
575 | }
576 |
577 | public function testLocalizedParameterFromTranslateUrl(): void
578 | {
579 | $model = new ModelWithTranslatableRoutes();
580 |
581 | $this->assertEquals(
582 | self::TEST_URL.'en/view/company',
583 | app('laravellocalization')->getURLFromRouteNameTranslated('en', 'LaravelLocalization::routes.view', ['id' => $model])
584 | );
585 |
586 | $this->assertEquals(
587 | self::TEST_URL.'es/ver/empresa',
588 | app('laravellocalization')->getURLFromRouteNameTranslated('es', 'LaravelLocalization::routes.view', ['id' => $model])
589 | );
590 | }
591 |
592 | public function testGetNonLocalizedURL(): void
593 | {
594 | $this->assertEquals(
595 | self::TEST_URL,
596 | app('laravellocalization')->getNonLocalizedURL(self::TEST_URL.'en')
597 | );
598 | $this->assertEquals(
599 | self::TEST_URL,
600 | app('laravellocalization')->getNonLocalizedURL(self::TEST_URL.'es')
601 | );
602 | $this->assertEquals(
603 | self::TEST_URL.'view/1',
604 | app('laravellocalization')->getNonLocalizedURL(self::TEST_URL.'en/view/1')
605 | );
606 | $this->assertEquals(
607 | self::TEST_URL.'ver/1',
608 | app('laravellocalization')->getNonLocalizedURL(self::TEST_URL.'es/ver/1')
609 | );
610 | }
611 |
612 | public function testGetDefaultLocale(): void
613 | {
614 | $this->assertEquals(
615 | 'en',
616 | app('laravellocalization')->getDefaultLocale()
617 | );
618 |
619 | app('laravellocalization')->setLocale('es');
620 | $this->refreshApplication('es');
621 |
622 | $this->assertEquals(
623 | 'en',
624 | app('laravellocalization')->getDefaultLocale()
625 | );
626 | }
627 |
628 | public function testGetSupportedLocales(): void
629 | {
630 | $this->assertEquals(
631 | $this->supportedLocales,
632 | app('laravellocalization')->getSupportedLocales()
633 | );
634 | }
635 |
636 | public function testGetCurrentLocaleName(): void
637 | {
638 | $this->assertEquals(
639 | 'English',
640 | app('laravellocalization')->getCurrentLocaleName()
641 | );
642 |
643 | $this->refreshApplication('es');
644 |
645 | $this->assertEquals(
646 | 'Spanish',
647 | app('laravellocalization')->getCurrentLocaleName()
648 | );
649 | }
650 |
651 | public function testGetCurrentLocaleRegional(): void
652 | {
653 | $this->assertEquals(
654 | 'en_GB',
655 | app('laravellocalization')->getCurrentLocaleRegional()
656 | );
657 |
658 | $this->refreshApplication('es');
659 |
660 | $this->assertEquals(
661 | 'es_ES',
662 | app('laravellocalization')->getCurrentLocaleRegional()
663 | );
664 | }
665 |
666 | public function testGetCurrentLocaleDirection(): void
667 | {
668 | $this->assertEquals(
669 | 'ltr',
670 | app('laravellocalization')->getCurrentLocaleDirection()
671 | );
672 |
673 | app('laravellocalization')->setLocale('es');
674 | $this->refreshApplication('es');
675 |
676 | $this->assertEquals(
677 | 'ltr',
678 | app('laravellocalization')->getCurrentLocaleDirection()
679 | );
680 | }
681 |
682 | public function testGetCurrentLocaleScript(): void
683 | {
684 | app('laravellocalization')->setLocale('es');
685 | $this->refreshApplication('es');
686 |
687 | $this->assertEquals(
688 | 'Latn',
689 | app('laravellocalization')->getCurrentLocaleScript()
690 | );
691 |
692 | app('laravellocalization')->setLocale('en');
693 | $this->refreshApplication('en');
694 |
695 | $this->assertEquals(
696 | 'Latn',
697 | app('laravellocalization')->getCurrentLocaleScript()
698 | );
699 | }
700 |
701 | public function testGetCurrentLocaleNativeReading(): void
702 | {
703 | $this->assertEquals(
704 | 'English',
705 | app('laravellocalization')->getCurrentLocaleNativeReading()
706 | );
707 |
708 | app('laravellocalization')->setLocale('es');
709 | $this->refreshApplication('es');
710 |
711 | $this->assertEquals(
712 | 'español',
713 | app('laravellocalization')->getCurrentLocaleNativeReading()
714 | );
715 | }
716 |
717 | public function testGetCurrentLocale(): void
718 | {
719 | $this->assertEquals(
720 | 'en',
721 | app('laravellocalization')->getCurrentLocale()
722 | );
723 |
724 | app('laravellocalization')->setLocale('es');
725 | $this->refreshApplication('es');
726 |
727 | $this->assertEquals(
728 | 'es',
729 | app('laravellocalization')->getCurrentLocale()
730 | );
731 |
732 | $this->assertNotEquals(
733 | 'en',
734 | app('laravellocalization')->getCurrentLocale()
735 | );
736 | }
737 |
738 | public function testGetSupportedLanguagesKeys(): void
739 | {
740 | $this->assertEquals(
741 | ['en', 'es'],
742 | app('laravellocalization')->getSupportedLanguagesKeys()
743 | );
744 | }
745 |
746 | public function testGetConfigRepository(): void
747 | {
748 | $this->assertEquals(
749 | app('config'),
750 | app('laravellocalization')->getConfigRepository('/view/1')
751 | );
752 | }
753 |
754 | public function testCreateUrlFromUri(): void
755 | {
756 | $this->assertEquals(
757 | 'http://localhost/view/1',
758 | app('laravellocalization')->createUrlFromUri('/view/1')
759 | );
760 |
761 | app('laravellocalization')->setLocale('es');
762 | $this->refreshApplication('es');
763 |
764 | $this->assertEquals(
765 | 'http://localhost/ver/1',
766 | app('laravellocalization')->createUrlFromUri('/ver/1')
767 | );
768 | }
769 |
770 |
771 | #[DataProvider('accept_language_variations_data')]
772 | public function testLanguageNegotiation($accept_string, $must_resolve_to, $asd = null): void {
773 |
774 | $full_config = include __DIR__ . '/full-config/config.php';
775 |
776 | $request = $this->createMock(\Illuminate\Http\Request::class);
777 | $request->expects($this->any())->method('header')->with('Accept-Language')->willReturn($accept_string);
778 |
779 | $negotiator = app(\Mcamara\LaravelLocalization\LanguageNegotiator::class,
780 | [
781 | 'defaultLocale' => 'wrong',
782 | 'supportedLanguages' => $full_config['supportedLocales'],
783 | 'request' => $request
784 | ]
785 | );
786 |
787 | $language = $negotiator->negotiateLanguage();
788 |
789 |
790 | $this->assertEquals($must_resolve_to, $language);
791 | }
792 |
793 |
794 | public static function accept_language_variations_data(): array {
795 | $variations = [
796 | ['en-GB', 'en-GB'],
797 | ['en-US', 'en-US'],
798 | ['en-ZA', 'en'],
799 | ['en', 'en'],
800 | ['az-AZ', 'az'],
801 | ['fr-CA,fr;q=0.8', 'fr-CA'],
802 | ['fr-150', 'fr'],
803 | ['zh-Hant-cn', 'zh-Hant'],
804 | ['zh-cn', 'zh'],
805 | ];
806 |
807 | $dataset = [];
808 | foreach ($variations as $variation) {
809 | $dataset[$variation[0]] = $variation;
810 | }
811 |
812 | return $dataset;
813 | }
814 |
815 | public function testLanguageNegotiationWithMapping(): void {
816 |
817 | $accept_string = 'en-GB';
818 | $must_resolve_to = 'uk';
819 |
820 | $mapping = [
821 | $accept_string => $must_resolve_to
822 | ];
823 |
824 | $full_config = include __DIR__ . '/full-config/config.php';
825 |
826 | $full_config['supportedLocales']['uk'] = $full_config['supportedLocales']['en-GB'];
827 | unset($full_config['supportedLocales']['en-GB']);
828 |
829 | app('config')->set('laravellocalization.localesMapping', $mapping);
830 |
831 | $request = $this->createMock(\Illuminate\Http\Request::class);
832 | $request->expects($this->any())->method('header')->with('Accept-Language')->willReturn($accept_string);
833 |
834 | $negotiator = app(\Mcamara\LaravelLocalization\LanguageNegotiator::class,
835 | [
836 | 'defaultLocale' => 'wrong',
837 | 'supportedLanguages' => $full_config['supportedLocales'],
838 | 'request' => $request
839 | ]
840 | );
841 |
842 | $language = $negotiator->negotiateLanguage();
843 |
844 | $this->assertEquals($must_resolve_to, $language);
845 | }
846 |
847 | public function testSetLocaleWithMapping(): void
848 | {
849 | app('config')->set('laravellocalization.localesMapping', [
850 | 'en' => 'custom',
851 | ]);
852 |
853 | $this->assertEquals('custom', app('laravellocalization')->setLocale('custom'));
854 | $this->assertEquals('en', app('laravellocalization')->getCurrentLocale());
855 |
856 | $this->assertTrue(app('laravellocalization')->checkLocaleInSupportedLocales('en'));
857 | $this->assertTrue(app('laravellocalization')->checkLocaleInSupportedLocales('custom'));
858 |
859 | $this->assertEquals('http://localhost/custom/some-route', app('laravellocalization')->localizeURL('some-route', 'en'));
860 | $this->assertEquals('http://localhost/custom/some-route', app('laravellocalization')->localizeURL('some-route', 'custom'));
861 | $this->assertEquals('http://localhost/custom', app('laravellocalization')->localizeURL('http://localhost/custom', 'en'));
862 | }
863 |
864 |
865 |
866 | public function testRedirectWithHiddenDefaultLocaleInUrlAndSavedLocale()
867 | {
868 | app('router')->group([
869 | 'prefix' => app('laravellocalization')->setLocale(),
870 | 'middleware' => [
871 | 'Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter',
872 | 'Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect',
873 | ],
874 | ], function (){
875 | app('router')->get('/', ['as'=> 'index', function () {
876 | return 'Index page';
877 | }, ]);
878 | });
879 |
880 | app('config')->set('laravellocalization.hideDefaultLocaleInURL', true);
881 |
882 | $savedLocale = 'es';
883 |
884 | $crawler = $this->call(
885 | 'GET',
886 | self::TEST_URL,
887 | [],
888 | ['locale' => $savedLocale],
889 | [],
890 | []
891 | );
892 |
893 | $this->assertResponseStatus(302);
894 | $this->assertRedirectedTo(self::TEST_URL . $savedLocale);
895 |
896 | $localeCookie = $crawler->headers->getCookies()[0];
897 | $this->assertEquals($savedLocale, $localeCookie->getValue());
898 | }
899 | }
900 |
--------------------------------------------------------------------------------
/tests/ModelWithTranslatableRoutes.php:
--------------------------------------------------------------------------------
1 | LaravelLocalization::class,
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/full-config/config.php:
--------------------------------------------------------------------------------
1 | [
9 | 'ace' => ['name' => 'Achinese', 'script' => 'Latn', 'native' => 'Aceh', 'regional' => ''],
10 | 'af' => ['name' => 'Afrikaans', 'script' => 'Latn', 'native' => 'Afrikaans', 'regional' => 'af_ZA'],
11 | 'agq' => ['name' => 'Aghem', 'script' => 'Latn', 'native' => 'Aghem', 'regional' => ''],
12 | 'ak' => ['name' => 'Akan', 'script' => 'Latn', 'native' => 'Akan', 'regional' => 'ak_GH'],
13 | 'an' => ['name' => 'Aragonese', 'script' => 'Latn', 'native' => 'aragonés', 'regional' => 'an_ES'],
14 | 'cch' => ['name' => 'Atsam', 'script' => 'Latn', 'native' => 'Atsam', 'regional' => ''],
15 | 'gn' => ['name' => 'Guaraní', 'script' => 'Latn', 'native' => 'Avañe’ẽ', 'regional' => ''],
16 | 'ae' => ['name' => 'Avestan', 'script' => 'Latn', 'native' => 'avesta', 'regional' => ''],
17 | 'ay' => ['name' => 'Aymara', 'script' => 'Latn', 'native' => 'aymar aru', 'regional' => 'ay_PE'],
18 | 'az' => ['name' => 'Azerbaijani (Latin)', 'script' => 'Latn', 'native' => 'azərbaycanca', 'regional' => 'az_AZ'],
19 | 'id' => ['name' => 'Indonesian', 'script' => 'Latn', 'native' => 'Bahasa Indonesia', 'regional' => 'id_ID'],
20 | 'ms' => ['name' => 'Malay', 'script' => 'Latn', 'native' => 'Bahasa Melayu', 'regional' => 'ms_MY'],
21 | 'bm' => ['name' => 'Bambara', 'script' => 'Latn', 'native' => 'bamanakan', 'regional' => ''],
22 | 'jv' => ['name' => 'Javanese (Latin)', 'script' => 'Latn', 'native' => 'Basa Jawa', 'regional' => ''],
23 | 'su' => ['name' => 'Sundanese', 'script' => 'Latn', 'native' => 'Basa Sunda', 'regional' => ''],
24 | 'bh' => ['name' => 'Bihari', 'script' => 'Latn', 'native' => 'Bihari', 'regional' => ''],
25 | 'bi' => ['name' => 'Bislama', 'script' => 'Latn', 'native' => 'Bislama', 'regional' => ''],
26 | 'nb' => ['name' => 'Norwegian Bokmål', 'script' => 'Latn', 'native' => 'Bokmål', 'regional' => 'nb_NO'],
27 | 'bs' => ['name' => 'Bosnian', 'script' => 'Latn', 'native' => 'bosanski', 'regional' => 'bs_BA'],
28 | 'br' => ['name' => 'Breton', 'script' => 'Latn', 'native' => 'brezhoneg', 'regional' => 'br_FR'],
29 | 'ca' => ['name' => 'Catalan', 'script' => 'Latn', 'native' => 'català', 'regional' => 'ca_ES'],
30 | 'ch' => ['name' => 'Chamorro', 'script' => 'Latn', 'native' => 'Chamoru', 'regional' => ''],
31 | 'ny' => ['name' => 'Chewa', 'script' => 'Latn', 'native' => 'chiCheŵa', 'regional' => ''],
32 | 'kde' => ['name' => 'Makonde', 'script' => 'Latn', 'native' => 'Chimakonde', 'regional' => ''],
33 | 'sn' => ['name' => 'Shona', 'script' => 'Latn', 'native' => 'chiShona', 'regional' => ''],
34 | 'co' => ['name' => 'Corsican', 'script' => 'Latn', 'native' => 'corsu', 'regional' => ''],
35 | 'cy' => ['name' => 'Welsh', 'script' => 'Latn', 'native' => 'Cymraeg', 'regional' => 'cy_GB'],
36 | 'da' => ['name' => 'Danish', 'script' => 'Latn', 'native' => 'dansk', 'regional' => 'da_DK'],
37 | 'se' => ['name' => 'Northern Sami', 'script' => 'Latn', 'native' => 'davvisámegiella', 'regional' => 'se_NO'],
38 | 'de' => ['name' => 'German', 'script' => 'Latn', 'native' => 'Deutsch', 'regional' => 'de_DE'],
39 | 'luo' => ['name' => 'Luo', 'script' => 'Latn', 'native' => 'Dholuo', 'regional' => ''],
40 | 'nv' => ['name' => 'Navajo', 'script' => 'Latn', 'native' => 'Diné bizaad', 'regional' => ''],
41 | 'dua' => ['name' => 'Duala', 'script' => 'Latn', 'native' => 'duálá', 'regional' => ''],
42 | 'et' => ['name' => 'Estonian', 'script' => 'Latn', 'native' => 'eesti', 'regional' => 'et_EE'],
43 | 'na' => ['name' => 'Nauru', 'script' => 'Latn', 'native' => 'Ekakairũ Naoero', 'regional' => ''],
44 | 'guz' => ['name' => 'Ekegusii', 'script' => 'Latn', 'native' => 'Ekegusii', 'regional' => ''],
45 | 'en' => ['name' => 'English', 'script' => 'Latn', 'native' => 'English', 'regional' => 'en_GB'],
46 | 'en-AU' => ['name' => 'Australian English', 'script' => 'Latn', 'native' => 'Australian English', 'regional' => 'en_AU'],
47 | 'en-GB' => ['name' => 'British English', 'script' => 'Latn', 'native' => 'British English', 'regional' => 'en_GB'],
48 | 'en-US' => ['name' => 'U.S. English', 'script' => 'Latn', 'native' => 'U.S. English', 'regional' => 'en_US'],
49 | 'es' => ['name' => 'Spanish', 'script' => 'Latn', 'native' => 'español', 'regional' => 'es_ES'],
50 | 'eo' => ['name' => 'Esperanto', 'script' => 'Latn', 'native' => 'esperanto', 'regional' => ''],
51 | 'eu' => ['name' => 'Basque', 'script' => 'Latn', 'native' => 'euskara', 'regional' => 'eu_ES'],
52 | 'ewo' => ['name' => 'Ewondo', 'script' => 'Latn', 'native' => 'ewondo', 'regional' => ''],
53 | 'ee' => ['name' => 'Ewe', 'script' => 'Latn', 'native' => 'eʋegbe', 'regional' => ''],
54 | 'fil' => ['name' => 'Filipino', 'script' => 'Latn', 'native' => 'Filipino', 'regional' => 'fil_PH'],
55 | 'fr' => ['name' => 'French', 'script' => 'Latn', 'native' => 'français', 'regional' => 'fr_FR'],
56 | 'fr-CA' => ['name' => 'Canadian French', 'script' => 'Latn', 'native' => 'français canadien', 'regional' => 'fr_CA'],
57 | 'fy' => ['name' => 'Western Frisian', 'script' => 'Latn', 'native' => 'frysk', 'regional' => 'fy_DE'],
58 | 'fur' => ['name' => 'Friulian', 'script' => 'Latn', 'native' => 'furlan', 'regional' => 'fur_IT'],
59 | 'fo' => ['name' => 'Faroese', 'script' => 'Latn', 'native' => 'føroyskt', 'regional' => 'fo_FO'],
60 | 'gaa' => ['name' => 'Ga', 'script' => 'Latn', 'native' => 'Ga', 'regional' => ''],
61 | 'ga' => ['name' => 'Irish', 'script' => 'Latn', 'native' => 'Gaeilge', 'regional' => 'ga_IE'],
62 | 'gv' => ['name' => 'Manx', 'script' => 'Latn', 'native' => 'Gaelg', 'regional' => 'gv_GB'],
63 | 'sm' => ['name' => 'Samoan', 'script' => 'Latn', 'native' => 'Gagana fa’a Sāmoa', 'regional' => ''],
64 | 'gl' => ['name' => 'Galician', 'script' => 'Latn', 'native' => 'galego', 'regional' => 'gl_ES'],
65 | 'ki' => ['name' => 'Kikuyu', 'script' => 'Latn', 'native' => 'Gikuyu', 'regional' => ''],
66 | 'gd' => ['name' => 'Scottish Gaelic', 'script' => 'Latn', 'native' => 'Gàidhlig', 'regional' => 'gd_GB'],
67 | 'ha' => ['name' => 'Hausa', 'script' => 'Latn', 'native' => 'Hausa', 'regional' => 'ha_NG'],
68 | 'bez' => ['name' => 'Bena', 'script' => 'Latn', 'native' => 'Hibena', 'regional' => ''],
69 | 'ho' => ['name' => 'Hiri Motu', 'script' => 'Latn', 'native' => 'Hiri Motu', 'regional' => ''],
70 | 'hr' => ['name' => 'Croatian', 'script' => 'Latn', 'native' => 'hrvatski', 'regional' => 'hr_HR'],
71 | 'bem' => ['name' => 'Bemba', 'script' => 'Latn', 'native' => 'Ichibemba', 'regional' => 'bem_ZM'],
72 | 'io' => ['name' => 'Ido', 'script' => 'Latn', 'native' => 'Ido', 'regional' => ''],
73 | 'ig' => ['name' => 'Igbo', 'script' => 'Latn', 'native' => 'Igbo', 'regional' => 'ig_NG'],
74 | 'rn' => ['name' => 'Rundi', 'script' => 'Latn', 'native' => 'Ikirundi', 'regional' => ''],
75 | 'ia' => ['name' => 'Interlingua', 'script' => 'Latn', 'native' => 'interlingua', 'regional' => 'ia_FR'],
76 | 'iu-Latn' => ['name' => 'Inuktitut (Latin)', 'script' => 'Latn', 'native' => 'Inuktitut', 'regional' => 'iu_CA'],
77 | 'sbp' => ['name' => 'Sileibi', 'script' => 'Latn', 'native' => 'Ishisangu', 'regional' => ''],
78 | 'nd' => ['name' => 'North Ndebele', 'script' => 'Latn', 'native' => 'isiNdebele', 'regional' => ''],
79 | 'nr' => ['name' => 'South Ndebele', 'script' => 'Latn', 'native' => 'isiNdebele', 'regional' => 'nr_ZA'],
80 | 'xh' => ['name' => 'Xhosa', 'script' => 'Latn', 'native' => 'isiXhosa', 'regional' => 'xh_ZA'],
81 | 'zu' => ['name' => 'Zulu', 'script' => 'Latn', 'native' => 'isiZulu', 'regional' => 'zu_ZA'],
82 | 'it' => ['name' => 'Italian', 'script' => 'Latn', 'native' => 'italiano', 'regional' => 'it_IT'],
83 | 'ik' => ['name' => 'Inupiaq', 'script' => 'Latn', 'native' => 'Iñupiaq', 'regional' => 'ik_CA'],
84 | 'dyo' => ['name' => 'Jola-Fonyi', 'script' => 'Latn', 'native' => 'joola', 'regional' => ''],
85 | 'kea' => ['name' => 'Kabuverdianu', 'script' => 'Latn', 'native' => 'kabuverdianu', 'regional' => ''],
86 | 'kaj' => ['name' => 'Jju', 'script' => 'Latn', 'native' => 'Kaje', 'regional' => ''],
87 | 'mh' => ['name' => 'Marshallese', 'script' => 'Latn', 'native' => 'Kajin M̧ajeļ', 'regional' => 'mh_MH'],
88 | 'kl' => ['name' => 'Kalaallisut', 'script' => 'Latn', 'native' => 'kalaallisut', 'regional' => 'kl_GL'],
89 | 'kln' => ['name' => 'Kalenjin', 'script' => 'Latn', 'native' => 'Kalenjin', 'regional' => ''],
90 | 'kr' => ['name' => 'Kanuri', 'script' => 'Latn', 'native' => 'Kanuri', 'regional' => ''],
91 | 'kcg' => ['name' => 'Tyap', 'script' => 'Latn', 'native' => 'Katab', 'regional' => ''],
92 | 'kw' => ['name' => 'Cornish', 'script' => 'Latn', 'native' => 'kernewek', 'regional' => 'kw_GB'],
93 | 'naq' => ['name' => 'Nama', 'script' => 'Latn', 'native' => 'Khoekhoegowab', 'regional' => ''],
94 | 'rof' => ['name' => 'Rombo', 'script' => 'Latn', 'native' => 'Kihorombo', 'regional' => ''],
95 | 'kam' => ['name' => 'Kamba', 'script' => 'Latn', 'native' => 'Kikamba', 'regional' => ''],
96 | 'kg' => ['name' => 'Kongo', 'script' => 'Latn', 'native' => 'Kikongo', 'regional' => ''],
97 | 'jmc' => ['name' => 'Machame', 'script' => 'Latn', 'native' => 'Kimachame', 'regional' => ''],
98 | 'rw' => ['name' => 'Kinyarwanda', 'script' => 'Latn', 'native' => 'Kinyarwanda', 'regional' => 'rw_RW'],
99 | 'asa' => ['name' => 'Kipare', 'script' => 'Latn', 'native' => 'Kipare', 'regional' => ''],
100 | 'rwk' => ['name' => 'Rwa', 'script' => 'Latn', 'native' => 'Kiruwa', 'regional' => ''],
101 | 'saq' => ['name' => 'Samburu', 'script' => 'Latn', 'native' => 'Kisampur', 'regional' => ''],
102 | 'ksb' => ['name' => 'Shambala', 'script' => 'Latn', 'native' => 'Kishambaa', 'regional' => ''],
103 | 'swc' => ['name' => 'Congo Swahili', 'script' => 'Latn', 'native' => 'Kiswahili ya Kongo', 'regional' => ''],
104 | 'sw' => ['name' => 'Swahili', 'script' => 'Latn', 'native' => 'Kiswahili', 'regional' => 'sw_KE'],
105 | 'dav' => ['name' => 'Dawida', 'script' => 'Latn', 'native' => 'Kitaita', 'regional' => ''],
106 | 'teo' => ['name' => 'Teso', 'script' => 'Latn', 'native' => 'Kiteso', 'regional' => ''],
107 | 'khq' => ['name' => 'Koyra Chiini', 'script' => 'Latn', 'native' => 'Koyra ciini', 'regional' => ''],
108 | 'ses' => ['name' => 'Songhay', 'script' => 'Latn', 'native' => 'Koyraboro senni', 'regional' => ''],
109 | 'mfe' => ['name' => 'Morisyen', 'script' => 'Latn', 'native' => 'kreol morisien', 'regional' => ''],
110 | 'ht' => ['name' => 'Haitian', 'script' => 'Latn', 'native' => 'Kreyòl ayisyen', 'regional' => 'ht_HT'],
111 | 'kj' => ['name' => 'Kuanyama', 'script' => 'Latn', 'native' => 'Kwanyama', 'regional' => ''],
112 | 'ksh' => ['name' => 'Kölsch', 'script' => 'Latn', 'native' => 'Kölsch', 'regional' => ''],
113 | 'ebu' => ['name' => 'Kiembu', 'script' => 'Latn', 'native' => 'Kĩembu', 'regional' => ''],
114 | 'mer' => ['name' => 'Kimîîru', 'script' => 'Latn', 'native' => 'Kĩmĩrũ', 'regional' => ''],
115 | 'lag' => ['name' => 'Langi', 'script' => 'Latn', 'native' => 'Kɨlaangi', 'regional' => ''],
116 | 'lah' => ['name' => 'Lahnda', 'script' => 'Latn', 'native' => 'Lahnda', 'regional' => ''],
117 | 'la' => ['name' => 'Latin', 'script' => 'Latn', 'native' => 'latine', 'regional' => ''],
118 | 'lv' => ['name' => 'Latvian', 'script' => 'Latn', 'native' => 'latviešu', 'regional' => 'lv_LV'],
119 | 'to' => ['name' => 'Tongan', 'script' => 'Latn', 'native' => 'lea fakatonga', 'regional' => ''],
120 | 'lt' => ['name' => 'Lithuanian', 'script' => 'Latn', 'native' => 'lietuvių', 'regional' => 'lt_LT'],
121 | 'li' => ['name' => 'Limburgish', 'script' => 'Latn', 'native' => 'Limburgs', 'regional' => 'li_BE'],
122 | 'ln' => ['name' => 'Lingala', 'script' => 'Latn', 'native' => 'lingála', 'regional' => ''],
123 | 'lg' => ['name' => 'Ganda', 'script' => 'Latn', 'native' => 'Luganda', 'regional' => 'lg_UG'],
124 | 'luy' => ['name' => 'Oluluyia', 'script' => 'Latn', 'native' => 'Luluhia', 'regional' => ''],
125 | 'lb' => ['name' => 'Luxembourgish', 'script' => 'Latn', 'native' => 'Lëtzebuergesch', 'regional' => 'lb_LU'],
126 | 'hu' => ['name' => 'Hungarian', 'script' => 'Latn', 'native' => 'magyar', 'regional' => 'hu_HU'],
127 | 'mgh' => ['name' => 'Makhuwa-Meetto', 'script' => 'Latn', 'native' => 'Makua', 'regional' => ''],
128 | 'mg' => ['name' => 'Malagasy', 'script' => 'Latn', 'native' => 'Malagasy', 'regional' => 'mg_MG'],
129 | 'mt' => ['name' => 'Maltese', 'script' => 'Latn', 'native' => 'Malti', 'regional' => 'mt_MT'],
130 | 'mtr' => ['name' => 'Mewari', 'script' => 'Latn', 'native' => 'Mewari', 'regional' => ''],
131 | 'mua' => ['name' => 'Mundang', 'script' => 'Latn', 'native' => 'Mundang', 'regional' => ''],
132 | 'mi' => ['name' => 'Māori', 'script' => 'Latn', 'native' => 'Māori', 'regional' => 'mi_NZ'],
133 | 'nl' => ['name' => 'Dutch', 'script' => 'Latn', 'native' => 'Nederlands', 'regional' => 'nl_NL'],
134 | 'nmg' => ['name' => 'Kwasio', 'script' => 'Latn', 'native' => 'ngumba', 'regional' => ''],
135 | 'yav' => ['name' => 'Yangben', 'script' => 'Latn', 'native' => 'nuasue', 'regional' => ''],
136 | 'nn' => ['name' => 'Norwegian Nynorsk', 'script' => 'Latn', 'native' => 'nynorsk', 'regional' => 'nn_NO'],
137 | 'oc' => ['name' => 'Occitan', 'script' => 'Latn', 'native' => 'occitan', 'regional' => 'oc_FR'],
138 | 'ang' => ['name' => 'Old English', 'script' => 'Runr', 'native' => 'Old English', 'regional' => ''],
139 | 'xog' => ['name' => 'Soga', 'script' => 'Latn', 'native' => 'Olusoga', 'regional' => ''],
140 | 'om' => ['name' => 'Oromo', 'script' => 'Latn', 'native' => 'Oromoo', 'regional' => 'om_ET'],
141 | 'ng' => ['name' => 'Ndonga', 'script' => 'Latn', 'native' => 'OshiNdonga', 'regional' => ''],
142 | 'hz' => ['name' => 'Herero', 'script' => 'Latn', 'native' => 'Otjiherero', 'regional' => ''],
143 | 'uz-Latn' => ['name' => 'Uzbek (Latin)', 'script' => 'Latn', 'native' => 'oʼzbekcha', 'regional' => 'uz_UZ'],
144 | 'nds' => ['name' => 'Low German', 'script' => 'Latn', 'native' => 'Plattdüütsch', 'regional' => 'nds_DE'],
145 | 'pl' => ['name' => 'Polish', 'script' => 'Latn', 'native' => 'polski', 'regional' => 'pl_PL'],
146 | 'pt' => ['name' => 'Portuguese', 'script' => 'Latn', 'native' => 'português', 'regional' => 'pt_PT'],
147 | 'pt-BR' => ['name' => 'Brazilian Portuguese', 'script' => 'Latn', 'native' => 'português do Brasil', 'regional' => 'pt_BR'],
148 | 'ff' => ['name' => 'Fulah', 'script' => 'Latn', 'native' => 'Pulaar', 'regional' => 'ff_SN'],
149 | 'pi' => ['name' => 'Pahari-Potwari', 'script' => 'Latn', 'native' => 'Pāli', 'regional' => ''],
150 | 'aa' => ['name' => 'Afar', 'script' => 'Latn', 'native' => 'Qafar', 'regional' => 'aa_ER'],
151 | 'ty' => ['name' => 'Tahitian', 'script' => 'Latn', 'native' => 'Reo Māohi', 'regional' => ''],
152 | 'ksf' => ['name' => 'Bafia', 'script' => 'Latn', 'native' => 'rikpa', 'regional' => ''],
153 | 'ro' => ['name' => 'Romanian', 'script' => 'Latn', 'native' => 'română', 'regional' => 'ro_RO'],
154 | 'cgg' => ['name' => 'Chiga', 'script' => 'Latn', 'native' => 'Rukiga', 'regional' => ''],
155 | 'rm' => ['name' => 'Romansh', 'script' => 'Latn', 'native' => 'rumantsch', 'regional' => ''],
156 | 'qu' => ['name' => 'Quechua', 'script' => 'Latn', 'native' => 'Runa Simi', 'regional' => ''],
157 | 'nyn' => ['name' => 'Nyankole', 'script' => 'Latn', 'native' => 'Runyankore', 'regional' => ''],
158 | 'ssy' => ['name' => 'Saho', 'script' => 'Latn', 'native' => 'Saho', 'regional' => ''],
159 | 'sc' => ['name' => 'Sardinian', 'script' => 'Latn', 'native' => 'sardu', 'regional' => 'sc_IT'],
160 | 'de-CH' => ['name' => 'Swiss High German', 'script' => 'Latn', 'native' => 'Schweizer Hochdeutsch', 'regional' => 'de_CH'],
161 | 'gsw' => ['name' => 'Swiss German', 'script' => 'Latn', 'native' => 'Schwiizertüütsch', 'regional' => ''],
162 | 'trv' => ['name' => 'Taroko', 'script' => 'Latn', 'native' => 'Seediq', 'regional' => ''],
163 | 'seh' => ['name' => 'Sena', 'script' => 'Latn', 'native' => 'sena', 'regional' => ''],
164 | 'nso' => ['name' => 'Northern Sotho', 'script' => 'Latn', 'native' => 'Sesotho sa Leboa', 'regional' => 'nso_ZA'],
165 | 'st' => ['name' => 'Southern Sotho', 'script' => 'Latn', 'native' => 'Sesotho', 'regional' => 'st_ZA'],
166 | 'tn' => ['name' => 'Tswana', 'script' => 'Latn', 'native' => 'Setswana', 'regional' => 'tn_ZA'],
167 | 'sq' => ['name' => 'Albanian', 'script' => 'Latn', 'native' => 'shqip', 'regional' => 'sq_AL'],
168 | 'sid' => ['name' => 'Sidamo', 'script' => 'Latn', 'native' => 'Sidaamu Afo', 'regional' => 'sid_ET'],
169 | 'ss' => ['name' => 'Swati', 'script' => 'Latn', 'native' => 'Siswati', 'regional' => 'ss_ZA'],
170 | 'sk' => ['name' => 'Slovak', 'script' => 'Latn', 'native' => 'slovenčina', 'regional' => 'sk_SK'],
171 | 'sl' => ['name' => 'Slovene', 'script' => 'Latn', 'native' => 'slovenščina', 'regional' => 'sl_SI'],
172 | 'so' => ['name' => 'Somali', 'script' => 'Latn', 'native' => 'Soomaali', 'regional' => 'so_SO'],
173 | 'sr-Latn' => ['name' => 'Serbian (Latin)', 'script' => 'Latn', 'native' => 'Srpski', 'regional' => 'sr_RS'],
174 | 'sh' => ['name' => 'Serbo-Croatian', 'script' => 'Latn', 'native' => 'srpskohrvatski', 'regional' => ''],
175 | 'fi' => ['name' => 'Finnish', 'script' => 'Latn', 'native' => 'suomi', 'regional' => 'fi_FI'],
176 | 'sv' => ['name' => 'Swedish', 'script' => 'Latn', 'native' => 'svenska', 'regional' => 'sv_SE'],
177 | 'sg' => ['name' => 'Sango', 'script' => 'Latn', 'native' => 'Sängö', 'regional' => ''],
178 | 'tl' => ['name' => 'Tagalog', 'script' => 'Latn', 'native' => 'Tagalog', 'regional' => 'tl_PH'],
179 | 'tzm-Latn' => ['name' => 'Central Atlas Tamazight (Latin)', 'script' => 'Latn', 'native' => 'Tamazight', 'regional' => ''],
180 | 'kab' => ['name' => 'Kabyle', 'script' => 'Latn', 'native' => 'Taqbaylit', 'regional' => 'kab_DZ'],
181 | 'twq' => ['name' => 'Tasawaq', 'script' => 'Latn', 'native' => 'Tasawaq senni', 'regional' => ''],
182 | 'shi' => ['name' => 'Tachelhit (Latin)', 'script' => 'Latn', 'native' => 'Tashelhit', 'regional' => ''],
183 | 'nus' => ['name' => 'Nuer', 'script' => 'Latn', 'native' => 'Thok Nath', 'regional' => ''],
184 | 'vi' => ['name' => 'Vietnamese', 'script' => 'Latn', 'native' => 'Tiếng Việt', 'regional' => 'vi_VN'],
185 | 'tg-Latn' => ['name' => 'Tajik (Latin)', 'script' => 'Latn', 'native' => 'tojikī', 'regional' => 'tg_TJ'],
186 | 'lu' => ['name' => 'Luba-Katanga', 'script' => 'Latn', 'native' => 'Tshiluba', 'regional' => 've_ZA'],
187 | 've' => ['name' => 'Venda', 'script' => 'Latn', 'native' => 'Tshivenḓa', 'regional' => ''],
188 | 'tw' => ['name' => 'Twi', 'script' => 'Latn', 'native' => 'Twi', 'regional' => ''],
189 | 'tr' => ['name' => 'Turkish', 'script' => 'Latn', 'native' => 'Türkçe', 'regional' => 'tr_TR'],
190 | 'ale' => ['name' => 'Aleut', 'script' => 'Latn', 'native' => 'Unangax tunuu', 'regional' => ''],
191 | 'ca-valencia' => ['name' => 'Valencian', 'script' => 'Latn', 'native' => 'valencià', 'regional' => ''],
192 | 'vai-Latn' => ['name' => 'Vai (Latin)', 'script' => 'Latn', 'native' => 'Viyamíĩ', 'regional' => ''],
193 | 'vo' => ['name' => 'Volapük', 'script' => 'Latn', 'native' => 'Volapük', 'regional' => ''],
194 | 'fj' => ['name' => 'Fijian', 'script' => 'Latn', 'native' => 'vosa Vakaviti', 'regional' => ''],
195 | 'wa' => ['name' => 'Walloon', 'script' => 'Latn', 'native' => 'Walon', 'regional' => 'wa_BE'],
196 | 'wae' => ['name' => 'Walser', 'script' => 'Latn', 'native' => 'Walser', 'regional' => 'wae_CH'],
197 | 'wen' => ['name' => 'Sorbian', 'script' => 'Latn', 'native' => 'Wendic', 'regional' => ''],
198 | 'wo' => ['name' => 'Wolof', 'script' => 'Latn', 'native' => 'Wolof', 'regional' => 'wo_SN'],
199 | 'ts' => ['name' => 'Tsonga', 'script' => 'Latn', 'native' => 'Xitsonga', 'regional' => 'ts_ZA'],
200 | 'dje' => ['name' => 'Zarma', 'script' => 'Latn', 'native' => 'Zarmaciine', 'regional' => ''],
201 | 'yo' => ['name' => 'Yoruba', 'script' => 'Latn', 'native' => 'Èdè Yorùbá', 'regional' => 'yo_NG'],
202 | 'de-AT' => ['name' => 'Austrian German', 'script' => 'Latn', 'native' => 'Österreichisches Deutsch', 'regional' => 'de_AT'],
203 | 'is' => ['name' => 'Icelandic', 'script' => 'Latn', 'native' => 'íslenska', 'regional' => 'is_IS'],
204 | 'cs' => ['name' => 'Czech', 'script' => 'Latn', 'native' => 'čeština', 'regional' => 'cs_CZ'],
205 | 'bas' => ['name' => 'Basa', 'script' => 'Latn', 'native' => 'Ɓàsàa', 'regional' => ''],
206 | 'mas' => ['name' => 'Masai', 'script' => 'Latn', 'native' => 'ɔl-Maa', 'regional' => ''],
207 | 'haw' => ['name' => 'Hawaiian', 'script' => 'Latn', 'native' => 'ʻŌlelo Hawaiʻi', 'regional' => ''],
208 | 'el' => ['name' => 'Greek', 'script' => 'Grek', 'native' => 'Ελληνικά', 'regional' => 'el_GR'],
209 | 'uz' => ['name' => 'Uzbek (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Ўзбек', 'regional' => 'uz_UZ'],
210 | 'az-Cyrl' => ['name' => 'Azerbaijani (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Азәрбајҹан', 'regional' => 'uz_UZ'],
211 | 'ab' => ['name' => 'Abkhazian', 'script' => 'Cyrl', 'native' => 'Аҧсуа', 'regional' => ''],
212 | 'os' => ['name' => 'Ossetic', 'script' => 'Cyrl', 'native' => 'Ирон', 'regional' => 'os_RU'],
213 | 'ky' => ['name' => 'Kyrgyz', 'script' => 'Cyrl', 'native' => 'Кыргыз', 'regional' => 'ky_KG'],
214 | 'sr' => ['name' => 'Serbian (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Српски', 'regional' => 'sr_RS'],
215 | 'av' => ['name' => 'Avaric', 'script' => 'Cyrl', 'native' => 'авар мацӀ', 'regional' => ''],
216 | 'ady' => ['name' => 'Adyghe', 'script' => 'Cyrl', 'native' => 'адыгэбзэ', 'regional' => ''],
217 | 'ba' => ['name' => 'Bashkir', 'script' => 'Cyrl', 'native' => 'башҡорт теле', 'regional' => ''],
218 | 'be' => ['name' => 'Belarusian', 'script' => 'Cyrl', 'native' => 'беларуская', 'regional' => 'be_BY'],
219 | 'bg' => ['name' => 'Bulgarian', 'script' => 'Cyrl', 'native' => 'български', 'regional' => 'bg_BG'],
220 | 'kv' => ['name' => 'Komi', 'script' => 'Cyrl', 'native' => 'коми кыв', 'regional' => ''],
221 | 'mk' => ['name' => 'Macedonian', 'script' => 'Cyrl', 'native' => 'македонски', 'regional' => 'mk_MK'],
222 | 'mn' => ['name' => 'Mongolian (Cyrillic)', 'script' => 'Cyrl', 'native' => 'монгол', 'regional' => 'mn_MN'],
223 | 'ce' => ['name' => 'Chechen', 'script' => 'Cyrl', 'native' => 'нохчийн мотт', 'regional' => 'ce_RU'],
224 | 'ru' => ['name' => 'Russian', 'script' => 'Cyrl', 'native' => 'русский', 'regional' => 'ru_RU'],
225 | 'sah' => ['name' => 'Yakut', 'script' => 'Cyrl', 'native' => 'саха тыла', 'regional' => ''],
226 | 'tt' => ['name' => 'Tatar', 'script' => 'Cyrl', 'native' => 'татар теле', 'regional' => 'tt_RU'],
227 | 'tg' => ['name' => 'Tajik (Cyrillic)', 'script' => 'Cyrl', 'native' => 'тоҷикӣ', 'regional' => 'tg_TJ'],
228 | 'tk' => ['name' => 'Turkmen', 'script' => 'Cyrl', 'native' => 'түркменче', 'regional' => 'tk_TM'],
229 | 'uk' => ['name' => 'Ukrainian', 'script' => 'Cyrl', 'native' => 'українська', 'regional' => 'uk_UA'],
230 | 'cv' => ['name' => 'Chuvash', 'script' => 'Cyrl', 'native' => 'чӑваш чӗлхи', 'regional' => 'cv_RU'],
231 | 'cu' => ['name' => 'Church Slavic', 'script' => 'Cyrl', 'native' => 'ѩзыкъ словѣньскъ', 'regional' => ''],
232 | 'kk' => ['name' => 'Kazakh', 'script' => 'Cyrl', 'native' => 'қазақ тілі', 'regional' => 'kk_KZ'],
233 | 'hy' => ['name' => 'Armenian', 'script' => 'Armn', 'native' => 'Հայերեն', 'regional' => 'hy_AM'],
234 | 'yi' => ['name' => 'Yiddish', 'script' => 'Hebr', 'native' => 'ייִדיש', 'regional' => 'yi_US'],
235 | 'he' => ['name' => 'Hebrew', 'script' => 'Hebr', 'native' => 'עברית', 'regional' => 'he_IL'],
236 | 'ug' => ['name' => 'Uyghur', 'script' => 'Arab', 'native' => 'ئۇيغۇرچە', 'regional' => 'ug_CN'],
237 | 'ur' => ['name' => 'Urdu', 'script' => 'Arab', 'native' => 'اردو', 'regional' => 'ur_PK'],
238 | 'ar' => ['name' => 'Arabic', 'script' => 'Arab', 'native' => 'العربية', 'regional' => 'ar_AE'],
239 | 'uz-Arab' => ['name' => 'Uzbek (Arabic)', 'script' => 'Arab', 'native' => 'اۉزبېک', 'regional' => ''],
240 | 'tg-Arab' => ['name' => 'Tajik (Arabic)', 'script' => 'Arab', 'native' => 'تاجیکی', 'regional' => 'tg_TJ'],
241 | 'sd' => ['name' => 'Sindhi', 'script' => 'Arab', 'native' => 'سنڌي', 'regional' => 'sd_IN'],
242 | 'fa' => ['name' => 'Persian', 'script' => 'Arab', 'native' => 'فارسی', 'regional' => 'fa_IR'],
243 | 'pa-Arab' => ['name' => 'Punjabi (Arabic)', 'script' => 'Arab', 'native' => 'پنجاب', 'regional' => 'pa_IN'],
244 | 'ps' => ['name' => 'Pashto', 'script' => 'Arab', 'native' => 'پښتو', 'regional' => 'ps_AF'],
245 | 'ks' => ['name' => 'Kashmiri (Arabic)', 'script' => 'Arab', 'native' => 'کأشُر', 'regional' => 'ks_IN'],
246 | 'ku' => ['name' => 'Kurdish', 'script' => 'Arab', 'native' => 'کوردی', 'regional' => 'ku_TR'],
247 | 'dv' => ['name' => 'Divehi', 'script' => 'Thaa', 'native' => 'ދިވެހިބަސް', 'regional' => 'dv_MV'],
248 | 'ks-Deva' => ['name' => 'Kashmiri (Devaganari)', 'script' => 'Deva', 'native' => 'कॉशुर', 'regional' => 'ks_IN'],
249 | 'kok' => ['name' => 'Konkani', 'script' => 'Deva', 'native' => 'कोंकणी', 'regional' => 'kok_IN'],
250 | 'doi' => ['name' => 'Dogri', 'script' => 'Deva', 'native' => 'डोगरी', 'regional' => 'doi_IN'],
251 | 'ne' => ['name' => 'Nepali', 'script' => 'Deva', 'native' => 'नेपाली', 'regional' => ''],
252 | 'pra' => ['name' => 'Prakrit', 'script' => 'Deva', 'native' => 'प्राकृत', 'regional' => ''],
253 | 'brx' => ['name' => 'Bodo', 'script' => 'Deva', 'native' => 'बड़ो', 'regional' => 'brx_IN'],
254 | 'bra' => ['name' => 'Braj', 'script' => 'Deva', 'native' => 'ब्रज भाषा', 'regional' => ''],
255 | 'mr' => ['name' => 'Marathi', 'script' => 'Deva', 'native' => 'मराठी', 'regional' => 'mr_IN'],
256 | 'mai' => ['name' => 'Maithili', 'script' => 'Tirh', 'native' => 'मैथिली', 'regional' => 'mai_IN'],
257 | 'raj' => ['name' => 'Rajasthani', 'script' => 'Deva', 'native' => 'राजस्थानी', 'regional' => ''],
258 | 'sa' => ['name' => 'Sanskrit', 'script' => 'Deva', 'native' => 'संस्कृतम्', 'regional' => 'sa_IN'],
259 | 'hi' => ['name' => 'Hindi', 'script' => 'Deva', 'native' => 'हिन्दी', 'regional' => 'hi_IN'],
260 | 'as' => ['name' => 'Assamese', 'script' => 'Beng', 'native' => 'অসমীয়া', 'regional' => 'as_IN'],
261 | 'bn' => ['name' => 'Bengali', 'script' => 'Beng', 'native' => 'বাংলা', 'regional' => 'bn_BD'],
262 | 'mni' => ['name' => 'Manipuri', 'script' => 'Beng', 'native' => 'মৈতৈ', 'regional' => 'mni_IN'],
263 | 'pa' => ['name' => 'Punjabi (Gurmukhi)', 'script' => 'Guru', 'native' => 'ਪੰਜਾਬੀ', 'regional' => 'pa_IN'],
264 | 'gu' => ['name' => 'Gujarati', 'script' => 'Gujr', 'native' => 'ગુજરાતી', 'regional' => 'gu_IN'],
265 | 'or' => ['name' => 'Oriya', 'script' => 'Orya', 'native' => 'ଓଡ଼ିଆ', 'regional' => 'or_IN'],
266 | 'ta' => ['name' => 'Tamil', 'script' => 'Taml', 'native' => 'தமிழ்', 'regional' => 'ta_IN'],
267 | 'te' => ['name' => 'Telugu', 'script' => 'Telu', 'native' => 'తెలుగు', 'regional' => 'te_IN'],
268 | 'kn' => ['name' => 'Kannada', 'script' => 'Knda', 'native' => 'ಕನ್ನಡ', 'regional' => 'kn_IN'],
269 | 'ml' => ['name' => 'Malayalam', 'script' => 'Mlym', 'native' => 'മലയാളം', 'regional' => 'ml_IN'],
270 | 'si' => ['name' => 'Sinhala', 'script' => 'Sinh', 'native' => 'සිංහල', 'regional' => 'si_LK'],
271 | 'th' => ['name' => 'Thai', 'script' => 'Thai', 'native' => 'ไทย', 'regional' => 'th_TH'],
272 | 'lo' => ['name' => 'Lao', 'script' => 'Laoo', 'native' => 'ລາວ', 'regional' => 'lo_LA'],
273 | 'bo' => ['name' => 'Tibetan', 'script' => 'Tibt', 'native' => 'པོད་སྐད་', 'regional' => 'bo_IN'],
274 | 'dz' => ['name' => 'Dzongkha', 'script' => 'Tibt', 'native' => 'རྫོང་ཁ', 'regional' => 'dz_BT'],
275 | 'my' => ['name' => 'Burmese', 'script' => 'Mymr', 'native' => 'မြန်မာဘာသာ', 'regional' => 'my_MM'],
276 | 'ka' => ['name' => 'Georgian', 'script' => 'Geor', 'native' => 'ქართული', 'regional' => 'ka_GE'],
277 | 'byn' => ['name' => 'Blin', 'script' => 'Ethi', 'native' => 'ብሊን', 'regional' => 'byn_ER'],
278 | 'tig' => ['name' => 'Tigre', 'script' => 'Ethi', 'native' => 'ትግረ', 'regional' => 'tig_ER'],
279 | 'ti' => ['name' => 'Tigrinya', 'script' => 'Ethi', 'native' => 'ትግርኛ', 'regional' => 'ti_ET'],
280 | 'am' => ['name' => 'Amharic', 'script' => 'Ethi', 'native' => 'አማርኛ', 'regional' => 'am_ET'],
281 | 'wal' => ['name' => 'Wolaytta', 'script' => 'Ethi', 'native' => 'ወላይታቱ', 'regional' => 'wal_ET'],
282 | 'chr' => ['name' => 'Cherokee', 'script' => 'Cher', 'native' => 'ᏣᎳᎩ', 'regional' => ''],
283 | 'iu' => ['name' => 'Inuktitut (Canadian Aboriginal Syllabics)', 'script' => 'Cans', 'native' => 'ᐃᓄᒃᑎᑐᑦ', 'regional' => 'iu_CA'],
284 | 'oj' => ['name' => 'Ojibwa', 'script' => 'Cans', 'native' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ', 'regional' => ''],
285 | 'cr' => ['name' => 'Cree', 'script' => 'Cans', 'native' => 'ᓀᐦᐃᔭᐍᐏᐣ', 'regional' => ''],
286 | 'km' => ['name' => 'Khmer', 'script' => 'Khmr', 'native' => 'ភាសាខ្មែរ', 'regional' => 'km_KH'],
287 | 'mn-Mong' => ['name' => 'Mongolian (Mongolian)', 'script' => 'Mong', 'native' => 'ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ', 'regional' => 'mn_MN'],
288 | 'shi-Tfng' => ['name' => 'Tachelhit (Tifinagh)', 'script' => 'Tfng', 'native' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', 'regional' => ''],
289 | 'tzm' => ['name' => 'Central Atlas Tamazight (Tifinagh)','script' => 'Tfng', 'native' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', 'regional' => ''],
290 | 'yue' => ['name' => 'Yue', 'script' => 'Hant', 'native' => '廣州話', 'regional' => 'yue_HK'],
291 | 'ja' => ['name' => 'Japanese', 'script' => 'Jpan', 'native' => '日本語', 'regional' => 'ja_JP'],
292 | 'zh' => ['name' => 'Chinese (Simplified)', 'script' => 'Hans', 'native' => '简体中文', 'regional' => 'zh_CN'],
293 | 'zh-Hant' => ['name' => 'Chinese (Traditional)', 'script' => 'Hant', 'native' => '繁體中文', 'regional' => 'zh_CN'],
294 | 'ii' => ['name' => 'Sichuan Yi', 'script' => 'Yiii', 'native' => 'ꆈꌠꉙ', 'regional' => ''],
295 | 'vai' => ['name' => 'Vai (Vai)', 'script' => 'Vaii', 'native' => 'ꕙꔤ', 'regional' => ''],
296 | 'jv-Java' => ['name' => 'Javanese (Javanese)', 'script' => 'Java', 'native' => 'ꦧꦱꦗꦮ', 'regional' => ''],
297 | 'ko' => ['name' => 'Korean', 'script' => 'Hang', 'native' => '한국어', 'regional' => 'ko_KR'],
298 | ],
299 |
300 | // Negotiate for the user locale using the Accept-Language header if it's not defined in the URL?
301 | // If false, system will take app.php locale attribute
302 | 'useAcceptLanguageHeader' => true,
303 |
304 | // If LaravelLocalizationRedirectFilter is active and hideDefaultLocaleInURL
305 | // is true, the url would not have the default application language
306 | //
307 | // IMPORTANT - When hideDefaultLocaleInURL is set to true, the unlocalized root is treated as the applications default locale "app.locale".
308 | // Because of this language negotiation using the Accept-Language header will NEVER occur when hideDefaultLocaleInURL is true.
309 | //
310 | 'hideDefaultLocaleInURL' => false,
311 |
312 | // If you want to display the locales in particular order in the language selector you should write the order here.
313 | //CAUTION: Please consider using the appropriate locale code otherwise it will not work
314 | //Example: 'localesOrder' => ['es','en'],
315 | 'localesOrder' => [],
316 |
317 | // If you want to use custom lang url segments like 'at' instead of 'de-AT', you can use the mapping to tallow the LanguageNegotiator to assign the descired locales based on HTTP Accept Language Header. For example you want ot use 'at', so map HTTP Accept Language Header 'de-AT' to 'at' (['de-AT' => 'at']).
318 | 'localesMapping' => [],
319 |
320 | // URLs which should not be processed, e.g. '/nova', '/nova/*', '/nova-api/*' or specific application URLs
321 | // Defaults to []
322 | 'urlsIgnored' => [],
323 | ];
324 |
--------------------------------------------------------------------------------
/tests/lang/en/routes.php:
--------------------------------------------------------------------------------
1 | 'about',
5 | 'view' => 'view/{id}',
6 | 'view_project' => 'view/{id}/project/{project_id?}',
7 | 'manage' => 'manage/{file_id?}',
8 | 'hello' => 'Hello world',
9 | 'test_text' => 'Test text',
10 | ];
11 |
--------------------------------------------------------------------------------
/tests/lang/es/routes.php:
--------------------------------------------------------------------------------
1 | 'acerca',
5 | 'view' => 'ver/{id}',
6 | 'view_project' => 'ver/{id}/proyecto/{project_id?}',
7 | 'manage' => 'administrar/{file_id?}',
8 | 'hello' => 'Hola mundo',
9 | 'test_text' => 'Texto de prueba',
10 | ];
11 |
--------------------------------------------------------------------------------