diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f95b139 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/vendor +/bin +composer.phar +composer.lock +.DS_Store +.idea \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..d3b8f8f --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,19 @@ +checks: + php: + code_rating: true + duplication: true + +filter: + paths: + - src/* + excluded_paths: + - tests/* + +build: + tests: + override: + - + command: phpunit --coverage-clover=coverage + coverage: + file: coverage + format: php-clover diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0394476 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.5 + - 5.6 + - hhvm + +before_script: + - travis_retry composer self-update + - travis_retry composer install --prefer-source --no-interaction + +script: phpunit --coverage-text + +notifications: + email: + - "adrian@anavallasuiza.com" + - "carlos@anavallasuiza.com" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c13f8ad --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# Anavel translation [![Build Status](https://travis-ci.org/anavel/translation.svg?branch=master)](https://travis-ci.org/anavel/translation) + +Manage laravel translation files from your admin panel. This package depends on [Anavel foundation](https://github.com/anavel/foundation) + +### Features + +* Easily manage app and vendor translation files. +* Automatically reorders translations (alphabetically) +* Easily create new language lines +* Arrays supported + +## Installation + + +## Configuration + +Publish translation config file with `php artisan vendor:publish` + +Include the files you want to manage within the `files` array, like this: + +``` + /* + |-------------------------------------------------------------------------- + | Files to translate + |-------------------------------------------------------------------------- + | + */ + 'files' => [ + 'user' => [ + 'aFileName', + 'anotherFileName' + ], + 'vendor' => [ + 'vendorname' => 'vendorFileName' + ] + ], +``` + +`user` is an array of filenames (without extension) located in Laravel's default folder (resources/lang/LOCALE_NAME). +`vendor` is an associative array of filenames (without extension), keyed by vendorname, located in Laravel's default folder (resources/lang/vendor/VENDORNAME/LOCALE_NAME). + +This package will read and then write those files, so your app must have write permissions to those folders. You must specify a disc driver for Laravel to use: + +config/anavel-translation.php: + +``` + /* + |-------------------------------------------------------------------------- + | File Disc Driver + |-------------------------------------------------------------------------- + | + | Disc driver pointing to resources/lang folder + | + */ + 'filedriver' => 'YOUR_DRIVER_NAME', +``` + +config/filesystem.php: + +``` + 'disks' => [ + [SOME OTHER FILE DRIVERS ], + 'YOUR_DRIVER_NAME' => [ + 'driver' => 'local', + 'root' => base_path('resources/lang'), + ], + ] +``` + +## Versioning + +If you use a versioning system (such as git) you should add the language folder to your gitignore. Otherwise, you might +get conflicts if different users update the translations. + +## How it works + + Translations reads the files that you specify in the config and displays their content in a form, tabbed by locale. + The translation key becomes the input label and the translation itself becomes the input value. + Locales are taken from the Anavel foundation config. + + To make the translation process easier, Translation shows the same language entries in all locales, even if a key is missing in a given locale. + In that case, the displayed text will be taken from the fallback locale (as Laravel does). + + When saved, translations are written back to the files. If a file doesn't exist in a locale, a new one is created. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1f0e69c --- /dev/null +++ b/composer.json @@ -0,0 +1,37 @@ +{ + "name": "anavel/translation", + "description": "Manage laravel translation files from your admin panel", + "authors": [ + { + "name": "Adrian P. Blunier", + "email": "adrian@anavallasuiza.com" + }, + { + "name": "Carlos Morales", + "email": "carlos@anavallasuiza.com" + } + ], + "require": { + "php": ">=5.5.9", + "illuminate/support": "5.1.*", + "illuminate/translation": "5.1.*", + "anavel/foundation": "dev-master" + }, + "require-dev": { + "orchestra/testbench": "~3.1", + "phpunit/phpunit": "~4.0", + "whatthejeff/nyancat-phpunit-resultprinter": "~1.2", + "mockery/mockery": "^0.9.4" + }, + "autoload": { + "psr-4": { + "Anavel\\Translation\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Anavel\\Translation\\Tests\\": "tests" + } + }, + "minimum-stability": "stable" +} diff --git a/config/anavel-translation.php b/config/anavel-translation.php new file mode 100644 index 0000000..ff13a69 --- /dev/null +++ b/config/anavel-translation.php @@ -0,0 +1,44 @@ + 'Translation', + + /* + |-------------------------------------------------------------------------- + | Displayed icon + |-------------------------------------------------------------------------- + | + */ + 'icon' => 'fa-globe', + + /* + |-------------------------------------------------------------------------- + | Files to translate + |-------------------------------------------------------------------------- + | + */ + 'files' => [ + 'user' => [ + + ], + 'vendor' => [ + + ] + ], + + /* + |-------------------------------------------------------------------------- + | File Disc Driver + |-------------------------------------------------------------------------- + | + | Disc driver pointing to resources/lang folder + | + */ + 'filedriver' => 'lang', +]; \ No newline at end of file diff --git a/lang/en/messages.php b/lang/en/messages.php new file mode 100644 index 0000000..16ef7df --- /dev/null +++ b/lang/en/messages.php @@ -0,0 +1,29 @@ + 'Create', + 'edit_title' => 'Edit', + 'show_title' => 'Show', + 'search_input' => 'Search', + 'create_button' => 'Create', + 'empty_list' => 'Empty table', + 'actions_table_header' => 'Actions', + 'show_button' => 'Show', + 'edit_button' => 'Edit', + 'delete_button' => 'Delete', + 'back_button' => 'Back', + 'cancel_button' => 'Cancel', + 'save_button' => 'Save', + 'confirm_button' => 'Confirm', + 'alert_translations_saved_title' => 'Everything OK!', + 'alert_translations_saved_text' => 'The new item was saved successfully', + 'alert_translations_destroy_title' => 'Everything OK!', + 'alert_translations_destroy_text' => 'The item was deleted successfully', + 'alert_empty_translations_title' => 'Nothing to update!', + 'alert_empty_translations_text' => 'No translations sent', + 'new_line' => 'New translation line', + 'new_line_key_label' => 'Key', + 'new_line_key_placeholder' => 'You can create arrays using dots: mainkey.key', + 'new_line_value_label' => 'Translation', + 'new_button' => 'Add new', +]; diff --git a/lang/es/messages.php b/lang/es/messages.php new file mode 100644 index 0000000..b5a5208 --- /dev/null +++ b/lang/es/messages.php @@ -0,0 +1,29 @@ + 'Crear', + 'edit_title' => 'Editar', + 'show_title' => 'Ver', + 'search_input' => 'Buscar', + 'create_button' => 'Crear', + 'empty_list' => 'Tabla vacía', + 'actions_table_header' => 'Acciones', + 'show_button' => 'Ver', + 'edit_button' => 'Editar', + 'delete_button' => 'Borrar', + 'back_button' => 'Volver', + 'cancel_button' => 'Cancelar', + 'save_button' => 'Guardar', + 'confirm_button' => 'Confirmar', + 'alert_translations_saved_title' => '¡Todo bien!', + 'alert_translations_saved_text' => 'El elemento ha sido guardado correctamente', + 'alert_translations_destroy_title' => '¡Todo bien!', + 'alert_translations_destroy_text' => 'El elemento ha sido borrado correctamente', + 'alert_empty_translations_title' => '¡Nada que actualizar!', + 'alert_empty_translations_text' => 'No se han recibido traducciones', + 'new_line' => 'Nueva línea de traducción', + 'new_line_key_label' => 'Clave', + 'new_line_key_placeholder' => 'Puedes crear arrays usando puntos: claveprincipal.clave', + 'new_line_value_label' => 'Traducción', + 'new_button' => 'Añadir nueva', +]; diff --git a/lang/gl/messages.php b/lang/gl/messages.php new file mode 100644 index 0000000..470455a --- /dev/null +++ b/lang/gl/messages.php @@ -0,0 +1,29 @@ + 'Crear', + 'edit_title' => 'Editar', + 'show_title' => 'Ver', + 'search_input' => 'Buscar', + 'create_button' => 'Crear', + 'empty_list' => 'Táboa vacía', + 'actions_table_header' => 'Accións', + 'show_button' => 'Ver', + 'edit_button' => 'Editar', + 'delete_button' => 'Borrar', + 'back_button' => 'Volver', + 'cancel_button' => 'Cancelar', + 'save_button' => 'Gardar', + 'confirm_button' => 'Confirmar', + 'alert_translations_saved_title' => '¡Todo ben!', + 'alert_translations_saved_text' => 'O elemento foi gardado correctamente', + 'alert_translations_destroy_title' => 'Todo ben!', + 'alert_translations_destroy_text' => 'O elemento foi borrado correctamente', + 'alert_empty_translations_title' => '¡Nada que actualizar!', + 'alert_empty_translations_text' => 'Non se recibiron traducións', + 'new_line' => 'Nova liña de tradución', + 'new_line_key_label' => 'Clave', + 'new_line_key_placeholder' => 'Podes crear arrays usando puntos: claveprincipal.clave', + 'new_line_value_label' => 'Tradución', + 'new_button' => 'Engadir nova', +]; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..f2a2d27 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,26 @@ + + + + + ./tests/ + + + + + + ./src/ + + + \ No newline at end of file diff --git a/src/Http/Controllers/FileController.php b/src/Http/Controllers/FileController.php new file mode 100644 index 0000000..2a76cc4 --- /dev/null +++ b/src/Http/Controllers/FileController.php @@ -0,0 +1,226 @@ +lang = config('anavel.translation_languages'); + $this->config = config('anavel-translation.files'); + $this->fallback = config('app.fallback_locale'); + } + + public function edit($param, $param2 = null) + { + $editLangsMissingKeys = []; + + $editLangs = $this->getLangsFromFile($param, $param2); + + // Add back keys from fallback_locale to other langs that don't have them. This way all langs have the same keys + // and it is easier for the user to translate and keep track of them. + foreach ($editLangs as $langKey => $lang) { + if (! empty($editLangs[$this->fallback])) { + if ($langKey === $this->fallback) { + $this->ksortTree($editLangs[$langKey]); + continue; + } + $missingKeys = $this->arrayDiffKeyRecursive($editLangs[$this->fallback], $editLangs[$langKey]); + + if (! empty($missingKeys)) { + foreach (array_keys($missingKeys) as $missingKey) { + $editLangs[$langKey][$missingKey] = $editLangs[$this->fallback][$missingKey]; + } + $editLangsMissingKeys[$langKey] = array_keys($missingKeys); + } + } + $this->ksortTree($editLangs[$langKey]); + } + + + return View::make('anavel-translation::pages.edit', compact('editLangs', 'editLangsMissingKeys')); + } + + public function update(Request $request, $param, $param2 = null) + { + if (! $request->has('translations')) { + session()->flash('anavel-alert', [ + 'type' => 'error', + 'icon' => 'fa-error', + 'title' => trans('anavel-translation::messages.alert_empty_translations_title'), + 'text' => trans('anavel-translation::messages.alert_empty_translations_text') + ]); + + return redirect()->back()->withInput(); + } + + $translations = $request->input('translations'); + + $disk = $this->getDisk(); + + foreach ($this->lang as $lang) { + if (! empty($translations[$lang])) { + $translation = $this->arrayFilterRecursive($translations[$lang]); + $fileRoute = empty($param2) ? $lang . '/' . $param . '.php' : 'vendor/' . $param . '/' . $lang . '/' . $param2 . '.php'; + $string = "put($fileRoute, $string); + } + } + + + session()->flash('anavel-alert', [ + 'type' => 'success', + 'icon' => 'fa-check', + 'title' => trans('anavel-translation::messages.alert_translations_saved_title'), + 'text' => trans('anavel-translation::messages.alert_translations_saved_text') + ]); + + return redirect(null, 200)->route('anavel-translation.file.edit', [$param, $param2]); + } + + public function create(Request $request, $param, $param2 = null) + { + if (! $request->has('translations-new')) { + session()->flash('anavel-alert', [ + 'type' => 'error', + 'icon' => 'fa-error', + 'title' => trans('anavel-translation::messages.alert_empty_new_translations_title'), + 'text' => trans('anavel-translation::messages.alert_empty_new_translations_text') + ]); + + return redirect()->back()->withInput(); + } + + $newLine = $request->input('translations-new'); + + + $langs = $this->getLangsFromFile($param, $param2); + + $disk = $this->getDisk(); + + //We'll only add this line to the main lang + $translation = $langs[$this->fallback]; + + //http://stackoverflow.com/questions/1417019/how-to-recursively-create-a-multidimensional-array#1417033 + + $key = $newLine['key']; + $val = $newLine['value']; + $path = explode('.', $key); + + $newTranslation = array(); + $tmp = &$newTranslation; + foreach ($path as $segment) { + $tmp[$segment] = array(); + $tmp = &$tmp[$segment]; + } + $tmp = $val; + + $translation[key($newTranslation)] = $newTranslation[key($newTranslation)]; + + + $fileRoute = empty($param2) ? $this->fallback . '/' . $param . '.php' : 'vendor/' . $param . '/' . $this->fallback . '/' . $param2 . '.php'; + $string = "put($fileRoute, $string); + + session()->flash('anavel-alert', [ + 'type' => 'success', + 'icon' => 'fa-check', + 'title' => trans('anavel-translation::messages.alert_translations_saved_title'), + 'text' => trans('anavel-translation::messages.alert_translations_saved_text') + ]); + + return redirect(null, 200)->route('anavel-translation.file.edit', [$param, $param2]); + } + + protected function getDisk() + { + $diskDriver = config('anavel-translation.filedriver'); + if (empty($diskDriver)) { + throw new \Exception('filedriver should be set in config'); + } + + return Storage::disk($diskDriver); + } + + protected function getLangsFromFile($param, $param2 = null) + { + $langs = []; + foreach ($this->lang as $lang) { + $file = empty($param2) ? $param : "$param::$param2"; + $transResult = trans($file, [], null, $lang); + /* + trans returns the original string if it can't find the translation. Since we are not + looking for a specific line but a whole file, an array should be returned. If that's not the case, the file doesn't exist. + */ + $langs[$lang] = is_array($transResult) ? $transResult : []; + } + + return $langs; + } + + protected function arrayDiffKeyRecursive(array $arr1, array $arr2) + { + $diff = array_diff_key($arr1, $arr2); + $intersect = array_intersect_key($arr1, $arr2); + + foreach ($intersect as $k => $v) { + if (is_array($arr1[$k]) && is_array($arr2[$k])) { + $d = $this->arrayDiffKeyRecursive($arr1[$k], $arr2[$k]); + + if ($d) { + $diff[$k] = $d; + } + } + } + + return $diff; + } + + protected function arrayFilterRecursive(array $array) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = $this->arrayFilterRecursive($value); + } + } + + return array_filter($array); + } + + /** + * @return bool + * @author Kevin van Zonneveld <kevin@vanzonneveld.net> + * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * @license http://www.opensource.org/licenses/bsd-license.php New BSD Licence + * @version SVN: Release: $Id: ksortTree.inc.php 223 2009-01-25 13:35:12Z kevin $ + * @link http://kevin.vanzonneveld.net/ + * + * @param array $array + */ + protected function ksortTree(&$array) + { + if (! is_array($array)) { + return false; + } + + ksort($array); + foreach ($array as $k => $v) { + $this->ksortTree($array[$k]); + } + + return true; + } +} \ No newline at end of file diff --git a/src/Http/Controllers/HomeController.php b/src/Http/Controllers/HomeController.php new file mode 100644 index 0000000..8f8ccce --- /dev/null +++ b/src/Http/Controllers/HomeController.php @@ -0,0 +1,33 @@ + 'translation', + 'namespace' => 'Anavel\Translation\Http\Controllers' + ], + function () { + Route::get('/', [ + 'as' => 'anavel-translation.home', + 'uses' => 'HomeController@index' + ]); + + Route::get('{param}/{param2?}', [ + 'as' => 'anavel-translation.file.edit', + 'uses' => 'FileController@edit' + ]); + + Route::post('{param}/{param2?}', [ + 'as' => 'anavel-translation.file.create', + 'uses' => 'FileController@create' + ]); + + Route::put('{param}/{param2?}', [ + 'as' => 'anavel-translation.file.update', + 'uses' => 'FileController@update' + ]); + } +); diff --git a/src/Providers/ViewComposersServiceProvider.php b/src/Providers/ViewComposersServiceProvider.php new file mode 100644 index 0000000..ff8fdf6 --- /dev/null +++ b/src/Providers/ViewComposersServiceProvider.php @@ -0,0 +1,27 @@ +app->view->composer('anavel-translation::molecules.sidebar.default', 'Anavel\Translation\View\Composers\SidebarComposer'); + } +} diff --git a/src/Services/ConfigurationReader.php b/src/Services/ConfigurationReader.php new file mode 100644 index 0000000..90d68d4 --- /dev/null +++ b/src/Services/ConfigurationReader.php @@ -0,0 +1,35 @@ +config) || empty($this->config)) { + return null; + } + + $params = func_get_args(); + + $lastParam = array_pop($params); + + $nestedConfig = $this->config; + + if (is_array($params) && count($params) > 0) { + foreach ($params as $configKey) { + if (! array_key_exists($configKey, $nestedConfig)) { + $nestedConfig = array(); + return null; + } + + $nestedConfig = $nestedConfig[$configKey]; + } + } + + if (array_key_exists($lastParam, $nestedConfig)) { + return $nestedConfig[$lastParam]; + } + + return null; + } +} diff --git a/src/TranslationModuleProvider.php b/src/TranslationModuleProvider.php new file mode 100644 index 0000000..689d0fc --- /dev/null +++ b/src/TranslationModuleProvider.php @@ -0,0 +1,90 @@ +loadViewsFrom(__DIR__.'/../views', 'anavel-translation'); + + $this->loadTranslationsFrom(__DIR__.'/../lang', 'anavel-translation'); + +// $this->publishes([ +// __DIR__.'/../public/js' => public_path('vendor/anavel-translation/js'), +// ], 'assets'); + + $this->publishes([ + __DIR__.'/../config/anavel-translation.php' => config_path('anavel-translation.php'), + ], 'config'); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->mergeConfigFrom(__DIR__.'/../config/anavel-translation.php', 'anavel-translation'); + + $this->app->register('Anavel\Translation\Providers\ViewComposersServiceProvider'); + + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return []; + } + + public function name() + { + return config('anavel-translation.name'); + } + + public function routes() + { + return __DIR__.'/Http/routes.php'; + } + + public function mainRoute() + { + return route('anavel-translation.home'); + } + + public function hasSidebar() + { + return true; + } + + public function sidebarMenu() + { + return 'anavel-translation::molecules.sidebar.default'; + } + + public function isActive() + { + $uri = Request::route()->uri(); + + if (strpos($uri, 'translation') !== false) { + return true; + } + + return false; + } +} diff --git a/src/View/Composers/SidebarComposer.php b/src/View/Composers/SidebarComposer.php new file mode 100644 index 0000000..246e644 --- /dev/null +++ b/src/View/Composers/SidebarComposer.php @@ -0,0 +1,50 @@ + route('anavel-translation.file.edit', [$userFile]), + 'name' => $userFile, + 'isActive' => URL::current() === route('anavel-translation.file.edit', [$userFile]) + ]; + } + } + if (array_key_exists('vendor', $files)) { + foreach ($files['vendor'] as $vendorKey => $vendorFile) { + $items[] = [ + 'route' => route('anavel-translation.file.edit', [$vendorKey, $vendorFile]), + 'name' => "$vendorKey : $vendorFile", + 'isActive' => URL::current() === route('anavel-translation.file.edit', [$vendorKey, $vendorFile]) + ]; + } + } + + $view->with([ + 'items' => $items + ]); + } +} diff --git a/tests/Config/ConfigurationReaderTest.php b/tests/Config/ConfigurationReaderTest.php new file mode 100644 index 0000000..7412b54 --- /dev/null +++ b/tests/Config/ConfigurationReaderTest.php @@ -0,0 +1,64 @@ +sut = new ConfigTraitClass(); + + $this->config = require __DIR__ . '/config.php'; + } + + public function test_returns_null_if_config_not_set() + { + $value = $this->sut->getConfigValue('something'); + + $this->assertNull($value); + } + + public function test_returns_null_if_value_not_set() + { + $this->sut->config = []; + + $value = $this->sut->getConfigValue('something'); + + $this->assertNull($value); + } + + public function test_call_with_one_param() + { + $this->sut->config = $this->config; + + $value = $this->sut->getConfigValue('single'); + + $this->assertEquals('one', $value); + } + + public function test_call_with_two_params() + { + $this->sut->config = $this->config; + + $value = $this->sut->getConfigValue('nested', 'second-level'); + + $this->assertEquals('two', $value); + } + +} + +class ConfigTraitClass +{ + public $config; + + use ConfigurationReader; +} \ No newline at end of file diff --git a/tests/Config/config.php b/tests/Config/config.php new file mode 100644 index 0000000..6357fa5 --- /dev/null +++ b/tests/Config/config.php @@ -0,0 +1,8 @@ + 'one', + + 'nested' => [ + 'second-level' => 'two' + ] +]; \ No newline at end of file diff --git a/tests/Controllers/FileControllerTest.php b/tests/Controllers/FileControllerTest.php new file mode 100644 index 0000000..676396d --- /dev/null +++ b/tests/Controllers/FileControllerTest.php @@ -0,0 +1,352 @@ + ['en', 'es']]); + config(['anavel-translation.filedriver' => 'diskdriver']); + config(['app.fallback_locale' => 'en']); + + + $this->sut = new FileController(); + + $this->config = [ + 'user' => [ + 'one' + ], + + 'vendor' => [ + 'vendorname' => 'vendorfile' + ] + ]; + } + + public function test_edit_returns_array_when_file_empty() + { + config(['anavel-translation.files' => $this->config]); + + $result = $this->sut->edit('test'); + + $this->assertObjectHasAttribute('data', $result); + $this->assertTrue($result->offsetExists('editLangs')); + $viewData = $result->offsetGet('editLangs'); + $this->assertArrayHasKey('en', $viewData); + $this->assertArrayHasKey('es', $viewData); + + $this->assertInternalType('array', $viewData['en']); + $this->assertInternalType('array', $viewData['es']); + } + + public function test_edit_returns_array_with_files_contents() + { + \App::instance('translator', $transMock = $this->mock('Illuminate\Filesystem\Filesystem\FileLoader')); + config(['anavel-translation.files' => $this->config]); + + $transMock->shouldReceive('trans')->andReturn(['yeah' => 'yeah']); + + $result = $this->sut->edit('test'); + + $this->assertObjectHasAttribute('data', $result); + $this->assertTrue($result->offsetExists('editLangs')); + $viewData = $result->offsetGet('editLangs'); + $this->assertArrayHasKey('en', $viewData); + $this->assertArrayHasKey('es', $viewData); + + $this->assertInternalType('array', $viewData['en']); + $this->assertInternalType('array', $viewData['es']); + + $this->assertArrayHasKey('yeah', $viewData['en']); + } + + public function test_edit_adds_missing_keys_from_fallback_locale() + { + \App::instance('translator', $transMock = $this->mock('Illuminate\Filesystem\Filesystem\FileLoader')); + config(['anavel-translation.files' => $this->config]); + + $transMock->shouldReceive('trans')->with('test', [], null, 'en')->andReturn([ + 'yeah' => 'yeah', + 'ohyeah' => 'yeah', + ]); + $transMock->shouldReceive('trans')->with('test', [], null, 'es')->andReturn([ + 'yeah' => 'yeah' + ]); + + $result = $this->sut->edit('test'); + + $this->assertObjectHasAttribute('data', $result); + $this->assertTrue($result->offsetExists('editLangs')); + $viewData = $result->offsetGet('editLangs'); + $this->assertArrayHasKey('en', $viewData); + $this->assertArrayHasKey('es', $viewData); + + $this->assertInternalType('array', $viewData['en']); + $this->assertInternalType('array', $viewData['es']); + + $this->assertArrayHasKey('yeah', $viewData['en']); + $this->assertArrayHasKey('ohyeah', $viewData['es']); + + $this->assertTrue($result->offsetExists('editLangsMissingKeys')); + $viewData = $result->offsetGet('editLangsMissingKeys'); + $this->assertArrayHasKey('es', $viewData); + $this->assertInternalType('array', $viewData['es']); + $this->assertContains('ohyeah', $viewData['es']); + } + + public function test_edit_calls_vendor_file_when_param2_not_empty() + { + \App::instance('translator', $transMock = $this->mock('Illuminate\Filesystem\Filesystem\FileLoader')); + config(['anavel-translation.files' => $this->config]); + + $transMock->shouldReceive('trans')->with('vendor::test', [], null, + \Mockery::any())->andReturn(['yeah' => 'yeah']); + + $result = $this->sut->edit('vendor', 'test'); + + $this->assertObjectHasAttribute('data', $result); + $this->assertTrue($result->offsetExists('editLangs')); + $viewData = $result->offsetGet('editLangs'); + $this->assertArrayHasKey('en', $viewData); + $this->assertArrayHasKey('es', $viewData); + + $this->assertInternalType('array', $viewData['en']); + $this->assertInternalType('array', $viewData['es']); + + $this->assertArrayHasKey('yeah', $viewData['en']); + } + + public function test_update_bails_if_empty_array() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations')->times(1)->andReturn(false); + + $result = $this->sut->update($requestMock, 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('error', $alert['type']); + } + + public function test_update_saves_array_to_file() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations')->times(1)->andReturn($returnArray = [ + 'en' => [ + 'key' => 'value' + ], + 'es' => [ + 'key' => 'value' + ] + ]); + + Storage::shouldReceive('disk')->times(1)->with('diskdriver')->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('en/test.php', \Mockery::any())->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('es/test.php', \Mockery::any())->andReturn(\Mockery::self()); + + $result = $this->sut->update($requestMock, 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('success', $alert['type']); + } + + public function test_update_saves_array_to_vendor_file() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations')->times(1)->andReturn($returnArray = [ + 'en' => [ + 'key' => 'value' + ], + 'es' => [ + 'key' => 'value' + ] + ]); + + Storage::shouldReceive('disk')->times(1)->with('diskdriver')->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('vendor/vendorname/en/test.php', + \Mockery::any())->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('vendor/vendorname/es/test.php', + \Mockery::any())->andReturn(\Mockery::self()); + + $result = $this->sut->update($requestMock, 'vendorname', 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('success', $alert['type']); + } + + public function test_update_filters_empty_keys() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations')->times(1)->andReturn($returnArray = [ + 'en' => [ + 'key' => 'value', + 'otherkey' => '' + ], + 'es' => [ + 'key' => 'value' + ] + ]); + + Storage::shouldReceive('disk')->times(1)->with('diskdriver')->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('vendor/vendorname/en/test.php', ' \'value\', +);')->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('vendor/vendorname/es/test.php', + \Mockery::any())->andReturn(\Mockery::self()); + + $result = $this->sut->update($requestMock, 'vendorname', 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('success', $alert['type']); + } + + public function test_update_throws_exception_if_disk_not_set() + { + $this->setExpectedException('Exception', 'filedriver should be set in config'); + + $requestMock = $this->mock('Illuminate\Http\Request'); + + config(['anavel-translation.filedriver' => null]); + + + $requestMock->shouldReceive('has')->with('translations')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations')->times(1)->andReturn($returnArray = [ + 'en' => [ + 'key' => 'value' + ], + 'es' => [ + 'key' => 'value' + ] + ]); + + $result = $this->sut->update($requestMock, 'test'); + } + + + public function test_create_bails_when_input_empty() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations-new')->times(1)->andReturn(false); + + $result = $this->sut->create($requestMock, 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('error', $alert['type']); + } + + public function test_create_throws_exception_if_disk_not_set() + { + $this->setExpectedException('Exception', 'filedriver should be set in config'); + + $requestMock = $this->mock('Illuminate\Http\Request'); + + config(['anavel-translation.filedriver' => null]); + + + $requestMock->shouldReceive('has')->with('translations-new')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations-new')->times(1)->andReturn($returnArray = [ + 'key' => 'somekey', + 'value' => 'somevalue' + ]); + + $result = $this->sut->create($requestMock, 'test'); + } + + public function test_create_adds_simple_line_to_fallback_locale() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations-new')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations-new')->times(1)->andReturn($returnArray = [ + 'key' => 'somekey', + 'value' => 'somevalue' + ]); + + Storage::shouldReceive('disk')->times(1)->with('diskdriver')->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('en/test.php', \Mockery::any())->andReturn(\Mockery::self()); + + $result = $this->sut->create($requestMock, 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('success', $alert['type']); + } + + public function test_create_adds_array_line_to_fallback_locale() + { + $requestMock = $this->mock('Illuminate\Http\Request'); + + $requestMock->shouldReceive('has')->with('translations-new')->times(1)->andReturn(true); + $requestMock->shouldReceive('input')->with('translations-new')->times(1)->andReturn($returnArray = [ + 'key' => 'some.arrayKey', + 'value' => 'somevalue' + ]); + + $finalArray = [ + 'some' => + [ + 'arrayKey' => 'somevalue', + ], + ]; + + $finalString = "times(1)->with('diskdriver')->andReturn(\Mockery::self()); + Storage::shouldReceive('put')->times(1)->with('en/test.php', $finalString)->andReturn(\Mockery::self()); + + $result = $this->sut->create($requestMock, 'test'); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $result); + $this->assertTrue($result->getSession()->has('anavel-alert')); + + $alert = $result->getSession()->get('anavel-alert'); + + $this->assertEquals('success', $alert['type']); + } + +} \ No newline at end of file diff --git a/tests/Controllers/HomeControllerTest.php b/tests/Controllers/HomeControllerTest.php new file mode 100644 index 0000000..ddc0067 --- /dev/null +++ b/tests/Controllers/HomeControllerTest.php @@ -0,0 +1,68 @@ +config = [ + 'user' => [ + 'one' + ], + + 'vendor' => [ + 'vendorname' => 'vendorfile' + ] + ]; + + $this->sut = new HomeController(); + } + + public function test_is_instance_of_controller() + { + $this->assertInstanceOf('Anavel\Foundation\Http\Controllers\Controller', $this->sut); + } + + public function test_edit_throws_exception_if_no_files_configured() + { + config(['anavel-translation.files' => null]); + $this->setExpectedException('Exception', 'No files configured'); + + $this->sut->index('test'); + } + + public function test_edit_throws_exception_if_files_is_not_array() + { + $this->setExpectedException('Exception', 'Files should be an array'); + config(['anavel-translation.files' => 'Chompy']); + + $this->sut->index('test'); + } + + public function test_edit_throws_exception_if_user_nor_vendor_are_found() + { + $this->setExpectedException('Exception', '"user" or "vendor" files should be set'); + config(['anavel-translation.files' => ['test']]); + + $this->sut->index('test'); + } + + public function test_redirects_if_files_configured() + { + config(['anavel-translation.files' => $this->config]); + + $response = $this->sut->index(); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $response); + } +} \ No newline at end of file diff --git a/tests/TestBase.php b/tests/TestBase.php new file mode 100644 index 0000000..83c7c5a --- /dev/null +++ b/tests/TestBase.php @@ -0,0 +1,46 @@ +set('anavel.translation_languages', ['gl', 'en', 'es']); + } + + protected function getPackageProviders($app) + { + return ['Anavel\Translation\TranslationModuleProvider']; + } + + + public function mock($className) + { + return Mockery::mock($className); + } + + public function tearDown() + { + Mockery::close(); + } +} diff --git a/views/atoms/form/translation-group.blade.php b/views/atoms/form/translation-group.blade.php new file mode 100644 index 0000000..21ebc5c --- /dev/null +++ b/views/atoms/form/translation-group.blade.php @@ -0,0 +1,23 @@ +
+ {{ $groupTitle }} + @foreach ($group as $childLineKey => $childLine) + @if(is_array($childLine)) + @include('anavel-translation::atoms.form.translation-group', [ + 'groupTitle' => $groupTitle . ' | ' .$childLineKey, + 'group' => $childLine, + 'formElementID' => "{$formElementID}[{$childLineKey}]" + ]) + @else +
+ + +
+ +
+
+ @endif + @endforeach +
\ No newline at end of file diff --git a/views/molecules/sidebar/default.blade.php b/views/molecules/sidebar/default.blade.php new file mode 100644 index 0000000..5469799 --- /dev/null +++ b/views/molecules/sidebar/default.blade.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/views/pages/edit.blade.php b/views/pages/edit.blade.php new file mode 100644 index 0000000..fd5c1d0 --- /dev/null +++ b/views/pages/edit.blade.php @@ -0,0 +1,103 @@ +@extends('anavel::layouts.master') + +@section('content-header') +

+ {{ config('anavel-translation.name') }} + {{ trans('anavel-translation::messages.edit_title') }} +

+@stop + +@section('content') + @if(! empty($editLangs)) + + +
+ +
+ {{ trans('anavel-translation::messages.new_line') }} +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+
+ @endif +@stop \ No newline at end of file