├── .editorconfig
├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── CONTRIBUTING.md
├── CREDITS
├── LICENSE.md
├── README.md
├── build
├── coverage
│ └── .gitignore
├── logs
│ └── .gitignore
├── pre-commit
└── setup.sh
├── composer.json
├── composer.lock
├── phpcs.xml
├── phpunit.xml.dist
├── src
├── MultiLang
│ ├── Config.php
│ ├── Console
│ │ ├── ExportCommand.php
│ │ ├── ImportCommand.php
│ │ ├── MigrationCommand.php
│ │ └── TextsCommand.php
│ ├── Facades
│ │ └── MultiLang.php
│ ├── Middleware
│ │ └── MultiLang.php
│ ├── Models
│ │ ├── Localizable.php
│ │ ├── LocalizableScope.php
│ │ └── Text.php
│ ├── MultiLang.php
│ ├── MultiLangServiceProvider.php
│ └── Repository.php
├── config
│ └── config.php
├── helpers.php
├── stubs
│ └── migrations
│ │ └── texts.stub
└── views
│ ├── app.blade.php
│ └── index.blade.php
└── tests
├── Bootstrap.php
└── Unit
├── AbstractTestCase.php
├── ConfigTest.php
├── HelpersTest.php
├── Middleware
└── MultiLangTest.php
├── MultiLangFacadeTest.php
├── MultiLangTest.php
└── RepositoryTest.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.yml]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | # Tab indentation (no size specified)
19 | [Makefile]
20 | indent_style = tab
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE & System Related Files #
2 | .buildpath
3 | .project
4 | .settings
5 | .DS_Store
6 | .idea
7 | .phpintel
8 | composer.phar
9 | .phpunit.result.cache
10 |
11 | # Local System Files (i.e. cache, logs, etc.) #
12 | /cache
13 | /tmp
14 |
15 | # Test Related Files #
16 | /phpunit.xml
17 |
18 | # Composer
19 | vendor/
20 |
21 | .fuse_hidden*
22 |
23 | # phpDocumentor Logs #
24 | phpdoc-*
25 |
26 | # OSX #
27 | ._*
28 | .Spotlight-V100
29 | .Trashes
30 | _ide_helper.php
31 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | external_code_coverage:
3 | timeout: 600
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: focal
2 | language: php
3 |
4 | php:
5 | - 7.4
6 | - 8.0
7 |
8 | sudo: false
9 |
10 | before_install:
11 | - composer self-update
12 |
13 | install:
14 | - travis_retry composer update --no-interaction --prefer-source
15 |
16 | script:
17 | - composer phpcs
18 | - composer coverage-clover
19 |
20 |
21 | after_script:
22 | - wget https://scrutinizer-ci.com/ocular.phar
23 | - then php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
24 |
25 | matrix:
26 | fast_finish: true
27 |
28 |
29 | notifications:
30 | on_success: never
31 | on_failure: always
32 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | -------------
3 |
4 | Before you contribute code to this project, please make sure it conforms to the PSR-2 coding standard
5 | and that the project unit tests still pass. The easiest way to contribute is to work on a checkout of the repository,
6 | or your own fork. If you do this, you can run the following commands to check if everything is ready to submit:
7 |
8 | cd project
9 | composer update
10 | ./vendor/bin/phpcs --standard=phpcs.xml -spn --encoding=utf-8 src/ --report-width=150
11 |
12 | Which should give you no output, indicating that there are no coding standard errors. And then:
13 |
14 | ./vendor/bin/phpunit
15 |
16 | Which should give you no failures or errors. You can ignore any skipped tests as these are for external tools.
17 |
18 | Pushing
19 | -------
20 |
21 | Development is based on the git flow branching model (see http://nvie.com/posts/a-successful-git-branching-model/ )
22 | If you fix a bug please push in hotfix branch.
23 | If you develop a new feature please create a new branch.
24 |
25 | Version
26 | -------
27 | Version number: 0.#version.#hotfix
28 |
--------------------------------------------------------------------------------
/CREDITS:
--------------------------------------------------------------------------------
1 | This is at least a partial credits-file of people that have
2 | contributed to the current project. It is sorted by name and
3 | formatted to allow easy grepping and beautification by
4 | scripts. The fields are: name (N), email (E), web-address
5 | (W) and description (D).
6 | Thanks,
7 |
8 | Avtandil Kikabidze
9 | ----------
10 |
11 | N: Avtandil Kikabidze aka LONGMAN
12 | E: akalongman@gmail.com
13 | W: http://longman.me
14 | D: Project owner, Maintainer
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The [MIT License](http://opensource.org/licenses/mit-license.php)
2 |
3 | Copyright (c) 2016 [Avtandil Kikabidze aka LONGMAN](https://github.com/akalongman)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel 5.x MultiLanguage
2 |
3 | [](https://travis-ci.org/akalongman/laravel-multilang)
4 | [](https://scrutinizer-ci.com/g/akalongman/laravel-multilang/?branch=master)
5 | [](https://scrutinizer-ci.com/g/akalongman/laravel-multilang/?branch=master)
6 | [](https://github.com/akalongman/laravel-multilang/releases)
7 | [](https://packagist.org/packages/longman/laravel-multilang)
8 | [](https://packagist.org/packages/longman/laravel-multilang)
9 | [](LICENSE.md)
10 |
11 | *This version of MultiLang package requires minimum PHP 7.0.
12 | For older PHP versions use MultiLang [1.x](https://github.com/akalongman/laravel-multilang/releases/tag/1.3.3)*
13 |
14 | This is a very useful package to integrate multi language (multi locale) functionality in Laravel 5.x.
15 | It includes a ServiceProvider to register the multilang and Middleware for automatic modification routes like `http://site.com/en/your-routes`.
16 |
17 | This package uses database for storing translations (it caches data on production environment for improving performance)
18 | Also package automatically adds in database missing keys (on the local environment only).
19 |
20 | ## Table of Contents
21 | - [Installation](#installation)
22 | - [Usage](#usage)
23 | - [Translating](#translating)
24 | - [Blade Templates](#blade-templates)
25 | - [URL Generation](#url-generation)
26 | - [Text Scopes](#text-scopes)
27 | - [Import/Export Texts](#importexport-texts)
28 | - [TODO](#todo)
29 | - [Troubleshooting](#troubleshooting)
30 | - [Contributing](#contributing)
31 | - [License](#license)
32 | - [Credits](#credits)
33 |
34 |
35 | ## Installation
36 |
37 | Install this package through [Composer](https://getcomposer.org/).
38 |
39 | Edit your project's `composer.json` file to require `longman/laravel-multilang`
40 |
41 | Create *composer.json* file:
42 | ```json
43 | {
44 | "name": "yourproject/yourproject",
45 | "type": "project",
46 | "require": {
47 | "longman/laravel-multilang": "~2.0"
48 | }
49 | }
50 | ```
51 | And run composer update
52 |
53 | **Or** run a command in your command line:
54 |
55 | composer require longman/laravel-multilang
56 |
57 | In Laravel the service provider and facade will automatically get registered.
58 |
59 | Copy the package config to your local config with the publish command:
60 |
61 | php artisan vendor:publish --provider="Longman\LaravelMultiLang\MultiLangServiceProvider"
62 |
63 | After run multilang migration command
64 |
65 | php artisan multilang:migration
66 |
67 | Its creates multilang migration file in your database/migrations folder. After you can run
68 |
69 | php artisan migrate
70 |
71 | Also if you want automatically change locale depending on url (like `http://site.com/en/your-routes`)
72 | you must add middleware in app/Http/Kernel.php
73 |
74 | I suggest add multilang after CheckForMaintenanceMode middleware
75 | ```php
76 | protected $middleware = [
77 | \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
78 | \Longman\LaravelMultiLang\Middleware\MultiLang::class,
79 | ];
80 | ```
81 |
82 | In your RoutesServiceProvider modify that:
83 | ```php
84 | MultiLang::routeGroup(function($router) {
85 | require app_path('Http/routes.php');
86 | });
87 | ```
88 |
89 | or directly in app/Http/routes.php file add multilang group:
90 | ```php
91 | MultiLang::routeGroup(function($router) {
92 | // your routes and route groups here
93 | });
94 | ```
95 |
96 | Or if you want only translate strings without automatic resolving locale and redirect,
97 | you can manually set locale in your application like:
98 | ```php
99 | App::setLocale('en');
100 | ```
101 |
102 |
103 | ## Usage
104 |
105 | ### Translating
106 | Everywhere in application you can use `t()` helper function like:
107 |
108 | ```php
109 | $string = t('Your translatable string');
110 | ```
111 |
112 | You can use markers for dynamic texts and pass any data like
113 | ```php
114 | $string = t('The :attribute must be a date after :date.', ['attribute' => 'Start Date', 'date' => '7 April 1986']);
115 | ```
116 | which will be return `The Start Date must be a date after 7 April 1986.`
117 |
118 | ### Blade Templates
119 | In blade templates you can use just `@t()` notation like
120 | ```php
121 | @t('Your translatable string')
122 | ```
123 | which is equivalent to `{{ t('Your translatable string') }}`
124 |
125 | ### URL Generation
126 | Also you can use `lang_url()` helper function for appending current lang marker in urls automatically.
127 |
128 | ```php
129 | $url = lang_url('users'); // which returns /en/users depending on your language (locale)
130 | ```
131 |
132 | You can force locale and get localized url for current url for example.
133 |
134 | ```php
135 | $url = lang_url('users', [], null, 'ka'); // which returns /ka/users ignoring current locale
136 | ```
137 | or
138 | ```php
139 | $url = lang_url('en/users', [], null, 'ka'); // also returns /ka/users
140 | ```
141 |
142 | Also you use named routes via `lang_route()` function
143 |
144 | ```php
145 | $url = lang_route('users'); // which returns en.users depending on your language (locale)
146 | ```
147 |
148 | Also `Request::locale()` always will return current locale.
149 |
150 | *Note*: Texts will be selected after firing Laravel's `LocaleUpdated` event. Therefore you should use MultiLang middleware, or manually set locale in the application.
151 |
152 | ### Text Scopes
153 | If you want group translations by some scope, in package available defining of scopes.
154 | For example to define scope `admin` in application, you should call:
155 |
156 | ```php
157 | app('multilang')->setScope('admin');
158 | ```
159 |
160 | before setting the locale.
161 |
162 | *Note*: Default scope is `global`
163 |
164 | ### Import/Export Texts
165 | For versioning texts with source code (git/svn) and easy management, there is possible import texts from yml file and also export in the file.
166 |
167 | yml file format is:
168 |
169 | ```yml
170 | -
171 | key: 'authorization'
172 | texts:
173 | en: 'Authorization'
174 | ge: 'ავტორიზაცია'
175 | -
176 | key: 'registration'
177 | texts:
178 | en: 'Registration'
179 | ge: 'რეგისტრაცია'
180 | ```
181 |
182 | Run commands for possible options and more information:
183 |
184 | php artisan help multilang:import
185 |
186 | php artisan help multilang:export
187 |
188 |
189 | ## TODO
190 |
191 | write more tests
192 |
193 | ## Troubleshooting
194 |
195 | If you like living on the edge, please report any bugs you find on the
196 | [laravel-multilang issues](https://github.com/akalongman/laravel-multilang/issues) page.
197 |
198 | ## Contributing
199 |
200 | Pull requests are welcome.
201 | See [CONTRIBUTING.md](CONTRIBUTING.md) for information.
202 |
203 | ## License
204 |
205 | Please see the [LICENSE](LICENSE.md) included in this repository for a full copy of the MIT license,
206 | which this project is licensed under.
207 |
208 | ## Credits
209 |
210 | - [Avtandil Kikabidze aka LONGMAN](https://github.com/akalongman)
211 |
212 | Full credit list in [CREDITS](CREDITS)
213 |
--------------------------------------------------------------------------------
/build/coverage/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/build/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/build/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | PROJECT=`php -r "echo dirname(dirname(dirname(realpath('$0'))));"`
4 | STAGED_FILES_CMD=`git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\\\.php`
5 |
6 | SFILES=${SFILES:-$STAGED_FILES_CMD}
7 |
8 | echo "Checking PHP Lint..."
9 | for FILE in $SFILES
10 | do
11 | php -l -d display_errors=0 $PROJECT/$FILE
12 | if [ $? != 0 ]
13 | then
14 | echo "Fix the error before commit."
15 | exit 1
16 | fi
17 | FILES="$FILES $PROJECT/$FILE"
18 | done
19 |
20 | if [ "$FILES" != "" ]
21 | then
22 | echo "Running Code Sniffer..."
23 | ./vendor/bin/phpcs --standard=phpcs.xml -p -n --encoding=utf-8 $FILES
24 | if [ $? != 0 ]
25 | then
26 | echo "Fix the error before commit."
27 | exit 1
28 | fi
29 | fi
30 |
31 | exit $?
--------------------------------------------------------------------------------
/build/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cp build/pre-commit .git/hooks/pre-commit
4 | chmod +x .git/hooks/pre-commit
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "longman/laravel-multilang",
3 | "type": "library",
4 | "description": "Package to integrate multi language (multi locale) functionality in Laravel 5.x",
5 | "keywords": [
6 | "locale",
7 | "localization",
8 | "translation",
9 | "language",
10 | "laravel",
11 | "package",
12 | "multilang"
13 | ],
14 | "license": "MIT",
15 | "homepage": "https://github.com/akalongman/laravel-multilang",
16 | "support": {
17 | "issues": "https://github.com/akalongman/laravel-multilang/issues",
18 | "source": "https://github.com/akalongman/laravel-multilang"
19 | },
20 | "authors": [
21 | {
22 | "name": "Avtandil Kikabidze aka LONGMAN",
23 | "email": "akalongman@gmail.com",
24 | "homepage": "https://longman.me",
25 | "role": "Maintainer, Developer"
26 | }
27 | ],
28 | "require": {
29 | "php": "^8.1.0",
30 | "ext-mbstring": "*",
31 | "symfony/yaml": "^6.0",
32 | "symfony/translation": "^6.0",
33 | "illuminate/console": "^10.0",
34 | "illuminate/support": "^10.0",
35 | "illuminate/database": "^10.0",
36 | "illuminate/http": "^10.0"
37 | },
38 | "require-dev": {
39 | "mockery/mockery": "~1.3",
40 | "phpunit/phpunit": "~10.0",
41 | "longman/php-code-style": "^10.0",
42 | "orchestra/testbench": "^8.5"
43 | },
44 | "autoload": {
45 | "psr-4": {
46 | "Longman\\LaravelMultiLang\\": "src/MultiLang"
47 | },
48 | "files": [
49 | "src/helpers.php"
50 | ]
51 | },
52 | "autoload-dev": {
53 | "psr-4": {
54 | "Tests\\": "tests/"
55 | }
56 | },
57 | "config": {
58 | "sort-packages": true,
59 | "allow-plugins": {
60 | "dealerdirect/phpcodesniffer-composer-installer": true
61 | }
62 | },
63 | "extra": {
64 | "laravel": {
65 | "providers": [
66 | "Longman\\LaravelMultiLang\\MultiLangServiceProvider"
67 | ],
68 | "aliases": {
69 | "MultiLang": "Longman\\LaravelMultiLang\\Facades\\MultiLang"
70 | }
71 | }
72 | },
73 | "scripts": {
74 | "phpcs": "./vendor/bin/phpcs --standard=phpcs.xml -spn --encoding=utf-8 src/ tests/ --report-width=150",
75 | "phpcbf": "./vendor/bin/phpcbf --standard=phpcs.xml -spn --encoding=utf-8 src/ tests/ --report-width=150",
76 | "test": "./vendor/bin/phpunit -c phpunit.xml.dist",
77 | "coverage-clover": "./vendor/bin/phpunit --stop-on-failure --coverage-clover build/logs/clover.xml",
78 | "coverage-html": "./vendor/bin/phpunit --stop-on-failure --coverage-html build/coverage"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | */tests/*
8 |
9 | *\.blade\.php$
10 |
11 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ./tests/
33 |
34 |
35 |
36 |
37 | ./src
38 |
39 | ./src/MultiLang/TextsTrait.php
40 | ./src/MultiLang/Console
41 | ./src/config
42 | ./src/stubs
43 | ./src/views
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/MultiLang/Config.php:
--------------------------------------------------------------------------------
1 | data = $data;
28 | }
29 |
30 | /**
31 | * Get config parameter
32 | *
33 | * @param string $key
34 | * @param mixed $default
35 | * @return array|mixed|null
36 | */
37 | public function get(?string $key = null, $default = null)
38 | {
39 | $array = $this->data;
40 |
41 | if ($key === null) {
42 | return $array;
43 | }
44 |
45 | if (array_key_exists($key, $array)) {
46 | return $array[$key];
47 | }
48 |
49 | foreach (explode('.', $key) as $segment) {
50 | if (is_array($array) && array_key_exists($segment, $array)) {
51 | $array = $array[$segment];
52 | } else {
53 | return $default;
54 | }
55 | }
56 |
57 | return $array;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/MultiLang/Console/ExportCommand.php:
--------------------------------------------------------------------------------
1 | table = config('multilang.db.texts_table', 'texts');
91 | $this->db = $this->getDatabase();
92 |
93 | $lang = $this->option('lang');
94 | if (! empty($lang)) {
95 | $this->langs = explode(',', $lang);
96 | }
97 |
98 | $scopes = $this->scopes;
99 | $scope = $this->option('scope');
100 | if (! empty($scope)) {
101 | $scopes = explode(',', $scope);
102 | foreach ($scopes as $scope) {
103 | if (! in_array($scope, $this->scopes)) {
104 | throw new InvalidArgumentException('Scope "' . $scope . '" is not found! Available scopes is ' . implode(', ', $this->scopes));
105 | }
106 | }
107 | }
108 |
109 | $path = $this->option('path', 'storage/multilang');
110 | $this->path = base_path($path);
111 | if (! is_dir($this->path)) {
112 | if (! mkdir($this->path, 0777, true)) {
113 | throw new InvalidArgumentException('unable to create the folder "' . $this->path . '"!');
114 | }
115 | }
116 | if (! is_writable($this->path)) {
117 | throw new InvalidArgumentException('Folder "' . $this->path . '" is not writable!');
118 | }
119 |
120 | $force = (bool) $this->option('force');
121 | $clear = (bool) $this->option('clear');
122 | foreach ($scopes as $scope) {
123 | $this->export($scope, $force, $clear);
124 | }
125 |
126 | return;
127 |
128 | /*$texts = Text::where('scope', 'site')->where('lang', 'en')->get()->toArray();
129 |
130 |
131 | $newTexts = [];
132 | foreach($texts as $text) {
133 | $arr = [];
134 | $arr['key'] = $text['key'];
135 | $arr['texts']['en'] = $text['value'];
136 | $arr['texts']['ir'] = $text['value'];
137 |
138 | $newTexts[] = $arr;
139 | }
140 |
141 | $yaml = Yaml::dump($newTexts, 3, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);*/
142 |
143 | $path = storage_path('texts/site.yml');
144 |
145 | //dump(file_put_contents($path, $yaml));
146 | //die;
147 |
148 | $value = Yaml::parse(file_get_contents($path));
149 | dump($value);
150 | die;
151 |
152 | //$this->info('Database backup restored successfully');
153 | }
154 |
155 | protected function export(string $scope = 'global', bool $force = false, bool $clear = false)
156 | {
157 | $dbTexts = $this->getTextsFromDb($scope);
158 |
159 | $fileTexts = ! $clear ? $this->getTextsFromFile($scope) : [];
160 |
161 | $textsToWrite = $force ? array_replace_recursive($fileTexts, $dbTexts) : array_replace_recursive($dbTexts, $fileTexts);
162 |
163 | // Reset keys
164 | $textsToWrite = array_values($textsToWrite);
165 |
166 | $yaml = Yaml::dump($textsToWrite, 3, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
167 |
168 | $path = $this->path . '/' . $scope . '.yml';
169 | $written = file_put_contents($path, $yaml);
170 | if (! $written) {
171 | $this->error('Export texts of "' . $scope . '" is failed!');
172 | }
173 |
174 | $this->info('Export texts of "' . $scope . '" is finished in "' . $path . '"');
175 | }
176 |
177 | /**
178 | * Get a texts from file.
179 | *
180 | * @param string $scope
181 | * @return array
182 | */
183 | protected function getTextsFromFile(string $scope): array
184 | {
185 | $fileTexts = [];
186 | $path = $this->path . '/' . $scope . '.yml';
187 | if (is_readable($path)) {
188 | $fileTexts = Yaml::parse(file_get_contents($path));
189 | }
190 |
191 | $formattedFileTexts = [];
192 | foreach ($fileTexts as $text) {
193 | $formattedFileTexts[$text['key']] = $text;
194 | }
195 |
196 | return $formattedFileTexts;
197 | }
198 |
199 | /**
200 | * Get a texts from database.
201 | *
202 | * @param string $scope
203 | * @return array
204 | */
205 | protected function getTextsFromDb(string $scope): array
206 | {
207 | $dbTexts = $this->db
208 | ->table($this->table)
209 | ->where('scope', $scope)
210 | ->get();
211 |
212 | $formattedDbTexts = [];
213 | foreach ($dbTexts as $text) {
214 | $key = $text->key;
215 | $lang = $text->lang;
216 | if (! isset($formattedDbTexts[$key])) {
217 | $formattedDbTexts[$key] = ['key' => $key];
218 | }
219 | $formattedDbTexts[$key]['texts'][$lang] = $text->value;
220 | }
221 |
222 | return $formattedDbTexts;
223 | }
224 |
225 | /**
226 | * Get a database connection instance.
227 | *
228 | * @return \Illuminate\Database\Connection
229 | */
230 | protected function getDatabase(): Connection
231 | {
232 | $connection = config('multilang.db.connection', 'default');
233 | $db = App::make(Database::class);
234 | if ($connection === 'default') {
235 | return $db->connection();
236 | }
237 |
238 | return $db->connection($connection);
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/src/MultiLang/Console/ImportCommand.php:
--------------------------------------------------------------------------------
1 | table = config('multilang.db.texts_table', 'texts');
87 | $this->db = $this->getDatabase();
88 |
89 | $lang = $this->option('lang');
90 | if (! empty($lang)) {
91 | $this->langs = explode(',', $lang);
92 | }
93 |
94 | $scopes = $this->scopes;
95 | $scope = $this->option('scope');
96 | if (! empty($scope)) {
97 | $scopes = explode(',', $scope);
98 | foreach ($scopes as $scope) {
99 | if (! in_array($scope, $this->scopes)) {
100 | throw new InvalidArgumentException('Scope "' . $scope . '" is not found! Available scopes is ' . implode(', ', $this->scopes));
101 | }
102 | }
103 | }
104 |
105 | $path = $this->option('path', 'storage/multilang');
106 | $this->path = base_path($path);
107 | if (! is_dir($this->path)) {
108 | throw new InvalidArgumentException('Folder "' . $this->path . '" is not accessible!');
109 | }
110 |
111 | $force = $this->option('force');
112 | $clear = $this->option('clear');
113 | foreach ($scopes as $scope) {
114 | $this->import($scope, $force, $clear);
115 | }
116 | }
117 |
118 | protected function import(string $scope = 'global', bool $force = false, bool $clear = false)
119 | {
120 | $path = $this->path . '/' . $scope . '.yml';
121 | if (! is_readable($path)) {
122 | $this->warn('File "' . $path . '" is not readable!');
123 |
124 | return false;
125 | }
126 | $data = Yaml::parse(file_get_contents($path));
127 | if (empty($data)) {
128 | $this->warn('File "' . $path . '" is empty!');
129 |
130 | return false;
131 | }
132 |
133 | if ($clear) {
134 | $this->db
135 | ->table($this->table)
136 | ->where('scope', $scope)
137 | ->delete();
138 | }
139 |
140 | $createdAt = Carbon::now()->toDateTimeString();
141 | $updatedAt = $createdAt;
142 | $inserted = 0;
143 | $updated = 0;
144 | foreach ($data as $text) {
145 | $key = $text['key'];
146 |
147 | foreach ($text['texts'] as $lang => $value) {
148 | if (! empty($this->langs) && ! in_array($lang, $this->langs)) {
149 | continue;
150 | }
151 |
152 | $row = $this->db
153 | ->table($this->table)
154 | ->where('scope', $scope)
155 | ->where('key', $key)
156 | ->where('lang', $lang)
157 | ->first();
158 |
159 | if (empty($row)) {
160 | // insert row
161 | $ins = [];
162 | $ins['key'] = $key;
163 | $ins['lang'] = $lang;
164 | $ins['scope'] = $scope;
165 | $ins['value'] = $value;
166 | $ins['created_at'] = $createdAt;
167 | $ins['updated_at'] = $updatedAt;
168 | $this->db
169 | ->table($this->table)
170 | ->insert($ins);
171 | $inserted++;
172 | } else {
173 | if ($force) {
174 | // force update row
175 | $upd = [];
176 | $upd['key'] = $key;
177 | $upd['lang'] = $lang;
178 | $upd['scope'] = $scope;
179 | $upd['value'] = $value;
180 | $upd['updated_at'] = $updatedAt;
181 | $this->db
182 | ->table($this->table)
183 | ->where('key', $key)
184 | ->where('lang', $lang)
185 | ->where('scope', $scope)
186 | ->update($upd);
187 | $updated++;
188 | }
189 | }
190 | }
191 | }
192 |
193 | $this->info('Import texts of "' . $scope . '" is finished. Inserted: ' . $inserted . ', Updated: ' . $updated);
194 | }
195 |
196 | /**
197 | * Get a database connection instance.
198 | *
199 | * @return \Illuminate\Database\Connection
200 | */
201 | protected function getDatabase(): Connection
202 | {
203 | $connection = config('multilang.db.connection', 'default');
204 | $db = App::make(Database::class);
205 | if ($connection === 'default') {
206 | return $db->connection();
207 | }
208 |
209 | return $db->connection($connection);
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/MultiLang/Console/MigrationCommand.php:
--------------------------------------------------------------------------------
1 | error('Couldn\'t create migration.' . PHP_EOL . 'Table name can\'t be empty. Check your configuration.');
45 |
46 | return;
47 | }
48 |
49 | $this->line('');
50 | $this->info('Tables: ' . $table);
51 |
52 | $message = 'A migration that creates "' . $table . '" tables will be created in database/migrations directory';
53 |
54 | $this->comment($message);
55 | $this->line('');
56 |
57 | if ($this->confirm('Proceed with the migration creation? [Yes|no]', true)) {
58 | $this->line('');
59 |
60 | $this->info('Creating migration...');
61 | if ($this->createMigration($table)) {
62 | $this->info('Migration successfully created!');
63 | } else {
64 | $this->error(
65 | 'Couldn\'t create migration.' . PHP_EOL . ' Check the write permissions
66 | within the database/migrations directory.',
67 | );
68 | }
69 |
70 | $this->line('');
71 | }
72 | }
73 |
74 | /**
75 | * Create the migration.
76 | *
77 | * @param string $table
78 | * @return bool
79 | */
80 | protected function createMigration(string $table): bool
81 | {
82 | $migrationFile = base_path('database/migrations') . '/' . date('Y_m_d_His') . '_create_multi_lang_texts_table.php';
83 |
84 | if (file_exists($migrationFile)) {
85 | return false;
86 | }
87 |
88 | $stubPath = __DIR__ . '/../../stubs/migrations/texts.stub';
89 | $content = file_get_contents($stubPath);
90 | if (empty($content)) {
91 | return false;
92 | }
93 |
94 | $data = str_replace('{{TEXTS_TABLE}}', $table, $content);
95 |
96 | if (! file_put_contents($migrationFile, $data)) {
97 | return false;
98 | }
99 |
100 | return true;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/MultiLang/Console/TextsCommand.php:
--------------------------------------------------------------------------------
1 | option('lang');
36 | $scope = $this->option('scope');
37 |
38 | $texts = app('multilang')->getAllTexts($lang, $scope);
39 |
40 | if (empty($texts)) {
41 | $this->info('Application texts is empty');
42 |
43 | return false;
44 | }
45 |
46 | $headers = ['#', 'Text Key', 'Language', 'Scope', 'Text Value'];
47 |
48 | $rows = [];
49 | $i = 1;
50 | foreach ($texts as $lang => $items) {
51 | foreach ($items as $key => $item) {
52 | $row = [$i, $key, $item->lang, $item->scope, $item->value];
53 | $rows[] = $row;
54 | $i++;
55 | }
56 | }
57 | $this->table($headers, $rows);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/MultiLang/Facades/MultiLang.php:
--------------------------------------------------------------------------------
1 | app = $app;
44 | $this->redirector = $redirector;
45 | }
46 |
47 | /**
48 | * Handle an incoming request.
49 | *
50 | * @param \Illuminate\Http\Request $request
51 | * @param \Closure $next
52 | * @return mixed
53 | */
54 | public function handle(Request $request, Closure $next)
55 | {
56 | if (! $this->app->bound(MultiLangLib::class)) {
57 | return $next($request);
58 | }
59 | $multilang = $this->app->make(MultiLangLib::class);
60 |
61 | $url = $multilang->getRedirectUrl($request);
62 |
63 | if (! empty($url)) {
64 | if ($request->expectsJson()) {
65 | return response('Not found', 404);
66 | } else {
67 | return $this->redirector->to($url);
68 | }
69 | }
70 |
71 | $locale = $multilang->detectLocale($request);
72 |
73 | $this->app->setLocale($locale);
74 |
75 | if ($multilang->getConfig()->get('set_carbon_locale')) {
76 | Carbon::setLocale($locale);
77 | }
78 |
79 | if ($multilang->getConfig()->get('set_system_locale')) {
80 | $locales = $multilang->getLocales();
81 | if (! empty($locales[$locale]['full_locale'])) {
82 | $lcList = $multilang->getConfig()->get('system_locale_lc', LC_ALL);
83 | if (is_array($lcList)) {
84 | foreach ($lcList as $lc) {
85 | setlocale((int) $lc, $locales[$locale]['full_locale']);
86 | }
87 | } else {
88 | setlocale((int) $lcList, $locales[$locale]['full_locale']);
89 | }
90 | }
91 | }
92 |
93 | return $next($request);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/MultiLang/Models/Localizable.php:
--------------------------------------------------------------------------------
1 | getTable() . '.' . $this->getLocalizableColumn();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/MultiLang/Models/LocalizableScope.php:
--------------------------------------------------------------------------------
1 | queryHasLocalizableColumn($builder)) {
26 | $builder->where($model->getQualifiedLocalizableColumn(), '=', app()->getLocale());
27 | }
28 | }
29 |
30 | /**
31 | * Check if query has "localizable" column
32 | *
33 | * @param \Illuminate\Database\Eloquent\Builder $builder
34 | * @return bool
35 | */
36 | protected function queryHasLocalizableColumn(Builder $builder)
37 | {
38 | $wheres = $builder->getQuery()->wheres;
39 | $column = $this->getLocalizableColumn($builder);
40 | if (! empty($wheres)) {
41 | foreach ($wheres as $where) {
42 | if (isset($where['column']) && $where['column'] === $column) {
43 | return true;
44 | }
45 | }
46 | }
47 |
48 | return false;
49 | }
50 |
51 | /**
52 | * Get the "localizable" column for the builder.
53 | *
54 | * @param \Illuminate\Database\Eloquent\Builder $builder
55 | * @return string
56 | */
57 | protected function getLocalizableColumn(Builder $builder)
58 | {
59 | if (count($builder->getQuery()->joins) > 0) {
60 | return $builder->getModel()->getQualifiedLocalizableColumn();
61 | } else {
62 | return $builder->getModel()->getLocalizableColumn();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/MultiLang/Models/Text.php:
--------------------------------------------------------------------------------
1 | environment = $environment;
96 |
97 | $this->setConfig($config);
98 |
99 | $this->setRepository(new Repository($this->config, $cache, $db));
100 | }
101 |
102 | /**
103 | * Set multilang config
104 | *
105 | * @param array $config
106 | * @return $this
107 | */
108 | public function setConfig(array $config): MultiLang
109 | {
110 | $this->config = new Config($config);
111 |
112 | return $this;
113 | }
114 |
115 | /**
116 | * Get multilang config
117 | *
118 | * @return \Longman\LaravelMultiLang\Config
119 | */
120 | public function getConfig(): Config
121 | {
122 |
123 | return $this->config;
124 | }
125 |
126 | /**
127 | * Set repository object
128 | *
129 | * @param \Longman\LaravelMultiLang\Repository $repository
130 | * @return $this
131 | */
132 | public function setRepository(Repository $repository): MultiLang
133 | {
134 | $this->repository = $repository;
135 |
136 | return $this;
137 | }
138 |
139 | /**
140 | * Get repository object
141 | *
142 | * @return \Longman\LaravelMultiLang\Repository
143 | */
144 | public function getRepository(): Repository
145 | {
146 | return $this->repository;
147 | }
148 |
149 | /**
150 | * Set application scope
151 | *
152 | * @param $scope
153 | * @return $this
154 | */
155 | public function setScope($scope): MultiLang
156 | {
157 | $this->scope = $scope;
158 |
159 | return $this;
160 | }
161 |
162 | /**
163 | * Get application scope
164 | *
165 | * @return string
166 | */
167 | public function getScope(): string
168 | {
169 | return $this->scope;
170 | }
171 |
172 | /**
173 | * Set locale
174 | *
175 | * @param string $lang
176 | * @return void
177 | */
178 | public function setLocale(string $lang)
179 | {
180 | if (! $lang) {
181 | throw new InvalidArgumentException('Locale is empty');
182 | }
183 | $this->lang = $lang;
184 | }
185 |
186 | public function loadTexts(?string $locale = null, ?string $scope = null): array
187 | {
188 | if (is_null($locale)) {
189 | $locale = $this->getLocale();
190 | }
191 |
192 | if (is_null($scope)) {
193 | $scope = $this->getScope();
194 | }
195 |
196 | if ($this->environment !== 'production' || $this->config->get('cache.enabled', true) === false) {
197 | $texts = $this->repository->loadFromDatabase($locale, $scope);
198 | } else {
199 | if ($this->repository->existsInCache($locale, $scope)) {
200 | $texts = $this->repository->loadFromCache($locale, $scope);
201 | } else {
202 | $texts = $this->repository->loadFromDatabase($locale, $scope);
203 | $this->repository->storeInCache($locale, $texts, $scope);
204 | }
205 | }
206 |
207 | $this->createTranslator($locale, $scope, $texts);
208 |
209 | $this->texts = $texts;
210 |
211 | return $texts;
212 | }
213 |
214 | /**
215 | * Get translated text
216 | *
217 | * @param string $key
218 | * @param array $replacements
219 | * @return string
220 | */
221 | public function get(string $key, array $replacements = []): string
222 | {
223 | if (! $this->getConfig()->get('use_texts', true)) {
224 | throw new InvalidArgumentException('Using texts from database is disabled in config');
225 | }
226 |
227 | if (empty($key)) {
228 | throw new InvalidArgumentException('Text key not provided');
229 | }
230 |
231 | if (! $this->lang) {
232 | return $key;
233 | }
234 |
235 | if (is_null($this->texts)) {
236 | // Load texts from storage
237 | $this->loadTexts();
238 | }
239 |
240 | if (! isset($this->texts[$key])) {
241 | $this->queueToSave($key);
242 | }
243 |
244 | if (! empty($replacements)) {
245 | $keys = array_keys($replacements);
246 | $keys = array_map(static function ($v) {
247 | return ':' . $v;
248 | }, $keys);
249 | $replacements = array_combine($keys, $replacements);
250 | }
251 |
252 | return $this->translator->trans($key, $replacements, $this->getScope());
253 | }
254 |
255 | /**
256 | * Get redirect url in middleware
257 | *
258 | * @param \Illuminate\Http\Request $request
259 | * @return string
260 | */
261 | public function getRedirectUrl(Request $request): string
262 | {
263 | $excludePatterns = $this->config->get('exclude_segments', []);
264 | if (! empty($excludePatterns)) {
265 | if (call_user_func_array([$request, 'is'], $excludePatterns)) {
266 | return '';
267 | }
268 | }
269 |
270 | $locale = $request->segment(1);
271 | $fallbackLocale = $this->config->get('default_locale', 'en');
272 | if (! empty($locale) && strlen($locale) === 2) {
273 | $locales = $this->config->get('locales', []);
274 |
275 | if (! isset($locales[$locale])) {
276 | $segments = $request->segments();
277 | $segments[0] = $fallbackLocale;
278 | $url = implode('/', $segments);
279 | $queryString = $request->server->get('QUERY_STRING');
280 | if ($queryString) {
281 | $url .= '?' . $queryString;
282 | }
283 |
284 | return $url;
285 | }
286 | } else {
287 | $segments = $request->segments();
288 | $url = $fallbackLocale . '/' . implode('/', $segments);
289 | $queryString = $request->server->get('QUERY_STRING');
290 | if ($queryString) {
291 | $url .= '?' . $queryString;
292 | }
293 |
294 | return $url;
295 | }
296 |
297 | return '';
298 | }
299 |
300 | /**
301 | * Detect locale based on url segment
302 | *
303 | * @param \Illuminate\Http\Request $request
304 | * @return string
305 | */
306 | public function detectLocale(Request $request): string
307 | {
308 | $locale = $request->segment(1);
309 | $locales = $this->config->get('locales');
310 |
311 | if (isset($locales[$locale])) {
312 | return $locales[$locale]['locale'] ?? $locale;
313 | }
314 |
315 | return (string) $this->config->get('default_locale', 'en');
316 | }
317 |
318 | /**
319 | * Wrap routes to available languages group
320 | *
321 | * @param \Closure $callback
322 | * @return void
323 | */
324 | public function routeGroup(Closure $callback)
325 | {
326 | $router = app('router');
327 |
328 | $locales = $this->config->get('locales', []);
329 |
330 | foreach ($locales as $locale => $val) {
331 | $router->group([
332 | 'prefix' => $locale,
333 | 'as' => $locale . '.',
334 | ], $callback);
335 | }
336 | }
337 |
338 | /**
339 | * Get texts
340 | *
341 | * @return array
342 | */
343 | public function getTexts(): array
344 | {
345 |
346 | return $this->texts;
347 | }
348 |
349 | /**
350 | * Get all texts
351 | *
352 | * @param string $lang
353 | * @param string $scope
354 | * @return array
355 | */
356 | public function getAllTexts(?string $lang = null, ?string $scope = null): array
357 | {
358 | return $this->repository->loadAllFromDatabase($lang, $scope);
359 | }
360 |
361 | /**
362 | * Set texts manually
363 | *
364 | * @param array $textsArray
365 | * @return \Longman\LaravelMultiLang\MultiLang
366 | */
367 | public function setTexts(array $textsArray): MultiLang
368 | {
369 | $texts = [];
370 | foreach ($textsArray as $key => $value) {
371 | $texts[$key] = $value;
372 | }
373 |
374 | $this->texts = $texts;
375 |
376 | $this->createTranslator($this->getLocale(), $this->getScope(), $texts);
377 |
378 | return $this;
379 | }
380 |
381 | /**
382 | * Get language prefixed url
383 | *
384 | * @param string $path
385 | * @param string $lang
386 | * @return string
387 | */
388 | public function getUrl(string $path, ?string $lang = null): string
389 | {
390 | $locale = $lang ?: $this->getLocale();
391 | if ($locale) {
392 | $path = $locale . '/' . $this->removeLocaleFromPath($path);
393 | }
394 |
395 | return $path;
396 | }
397 |
398 | /**
399 | * Get language prefixed route
400 | *
401 | * @param string $name
402 | * @return string
403 | */
404 | public function getRoute(string $name): string
405 | {
406 | $locale = $this->getLocale();
407 | if ($locale) {
408 | $name = $locale . '.' . $name;
409 | }
410 |
411 | return $name;
412 | }
413 |
414 | /**
415 | * Check if autosave allowed
416 | *
417 | * @return bool
418 | */
419 | public function autoSaveIsAllowed()
420 | {
421 | if ($this->environment === 'local' && $this->config->get('db.autosave', true)) {
422 | return true;
423 | }
424 |
425 | return false;
426 | }
427 |
428 | /**
429 | * Get locale
430 | *
431 | * @return string
432 | */
433 | public function getLocale()
434 | {
435 | return $this->lang ?? $this->config->get('default_locale');
436 | }
437 |
438 | /**
439 | * Get available locales
440 | *
441 | * @return array
442 | */
443 | public function getLocales(): array
444 | {
445 | return (array) $this->config->get('locales');
446 | }
447 |
448 | /**
449 | * Save missing texts
450 | *
451 | * @return bool
452 | */
453 | public function saveTexts(): bool
454 | {
455 | if (empty($this->newTexts)) {
456 | return false;
457 | }
458 |
459 | return $this->repository->save($this->newTexts, $this->scope);
460 | }
461 |
462 | protected function createTranslator(string $locale, string $scope, array $texts): Translator
463 | {
464 | $this->translator = new Translator($locale);
465 | $this->translator->addLoader('array', new ArrayLoader());
466 | $this->translator->addResource('array', $texts, $locale, $scope);
467 |
468 | return $this->translator;
469 | }
470 |
471 | /**
472 | * Queue missing texts
473 | *
474 | * @param string $key
475 | * @return void
476 | */
477 | protected function queueToSave(string $key)
478 | {
479 | $this->newTexts[$key] = $key;
480 | }
481 |
482 | /**
483 | * Remove locale from the path
484 | *
485 | * @param string $path
486 | * @return string
487 | */
488 | private function removeLocaleFromPath(string $path): string
489 | {
490 | $langPath = $path;
491 |
492 | // Remove domain from path
493 | $appUrl = config('app.url', '');
494 | if (! empty($appUrl) && mb_substr($langPath, 0, mb_strlen($appUrl)) === $appUrl) {
495 | $langPath = ltrim(str_replace($appUrl, '', $langPath), '/');
496 | }
497 |
498 | $locales = $this->config->get('locales');
499 | $locale = mb_substr($langPath, 0, 2);
500 | if (isset($locales[$locale])) {
501 | return mb_substr($langPath, 3);
502 | }
503 |
504 | return $path;
505 | }
506 | }
507 |
--------------------------------------------------------------------------------
/src/MultiLang/MultiLangServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes(
28 | [
29 | __DIR__ . '/../config/config.php' => config_path('multilang.php'),
30 | __DIR__ . '/../views' => base_path('resources/views/vendor/multilang'),
31 | ],
32 | );
33 |
34 | // Append the country settings
35 | $this->mergeConfigFrom(
36 | __DIR__ . '/../config/config.php',
37 | 'multilang',
38 | );
39 |
40 | // Register blade directives
41 | $this->getBlade()->directive('t', static function ($expression) {
42 | return "";
43 | });
44 |
45 | $this->app['events']->listen(RouteMatched::class, function () {
46 | $scope = $this->app['config']->get('app.scope');
47 | if ($scope && $scope !== 'global') {
48 | $this->app['multilang']->setScope($scope);
49 | }
50 | });
51 |
52 | $this->app['events']->listen(LocaleUpdated::class, function ($event) {
53 | $this->app['multilang']->setLocale($event->locale);
54 | });
55 |
56 | $this->loadViewsFrom(__DIR__ . '/../views', 'multilang');
57 | }
58 |
59 | /**
60 | * Register any application services.
61 | *
62 | * @return void
63 | */
64 | public function register()
65 | {
66 | $configPath = __DIR__ . '/../config/config.php';
67 | $this->mergeConfigFrom($configPath, 'multilang');
68 |
69 | $this->app->singleton('multilang', function ($app) {
70 | $environment = $app->environment();
71 | $config = $app['config']->get('multilang');
72 |
73 | $multilang = new MultiLang(
74 | $environment,
75 | $config,
76 | $app['cache'],
77 | $app['db'],
78 | );
79 |
80 | if ($multilang->autoSaveIsAllowed()) {
81 | $app->terminating(function () use ($multilang) {
82 | $scope = $this->app['config']->get('app.scope');
83 | if ($scope && $scope !== 'global') {
84 | $multilang->setScope($scope);
85 | }
86 |
87 | return $multilang->saveTexts();
88 | });
89 | }
90 |
91 | return $multilang;
92 | });
93 |
94 | $this->app->alias('multilang', MultiLang::class);
95 |
96 | $this->app->singleton(
97 | 'command.multilang.migration',
98 | static function () {
99 | return new MigrationCommand();
100 | },
101 | );
102 |
103 | $this->app->singleton(
104 | 'command.multilang.texts',
105 | static function () {
106 | return new TextsCommand();
107 | },
108 | );
109 |
110 | $this->app->singleton(
111 | 'command.multilang.import',
112 | static function () {
113 | return new ImportCommand();
114 | },
115 | );
116 |
117 | $this->app->singleton(
118 | 'command.multilang.export',
119 | static function () {
120 | return new ExportCommand();
121 | },
122 | );
123 |
124 | $this->commands(
125 | [
126 | 'command.multilang.migration',
127 | 'command.multilang.texts',
128 | 'command.multilang.import',
129 | 'command.multilang.export',
130 | ],
131 | );
132 |
133 | $this->app->make('request')->macro('locale', static function () {
134 | return app('multilang')->getLocale();
135 | });
136 | }
137 |
138 | /**
139 | * Get the services provided by the provider.
140 | *
141 | * @return array
142 | */
143 | public function provides()
144 | {
145 | return [
146 | 'multilang',
147 | MultiLang::class,
148 | 'command.multilang.migration',
149 | 'command.multilang.texts',
150 | 'command.multilang.import',
151 | 'command.multilang.export',
152 | ];
153 | }
154 |
155 | private function getBlade(): BladeCompiler
156 | {
157 | return $this->app->make('view')->getEngineResolver()->resolve('blade')->getCompiler();
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/MultiLang/Repository.php:
--------------------------------------------------------------------------------
1 | config = $config;
48 | $this->cache = $cache;
49 | $this->db = $db;
50 | }
51 |
52 | /**
53 | * Get cache key name based on lang and scope
54 | *
55 | * @param string $lang
56 | * @param string $scope
57 | * @return string
58 | */
59 | public function getCacheName(string $lang, ?string $scope = null): string
60 | {
61 | $key = $this->config->get('db.texts_table', 'texts') . '_' . $lang;
62 | if (! is_null($scope)) {
63 | $key .= '_' . $scope;
64 | }
65 |
66 | return $key;
67 | }
68 |
69 | /**
70 | * Load texts from database storage
71 | *
72 | * @param string $lang
73 | * @param string $scope
74 | * @return array
75 | */
76 | public function loadFromDatabase(string $lang, ?string $scope = null): array
77 | {
78 | $query = $this->getDb()->table($this->getTableName())
79 | ->where('lang', $lang);
80 |
81 | if (! is_null($scope) && $scope !== 'global') {
82 | $query = $query->whereNested(static function ($query) use ($scope) {
83 | $query->where('scope', 'global');
84 | $query->orWhere('scope', $scope);
85 | });
86 | } else {
87 | $query = $query->where('scope', 'global');
88 | }
89 |
90 | $texts = $query->get(['key', 'value', 'lang', 'scope']);
91 |
92 | $array = [];
93 | foreach ($texts as $row) {
94 | $array[$row->key] = $row->value;
95 | }
96 |
97 | return $array;
98 | }
99 |
100 | /**
101 | * Load all texts from database storage
102 | *
103 | * @param string $lang
104 | * @param string $scope
105 | * @return array
106 | */
107 | public function loadAllFromDatabase(?string $lang = null, ?string $scope = null): array
108 | {
109 | $query = $this->getDb()->table($this->getTableName());
110 |
111 | if (! is_null($lang)) {
112 | $query = $query->where('lang', $lang);
113 | }
114 |
115 | if (! is_null($scope)) {
116 | $query = $query->whereNested(static function ($query) use ($scope) {
117 | $query->where('scope', 'global');
118 | $query->orWhere('scope', $scope);
119 | });
120 | }
121 |
122 | $texts = $query->get();
123 |
124 | $array = [];
125 | foreach ($texts as $row) {
126 | $array[$row->lang][$row->key] = $row;
127 | }
128 |
129 | return $array;
130 | }
131 |
132 | /**
133 | * Load texts from cache storage
134 | *
135 | * @param string $lang
136 | * @param string $scope
137 | * @return array
138 | */
139 | public function loadFromCache(string $lang, ?string $scope = null): array
140 | {
141 | $texts = $this->getCache()->get($this->getCacheName($lang, $scope), []);
142 |
143 | return $texts;
144 | }
145 |
146 | /**
147 | * Store texts in cache
148 | *
149 | * @param string $lang
150 | * @param array $texts
151 | * @param string $scope
152 | * @return $this
153 | */
154 | public function storeInCache(string $lang, array $texts, ?string $scope = null): Repository
155 | {
156 | $this->getCache()->put($this->getCacheName($lang, $scope), $texts, $this->config->get('cache.lifetime', 1440));
157 |
158 | return $this;
159 | }
160 |
161 | /**
162 | * Check if we must load texts from cache
163 | *
164 | * @param string $lang
165 | * @param string $scope
166 | * @return bool
167 | */
168 | public function existsInCache(string $lang, ?string $scope = null): bool
169 | {
170 | return $this->getCache()->has($this->getCacheName($lang, $scope));
171 | }
172 |
173 | /**
174 | * Save missing texts in database
175 | *
176 | * @param array $texts
177 | * @param string $scope
178 | * @return bool
179 | */
180 | public function save(array $texts, ?string $scope = null): bool
181 | {
182 | if (empty($texts)) {
183 | return false;
184 | }
185 |
186 | $table = $this->getTableName();
187 | $locales = $this->config->get('locales', []);
188 | if (is_null($scope)) {
189 | $scope = 'global';
190 | }
191 |
192 | $now = Carbon::now()->toDateTimeString();
193 | foreach ($texts as $k => $v) {
194 | foreach ($locales as $lang => $localeData) {
195 | $exists = $this->getDb()
196 | ->table($table)
197 | ->where([
198 | 'key' => $k,
199 | 'lang' => $lang,
200 | 'scope' => $scope,
201 | ])->first();
202 |
203 | if ($exists) {
204 | continue;
205 | }
206 |
207 | $this->getDb()
208 | ->table($table)
209 | ->insert([
210 | 'key' => $k,
211 | 'lang' => $lang,
212 | 'scope' => $scope,
213 | 'value' => $v,
214 | 'created_at' => $now,
215 | 'updated_at' => $now,
216 | ]);
217 | }
218 | }
219 |
220 | return true;
221 | }
222 |
223 | /**
224 | * Get texts table name
225 | *
226 | * @return string
227 | */
228 | public function getTableName(): string
229 | {
230 | $table = $this->config->get('db.texts_table', 'texts');
231 |
232 | return (string) $table;
233 | }
234 |
235 | /**
236 | * Get a database connection instance.
237 | *
238 | * @return \Illuminate\Database\Connection
239 | */
240 | protected function getDb(): Connection
241 | {
242 | $connection = $this->config->get('db.connection');
243 | if ($connection === 'default') {
244 | return $this->db->connection();
245 | }
246 |
247 | return $this->db->connection($connection);
248 | }
249 |
250 | /**
251 | * Get a cache driver instance.
252 | *
253 | * @return \Illuminate\Contracts\Cache\Repository
254 | */
255 | protected function getCache(): CacheRepository
256 | {
257 | $store = $this->config->get('cache.store', 'default');
258 | if ($store === 'default') {
259 | return $this->cache->store();
260 | }
261 |
262 | return $this->cache->store($store);
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | [
15 | 'en' => [
16 | 'name' => 'English',
17 | 'native_name' => 'English',
18 | 'locale' => 'en', // ISO 639-1
19 | 'canonical_locale' => 'en_GB', // ISO 3166-1
20 | 'full_locale' => 'en_GB.UTF-8',
21 | ],
22 |
23 | // Add yours here
24 | ],
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Fallback locale/language
29 | |--------------------------------------------------------------------------
30 | |
31 | | Fallback locale for routing
32 | |
33 | */
34 | 'default_locale' => 'en',
35 |
36 | /*
37 | |--------------------------------------------------------------------------
38 | | Set Carbon locale
39 | |--------------------------------------------------------------------------
40 | |
41 | | Call Carbon::setLocale($locale) and set current locale in middleware
42 | |
43 | */
44 | 'set_carbon_locale' => true,
45 |
46 | /*
47 | |--------------------------------------------------------------------------
48 | | Set System locale
49 | |--------------------------------------------------------------------------
50 | |
51 | | Call setlocale() and set current locale in middleware
52 | |
53 | */
54 | 'set_system_locale' => true,
55 |
56 | /*
57 | |--------------------------------------------------------------------------
58 | | Locale LC
59 | |--------------------------------------------------------------------------
60 | |
61 | | Which locale to set. You can specify array of locale types, e.g LC_TIME, LC_CTYPE
62 | |
63 | */
64 | 'system_locale_lc' => LC_ALL,
65 |
66 | /*
67 | |--------------------------------------------------------------------------
68 | | Exclude segments from redirect
69 | |--------------------------------------------------------------------------
70 | |
71 | | Exclude segments from redirects in the middleware
72 | |
73 | */
74 | 'exclude_segments' => [
75 | //
76 | ],
77 |
78 | /*
79 | |--------------------------------------------------------------------------
80 | | Texts Management
81 | |--------------------------------------------------------------------------
82 | */
83 |
84 | /*
85 | |--------------------------------------------------------------------------
86 | | Use Texts from Database/Cache
87 | |--------------------------------------------------------------------------
88 | |
89 | | Load or not translations from database/cache
90 | |
91 | */
92 | 'use_texts' => true,
93 |
94 | /*
95 | |--------------------------------------------------------------------------
96 | | Cache Configuration
97 | |--------------------------------------------------------------------------
98 | |
99 | | Cache parameters
100 | |
101 | */
102 | 'cache' => [
103 | 'enabled' => true,
104 | 'store' => env('CACHE_DRIVER', 'default'),
105 | 'lifetime' => 1440,
106 | ],
107 |
108 | /*
109 | |--------------------------------------------------------------------------
110 | | DB Configuration
111 | |--------------------------------------------------------------------------
112 | |
113 | | DB parameters
114 | |
115 | */
116 | 'db' => [
117 | 'autosave' => true, // Autosave missing texts in the database. Only when environment is local
118 | 'connection' => env('DB_CONNECTION', 'default'),
119 | 'texts_table' => 'texts',
120 | ],
121 |
122 | ];
123 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | getUrl($path, $locale);
24 |
25 | return url($path, $parameters, $secure);
26 | }
27 | }
28 |
29 | if (! function_exists('lang_redirect')) {
30 | /**
31 | * Get an instance of the redirector.
32 | *
33 | * @param string|null $to
34 | * @param int $status
35 | * @param array $headers
36 | * @param bool $secure
37 | * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
38 | */
39 | function lang_redirect(?string $to = null, int $status = 302, array $headers = [], ?bool $secure = null)
40 | {
41 | if (is_null($to)) {
42 | return app('redirect');
43 | }
44 |
45 | $multilang = app('multilang');
46 |
47 | $to = $multilang->getUrl($to);
48 |
49 | return app('redirect')->to($to, $status, $headers, $secure);
50 | }
51 | }
52 |
53 | if (! function_exists('lang_route')) {
54 | /**
55 | * Get route by name
56 | *
57 | * @param string $name
58 | * @param array $parameters
59 | * @param bool $absolute
60 | * @return string
61 | */
62 | function lang_route(string $name, array $parameters = [], bool $absolute = true): string
63 | {
64 | $multilang = app('multilang');
65 |
66 | $name = $multilang->getRoute($name);
67 |
68 | return app('url')->route($name, $parameters, $absolute);
69 | }
70 | }
71 |
72 | if (! function_exists('t')) {
73 | /**
74 | * Get translated text
75 | *
76 | * @param string $text
77 | * @param array $replace
78 | * @return string
79 | */
80 | function t(string $text, array $replace = []): string
81 | {
82 | $text = app('multilang')->get($text, $replace);
83 |
84 | return $text;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/stubs/migrations/texts.stub:
--------------------------------------------------------------------------------
1 | table = '{{TEXTS_TABLE}}';
18 |
19 | }
20 |
21 | /**
22 | * Run the migrations.
23 | *
24 | * @return void
25 | */
26 | public function up()
27 | {
28 | Schema::create($this->table, function (Blueprint $table) {
29 | $table->char('key');
30 | $table->char('lang', 2);
31 | $table->longText('value');
32 | $table->enum('scope', ['admin', 'site', 'global'])->default('global');
33 | $table->timestamps();
34 | $table->primary(['key', 'lang', 'scope']);
35 | });
36 | }
37 |
38 | /**
39 | * Reverse the migrations.
40 | *
41 | * @return void
42 | */
43 | public function down()
44 | {
45 | Schema::drop($this->table);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Text management
9 |
10 |
11 |
12 |
13 |
14 | @yield('content')
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/views/index.blade.php:
--------------------------------------------------------------------------------
1 | @extends('multilang::app')
2 |
3 | @section('content')
4 |
33 | @if (count(session('errors')) > 0)
34 |
35 |
36 | @foreach ($errors->all() as $error)
37 | - {{ $error }}
38 | @endforeach
39 |
40 |
41 | @endif
42 |
80 |
81 | @endsection
82 |
--------------------------------------------------------------------------------
/tests/Bootstrap.php:
--------------------------------------------------------------------------------
1 | app['db']->getSchemaBuilder();
31 | $schema->dropIfExists('texts');
32 | $schema->create('texts', static function (Blueprint $table) {
33 | $table->char('key');
34 | $table->char('lang', 2);
35 | $table->text('value')->nullable();
36 | $table->enum('scope', ['admin', 'site', 'global'])->default('global');
37 | $table->timestamps();
38 | $table->primary(['key', 'lang', 'scope']);
39 | });
40 |
41 | for ($i = 0; $i <= 10; $i++) {
42 | $this->app->db->table('texts')->insert([
43 | 'key' => 'text key ' . $i,
44 | 'lang' => 'ka',
45 | 'value' => 'text value ' . $i,
46 | ]);
47 | }
48 | }
49 |
50 | protected function getMultilang(string $env = 'testing', array $config = []): MultiLang
51 | {
52 | $cache = $this->app->cache;
53 | $database = $this->app->db;
54 |
55 | $defaultConfig = include __DIR__ . '/../../src/config/config.php';
56 | $config = array_replace_recursive($defaultConfig, $config);
57 |
58 | $multilang = new MultiLang($env, $config, $cache, $database);
59 |
60 | return $multilang;
61 | }
62 |
63 | protected function getRepository(array $config = []): Repository
64 | {
65 | $cache = $this->app->cache;
66 | $database = $this->app->db;
67 |
68 | $defaultConfig = include __DIR__ . '/../../src/config/config.php';
69 | $config = array_replace_recursive($defaultConfig, $config);
70 |
71 | $config = $this->getConfig($config);
72 |
73 | $repository = new Repository($config, $cache, $database);
74 |
75 | return $repository;
76 | }
77 |
78 | protected function getConfig(array $config): Config
79 | {
80 | $configObject = new Config($config);
81 | return $configObject;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tests/Unit/ConfigTest.php:
--------------------------------------------------------------------------------
1 | [
16 | 'ka' => [
17 | 'name' => 'Georgian',
18 | ],
19 | ],
20 | ];
21 | $config = $this->getConfig($configData);
22 |
23 | $this->assertEquals($configData, $config->get());
24 | }
25 |
26 | /**
27 | * @test
28 | */
29 | public function set_get()
30 | {
31 | $config = [
32 | 'locales' => [
33 | 'ka' => [
34 | 'name' => 'Georgian',
35 | ],
36 | ],
37 | ];
38 | $config = $this->getConfig($config);
39 |
40 | $this->assertEquals('Georgian', $config->get('locales.ka.name'));
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function get_default_value()
47 | {
48 | $config = [
49 | 'locales' => [
50 | 'ka' => [
51 | 'name' => 'Georgian',
52 | ],
53 | ],
54 | ];
55 | $config = $this->getConfig($config);
56 |
57 | $this->assertEquals('Thai', $config->get('locales.th.name', 'Thai'));
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function table_name()
64 | {
65 | $config = [
66 | 'db' => [
67 | 'autosave' => true,
68 | 'connection' => 'mysql',
69 | 'texts_table' => 'texts',
70 | ],
71 | ];
72 |
73 | $config = $this->getConfig($config);
74 |
75 | $this->assertEquals('texts', $config->get('db.texts_table'));
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Unit/HelpersTest.php:
--------------------------------------------------------------------------------
1 | createTable();
19 | }
20 |
21 | /**
22 | * @test
23 | */
24 | public function t_should_return_valid_translation()
25 | {
26 | /** @var \Longman\LaravelMultiLang\MultiLang $multilang */
27 | $multilang = app('multilang');
28 |
29 | $texts = [
30 | 'text1' => 'value1',
31 | 'text2' => 'value2',
32 | 'te.x-t/3' => 'value3',
33 | ];
34 |
35 | $multilang->setLocale('ka');
36 | $multilang->setTexts($texts);
37 |
38 | $this->assertEquals('value1', t('text1'));
39 | }
40 |
41 | /**
42 | * @test
43 | */
44 | public function lang_url_should_return_valid_url()
45 | {
46 | /** @var \Longman\LaravelMultiLang\MultiLang $multilang */
47 | $multilang = app('multilang');
48 |
49 | $texts = [
50 | 'text1' => 'value1',
51 | 'text2' => 'value2',
52 | 'te.x-t/3' => 'value3',
53 | ];
54 |
55 | $multilang->setLocale('ka', $texts);
56 |
57 | $this->assertEquals('http://localhost/ka/users/list', lang_url('users/list'));
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function lang_url_should_return_url_generator_if_path_is_null()
64 | {
65 | $this->assertInstanceOf(UrlGenerator::class, lang_url());
66 | }
67 |
68 | /** @test */
69 | public function lang_redirect_should_return_redirector_instance_if_path_is_null()
70 | {
71 | $this->assertInstanceOf(Redirector::class, lang_redirect());
72 | }
73 |
74 | /** @test */
75 | public function lang_redirect_should_return_redirect_response()
76 | {
77 | /** @var \Longman\LaravelMultiLang\MultiLang $multilang */
78 | $multilang = app('multilang');
79 | $multilang->setLocale('ka');
80 |
81 | $redirect = lang_redirect('path', 302, ['X-header' => 'value']);
82 |
83 | $this->assertInstanceOf(RedirectResponse::class, $redirect);
84 | $this->assertEquals('http://localhost/ka/path', $redirect->getTargetUrl());
85 | $this->assertEquals($redirect->headers->get('X-header'), 'value');
86 | $this->assertEquals($redirect->getStatusCode(), 302);
87 | }
88 |
89 | /** @test */
90 | public function lang_route_should_throw_exception_if_route_is_not_defined()
91 | {
92 | $this->expectException(InvalidArgumentException::class);
93 | lang_route('missing-route');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/Unit/Middleware/MultiLangTest.php:
--------------------------------------------------------------------------------
1 | createTable();
18 | }
19 |
20 | /**
21 | * @test
22 | */
23 | public function handle_no_redirect()
24 | {
25 | $multilang = $this->getMultilang();
26 | $multilang->setLocale('en');
27 | $middleware = new MultiLangMiddleware($this->app, $this->app->redirect, $multilang);
28 |
29 | $request = new Request(
30 | $query = [],
31 | $request = [],
32 | $attributes = [],
33 | $cookies = [],
34 | $files = [],
35 | $server = ['REQUEST_URI' => '/en/auth/login'],
36 | $content = null,
37 | );
38 |
39 | $result = $middleware->handle($request, static function (Request $request) {
40 |
41 | return 'no_redirect';
42 | });
43 |
44 | $this->assertEquals('no_redirect', $result);
45 | }
46 |
47 | /**
48 | * @test
49 | */
50 | public function handle_non_exists_language_must_redirect()
51 | {
52 | $multilang = $this->getMultilang();
53 | $multilang->setLocale('en');
54 | $middleware = new MultiLangMiddleware($this->app, $this->app->redirect, $multilang);
55 |
56 | $request = new Request(
57 | $query = [],
58 | $request = [],
59 | $attributes = [],
60 | $cookies = [],
61 | $files = [],
62 | $server = ['REQUEST_URI' => '/ka/auth/login'],
63 | $content = null,
64 | );
65 |
66 | $result = $middleware->handle($request, static function () {
67 | return 'no_redirect';
68 | });
69 |
70 | $location = $result->headers->get('location');
71 |
72 | $this->assertEquals('http://localhost/en/auth/login', $location);
73 | }
74 |
75 | /**
76 | * @test
77 | */
78 | public function handle_no_language_must_redirect()
79 | {
80 | $multilang = $this->getMultilang();
81 | $multilang->setLocale('en');
82 | $middleware = new MultiLangMiddleware($this->app, $this->app->redirect, $multilang);
83 |
84 | $request = new Request(
85 | $query = [],
86 | $request = [],
87 | $attributes = [],
88 | $cookies = [],
89 | $files = [],
90 | $server = ['REQUEST_URI' => '/auth/login'],
91 | $content = null,
92 | );
93 |
94 | $result = $middleware->handle($request, static function () {
95 | return 'no_redirect';
96 | });
97 |
98 | $location = $result->headers->get('location');
99 |
100 | $this->assertEquals('http://localhost/en/auth/login', $location);
101 | }
102 |
103 | /**
104 | * @test
105 | */
106 | public function handle_query_string()
107 | {
108 | $multilang = $this->getMultilang();
109 | $multilang->setLocale('en');
110 | $middleware = new MultiLangMiddleware($this->app, $this->app->redirect, $multilang);
111 |
112 | $queryString = 'param1=value1¶m2=value2';
113 | $requestUri = '/ka/auth/login?' . $queryString;
114 |
115 | $request = new Request(
116 | $query = [],
117 | $request = [],
118 | $attributes = [],
119 | $cookies = [],
120 | $files = [],
121 | $server = ['REQUEST_URI' => $requestUri, 'QUERY_STRING' => $queryString],
122 | $content = null,
123 | );
124 |
125 | $result = $middleware->handle($request, static function () {
126 | return 'no_redirect';
127 | });
128 |
129 | $location = $result->headers->get('location');
130 |
131 | $this->assertEquals('http://localhost/en/auth/login?' . $queryString, $location);
132 | }
133 |
134 | /**
135 | * @test
136 | */
137 | public function return_404_not_found_response_when_json_is_requested_on_non_existing_url()
138 | {
139 | $multilang = $this->getMultilang();
140 | $multilang->setLocale('en');
141 | $middleware = new MultiLangMiddleware($this->app, $this->app->redirect, $multilang);
142 |
143 | $request = new Request(
144 | $query = [],
145 | $request = [],
146 | $attributes = [],
147 | $cookies = [],
148 | $files = [],
149 | $server = ['REQUEST_URI' => '/ka/auth/login'],
150 | $content = null,
151 | );
152 | $request->headers->set('accept', 'application/json');
153 |
154 | /** @var \Illuminate\Http\Response $response */
155 | $response = $middleware->handle($request, static function () {
156 | return '404';
157 | });
158 |
159 | $this->assertTrue($request->expectsJson());
160 | $this->assertEquals(404, $response->getStatusCode());
161 | $this->assertEquals('Not found', $response->getContent());
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/tests/Unit/MultiLangFacadeTest.php:
--------------------------------------------------------------------------------
1 | app['db']->getSchemaBuilder();
18 |
19 | $schema->create('texts', static function (Blueprint $table) {
20 | $table->char('key');
21 | $table->char('lang', 2);
22 | $table->text('value')->default('');
23 | $table->enum('scope', ['admin', 'site', 'global'])->default('global');
24 | $table->timestamps();
25 | $table->primary(['key', 'lang', 'scope']);
26 | });
27 |
28 | $this->inited = true;
29 | }
30 |
31 | /** @test */
32 | public function call_facade()
33 | {
34 | MultiLangFacade::setLocale('en');
35 | $texts = [
36 | 'text1' => 'Custom Text',
37 | ];
38 | MultiLangFacade::setTexts($texts);
39 |
40 | $this->assertEquals(MultiLangFacade::get('text1'), 'Custom Text');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Unit/MultiLangTest.php:
--------------------------------------------------------------------------------
1 | createTable();
20 | }
21 |
22 | /**
23 | * @test
24 | */
25 | public function get_locale()
26 | {
27 | $multilang = $this->getMultilang();
28 | $multilang->setLocale('ka');
29 |
30 | $this->assertEquals('ka', $multilang->getLocale());
31 |
32 | $multilang = $this->getMultilang();
33 | $multilang->setLocale('en');
34 |
35 | $this->assertEquals('en', $multilang->getLocale());
36 | }
37 |
38 | /**
39 | * @test
40 | */
41 | public function get_url()
42 | {
43 | $multilang = $this->getMultilang();
44 | $multilang->setLocale('ka');
45 |
46 | $this->assertEquals('ka/users', $multilang->getUrl('users'));
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function get_url_with_forced_locale()
53 | {
54 | $config = [
55 | 'locales' => [
56 | 'en' => [
57 | 'name' => 'English',
58 | 'native_name' => 'English',
59 | 'default' => true,
60 | ],
61 | 'ka' => [
62 | 'name' => 'Georgian',
63 | 'native_name' => 'ქართული',
64 | 'default' => false,
65 | ],
66 | ],
67 | ];
68 | $multilang = $this->getMultilang('local', $config);
69 | $multilang->setLocale('ka');
70 |
71 | $this->assertEquals('en/users', $multilang->getUrl('users', 'en'));
72 | $this->assertEquals('en/users', $multilang->getUrl('ka/users', 'en'));
73 | // With locale which not exists
74 | $this->assertEquals('en/ss/users', $multilang->getUrl('ss/users', 'en'));
75 | }
76 |
77 | /**
78 | * @test
79 | */
80 | public function get_route()
81 | {
82 | $multilang = $this->getMultilang();
83 | $multilang->setLocale('ka');
84 |
85 | $this->assertEquals('ka.users', $multilang->getRoute('users'));
86 | }
87 |
88 | /**
89 | * @test
90 | */
91 | public function check_get_texts()
92 | {
93 | $multilang = $this->getMultilang('testing');
94 | $texts = [
95 | 'text1' => 'value1',
96 | 'text2' => 'value2',
97 | 'te.x-t/3' => 'value3',
98 | ];
99 |
100 | $multilang->setLocale('ka');
101 | $multilang->setTexts($texts);
102 |
103 | $this->assertEquals($texts, $multilang->getTexts());
104 | }
105 |
106 | /**
107 | * @test
108 | */
109 | public function get_text_value()
110 | {
111 | $multilang = $this->getMultilang();
112 | $multilang->setLocale('ka');
113 |
114 | $multilang->setTexts([
115 | 'text1' => 'value1',
116 | 'text2' => 'value2',
117 | 'te.x-t/3' => 'value3',
118 | ]);
119 |
120 | $this->assertEquals('value1', $multilang->get('text1'));
121 |
122 | $this->assertEquals('value3', $multilang->get('te.x-t/3'));
123 | }
124 |
125 | /**
126 | * @test
127 | */
128 | public function should_return_key_when_no_lang()
129 | {
130 | $multilang = $this->getMultilang();
131 |
132 | $this->assertEquals('value5', $multilang->get('value5'));
133 | }
134 |
135 | /**
136 | * @test
137 | */
138 | public function should_return_key()
139 | {
140 | $multilang = $this->getMultilang();
141 | $multilang->setLocale('ka');
142 |
143 | $multilang->setTexts([
144 | 'text1' => 'value1',
145 | 'text2' => 'value2',
146 | 'te.x-t/3' => 'value3',
147 | ]);
148 |
149 | $this->assertEquals('value5', $multilang->get('value5'));
150 | }
151 |
152 | /**
153 | * @test
154 | */
155 | public function set_get_texts()
156 | {
157 | $multilang = $this->getMultilang();
158 | $multilang->setLocale('ka');
159 |
160 | $texts = [
161 | 'text1' => 'value1',
162 | 'text2' => 'value2',
163 | 'te.x-t/3 dsasad sadadas' => 'value3',
164 | ];
165 |
166 | $multilang->setTexts($texts);
167 |
168 | $this->assertEquals($texts, $multilang->getTexts());
169 | }
170 |
171 | /** @test */
172 | public function set_empty_locale()
173 | {
174 | $this->expectException(TypeError::class);
175 | $multilang = $this->getMultilang();
176 | $multilang->setLocale(null);
177 | }
178 |
179 | /** @test */
180 | public function get_string_without_key()
181 | {
182 | $this->expectException(TypeError::class);
183 | $multilang = $this->getMultilang();
184 | $multilang->setLocale('ka');
185 |
186 | $multilang->get(null);
187 | }
188 |
189 | /**
190 | * @test
191 | */
192 | public function check_must_load_from_cache()
193 | {
194 | $multilang = $this->getMultilang('production');
195 | $multilang->setLocale('ka');
196 |
197 | $texts = [
198 | 'text1' => 'value1',
199 | 'text2' => 'value2',
200 | ];
201 |
202 | $this->app->cache->put($multilang->getRepository()->getCacheName('ka'), $texts, 1440);
203 |
204 | $this->assertTrue($multilang->getRepository()->existsInCache('ka'));
205 |
206 | $this->app->cache->forget($multilang->getRepository()->getCacheName('ka'));
207 | }
208 |
209 | /**
210 | * @test
211 | */
212 | public function store_load_from_cache()
213 | {
214 | $multilang = $this->getMultilang('production');
215 | $multilang->setLocale('ka');
216 | $multilang->loadTexts();
217 |
218 | $this->assertTrue($multilang->getRepository()->existsInCache('ka', 'global'));
219 |
220 | $texts = [];
221 | for ($i = 0; $i <= 10; $i++) {
222 | $texts['text key ' . $i] = 'text value ' . $i;
223 | }
224 |
225 | $this->assertEquals($texts, $multilang->getRepository()->loadFromCache('ka', 'global'));
226 | }
227 |
228 | /**
229 | * @test
230 | */
231 | public function check_autosave_allowed()
232 | {
233 | $multilang = $this->getMultilang('local');
234 | $multilang->setLocale('ka');
235 |
236 | $this->assertTrue($multilang->autoSaveIsAllowed());
237 |
238 | $multilang = $this->getMultilang('production');
239 | $multilang->setLocale('ka');
240 |
241 | $this->assertFalse($multilang->autoSaveIsAllowed());
242 | }
243 |
244 | /**
245 | * @test
246 | */
247 | public function check_autosave()
248 | {
249 | $multilang = $this->getMultilang('local');
250 | $multilang->setLocale('en');
251 |
252 | $this->assertFalse($multilang->saveTexts());
253 |
254 | $strings = [
255 | 'aaaaa1' => 'aaaaa1',
256 | 'aaaaa2' => 'aaaaa2',
257 | 'aaaaa3' => 'aaaaa3',
258 | ];
259 | foreach ($strings as $string) {
260 | $multilang->get($string);
261 | }
262 |
263 | $this->assertTrue($multilang->saveTexts());
264 |
265 | $multilang = $this->getMultilang('local');
266 | $multilang->setLocale('en');
267 | $multilang->loadTexts();
268 |
269 | $this->assertEquals($strings, $multilang->getTexts());
270 | }
271 |
272 | /**
273 | * @test
274 | */
275 | public function check_autosave_for_all_langs()
276 | {
277 | $config = [
278 | 'locales' => [
279 | 'en' => [
280 | 'name' => 'English',
281 | 'native_name' => 'English',
282 | 'default' => true,
283 | ],
284 | 'ka' => [
285 | 'name' => 'Georgian',
286 | 'native_name' => 'ქართული',
287 | 'default' => false,
288 | ],
289 | ],
290 | ];
291 |
292 | $multilang = $this->getMultilang('local', $config);
293 | $multilang->setLocale('en');
294 |
295 | $this->assertFalse($multilang->saveTexts());
296 |
297 | $strings = [
298 | 'keyyy1',
299 | 'keyyy2',
300 | 'keyyy3',
301 | ];
302 | foreach ($strings as $string) {
303 | $multilang->get($string);
304 | }
305 |
306 | $this->assertTrue($multilang->saveTexts());
307 |
308 | $multilang = $this->getMultilang('local');
309 | $multilang->setLocale('ka');
310 | $multilang->loadTexts();
311 |
312 | $this->assertEquals('ka', $multilang->getLocale('ka'));
313 |
314 | $texts = $multilang->getTexts();
315 |
316 | foreach ($strings as $string) {
317 | $this->assertArrayHasKey($string, $texts);
318 | }
319 | }
320 |
321 | /**
322 | * @test
323 | */
324 | public function check_autosave_if_exists()
325 | {
326 | $multilang = $this->getMultilang('local');
327 | $multilang->setLocale('en');
328 |
329 | $this->assertFalse($multilang->saveTexts());
330 |
331 | $strings = [
332 | 'aaaaa1' => 'aaaaa1',
333 | 'aaaaa2' => 'aaaaa2',
334 | 'aaaaa3' => 'aaaaa3',
335 | ];
336 | foreach ($strings as $string) {
337 | $multilang->get($string);
338 | }
339 |
340 | $this->assertTrue($multilang->saveTexts());
341 |
342 | $this->assertTrue($multilang->saveTexts());
343 | }
344 |
345 | /**
346 | * @test
347 | */
348 | public function get_locales()
349 | {
350 | $config = [
351 | 'locales' => [
352 | 'en' => [
353 | 'name' => 'English',
354 | ],
355 | 'ka' => [
356 | 'name' => 'Georgian',
357 | ],
358 | 'az' => [
359 | 'name' => 'Azerbaijanian',
360 | ],
361 | ],
362 | ];
363 |
364 | $multilang = $this->getMultilang('local', $config);
365 | $multilang->setLocale('en');
366 |
367 | $this->assertEquals(3, count($multilang->getLocales()));
368 | }
369 |
370 | /**
371 | * @test
372 | */
373 | public function should_replace_markers()
374 | {
375 | $multilang = $this->getMultilang('local');
376 | $multilang->setLocale('en');
377 |
378 | $texts = [
379 | 'text1' => 'The :attribute must be a date after :date.',
380 | ];
381 |
382 | $multilang->setTexts($texts);
383 |
384 | $this->assertEquals(
385 | $multilang->get('text1', ['attribute' => 'Start Date', 'date' => '7 April 1986']),
386 | 'The Start Date must be a date after 7 April 1986.',
387 | );
388 | }
389 |
390 | /**
391 | * @TODO
392 | */
393 | public function check_redirect_url()
394 | {
395 | $multilang = $this->getMultilang('local');
396 | $multilang->setLocale('en');
397 |
398 | $this->assertFalse($multilang->saveTexts());
399 |
400 | $strings = [
401 | 'aaaaa1' => 'aaaaa1',
402 | 'aaaaa2' => 'aaaaa2',
403 | 'aaaaa3' => 'aaaaa3',
404 | ];
405 | foreach ($strings as $string) {
406 | $multilang->get($string);
407 | }
408 |
409 | $this->assertTrue($multilang->saveTexts());
410 |
411 | $this->assertTrue($multilang->saveTexts());
412 | }
413 |
414 | /** @test */
415 | public function scope_setter_and_getter()
416 | {
417 | $instance = $this->getMultilang();
418 | $instance->setScope('test-scope');
419 |
420 | $this->assertInstanceOf(MultiLang::class, $instance);
421 | $this->assertEquals('test-scope', $instance->getScope());
422 | }
423 |
424 | /** @test */
425 | public function load_texts_from_cache_repository()
426 | {
427 | $multilang = $this->getMultilang('production');
428 | $multilang->setLocale('ka');
429 | $texts = $multilang->getRepository()->loadFromDatabase('ka', 'global');
430 | $multilang->getRepository()->storeInCache('ka', $texts, 'global');
431 |
432 | $this->assertTrue($multilang->getRepository()->existsInCache('ka', 'global'));
433 | $cacheTexts = $multilang->loadTexts('ka', 'global');
434 | $this->assertCount(count($texts), $cacheTexts);
435 | }
436 |
437 | /** @test */
438 | public function detect_locale_should_return_default_locale_if_non_set()
439 | {
440 | $multilang = $this->getMultilang();
441 | $fallback = $multilang->detectLocale(new Request());
442 |
443 | $this->assertEquals('en', $fallback);
444 | }
445 |
446 | /** @test */
447 | public function get_all_texts_should_return_all_from_database()
448 | {
449 | $multilang = $this->getMultilang();
450 | $this->assertCount(11, $multilang->getAllTexts('ka', 'global')['ka']);
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/tests/Unit/RepositoryTest.php:
--------------------------------------------------------------------------------
1 | createTable();
18 | }
19 |
20 | /**
21 | * @test
22 | */
23 | public function check_set_get_cache_name()
24 | {
25 | $repository = $this->getRepository();
26 |
27 | $this->assertEquals('texts_ka', $repository->getCacheName('ka'));
28 | }
29 |
30 | /**
31 | * @test
32 | */
33 | public function check_set_get_cache_name_with_scope()
34 | {
35 | $repository = $this->getRepository();
36 |
37 | $this->assertEquals('texts_ka_scope_name', $repository->getCacheName('ka', 'scope_name'));
38 | }
39 |
40 | /**
41 | * @test
42 | */
43 | public function check_set_get_table_name()
44 | {
45 | $repository = $this->getRepository(['db' => ['texts_table' => 'mytable']]);
46 |
47 | $this->assertEquals('mytable', $repository->getTableName());
48 | }
49 |
50 | /**
51 | * @test
52 | */
53 | public function save_and_load_from_database()
54 | {
55 | $config = [
56 | 'locales' => [
57 | 'en' => [
58 | 'name' => 'English',
59 | ],
60 | 'az' => [
61 | 'name' => 'Azerbaijanian',
62 | ],
63 | ],
64 | ];
65 | $repository = $this->getRepository($config);
66 |
67 | $texts = [
68 | 'text1' => 'value1',
69 | 'text2' => 'value2',
70 | 'text3' => 'value3',
71 | ];
72 |
73 | $textsScoped = [
74 | 'text1' => 'value1 scoped',
75 | 'text2' => 'value2 scoped',
76 | 'text3' => 'value3 scoped',
77 | ];
78 |
79 | $repository->save($texts);
80 | $repository->save($textsScoped, 'site');
81 |
82 | $this->assertFalse($repository->save([]));
83 | $this->assertEquals($texts, $repository->loadFromDatabase('en'));
84 | $this->assertEquals($texts, $repository->loadFromDatabase('az'));
85 | $this->assertEquals($textsScoped, $repository->loadFromDatabase('en', 'site'));
86 | $this->assertEquals($textsScoped, $repository->loadFromDatabase('az', 'site'));
87 | }
88 |
89 | /**
90 | * @test
91 | */
92 | public function save_and_load_from_cache()
93 | {
94 | $config = [
95 | 'locales' => [
96 | 'en' => [
97 | 'name' => 'English',
98 | ],
99 | 'az' => [
100 | 'name' => 'Azerbaijanian',
101 | ],
102 | ],
103 | ];
104 | $repository = $this->getRepository($config);
105 |
106 | $texts = [
107 | 'text1' => 'value1',
108 | 'text2' => 'value2',
109 | 'text3' => 'value3',
110 | ];
111 |
112 | $repository->storeInCache('en', $texts);
113 | $repository->storeInCache('az', $texts);
114 |
115 | $this->assertTrue($repository->existsInCache('en'));
116 | $this->assertTrue($repository->existsInCache('az'));
117 |
118 | $this->assertEquals($texts, $repository->loadFromCache('en'));
119 | $this->assertEquals($texts, $repository->loadFromCache('az'));
120 | }
121 | }
122 |
--------------------------------------------------------------------------------