├── .editorconfig ├── .formatter.yml ├── .gitignore ├── .php_cs ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── translation-server ├── box.json ├── build └── translation-server.phar ├── composer.json ├── phpunit.xml.dist ├── src ├── TranslationServer │ ├── Command │ │ ├── Abstracts │ │ │ └── AbstractTranslationServerCommand.php │ │ ├── AddCommand.php │ │ ├── GuessCommand.php │ │ ├── MetricsCommand.php │ │ └── SortCommand.php │ ├── Console │ │ └── Application.php │ ├── Finder │ │ ├── ConfigFinder.php │ │ └── FileFinder.php │ ├── Loader │ │ ├── ConfigLoader.php │ │ └── MetricsLoader.php │ ├── Model │ │ ├── Abstracts │ │ │ ├── RepositoryAccessible.php │ │ │ └── TranslationAccessible.php │ │ ├── Interfaces │ │ │ ├── Saveable.php │ │ │ └── Sortable.php │ │ ├── Project.php │ │ ├── Repository.php │ │ ├── RepositoryCollection.php │ │ ├── Translation.php │ │ └── TranslationCollection.php │ ├── Picker │ │ └── TranslationPicker.php │ └── TranslationServer.php └── bootstrap.php └── tests ├── Fixtures ├── domain.ca.yml └── domain.en.yml └── TranslationServer └── Model ├── Abstracts └── AbstractModelTest.php ├── ProjectTest.php └── RepositoryTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; top-most EditorConfig file 2 | root = true 3 | 4 | ; Unix-style newlines 5 | [*] 6 | end_of_line = LF 7 | 8 | [*.php] 9 | indent_style = space 10 | indent_size = 4 -------------------------------------------------------------------------------- /.formatter.yml: -------------------------------------------------------------------------------- 1 | use-sort: 2 | group: 3 | - _main 4 | - Mmoreram 5 | group-type: each 6 | sort-type: alph 7 | sort-direction: asc 8 | 9 | strict: true 10 | header: | 11 | /* 12 | * This file is part of the translation-server package 13 | * 14 | * Copyright (c) 2015 Marc Morera 15 | * 16 | * For the full copyright and license information, please view the LICENSE 17 | * file that was distributed with this source code. 18 | * 19 | * Feel free to edit as you please, and have fun. 20 | * 21 | * @author Marc Morera 22 | */ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !/bin/compile 2 | !/bin/translation-server 3 | /bin/* 4 | /build 5 | /translation-server.phar 6 | /vendor 7 | composer.lock 8 | composer.phar 9 | phpunit.xml 10 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | level(Symfony\CS\FixerInterface::SYMFONY_LEVEL) 6 | // and extra fixers: 7 | ->fixers(array( 8 | 'concat_with_spaces', 9 | 'multiline_spaces_before_semicolon', 10 | 'short_array_syntax', 11 | '-remove_lines_between_uses' 12 | )) 13 | ->finder( 14 | Symfony\CS\Finder\DefaultFinder::create() 15 | ->in('src/') 16 | ->in('tests/') 17 | ) 18 | ; 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 7.1 7 | 8 | install: 9 | - composer update --prefer-source --no-interaction 10 | 11 | script: 12 | - vendor/bin/phpunit --coverage-text 13 | 14 | notifications: 15 | email: false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Marc Morera 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Translation Server 2 | ================== 3 | 4 | This Translation Server aims to provide a very easy, intuitive and fast way of 5 | adding new translations in your projects, following the Symfony Standards, and 6 | using the console as interface. 7 | 8 | ## Tags 9 | 10 | * Use last unstable version ( alias of `dev-master` ) to stay in last commit 11 | * Use last stable version tag to stay in a stable release. 12 | * [![Latest Unstable Version](https://poser.pugx.org/mmoreram/translation-server/v/unstable.png)](https://packagist.org/packages/mmoreram/translation-server) 13 | [![Latest Stable Version](https://poser.pugx.org/mmoreram/translation-server/v/stable.png)](https://packagist.org/packages/mmoreram/translation-server) 14 | 15 | ## Install 16 | 17 | Install Translation Server in this way: 18 | 19 | ``` bash 20 | $ composer global require mmoreram/translation-server=dev-master 21 | ``` 22 | 23 | If it is the first time you globally install a dependency then make sure 24 | you include `~/.composer/vendor/bin` in $PATH as shown [here](http://getcomposer.org/doc/03-cli.md#global). 25 | 26 | ### Always keep your Translation Server installation up to date: 27 | 28 | ``` bash 29 | $ composer global update mmoreram/translation-server 30 | ``` 31 | 32 | ### .phar file 33 | 34 | You can also use already last built `.phar`. 35 | 36 | ``` bash 37 | $ git clone git@github.com:mmoreram/translation-server.git 38 | $ cd translation-server 39 | $ php build/translation-server.phar 40 | ``` 41 | 42 | You can copy the `.phar` file as a global script 43 | 44 | ``` bash 45 | $ cp build/translation-server.phar /usr/local/bin/translation-server 46 | ``` 47 | 48 | ### Compile 49 | 50 | Finally you can also compile your own version of the package. ( You need set `phar.readonly = Off` in your php.ini ). 51 | 52 | ``` bash 53 | $ git clone git@github.com:mmoreram/translation-server.git 54 | $ cd translation-server 55 | $ composer update 56 | $ php bin/compile 57 | $ sudo chmod +x build/translation-server.phar 58 | $ build/translation-server.phar 59 | ``` 60 | 61 | You can copy the `.phar` file as a global script 62 | 63 | ``` bash 64 | $ cp build/translation-server.phar /usr/local/bin/translation-server 65 | ``` 66 | 67 | ## Config 68 | 69 | If your project wants to provide support for this project, make sure you place 70 | the definition about where are your translations, your master language and what 71 | languages do you support in a file called `.translation.yml`. 72 | 73 | This file has this format (no needs to explain it, right? ^^) 74 | 75 | ``` yml 76 | master_language: en 77 | languages: 78 | - en 79 | - es 80 | - ca 81 | - fr 82 | - de 83 | - it 84 | - fi 85 | - eu 86 | - gl 87 | - eo 88 | - nl 89 | paths: 90 | - src/Folder/*/Resources/translations 91 | 92 | ``` 93 | 94 | you can also define where to search the `.translation.yml` file using the 95 | `--config|-c` option 96 | 97 | ``` bash 98 | $ translation-server translation:server:view --config="src/" 99 | ``` 100 | 101 | ## Commands 102 | 103 | This server provides a set of commands useful for your project. Let's see all 104 | these commands one by one. 105 | 106 | ``` bash 107 | Console Tool 108 | 109 | Usage: 110 | [options] command [arguments] 111 | 112 | Options: 113 | --help -h Display this help message. 114 | --quiet -q Do not output any message. 115 | --verbose -v|vv|vvv Increase the verbosity of messages 116 | --version -V Display this application version. 117 | --ansi Force ANSI output. 118 | --no-ansi Disable ANSI output. 119 | --no-interaction -n Do not ask any interactive question. 120 | 121 | Available commands: 122 | help Displays help for a command 123 | list Lists commands 124 | translation 125 | translation:server:add Add new translation 126 | translation:server:sort Sort translations 127 | translation:server:view View statics about the server 128 | ``` 129 | 130 | ### Translations statics 131 | 132 | You can see all your translation statics by using this command. Without any 133 | extra configuration, you will be able to see statics of all the project. 134 | 135 | ``` bash 136 | $ translation-server translation:server:view 137 | 138 | [Trans Server] Command started at Thu, 08 Oct 2015 00:52:41 +0200 139 | [Trans Server] Translations for [en] is 100% completed. 0 missing 140 | [Trans Server] Translations for [ca] is 99.57% completed. 4 missing 141 | [Trans Server] Translations for [es] is 99.57% completed. 4 missing 142 | [Trans Server] Translations for [fr] is 78.34% completed. 203 missing 143 | [Trans Server] Translations for [de] is 68.84% completed. 292 missing 144 | [Trans Server] Translations for [it] is 0.11% completed. 936 missing 145 | [Trans Server] Translations for [nl] is 0% completed. 937 missing 146 | [Trans Server] Translations for [eo] is 0% completed. 937 missing 147 | [Trans Server] Translations for [gl] is 0% completed. 937 missing 148 | [Trans Server] Translations for [eu] is 0% completed. 937 missing 149 | [Trans Server] Translations for [fi] is 0% completed. 937 missing 150 | [Trans Server] Command finished in 932 milliseconds 151 | [Trans Server] Max memory used: 13893632 bytes 152 | ``` 153 | 154 | #### Filtering by language 155 | 156 | You can filter all results by language using the option [--language|-l] in 157 | all your commands. This is just a mask, so if you define one or more language, 158 | will simply mask all results provided to you. 159 | 160 | ``` bash 161 | $ translation-server translation:server:view --language es -l ca 162 | 163 | [Trans Server] Command started at Thu, 08 Oct 2015 00:54:09 +0200 164 | [Trans Server] Translations for [en] is 100% completed. 0 missing 165 | [Trans Server] Translations for [ca] is 99.57% completed. 4 missing 166 | [Trans Server] Translations for [es] is 99.57% completed. 4 missing 167 | [Trans Server] Command finished in 917 milliseconds 168 | [Trans Server] Max memory used: 13893632 bytes 169 | ``` 170 | 171 | As you can see, you can provide several languages 172 | 173 | #### Filtering by domain 174 | 175 | You can filter all results by domain using the option [--domain|-d] in 176 | all your commands. This is just a mask, so if you define one or more domains, 177 | will simply mask all results provided to you. 178 | 179 | ``` bash 180 | $ translation-server translation:server:view --domain routes 181 | 182 | [Trans Server] Command started at Thu, 08 Oct 2015 00:56:16 +0200 183 | [Trans Server] Translations for [de] is 100% completed. 0 missing 184 | [Trans Server] Translations for [en] is 100% completed. 0 missing 185 | [Trans Server] Translations for [ca] is 100% completed. 0 missing 186 | [Trans Server] Translations for [es] is 100% completed. 0 missing 187 | [Trans Server] Translations for [eo] is 0% completed. 38 missing 188 | [Trans Server] Translations for [nl] is 0% completed. 38 missing 189 | [Trans Server] Translations for [gl] is 0% completed. 38 missing 190 | [Trans Server] Translations for [it] is 0% completed. 38 missing 191 | [Trans Server] Translations for [fr] is 0% completed. 38 missing 192 | [Trans Server] Translations for [fi] is 0% completed. 38 missing 193 | [Trans Server] Translations for [eu] is 0% completed. 38 missing 194 | [Trans Server] Command finished in 842 milliseconds 195 | [Trans Server] Max memory used: 13369344 bytes 196 | ``` 197 | 198 | You can provide as well several domains, even mix languages and domains. 199 | 200 | ### Asking for new translations 201 | 202 | The power of this tool is that, just telling what language you'd like to work 203 | with, the tool will ask you interactively some translations. 204 | 205 | Let's see an example. In that case we will ask some translations in Basque, but 206 | we only want to add routing translations, marked with the domain `routes`. 207 | 208 | ``` bash 209 | $ translation-server translation:server:add --language eu --domain routes 210 | 211 | [Trans Server] Command started at Thu, 08 Oct 2015 00:59:43 +0200 212 | [Trans Server] Language : eu 213 | [Trans Server] Key : store_cart_nav 214 | [Trans Server] Original : /cart/nav 215 | [Trans Server] Translation : [] 216 | ``` 217 | 218 | At this point, the prompt will wait for your response here. 219 | As soon as you have introduced your translation, just press *Enter* and your 220 | translation will be stored in it's place. 221 | 222 | This means that if a new file must be created in order to store your 223 | translation, for example if you are creating a new language, the process will 224 | create it. 225 | 226 | ``` bash 227 | $ translation-server translation:server:add --language eu --domain routes 228 | 229 | [Trans Server] Command started at Thu, 08 Oct 2015 00:59:43 +0200 230 | [Trans Server] Language : eu 231 | [Trans Server] Key : store_cart_nav 232 | [Trans Server] Original : /cart/nav 233 | [Trans Server] Translation : /saskia/nab 234 | 235 | [Trans Server] Command started at Thu, 08 Oct 2015 01:00:25 +0200 236 | [Trans Server] Language : eu 237 | [Trans Server] Key : store_checkout_address 238 | [Trans Server] Original : /cart/address 239 | [Trans Server] Translation : [] 240 | ``` 241 | 242 | As soon as the value is saved, the system will ask for another translation. As 243 | long as you don't press *Ctrl+C*, the system will do it once and again. 244 | 245 | ### Guessing missing translations 246 | 247 | For lazy people, you can even translate your site using Google Translate. Of 248 | course we strongly recommend you to use this feature carefully, with a post 249 | checking of the results. 250 | 251 | ``` bash 252 | $ translation-server translation:server:guess --language eu 253 | 254 | [Trans Server] Command started at Thu, 08 Oct 2015 00:59:43 +0200 255 | [Trans Server] Language : eu 256 | [Trans Server] Key : store_cart_nav 257 | [Trans Server] Original : /cart/nav 258 | [Trans Server] Translation : /saskia/nab 259 | ``` 260 | 261 | The command will start guessing your missing translations until you decide to 262 | stop with *Ctrl+C*. 263 | 264 | This project uses the library 265 | [Stichoza/google-translate-php](https://github.com/Stichoza/google-translate-php). 266 | Easy and simple. Big kudos! 267 | 268 | Of course, you can filter by language and domain. 269 | 270 | ### Sorting your translations 271 | 272 | You can sort all your translations as well, just using this great command. 273 | 274 | ``` bash 275 | $ translation-server translation:server:sort 276 | 277 | [Trans Server] Command started at Thu, 08 Oct 2015 01:06:15 +0200 278 | [Trans Server] Your translations have been sorted successfuly 279 | [Trans Server] Command finished in 1288 milliseconds 280 | [Trans Server] Max memory used: 13631488 bytes 281 | ``` 282 | 283 | Again, you can filter by language and domain. 284 | -------------------------------------------------------------------------------- /bin/translation-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 15 | */ 16 | 17 | require_once __DIR__ . '/../src/bootstrap.php'; 18 | 19 | use Mmoreram\TranslationServer\Console\Application; 20 | 21 | error_reporting(-1); 22 | ini_set('display_errors', "1"); 23 | 24 | // run the command application 25 | $application = new Application(); 26 | $application->run(); -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "SHA1", 3 | "alias": "translation-server.phar", 4 | "banner": "This file is part of the translation-server package\n\nCopyright (c) 2015 Marc Morera\n\nFor the full copyright and license information, please view the LICENSE\nfile that was distributed with this source code.\n\nFeel free to edit as you please, and have fun.\n\n@author Marc Morera ", 5 | "chmod": "0755", 6 | "compactors": [ 7 | "Herrera\\Box\\Compactor\\Json", 8 | "Herrera\\Box\\Compactor\\Php" 9 | ], 10 | "directories": "src", 11 | "extract": true, 12 | "files": [ 13 | "LICENSE", 14 | "vendor/autoload.php" 15 | ], 16 | "finder": [ 17 | { 18 | "exclude": [ 19 | "tests", 20 | "Tests" 21 | ], 22 | "in": [ 23 | "vendor/guzzlehttp", 24 | "vendor/stichoza", 25 | "vendor/paragonie", 26 | "vendor/composer", 27 | "vendor/symfony" 28 | ], 29 | "name": "*.php" 30 | } 31 | ], 32 | "git-version": "package_version", 33 | "main": "bin/translation-server", 34 | "output": "build/translation-server.phar", 35 | "stub": true 36 | } 37 | -------------------------------------------------------------------------------- /build/translation-server.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmoreram/translation-server/2a100de158c8ece231bac7c557dfd800ae2d5b1e/build/translation-server.phar -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmoreram/translation-server", 3 | "type": "library", 4 | "description": "PHP translation server", 5 | "keywords": [ 6 | "php", "translation", "server" 7 | ], 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Marc Morera", 12 | "email": "yuhu@mmoreram.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=7.1", 17 | "symfony/console": "^3.2", 18 | "symfony/process": "^3.2", 19 | "symfony/finder": "^3.2", 20 | "symfony/filesystem": "^3.2", 21 | "symfony/stopwatch": "^3.2", 22 | "symfony/yaml": "^3.2", 23 | "symfony/event-dispatcher": "^3.2", 24 | "symfony/property-access": "^3.2", 25 | 26 | "stichoza/google-translate-php": "^3.1" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^5.6.4", 30 | "friendsofphp/php-cs-fixer": "^1.12.4", 31 | "mmoreram/php-formatter": "^1.3.1" 32 | }, 33 | "bin": [ 34 | "bin/translation-server" 35 | ], 36 | "autoload": { 37 | "psr-4": {"Mmoreram\\TranslationServer\\": "src/TranslationServer"} 38 | }, 39 | "autoload-dev": { 40 | "psr-4": {"Mmoreram\\TranslationServer\\Tests\\": "tests/TranslationServer"} 41 | }, 42 | "scripts": { 43 | "fix-code": [ 44 | "vendor/bin/php-cs-fixer fix --config-file=.php_cs", 45 | "vendor/bin/php-formatter f:h:f . --exclude=vendor --verbose", 46 | "vendor/bin/php-formatter f:s:f . --exclude=vendor --verbose", 47 | "vendor/bin/php-formatter f:u:s . --exclude=vendor --verbose" 48 | ], 49 | "test": "vendor/bin/phpunit" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ./tests/ 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/TranslationServer/Command/Abstracts/AbstractTranslationServerCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Command\Abstracts; 19 | 20 | use Symfony\Component\Console\Command\Command; 21 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 22 | use Symfony\Component\Console\Input\InputInterface; 23 | use Symfony\Component\Console\Input\InputOption; 24 | use Symfony\Component\Console\Output\OutputInterface; 25 | use Symfony\Component\Stopwatch\Stopwatch; 26 | use Symfony\Component\Stopwatch\StopwatchEvent; 27 | 28 | use Mmoreram\TranslationServer\Finder\ConfigFinder; 29 | use Mmoreram\TranslationServer\Loader\ConfigLoader; 30 | use Mmoreram\TranslationServer\Model\Project; 31 | 32 | /** 33 | * Class AbstractTranslationServerCommand. 34 | */ 35 | class AbstractTranslationServerCommand extends Command 36 | { 37 | /** 38 | * @var Stopwatch 39 | * 40 | * Stopwatch instance 41 | */ 42 | private $stopwatch; 43 | 44 | /** 45 | * configure. 46 | */ 47 | protected function configure() 48 | { 49 | $this 50 | ->addOption( 51 | '--config', 52 | '-c', 53 | InputOption::VALUE_OPTIONAL, 54 | 'Config file directory', 55 | getcwd() 56 | ) 57 | ->addOption( 58 | '--domain', 59 | '-d', 60 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 61 | 'Desired domains', 62 | [] 63 | ) 64 | ->addOption( 65 | '--language', 66 | '-l', 67 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 68 | 'Desired languages', 69 | [] 70 | ); 71 | } 72 | 73 | /** 74 | * Create project given an input instance. 75 | * 76 | * @param InputInterface $input 77 | * 78 | * @return Project 79 | */ 80 | public function createProjectByInput(InputInterface $input) : Project 81 | { 82 | $configFinder = new ConfigFinder(); 83 | $configLoader = new ConfigLoader(); 84 | 85 | /** 86 | * This section is just for finding the right values to work with in 87 | * this execution. 88 | * 89 | * $options array will have, after this block, all these values 90 | */ 91 | $configPath = rtrim($input->getOption('config'), DIRECTORY_SEPARATOR); 92 | $configValues = $configLoader->loadConfigValues( 93 | $configFinder->findConfigFile($configPath) 94 | ); 95 | 96 | $masterLanguage = $configValues['master_language']; 97 | $languages = $configValues['languages']; 98 | $filePaths = $configValues['paths']; 99 | 100 | return Project::create( 101 | $masterLanguage, 102 | $languages, 103 | $filePaths 104 | ); 105 | } 106 | 107 | /** 108 | * Start command. 109 | * 110 | * @param OutputInterface $output 111 | * @param bool $longCommand 112 | */ 113 | protected function startCommand( 114 | OutputInterface $output, 115 | bool $longCommand = false 116 | ) { 117 | $this->configureFormatter($output); 118 | $this->stopwatch = new Stopwatch(); 119 | $this->startStopWatch('command'); 120 | $output->writeln(''); 121 | $this 122 | ->printMessage( 123 | $output, 124 | $this->getProjectHeader(), 125 | 'Command started at ' . date('r') 126 | ); 127 | 128 | if ($longCommand) { 129 | $this 130 | ->printMessage( 131 | $output, 132 | $this->getProjectHeader(), 133 | 'This process may take a few minutes. Please, be patient' 134 | ); 135 | } 136 | } 137 | 138 | /** 139 | * Configure formatter with Elcodi specific style. 140 | * 141 | * @param OutputInterface $output 142 | */ 143 | protected function configureFormatter(OutputInterface $output) 144 | { 145 | $formatter = $output->getFormatter(); 146 | $formatter->setStyle('header', new OutputFormatterStyle('green')); 147 | $formatter->setStyle('failheader', new OutputFormatterStyle('red')); 148 | $formatter->setStyle('body', new OutputFormatterStyle('white')); 149 | } 150 | 151 | /** 152 | * Start stopwatch. 153 | * 154 | * @param string $eventName 155 | * 156 | * @return StopwatchEvent 157 | */ 158 | protected function startStopWatch($eventName) : StopwatchEvent 159 | { 160 | return $this 161 | ->stopwatch 162 | ->start($eventName); 163 | } 164 | 165 | /** 166 | * Print message. 167 | * 168 | * @param OutputInterface $output 169 | * @param string $header 170 | * @param string $body 171 | */ 172 | protected function printMessage( 173 | OutputInterface $output, 174 | $header, 175 | $body 176 | ) { 177 | $message = sprintf( 178 | '
%s
%s', 179 | '[' . $header . ']', 180 | $body 181 | ); 182 | $output->writeln($message); 183 | } 184 | 185 | /** 186 | * Print message. 187 | * 188 | * @param OutputInterface $output 189 | * @param string $header 190 | * @param string $body 191 | */ 192 | protected function printMessageFail( 193 | OutputInterface $output, 194 | string $header, 195 | string $body 196 | ) { 197 | $message = sprintf( 198 | '%s %s', 199 | '[' . $header . ']', 200 | $body 201 | ); 202 | $output->writeln($message); 203 | } 204 | 205 | /** 206 | * Finish command. 207 | * 208 | * @param OutputInterface $output 209 | */ 210 | protected function finishCommand(OutputInterface $output) 211 | { 212 | $event = $this->stopStopWatch('command'); 213 | $this 214 | ->printMessage( 215 | $output, 216 | $this->getProjectHeader(), 217 | 'Command finished in ' . $event->getDuration() . ' milliseconds' 218 | ); 219 | $this->printMessage( 220 | $output, 221 | $this->getProjectHeader(), 222 | 'Max memory used: ' . $event->getMemory() . ' bytes' 223 | ); 224 | $output->writeln(''); 225 | } 226 | 227 | /** 228 | * Stop stopwatch. 229 | * 230 | * @param string $eventName Event name 231 | * 232 | * @return StopwatchEvent 233 | */ 234 | protected function stopStopWatch($eventName) : StopwatchEvent 235 | { 236 | return $this 237 | ->stopwatch 238 | ->stop($eventName); 239 | } 240 | 241 | /** 242 | * Get project header. 243 | * 244 | * @return string 245 | */ 246 | protected function getProjectHeader() : string 247 | { 248 | return 'Trans Server'; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/TranslationServer/Command/AddCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Command; 19 | 20 | use Exception; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Symfony\Component\Console\Output\OutputInterface; 23 | use Symfony\Component\Console\Question\Question; 24 | 25 | use Mmoreram\TranslationServer\Command\Abstracts\AbstractTranslationServerCommand; 26 | use Mmoreram\TranslationServer\Model\Translation; 27 | 28 | /** 29 | * Class AddCommand. 30 | */ 31 | class AddCommand extends AbstractTranslationServerCommand 32 | { 33 | /** 34 | * configure. 35 | */ 36 | protected function configure() 37 | { 38 | $this 39 | ->setName('translation:server:add') 40 | ->setDescription('Add new translation'); 41 | 42 | parent::configure(); 43 | } 44 | 45 | /** 46 | * Execute command. 47 | * 48 | * @param InputInterface $input Input 49 | * @param OutputInterface $output Output 50 | * 51 | * @return int|null|void 52 | * 53 | * @throws Exception 54 | */ 55 | protected function execute(InputInterface $input, OutputInterface $output) 56 | { 57 | $this->startCommand($output, false); 58 | $domains = $input->getOption('domain'); 59 | $languages = $input->getOption('language'); 60 | $project = $this->createProjectByInput($input); 61 | $questionHelper = $this->getHelper('question'); 62 | 63 | while (true) { 64 | $randomTranslation = $project->getRandomMissingTranslation( 65 | $domains, 66 | $languages 67 | ); 68 | 69 | if (!($randomTranslation instanceof Translation)) { 70 | $this 71 | ->printMessage( 72 | $output, 73 | 'Trans Server', 74 | 'No more translations for you!' 75 | ); 76 | 77 | break; 78 | } 79 | 80 | $this->printMessage( 81 | $output, 82 | 'Trans Server', 83 | 'Language : ' . $randomTranslation->getLanguage() 84 | ); 85 | $this->printMessage( 86 | $output, 87 | 'Trans Server', 88 | 'Key : ' . $randomTranslation->getKey() 89 | ); 90 | $this->printMessage( 91 | $output, 92 | 'Trans Server', 93 | 'Original : ' . $randomTranslation 94 | ->getMasterTranslation() 95 | ->getValue() 96 | ); 97 | 98 | $question = new Question('[Trans Server] Translation : '); 99 | $translationValue = $questionHelper->ask( 100 | $input, 101 | $output, 102 | $question 103 | ); 104 | 105 | if (false === $translationValue) { 106 | break; 107 | } 108 | 109 | $randomTranslation->setValue($translationValue); 110 | $masterStructure = $randomTranslation->getStructure(); 111 | $this->overwriteLastValueFromStructure( 112 | $masterStructure, 113 | $translationValue 114 | ); 115 | $randomTranslation->setStructure($masterStructure); 116 | 117 | $project->addTranslation($randomTranslation); 118 | $project->save(); 119 | $output->writeln(''); 120 | } 121 | 122 | $this->finishCommand($output); 123 | } 124 | 125 | /** 126 | * Overwrite the last child of a structure for given value. 127 | * 128 | * @param array $structure 129 | * @param mixed $value 130 | */ 131 | protected function overwriteLastValueFromStructure( 132 | array &$structure, 133 | $value 134 | ) { 135 | $pointer = &$structure; 136 | $currentKey = key($pointer); 137 | $currentValue = &$pointer[$currentKey]; 138 | while (is_array($currentValue)) { 139 | $pointer = &$currentValue; 140 | $currentKey = key($pointer); 141 | $currentValue = &$pointer[$currentKey]; 142 | } 143 | 144 | $pointer[$currentKey] = $value; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/TranslationServer/Command/GuessCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Command; 19 | 20 | use Exception; 21 | use Stichoza\GoogleTranslate\TranslateClient; 22 | use Symfony\Component\Console\Input\InputInterface; 23 | use Symfony\Component\Console\Output\OutputInterface; 24 | 25 | use Mmoreram\TranslationServer\Model\Translation; 26 | 27 | /** 28 | * Class GuessCommand. 29 | */ 30 | class GuessCommand extends AddCommand 31 | { 32 | /** 33 | * configure. 34 | */ 35 | protected function configure() 36 | { 37 | parent::configure(); 38 | 39 | $this 40 | ->setName('translation:server:guess') 41 | ->setDescription('Guess missing translations'); 42 | } 43 | 44 | /** 45 | * Execute command. 46 | * 47 | * @param InputInterface $input Input 48 | * @param OutputInterface $output Output 49 | * 50 | * @return int|null|void 51 | * 52 | * @throws Exception 53 | */ 54 | protected function execute(InputInterface $input, OutputInterface $output) 55 | { 56 | $this->startCommand($output, false); 57 | $domains = $input->getOption('domain'); 58 | $languages = $input->getOption('language'); 59 | $project = $this->createProjectByInput($input); 60 | 61 | while (true) { 62 | $randomTranslation = $project->getRandomMissingTranslation( 63 | $domains, 64 | $languages 65 | ); 66 | 67 | if (!($randomTranslation instanceof Translation)) { 68 | $this 69 | ->printMessage( 70 | $output, 71 | 'Trans Server', 72 | 'No more translations for you!' 73 | ); 74 | 75 | break; 76 | } 77 | 78 | $masterTranslation = $randomTranslation->getMasterTranslation(); 79 | $translator = new TranslateClient( 80 | $masterTranslation->getLanguage(), 81 | $randomTranslation->getLanguage() 82 | ); 83 | $translation = $translator->translate( 84 | $masterTranslation->getValue() 85 | ); 86 | 87 | $this 88 | ->printMessage( 89 | $output, 90 | 'Trans Server', 91 | 'Language : ' . $randomTranslation->getLanguage() 92 | ); 93 | $this->printMessage( 94 | $output, 95 | 'Trans Server', 96 | 'Key : ' . $randomTranslation->getKey() 97 | ); 98 | $this->printMessage( 99 | $output, 100 | 'Trans Server', 101 | 'Original : ' . $randomTranslation 102 | ->getMasterTranslation() 103 | ->getValue() 104 | ); 105 | $this->printMessage( 106 | $output, 107 | 'Trans Server', 108 | 'Translation : ' . $translation 109 | ); 110 | 111 | $randomTranslation->setValue($translation); 112 | $masterStructure = $randomTranslation->getStructure(); 113 | $this->overwriteLastValueFromStructure( 114 | $masterStructure, 115 | $translation 116 | ); 117 | $randomTranslation->setStructure($masterStructure); 118 | 119 | $project 120 | ->addTranslation($randomTranslation) 121 | ->save(); 122 | $output->writeln(''); 123 | } 124 | 125 | $this->finishCommand($output); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/TranslationServer/Command/MetricsCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Command; 19 | 20 | use Exception; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Symfony\Component\Console\Output\OutputInterface; 23 | 24 | use Mmoreram\TranslationServer\Command\Abstracts\AbstractTranslationServerCommand; 25 | use Mmoreram\TranslationServer\Loader\MetricsLoader; 26 | 27 | /** 28 | * Class MetricsCommand. 29 | */ 30 | class MetricsCommand extends AbstractTranslationServerCommand 31 | { 32 | /** 33 | * configure. 34 | */ 35 | protected function configure() 36 | { 37 | $this 38 | ->setName('translation:server:view') 39 | ->setDescription('View statics about the server'); 40 | 41 | parent::configure(); 42 | } 43 | 44 | /** 45 | * Execute command. 46 | * 47 | * @param InputInterface $input Input 48 | * @param OutputInterface $output Output 49 | * 50 | * @return int|null|void 51 | * 52 | * @throws Exception 53 | */ 54 | protected function execute(InputInterface $input, OutputInterface $output) 55 | { 56 | $this->startCommand($output, false); 57 | $inputDomains = $input->getOption('domain'); 58 | $inputLanguages = $input->getOption('language'); 59 | 60 | $project = $this->createProjectByInput($input); 61 | $metricsLoader = new MetricsLoader(); 62 | $metrics = $metricsLoader 63 | ->getTotalMetrics( 64 | $project, 65 | $inputDomains, 66 | $inputLanguages 67 | ); 68 | 69 | $masterLanguage = $project->getMasterLanguage(); 70 | $masterLanguageKeys = $metrics[$masterLanguage]; 71 | foreach ($metrics as $language => $percentFinished) { 72 | $languageTranslations = $metrics[$language]; 73 | $languageTranslationsMissing = $masterLanguageKeys - $languageTranslations; 74 | $languageCompleted = ($masterLanguageKeys) ? round((100 / $masterLanguageKeys) * $languageTranslations, 2) : 0; 75 | $this 76 | ->printMessage( 77 | $output, 78 | 'Trans Server', 79 | 'Translations for [' . $language . '] is ' . $languageCompleted . '% completed. ' . $languageTranslationsMissing . ' missing' 80 | ); 81 | } 82 | 83 | $this->finishCommand($output); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/TranslationServer/Command/SortCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Command; 19 | 20 | use Exception; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Symfony\Component\Console\Output\OutputInterface; 23 | 24 | use Mmoreram\TranslationServer\Command\Abstracts\AbstractTranslationServerCommand; 25 | 26 | /** 27 | * Class SortCommand. 28 | */ 29 | class SortCommand extends AbstractTranslationServerCommand 30 | { 31 | /** 32 | * configure. 33 | */ 34 | protected function configure() 35 | { 36 | $this 37 | ->setName('translation:server:sort') 38 | ->setDescription('Sort translations'); 39 | 40 | parent::configure(); 41 | } 42 | 43 | /** 44 | * Execute command. 45 | * 46 | * @param InputInterface $input Input 47 | * @param OutputInterface $output Output 48 | * 49 | * @return int|null|void 50 | * 51 | * @throws Exception 52 | */ 53 | protected function execute(InputInterface $input, OutputInterface $output) 54 | { 55 | $this->startCommand($output, false); 56 | $project = $this->createProjectByInput($input); 57 | $project->sort(); 58 | $project->save(); 59 | 60 | $this 61 | ->printMessage( 62 | $output, 63 | 'Trans Server', 64 | 'Your translations have been sorted successfuly' 65 | ); 66 | 67 | $this->finishCommand($output); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/TranslationServer/Console/Application.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Console; 19 | 20 | use Symfony\Component\Console\Application as BaseApplication; 21 | 22 | use Mmoreram\TranslationServer\Command; 23 | 24 | /** 25 | * Class Application. 26 | */ 27 | class Application extends BaseApplication 28 | { 29 | /** 30 | * Construct method. 31 | */ 32 | public function __construct() 33 | { 34 | if (function_exists('ini_set') && extension_loaded('xdebug')) { 35 | ini_set('xdebug.show_exception_trace', '0'); 36 | ini_set('xdebug.scream', '0'); 37 | } 38 | 39 | if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) { 40 | date_default_timezone_set(@date_default_timezone_get()); 41 | } 42 | 43 | parent::__construct('TranslationServer', '@package_version@'); 44 | } 45 | 46 | /** 47 | * Initializes all the composer commands. 48 | */ 49 | protected function getDefaultCommands() 50 | { 51 | $commands = parent::getDefaultCommands(); 52 | $commands[] = new Command\MetricsCommand(); 53 | $commands[] = new Command\AddCommand(); 54 | $commands[] = new Command\SortCommand(); 55 | $commands[] = new Command\GuessCommand(); 56 | 57 | return $commands; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TranslationServer/Finder/ConfigFinder.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Finder; 19 | 20 | use Exception; 21 | use Symfony\Component\Yaml\Parser as YamlParser; 22 | 23 | use Mmoreram\TranslationServer\TranslationServer; 24 | 25 | /** 26 | * Class ConfigFinder. 27 | */ 28 | class ConfigFinder 29 | { 30 | /** 31 | * Load, if exists, specific project configuration. 32 | * 33 | * @param string $path 34 | * 35 | * @return array 36 | * 37 | * @throws Exception Config file not found 38 | */ 39 | public function findConfigFile(string $path) : array 40 | { 41 | $configFilePath = rtrim($path, '/') . '/' . TranslationServer::CONFIG_FILE_NAME; 42 | if (!is_file($configFilePath)) { 43 | throw new Exception('File ".translation.yml" not found'); 44 | } 45 | 46 | $yamlParser = new YamlParser(); 47 | $config = $yamlParser->parse(file_get_contents($configFilePath)); 48 | 49 | return $config; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/TranslationServer/Finder/FileFinder.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Finder; 19 | 20 | use Symfony\Component\Finder\Finder; 21 | 22 | /** 23 | * Class FileFinder. 24 | */ 25 | class FileFinder 26 | { 27 | /** 28 | * Find all php files by path. 29 | * 30 | * @param string $path 31 | * 32 | * @return Finder 33 | */ 34 | public function findTranslationsFilesByPaths(string $path) : Finder 35 | { 36 | $finder = new Finder(); 37 | $finder 38 | ->files() 39 | ->in($path) 40 | ->name('*.php'); 41 | 42 | return $finder; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TranslationServer/Loader/ConfigLoader.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Loader; 19 | 20 | use Exception; 21 | 22 | /** 23 | * Class ConfigLoader. 24 | */ 25 | class ConfigLoader 26 | { 27 | /** 28 | * This method parses the config file, if exists, and determines the real 29 | * options values. 30 | * 31 | * @param array $configValues 32 | * 33 | * @return array 34 | * 35 | * @throws Exception Default locale not found 36 | * @throws Exception Paths not found 37 | */ 38 | public function loadConfigValues(array $configValues) : array 39 | { 40 | if (!isset($configValues['master_language'])) { 41 | throw new Exception('Your configuration file must define a master_language'); 42 | } 43 | 44 | if ( 45 | !isset($configValues['languages']) || 46 | !is_array($configValues['languages']) 47 | ) { 48 | throw new Exception('Your configuration file must define a set languages for translations'); 49 | } 50 | 51 | if ( 52 | !isset($configValues['paths']) || 53 | !is_array($configValues['paths']) 54 | ) { 55 | throw new Exception('Your configuration file must define a set of paths where to find translations'); 56 | } 57 | 58 | return $configValues; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TranslationServer/Loader/MetricsLoader.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Loader; 19 | 20 | use Mmoreram\TranslationServer\Model\Project; 21 | 22 | /** 23 | * Class MetricsLoader. 24 | */ 25 | class MetricsLoader 26 | { 27 | /** 28 | * Get total metrics given a Project. 29 | * 30 | * The format is that one 31 | * 32 | * [ 33 | * 'en' => 267, 34 | * 'ca' => 120, 35 | * 'es' => 12, 36 | * ] 37 | * 38 | * @param Project $project 39 | * @param array $domains 40 | * @param array $languages 41 | * 42 | * @return array 43 | */ 44 | public function getTotalMetrics( 45 | Project $project, 46 | array $domains = [], 47 | array $languages = [] 48 | ) : array { 49 | $metrics = []; 50 | $masterLanguage = $project->getMasterLanguage(); 51 | $languages = empty($languages) 52 | ? $project->getAvailableLanguages() 53 | : $languages; 54 | 55 | $languages = array_intersect( 56 | $languages, 57 | $project->getAvailableLanguages() 58 | ); 59 | unset($languages[$masterLanguage]); 60 | 61 | $masterKeys = $project->getKeys( 62 | $domains, 63 | [$masterLanguage] 64 | ); 65 | $masterCount = count($masterKeys); 66 | $metrics[$masterLanguage] = $masterCount; 67 | 68 | foreach ($languages as $language) { 69 | $languageKeys = $project->getKeys($domains, [$language]); 70 | $existentLanguageTranslations = array_intersect( 71 | $languageKeys, 72 | $masterKeys 73 | ); 74 | 75 | $metrics[$language] = count($existentLanguageTranslations); 76 | } 77 | arsort($metrics); 78 | 79 | return $metrics; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Abstracts/RepositoryAccessible.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model\Abstracts; 19 | 20 | use Mmoreram\TranslationServer\Model\Translation; 21 | 22 | /** 23 | * Class RepositoryAccessible. 24 | */ 25 | abstract class RepositoryAccessible extends TranslationAccessible 26 | { 27 | /** 28 | * Get translations. 29 | * 30 | * @param array $domains 31 | * @param array $languages 32 | * 33 | * @return Translation[] 34 | */ 35 | abstract public function getRepositories( 36 | array $domains = [], 37 | array $languages = [] 38 | ) : array; 39 | } 40 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Abstracts/TranslationAccessible.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model\Abstracts; 19 | 20 | use Mmoreram\TranslationServer\Model\Translation; 21 | 22 | /** 23 | * Class TranslationAccessible. 24 | */ 25 | abstract class TranslationAccessible 26 | { 27 | /** 28 | * Get translations. 29 | * 30 | * @param array $domains Domains 31 | * @param array $languages Languages 32 | * @param callable $filter Filter function 33 | * 34 | * @return Translation[] 35 | */ 36 | abstract public function getTranslations( 37 | array $domains = [], 38 | array $languages = [], 39 | callable $filter = null 40 | ) : array; 41 | 42 | /** 43 | * Get available keys. 44 | * 45 | * @param array $domains 46 | * @param array $languages 47 | * 48 | * @return array 49 | */ 50 | public function getKeys( 51 | array $domains = [], 52 | array $languages = [] 53 | ) : array { 54 | return array_reduce( 55 | $this->getTranslations( 56 | $domains, 57 | $languages 58 | ), 59 | function (array $keys, Translation $translation) { 60 | $keys[] = $translation->getKey(); 61 | 62 | return $keys; 63 | }, 64 | [] 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Interfaces/Saveable.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model\Interfaces; 19 | 20 | /** 21 | * Interface Saveable. 22 | */ 23 | interface Saveable 24 | { 25 | /** 26 | * Save structure. 27 | */ 28 | public function save(); 29 | } 30 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Interfaces/Sortable.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model\Interfaces; 19 | 20 | /** 21 | * Interface Sortable. 22 | */ 23 | interface Sortable 24 | { 25 | /** 26 | * Save structure. 27 | */ 28 | public function sort(); 29 | } 30 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Project.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model; 19 | 20 | use Exception; 21 | use Symfony\Component\Finder\Finder; 22 | 23 | use Mmoreram\TranslationServer\Model\Abstracts\RepositoryAccessible; 24 | use Mmoreram\TranslationServer\Model\Interfaces\Saveable; 25 | use Mmoreram\TranslationServer\Model\Interfaces\Sortable; 26 | 27 | /** 28 | * Class Project. 29 | */ 30 | class Project extends RepositoryAccessible implements 31 | Sortable, 32 | Saveable 33 | { 34 | /** 35 | * @var string 36 | * 37 | * Master language 38 | */ 39 | private $masterLanguage; 40 | 41 | /** 42 | * @var string[] 43 | * 44 | * Available languages 45 | */ 46 | private $availableLanguages; 47 | 48 | /** 49 | * @var string[] 50 | * 51 | * Paths 52 | */ 53 | private $paths; 54 | 55 | /** 56 | * @var RepositoryCollection 57 | * 58 | * Repository collection 59 | */ 60 | private $repositoryCollection; 61 | 62 | /** 63 | * Construct. 64 | * 65 | * @param RepositoryCollection $repositoryCollection 66 | * @param string $masterLanguage 67 | * @param string[] $availableLanguages 68 | * @param string[] $paths 69 | */ 70 | private function __construct( 71 | RepositoryCollection $repositoryCollection, 72 | string $masterLanguage, 73 | array $availableLanguages, 74 | array $paths 75 | ) { 76 | $this->repositoryCollection = $repositoryCollection; 77 | $this->masterLanguage = $masterLanguage; 78 | $this->availableLanguages = $availableLanguages; 79 | $this->paths = $paths; 80 | } 81 | 82 | /** 83 | * Get MasterLanguage. 84 | * 85 | * @return string 86 | */ 87 | public function getMasterLanguage() : string 88 | { 89 | return $this->masterLanguage; 90 | } 91 | 92 | /** 93 | * Get AvailableLanguages. 94 | * 95 | * @return string[] 96 | */ 97 | public function getAvailableLanguages() : array 98 | { 99 | return $this->availableLanguages; 100 | } 101 | 102 | /** 103 | * Get Paths. 104 | * 105 | * @return string[] 106 | */ 107 | public function getPaths() : array 108 | { 109 | return $this->paths; 110 | } 111 | 112 | /** 113 | * Get Repositories. 114 | * 115 | * @param array $domains 116 | * @param array $languages 117 | * 118 | * @return Repository[] 119 | */ 120 | public function getRepositories( 121 | array $domains = [], 122 | array $languages = [] 123 | ) : array { 124 | return $this 125 | ->repositoryCollection 126 | ->getRepositories( 127 | $domains, 128 | $languages 129 | ); 130 | } 131 | 132 | /** 133 | * Get Translations. 134 | * 135 | * @param array $domains 136 | * @param array $languages 137 | * @param callable $filter 138 | * 139 | * @return Translation[] 140 | */ 141 | public function getTranslations( 142 | array $domains = [], 143 | array $languages = [], 144 | callable $filter = null 145 | ) : array { 146 | return $this 147 | ->repositoryCollection 148 | ->getTranslations( 149 | $domains, 150 | $languages, 151 | $filter 152 | ); 153 | } 154 | 155 | /** 156 | * Get Translation given the language and the key. 157 | * 158 | * @param string $language 159 | * @param string $key 160 | * 161 | * @return Translation 162 | */ 163 | public function getTranslation( 164 | string $language, 165 | string $key 166 | ) : Translation { 167 | $languages = $this 168 | ->repositoryCollection 169 | ->getTranslations( 170 | [], 171 | [$language], 172 | function (Translation $translation) use ($key) { 173 | return $translation->getKey() === $key; 174 | } 175 | ); 176 | 177 | $firstLanguage = reset($languages); 178 | 179 | return $firstLanguage; 180 | } 181 | 182 | /** 183 | * Get random Translation. If no translations are available, return null. 184 | * 185 | * @param array $domains 186 | * @param array $languages 187 | * 188 | * @return Translation|null 189 | * 190 | * @throws Exception You cannot search missing translations by master language 191 | */ 192 | public function getRandomMissingTranslation( 193 | array $domains = [], 194 | array $languages = [] 195 | ) : ? Translation { 196 | if (in_array($this->masterLanguage, $languages)) { 197 | throw new Exception('You cannot search by master language missing translations'); 198 | } 199 | 200 | $candidates = []; 201 | $masterKeys = $this->getKeys( 202 | $domains, 203 | [$this->masterLanguage] 204 | ); 205 | 206 | $languages = $languages ?: $this->getAvailableLanguages(); 207 | 208 | foreach ($languages as $language) { 209 | $languageKeys = $this->getKeys( 210 | $domains, 211 | [$language] 212 | ); 213 | 214 | $missingKeys = array_diff( 215 | $masterKeys, 216 | $languageKeys 217 | ); 218 | foreach ($missingKeys as $missingKey) { 219 | $candidates[] = [ 220 | 'language' => $language, 221 | 'key' => $missingKey, 222 | ]; 223 | } 224 | } 225 | 226 | if (empty($candidates)) { 227 | return null; 228 | } 229 | 230 | $candidateKey = array_rand($candidates); 231 | $candidate = $candidates[$candidateKey]; 232 | $masterCandidate = $this->getTranslation( 233 | $this->masterLanguage, 234 | $candidate['key'] 235 | ); 236 | 237 | $candidateTranslation = Translation::create( 238 | $candidate['key'], 239 | '', 240 | $candidate['language'] 241 | ); 242 | 243 | $candidateTranslation->setStructure($masterCandidate->getStructure()); 244 | $candidateTranslation->setMasterTranslation($masterCandidate); 245 | 246 | return $candidateTranslation; 247 | } 248 | 249 | /** 250 | * Add translation. 251 | * 252 | * @param Translation $translation 253 | * 254 | * @throws Exception Inserting a master translation is not allowed 255 | */ 256 | public function addTranslation(Translation $translation) 257 | { 258 | if ($translation->getLanguage() === $this->masterLanguage) { 259 | throw new Exception('You cannot insert a new translation for master language'); 260 | } 261 | 262 | $originalRepository = $translation 263 | ->getMasterTranslation() 264 | ->getRepository(); 265 | 266 | $newRepositoryDirname = $originalRepository->getDirname(); 267 | $newRepositoryDomain = $originalRepository->getDomain(); 268 | $newRepositoryLanguage = $translation->getLanguage(); 269 | 270 | /** 271 | * We check if this repository exists. If not, we create it. 272 | * Otherwise, we use existing one. 273 | */ 274 | $expectedRepositories = array_filter( 275 | $this 276 | ->repositoryCollection 277 | ->getRepositories(), 278 | function (Repository $repository) use ($newRepositoryLanguage, $newRepositoryDomain, $newRepositoryDirname) { 279 | return 280 | $repository->getDomain() === $newRepositoryDomain && 281 | $repository->getLanguage() === $newRepositoryLanguage && 282 | $repository->getDirname() === $newRepositoryDirname; 283 | } 284 | ); 285 | 286 | $expectedRepository = reset($expectedRepositories); 287 | 288 | if (!($expectedRepository instanceof Repository)) { 289 | $newRepositoryPath = Repository::buildRepositoryPath( 290 | $newRepositoryDirname, 291 | $newRepositoryDomain, 292 | $newRepositoryLanguage 293 | ); 294 | $expectedRepository = Repository::createEmptyByFilePath($newRepositoryPath); 295 | $this 296 | ->repositoryCollection 297 | ->addRepository($expectedRepository); 298 | } 299 | 300 | $expectedRepository->addTranslation($translation); 301 | } 302 | 303 | /** 304 | * Save structure. 305 | */ 306 | public function sort() 307 | { 308 | $this 309 | ->repositoryCollection 310 | ->sort(); 311 | } 312 | 313 | /** 314 | * Save structure. 315 | */ 316 | public function save() 317 | { 318 | $this 319 | ->repositoryCollection 320 | ->save(); 321 | } 322 | 323 | /** 324 | * Create a project from a list of paths. 325 | * 326 | * @param string $masterLanguage 327 | * @param string[] $availableLanguages 328 | * @param string[] $paths 329 | * 330 | * @return Project 331 | */ 332 | public static function create( 333 | $masterLanguage, 334 | array $availableLanguages, 335 | array $paths 336 | ) : Project { 337 | $finder = new Finder(); 338 | $finder 339 | ->files() 340 | ->in($paths) 341 | ->name('*.yml') 342 | ->name('*.yaml'); 343 | 344 | $repositories = []; 345 | foreach ($finder as $file) { 346 | $filepath = $file->getRealpath(); 347 | $repositories[] = Repository::createByFilePath($filepath); 348 | } 349 | $repositoryCollection = RepositoryCollection::create($repositories); 350 | 351 | return new self( 352 | $repositoryCollection, 353 | $masterLanguage, 354 | $availableLanguages, 355 | $paths 356 | ); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Repository.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model; 19 | 20 | use Symfony\Component\Yaml\Dumper as YamlDumper; 21 | use Symfony\Component\Yaml\Parser as YamlParser; 22 | 23 | use Mmoreram\TranslationServer\Model\Abstracts\TranslationAccessible; 24 | use Mmoreram\TranslationServer\Model\Interfaces\Saveable; 25 | use Mmoreram\TranslationServer\Model\Interfaces\Sortable; 26 | 27 | /** 28 | * Class Repository. 29 | */ 30 | class Repository extends TranslationAccessible implements 31 | Sortable, 32 | Saveable 33 | { 34 | /** 35 | * @var string 36 | * 37 | * Path 38 | */ 39 | private $path; 40 | 41 | /** 42 | * @var string 43 | * 44 | * Language 45 | */ 46 | private $language; 47 | 48 | /** 49 | * @var string 50 | * 51 | * Domain 52 | */ 53 | private $domain; 54 | 55 | /** 56 | * @var TranslationCollection 57 | * 58 | * Translation collection 59 | */ 60 | private $translationCollection; 61 | 62 | /** 63 | * Construct. 64 | * 65 | * @param TranslationCollection $translationCollection 66 | * @param string $path 67 | * @param string $language 68 | * @param string $domain 69 | */ 70 | private function __construct( 71 | TranslationCollection $translationCollection, 72 | string $path, 73 | string $language, 74 | string $domain 75 | ) { 76 | $this->translationCollection = $translationCollection; 77 | $this->path = $path; 78 | $this->language = $language; 79 | $this->domain = $domain; 80 | 81 | foreach ($this 82 | ->translationCollection 83 | ->getTranslations() as $translation) { 84 | $translation->setRepository($this); 85 | } 86 | } 87 | 88 | /** 89 | * Create a new repository given a file path. 90 | * 91 | * @param string $filepath 92 | * 93 | * @return Repository 94 | */ 95 | public static function createByFilePath($filepath) : Repository 96 | { 97 | $filename = basename($filepath); 98 | list($domain, $language, $extension) = explode('.', $filename, 3); 99 | 100 | $yamlParser = new YamlParser(); 101 | $data = $yamlParser->parse(file_get_contents($filepath)); 102 | 103 | $emptyArray = []; 104 | $translations = []; 105 | self::createPlainRepresentationByArray( 106 | $translations, 107 | $language, 108 | ($data) ?: [], 109 | $emptyArray, 110 | '' 111 | ); 112 | 113 | $translationCollection = TranslationCollection::create($translations); 114 | 115 | return new self( 116 | $translationCollection, 117 | $filepath, 118 | $language, 119 | $domain 120 | ); 121 | } 122 | 123 | /** 124 | * Create empty repository given it's path. 125 | * 126 | * @param string $filepath 127 | * 128 | * @return Repository 129 | */ 130 | public static function createEmptyByFilePath(string $filepath) : Repository 131 | { 132 | $filename = basename($filepath); 133 | list($domain, $language, $extension) = explode('.', $filename, 3); 134 | 135 | return new self( 136 | TranslationCollection::create([]), 137 | $filepath, 138 | $language, 139 | $domain 140 | ); 141 | } 142 | 143 | /** 144 | * Given an array, return a list of plain keys and values. 145 | * 146 | * @param array $translations 147 | * @param string $language 148 | * @param array $data 149 | * @param array $structure 150 | * @param string $prefix 151 | */ 152 | private static function createPlainRepresentationByArray( 153 | array &$translations, 154 | string $language, 155 | array $data, 156 | array $structure, 157 | string $prefix 158 | ) { 159 | foreach ($data as $key => $value) { 160 | $currentStructure = $structure; 161 | if (is_array($value)) { 162 | $emptyArray = []; 163 | self::appendValueIntoStructure( 164 | $currentStructure, 165 | $key, 166 | $emptyArray 167 | ); 168 | self::createPlainRepresentationByArray( 169 | $translations, 170 | $language, 171 | $value, 172 | $currentStructure, 173 | $prefix . '.' . $key 174 | ); 175 | } else { 176 | $builtKey = trim($prefix . '.' . $key, '.'); 177 | $translation = Translation::create( 178 | $builtKey, 179 | $value, 180 | $language 181 | ); 182 | 183 | self::appendValueIntoStructure( 184 | $currentStructure, 185 | $key, 186 | $value 187 | ); 188 | $translation->setStructure($currentStructure); 189 | $translations[] = $translation; 190 | } 191 | } 192 | } 193 | 194 | /** 195 | * Add an element at the end of an array recursively (last child). 196 | * 197 | * @param array $structure 198 | * @param string|int $key 199 | * @param mixed $value 200 | */ 201 | private static function appendValueIntoStructure( 202 | array &$structure, 203 | $key, 204 | $value 205 | ) { 206 | $pointer = &$structure; 207 | while (!empty($pointer)) { 208 | $currentKey = key($pointer); 209 | $pointer = &$pointer[$currentKey]; 210 | } 211 | $pointer[$key] = $value; 212 | } 213 | 214 | /** 215 | * Add translation. 216 | * 217 | * @param Translation $translation 218 | */ 219 | public function addTranslation(Translation $translation) 220 | { 221 | $translation->setRepository($this); 222 | $this 223 | ->translationCollection 224 | ->addTranslation($translation); 225 | } 226 | 227 | /** 228 | * Get Path. 229 | * 230 | * @return string 231 | */ 232 | public function getPath() : string 233 | { 234 | return $this->path; 235 | } 236 | 237 | /** 238 | * Get Path. 239 | * 240 | * @return string 241 | */ 242 | public function buildPath() : string 243 | { 244 | return self::buildRepositoryPath( 245 | dirname($this->path), 246 | $this->domain, 247 | $this->language 248 | ); 249 | } 250 | 251 | /** 252 | * Get Dirname. 253 | * 254 | * @return string 255 | */ 256 | public function getDirname() : string 257 | { 258 | return dirname($this->path); 259 | } 260 | 261 | /** 262 | * Get Language. 263 | * 264 | * @return string 265 | */ 266 | public function getLanguage() : string 267 | { 268 | return $this->language; 269 | } 270 | 271 | /** 272 | * Get Domain. 273 | * 274 | * @return string 275 | */ 276 | public function getDomain() : string 277 | { 278 | return $this->domain; 279 | } 280 | 281 | /** 282 | * Get translations. 283 | * 284 | * @param array $domains 285 | * @param array $languages 286 | * @param callable $filter 287 | * 288 | * @return Translation[] 289 | */ 290 | public function getTranslations( 291 | array $domains = [], 292 | array $languages = [], 293 | callable $filter = null 294 | ) : array { 295 | return $this 296 | ->translationCollection 297 | ->getTranslations( 298 | $domains, 299 | $languages, 300 | $filter 301 | ); 302 | } 303 | 304 | /** 305 | * Save structure. 306 | */ 307 | public function sort() 308 | { 309 | $this 310 | ->translationCollection 311 | ->sort(); 312 | } 313 | 314 | /** 315 | * Save structure. 316 | */ 317 | public function save() 318 | { 319 | $translationStructure = []; 320 | foreach ($this->getTranslations() as $translation) { 321 | $translationStructure = array_merge_recursive( 322 | $translationStructure, 323 | $translation->getStructure() 324 | ); 325 | } 326 | 327 | $dumper = new YamlDumper(); 328 | $yaml = $dumper->dump($translationStructure, 1000); 329 | 330 | file_put_contents( 331 | $this->buildPath(), 332 | $yaml 333 | ); 334 | } 335 | 336 | /** 337 | * Build the repository path given the basename, the domain and the language. 338 | * 339 | * @param string $basename 340 | * @param string $domain 341 | * @param string $language 342 | * 343 | * @return string 344 | */ 345 | public static function buildRepositoryPath( 346 | string $basename, 347 | string $domain, 348 | string $language 349 | ) : string { 350 | return sprintf('%s/%s.%s.yml', 351 | $basename, 352 | $domain, 353 | $language 354 | ); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/RepositoryCollection.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model; 19 | 20 | use Mmoreram\TranslationServer\Model\Abstracts\RepositoryAccessible; 21 | use Mmoreram\TranslationServer\Model\Interfaces\Saveable; 22 | use Mmoreram\TranslationServer\Model\Interfaces\Sortable; 23 | 24 | /** 25 | * Class RepositoryCollection. 26 | */ 27 | class RepositoryCollection extends RepositoryAccessible implements 28 | Sortable, 29 | Saveable 30 | { 31 | /** 32 | * @var Repository[] 33 | * 34 | * Repository 35 | */ 36 | private $repositories; 37 | 38 | /** 39 | * Constructor. 40 | * 41 | * @param Repository[] $repositories Repositories 42 | */ 43 | private function __construct(array $repositories) 44 | { 45 | $this->repositories = $repositories; 46 | } 47 | 48 | /** 49 | * Add repository. 50 | * 51 | * @param Repository $repository 52 | */ 53 | public function addRepository(Repository $repository) 54 | { 55 | $this->repositories[] = $repository; 56 | } 57 | 58 | /** 59 | * Get Repositories. 60 | * 61 | * @param array $domains 62 | * @param array $languages 63 | * 64 | * @return Repository[] 65 | */ 66 | public function getRepositories( 67 | array $domains = [], 68 | array $languages = [] 69 | ) : array { 70 | $repositories = array_filter( 71 | $this->repositories, 72 | function (Repository $repository) use ($domains) { 73 | return 74 | empty($domains) || 75 | in_array($repository->getDomain(), $domains); 76 | } 77 | ); 78 | 79 | return array_filter( 80 | $repositories, 81 | function (Repository $repository) use ($languages) { 82 | return 83 | empty($languages) || 84 | in_array($repository->getLanguage(), $languages); 85 | } 86 | ); 87 | } 88 | 89 | /** 90 | * Get translations. 91 | * 92 | * @param array $domains 93 | * @param array $languages 94 | * @param callable $filter 95 | * 96 | * @return Translation[] 97 | */ 98 | public function getTranslations( 99 | array $domains = [], 100 | array $languages = [], 101 | callable $filter = null 102 | ) : array { 103 | return array_reduce( 104 | $this->getRepositories( 105 | $domains, 106 | $languages 107 | ), 108 | function (array $translations, Repository $repository) use ($domains, $languages, $filter) { 109 | return array_merge( 110 | $translations, 111 | $repository->getTranslations( 112 | $domains, 113 | $languages, 114 | $filter 115 | ) 116 | ); 117 | }, 118 | [] 119 | ); 120 | } 121 | 122 | /** 123 | * Save structure. 124 | */ 125 | public function sort() 126 | { 127 | foreach ($this->getRepositories() as $repository) { 128 | $repository->sort(); 129 | } 130 | } 131 | 132 | /** 133 | * Save structure. 134 | */ 135 | public function save() 136 | { 137 | foreach ($this->getRepositories() as $repository) { 138 | $repository->save(); 139 | } 140 | } 141 | 142 | /** 143 | * Create new instance of Repository collection. 144 | * 145 | * @param Repository[] $repositories 146 | * 147 | * @return RepositoryCollection 148 | */ 149 | public static function create(array $repositories) : RepositoryCollection 150 | { 151 | return new self($repositories); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/Translation.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model; 19 | 20 | /** 21 | * Class Translation. 22 | */ 23 | class Translation 24 | { 25 | /** 26 | * @var string|int 27 | * 28 | * Key 29 | */ 30 | private $key; 31 | 32 | /** 33 | * @var array 34 | * 35 | * Structure 36 | */ 37 | private $structure; 38 | 39 | /** 40 | * @var string 41 | * 42 | * Value 43 | */ 44 | private $value; 45 | 46 | /** 47 | * @var string 48 | * 49 | * Language 50 | */ 51 | private $language; 52 | 53 | /** 54 | * @var Repository 55 | * 56 | * Repository 57 | */ 58 | private $repository; 59 | 60 | /** 61 | * @var Translation 62 | * 63 | * Master translation 64 | */ 65 | private $masterTranslation; 66 | 67 | /** 68 | * Constructor. 69 | * 70 | * @param string|int $key 71 | * @param mixed $value 72 | * @param string $language 73 | */ 74 | private function __construct( 75 | $key, 76 | $value, 77 | string $language 78 | ) { 79 | $this->key = $key; 80 | $this->value = $value; 81 | $this->language = $language; 82 | } 83 | 84 | /** 85 | * Sets Structure. 86 | * 87 | * @param array $structure 88 | */ 89 | public function setStructure(array $structure) 90 | { 91 | $this->structure = $structure; 92 | } 93 | 94 | /** 95 | * Get Structure. 96 | * 97 | * @return array 98 | */ 99 | public function getStructure() : array 100 | { 101 | return $this->structure; 102 | } 103 | 104 | /** 105 | * Get Key. 106 | * 107 | * @return string|int 108 | */ 109 | public function getKey() 110 | { 111 | return $this->key; 112 | } 113 | 114 | /** 115 | * Get Value. 116 | * 117 | * @return mixed 118 | */ 119 | public function getValue() 120 | { 121 | return $this->value; 122 | } 123 | 124 | /** 125 | * Set Value. 126 | * 127 | * @param mixed $value 128 | */ 129 | public function setValue($value) 130 | { 131 | $this->value = $value; 132 | } 133 | 134 | /** 135 | * Get MasterTranslation. 136 | * 137 | * @return Translation 138 | */ 139 | public function getMasterTranslation() : Translation 140 | { 141 | return $this->masterTranslation; 142 | } 143 | 144 | /** 145 | * Sets MasterTranslation. 146 | * 147 | * @param Translation $masterTranslation 148 | */ 149 | public function setMasterTranslation(Translation $masterTranslation) 150 | { 151 | $this->masterTranslation = $masterTranslation; 152 | } 153 | 154 | /** 155 | * Get Language. 156 | * 157 | * @return string 158 | */ 159 | public function getLanguage() : string 160 | { 161 | return $this->language; 162 | } 163 | 164 | /** 165 | * Get Repository. 166 | * 167 | * @return Repository 168 | */ 169 | public function getRepository() : Repository 170 | { 171 | return $this->repository; 172 | } 173 | 174 | /** 175 | * Sets Repository. 176 | * 177 | * @param Repository $repository 178 | */ 179 | public function setRepository(Repository $repository) 180 | { 181 | $this->repository = $repository; 182 | } 183 | 184 | /** 185 | * Create new translation. 186 | * 187 | * @param string|int $key 188 | * @param mixed $value 189 | * @param string $language 190 | * 191 | * @return Translation 192 | */ 193 | public static function create( 194 | $key, 195 | $value, 196 | string $language 197 | ) { 198 | return new self( 199 | $key, 200 | $value, 201 | $language 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/TranslationServer/Model/TranslationCollection.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Model; 19 | 20 | use Mmoreram\TranslationServer\Model\Abstracts\TranslationAccessible; 21 | use Mmoreram\TranslationServer\Model\Interfaces\Sortable; 22 | 23 | /** 24 | * Class TranslationCollection. 25 | */ 26 | class TranslationCollection extends TranslationAccessible implements 27 | Sortable 28 | { 29 | /** 30 | * @var Translation[] 31 | * 32 | * Translation 33 | */ 34 | private $translations; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param Translation[] $translations 40 | */ 41 | private function __construct(array $translations) 42 | { 43 | $this->translations = $translations; 44 | } 45 | 46 | /** 47 | * Add translation. 48 | * 49 | * @param Translation $translation 50 | */ 51 | public function addTranslation(Translation $translation) 52 | { 53 | $this->translations[] = $translation; 54 | } 55 | 56 | /** 57 | * Get translations. 58 | * 59 | * @param array $domains 60 | * @param array $languages 61 | * @param callable $filter 62 | * 63 | * @return Translation[] 64 | */ 65 | public function getTranslations( 66 | array $domains = [], 67 | array $languages = [], 68 | callable $filter = null 69 | ) : array { 70 | $translations = array_filter( 71 | $this->translations, 72 | function (Translation $translation) { 73 | return $translation->getValue() != ''; 74 | } 75 | ); 76 | 77 | if (is_callable($filter)) { 78 | $translations = array_filter( 79 | $translations, 80 | $filter 81 | ); 82 | } 83 | 84 | return $translations; 85 | } 86 | 87 | /** 88 | * Save structure. 89 | */ 90 | public function sort() 91 | { 92 | $this->recursiveSort( 93 | $this->translations 94 | ); 95 | } 96 | 97 | /** 98 | * Sort array level. 99 | * 100 | * @param array $element 101 | */ 102 | private function recursiveSort(array &$element) 103 | { 104 | foreach ($element as &$value) { 105 | if (is_array($value)) { 106 | $this->recursiveSort($value); 107 | } 108 | } 109 | 110 | uasort($element, function ( 111 | Translation $a, 112 | Translation $b 113 | ) { 114 | return strcmp($a->getKey(), $b->getKey()); 115 | }); 116 | } 117 | 118 | /** 119 | * Create new instance of Translation collection. 120 | * 121 | * @param Translation[] $translations 122 | * 123 | * @return TranslationCollection 124 | */ 125 | public static function create(array $translations) 126 | { 127 | return new self($translations); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/TranslationServer/Picker/TranslationPicker.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer; 19 | 20 | use Mmoreram\TranslationServer\Model\Project; 21 | use Mmoreram\TranslationServer\Model\Repository; 22 | use Mmoreram\TranslationServer\Model\Translation; 23 | 24 | /** 25 | * Class TranslationPicker. 26 | */ 27 | class TranslationPicker 28 | { 29 | /** 30 | * Given a Project, a set of languages and a set of domains, return a random 31 | * non filled yet Translation object. 32 | * 33 | * If the flag revision is enabled, then only already translated elements 34 | * will be picked up 35 | * 36 | * Return translation found or null if none translation is available 37 | * 38 | * @param Project $project Project 39 | * @param array $languages Languages 40 | * @param array $domains Domains 41 | * @param bool $revision Revision only 42 | * 43 | * @return Translation|null 44 | */ 45 | public function pickUpTranslation( 46 | Project $project, 47 | array $languages, 48 | array $domains, 49 | bool $revision 50 | ) : ? Translation { 51 | $repositories = $project 52 | ->getRepositories( 53 | $domains, 54 | $languages 55 | ); 56 | 57 | $translations = array_reduce( 58 | $repositories, 59 | function (Repository $repository) use ($revision) { 60 | $translations = $repository->getTranslations(); 61 | 62 | return array_filter( 63 | $translations, 64 | function (Translation $translation) use ($revision) { 65 | return $revision === ($translation->getValue() == ''); 66 | } 67 | ); 68 | } 69 | ); 70 | 71 | $position = array_rand($translations); 72 | 73 | return $translations[$position]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/TranslationServer/TranslationServer.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer; 19 | 20 | /** 21 | * Class TranslationServer. 22 | */ 23 | class TranslationServer 24 | { 25 | /** 26 | * @var string 27 | * 28 | * Configuration file 29 | */ 30 | const CONFIG_FILE_NAME = '.translation.yml'; 31 | } 32 | -------------------------------------------------------------------------------- /src/bootstrap.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | function includeIfExists($file) 19 | { 20 | return file_exists($file) ? include $file : false; 21 | } 22 | 23 | if ((!$loader = includeIfExists(__DIR__ . '/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__ . '/../../../autoload.php'))) { 24 | echo 'You must set up the project dependencies, run the following commands:' . PHP_EOL . 25 | 'curl -sS https://getcomposer.org/installer | php' . PHP_EOL . 26 | 'php composer.phar install' . PHP_EOL; 27 | exit(1); 28 | } 29 | 30 | return $loader; 31 | -------------------------------------------------------------------------------- /tests/Fixtures/domain.ca.yml: -------------------------------------------------------------------------------- 1 | example2: 2 | even: 3 | another: 4 | subdomain: 5 | key4: valor4 6 | example: 7 | another: 8 | subdomain: 9 | key3: valor3 10 | 11 | subdomain: 12 | key: valor 13 | key2: valor2 14 | -------------------------------------------------------------------------------- /tests/Fixtures/domain.en.yml: -------------------------------------------------------------------------------- 1 | example: 2 | subdomain: 3 | key: value 4 | key2: value2 5 | another: 6 | subdomain: 7 | key3: value3 8 | last: 9 | one: super1 10 | example2: 11 | even: 12 | another: 13 | subdomain: 14 | key4: value4 15 | -------------------------------------------------------------------------------- /tests/TranslationServer/Model/Abstracts/AbstractModelTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Tests\Model\Abstracts; 19 | 20 | use PHPUnit_Framework_TestCase; 21 | 22 | use Mmoreram\TranslationServer\Model\Project; 23 | use Mmoreram\TranslationServer\Model\Repository; 24 | 25 | /** 26 | * Class AbstractModelTest. 27 | */ 28 | abstract class AbstractModelTest extends PHPUnit_Framework_TestCase 29 | { 30 | /** 31 | * Get project. 32 | */ 33 | protected function getProject() 34 | { 35 | $paths = [dirname(__FILE__) . '/../../../Fixtures']; 36 | 37 | return Project::create( 38 | 'en', 39 | ['en', 'ca'], 40 | $paths 41 | ); 42 | } 43 | 44 | /** 45 | * Get project. 46 | */ 47 | protected function getRepository() 48 | { 49 | $path = dirname(__FILE__) . '/../../../Fixtures/domain.ca.yml'; 50 | 51 | return Repository::createByFilePath($path); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/TranslationServer/Model/ProjectTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Tests\Model; 19 | 20 | use Mmoreram\TranslationServer\Tests\Model\Abstracts\AbstractModelTest; 21 | 22 | /** 23 | * Class ProjectTest. 24 | */ 25 | class ProjectTest extends AbstractModelTest 26 | { 27 | /** 28 | * Test creation by paths. 29 | * 30 | * @dataProvider dataCreateProjectByPaths 31 | */ 32 | public function testCreateProjectByPaths( 33 | array $domains, 34 | array $languages, 35 | $value 36 | ) { 37 | $project = $this->getProject(); 38 | $this->assertCount( 39 | $value, 40 | $project->getRepositories( 41 | $domains, 42 | $languages 43 | ) 44 | ); 45 | } 46 | 47 | /** 48 | * Data for testCreateProjectByPaths. 49 | */ 50 | public function dataCreateProjectByPaths() 51 | { 52 | return [ 53 | [['domain'], ['ca'], 1], 54 | [['domain'], ['en', 'ca'], 2], 55 | [['domain'], [], 2], 56 | [[], [], 2], 57 | [[], ['fr'], 0], 58 | [['domain'], ['fr'], 0], 59 | [['anotherdomain'], ['ca'], 0], 60 | [['anotherdomain'], [], 0], 61 | ]; 62 | } 63 | 64 | /** 65 | * Test get random translation. 66 | */ 67 | public function testGetRandomTranslation() 68 | { 69 | $project = $this->getProject(); 70 | $this->assertInstanceOf( 71 | 'Mmoreram\TranslationServer\Model\Translation', 72 | $project->getRandomMissingTranslation() 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/TranslationServer/Model/RepositoryTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace Mmoreram\TranslationServer\Tests\Model; 19 | 20 | use Mmoreram\TranslationServer\Model\Translation; 21 | use Mmoreram\TranslationServer\Tests\Model\Abstracts\AbstractModelTest; 22 | 23 | /** 24 | * Class RepositoryTest. 25 | */ 26 | class RepositoryTest extends AbstractModelTest 27 | { 28 | /** 29 | * Test creation by file path. 30 | */ 31 | public function testCreateByFilePath() 32 | { 33 | $repository = $this->getRepository(); 34 | $translations = $repository->getTranslations(); 35 | 36 | $this->assertCount(4, $translations); 37 | $this->assertEquals('domain', $repository->getDomain()); 38 | $this->assertEquals('ca', $repository->getLanguage()); 39 | 40 | /** 41 | * @var Translation 42 | */ 43 | $firstTranslation = reset($translations); 44 | 45 | $this->assertEquals('example2.even.another.subdomain.key4', $firstTranslation->getKey()); 46 | $this->assertEquals('valor4', $firstTranslation->getValue()); 47 | $this->assertEquals([ 48 | 'example2' => [ 49 | 'even' => [ 50 | 'another' => [ 51 | 'subdomain' => [ 52 | 'key4' => 'valor4', 53 | ], 54 | ], 55 | ], 56 | ], 57 | ], $firstTranslation->getStructure()); 58 | } 59 | 60 | /** 61 | * Test sorting Repository. 62 | */ 63 | public function testSort() 64 | { 65 | $repository = $this->getRepository(); 66 | $repository->sort(); 67 | $translations = $repository->getTranslations(); 68 | 69 | $this->assertCount(4, $translations); 70 | 71 | /** 72 | * @var Translation 73 | */ 74 | $firstTranslation = reset($translations); 75 | $this->assertEquals('example.another.subdomain.key3', $firstTranslation->getKey()); 76 | } 77 | 78 | /** 79 | * Test add translation. 80 | */ 81 | public function testAddTranslation() 82 | { 83 | $repository = $this->getRepository(); 84 | $translation = $this->createMock('Mmoreram\TranslationServer\Model\Translation'); 85 | $repository->addTranslation($translation); 86 | 87 | $translation 88 | ->expects($this->any()) 89 | ->method('getValue') 90 | ->willReturn('value'); 91 | $this->assertCount(5, $repository->getTranslations()); 92 | } 93 | } 94 | --------------------------------------------------------------------------------