diff --git a/README.md b/README.md index 5a7acfa..6bfc24f 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,10 @@ Use the [github issue tool](https://github.com/potsky/laravel-localization-helpe ## 5. Upgrade notices +### From `v2.x.1` to `v2.x.2` + +- Parameter `obsolete_array_key` has been added in the [configuration file](https://github.com/potsky/laravel-localization-helpers/tree/master/src/config). Add it in your configuration file. + ### From `v1.x.x` to `v2.x.x` - First you need to update your composer file to set the correct version @@ -280,6 +284,11 @@ Use the [github issue tool](https://github.com/potsky/laravel-localization-helpe ## 6. Change Log +### v2.x.2 + +- show obsolete lemma when it is in array ([#21](https://github.com/potsky/laravel-localization-helpers/issues/21)) +- fix a bug when using obsolete option ([#22](https://github.com/potsky/laravel-localization-helpers/issues/22)) + ### v2.x.1 - fix a bug when using backup files and when a dot is in your laravel installation path ([#20](https://github.com/potsky/laravel-localization-helpers/issues/20)) diff --git a/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationAbstract.php b/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationAbstract.php index bbe3ff7..a6764a5 100644 --- a/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationAbstract.php +++ b/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationAbstract.php @@ -12,20 +12,24 @@ abstract class LocalizationAbstract extends Command implements MessageBagInterfa const SUCCESS = 0; const ERROR = 1; + /** + * Init log file for first log + * + * @var boolean + */ + protected static $logInFileFirst = true; /** * Config repository. * * @var \Illuminate\Config\Repository */ protected $configRepository; - /** * The localization manager * * @var Localization */ protected $manager; - /** * Should commands display something * @@ -122,4 +126,34 @@ public function writeError( $s ) parent::error( $s ); } } + + /** + * Log in a file for debug purpose only + * + * @param mixed $txt + * @param string $logFile + * + * @codeCoverageIgnore + */ + protected function logInFile( $txt = '' , $logFile = '/tmp/llh.log' ) + { + if ( ! is_string( $txt ) ) + { + $txt = print_r( $txt , true ); + } + + $txt = '==> ' . date( 'Y/m/d H:i:s' ) . ' ==> ' . $txt . "\n"; + + if ( self::$logInFileFirst === true ) + { + file_put_contents( $logFile , $txt ); + + self::$logInFileFirst = false; + } + else + { + file_put_contents( $logFile , $txt , FILE_APPEND ); + } + } + } diff --git a/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationMissing.php b/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationMissing.php index 9955fdd..833bced 100644 --- a/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationMissing.php +++ b/src/Potsky/LaravelLocalizationHelpers/Command/LocalizationMissing.php @@ -82,6 +82,15 @@ class LocalizationMissing extends LocalizationAbstract */ protected $code_style_level = null; + /** + * The obsolete lemma array key in which to store obsolete lemma + * + * @var string + * + * @since 2.x.2 + */ + protected $obsolete_array_key = 'LLH:obsolete'; + /** * Create a new command instance. * @@ -99,6 +108,14 @@ public function __construct( Repository $configRepository ) $this->editor = Config::get( Localization::PREFIX_LARAVEL_CONFIG . 'editor_command_line' ); $this->code_style_fixers = Config::get( Localization::PREFIX_LARAVEL_CONFIG . 'code_style.fixers' ); $this->code_style_level = Config::get( Localization::PREFIX_LARAVEL_CONFIG . 'code_style.level' ); + + // @since 2.x.2 + // Users who have not upgraded their configuration file must have a default + // but users may want to set it to null to keep the old buggy behaviour + if ( Config::has( Localization::PREFIX_LARAVEL_CONFIG . 'obsolete_array_key' ) ) + { + $this->obsolete_array_key = Config::get( Localization::PREFIX_LARAVEL_CONFIG . 'obsolete_array_key' ); + } } /** @@ -108,9 +125,10 @@ public function __construct( Repository $configRepository ) */ public function fire() { - $folders = $this->manager->getPath( $this->folders ); - $this->display = ! $this->option( 'silent' ); - $extension = $this->option( 'php-file-extension' ); + $folders = $this->manager->getPath( $this->folders ); + $this->display = ! $this->option( 'silent' ); + $extension = $this->option( 'php-file-extension' ); + $obsolete_prefix = ( empty( $this->obsolete_array_key ) ) ? '' : $this->obsolete_array_key . '.'; ////////////////////////////////////////////////// // Display where translations are searched in // @@ -195,6 +213,7 @@ public function fire() { switch ( $e->getCode() ) { + //@codeCoverageIgnoreStart case Localization::NO_LANG_FOLDER_FOUND_IN_THESE_PATHS: $this->writeError( "No lang folder found in these paths:" ); foreach ( $e->getParameter() as $path ) @@ -202,6 +221,7 @@ public function fire() $this->writeError( "- " . $path ); } break; + //@codeCoverageIgnoreEnd case Localization::NO_LANG_FOLDER_FOUND_IN_YOUR_CUSTOM_PATH: $this->writeError( 'No lang folder found in your custom path: "' . $e->getParameter() . '"' ); @@ -241,6 +261,7 @@ public function fire() { $this->writeLine( '' ); } + $this->writeLine( ' ' . $this->manager->getShortPath( $file_lang_path ) ); if ( ! is_writable( dirname( $file_lang_path ) ) ) @@ -287,16 +308,37 @@ public function fire() } /** @noinspection PhpIncludeInspection */ - $a = include( $file_lang_path ); - $old_lemmas = ( is_array( $a ) ) ? array_dot( $a ) : array(); - $new_lemmas = array_dot( $array ); - $final_lemmas = array(); - $display_already_comment = false; - $something_to_do = false; - $i = 0; - $obsolete_lemmas = array_diff_key( $old_lemmas , $new_lemmas ); - $welcome_lemmas = array_diff_key( $new_lemmas , $old_lemmas ); - $already_lemmas = array_intersect_key( $old_lemmas , $new_lemmas ); + $a = include( $file_lang_path ); + $old_lemmas_with_obsolete = ( is_array( $a ) ) ? array_dot( $a ) : array(); + $new_lemmas = array_dot( $array ); + $final_lemmas = array(); + $display_already_comment = false; + $something_to_do = false; + $i = 0; + + // Remove the obsolete prefix key + $old_lemmas = array(); + $obsolete_prefix_length = strlen( $obsolete_prefix ); + foreach ( $old_lemmas_with_obsolete as $key => $value ) + { + if ( starts_with( $key , $obsolete_prefix ) ) + { + $key = substr( $key , $obsolete_prefix_length ); + if ( ! isset( $old_lemmas[ $key ] ) ) + { + $old_lemmas[ $key ] = $value; + } + } + else + { + $old_lemmas[ $key ] = $value; + } + } + + $obsolete_lemmas = array_diff_key( $old_lemmas , $new_lemmas ); + $welcome_lemmas = array_diff_key( $new_lemmas , $old_lemmas ); + $already_lemmas = array_intersect_key( $old_lemmas , $new_lemmas ); + ksort( $obsolete_lemmas ); ksort( $welcome_lemmas ); ksort( $already_lemmas ); @@ -306,12 +348,13 @@ public function fire() ////////////////////////// if ( count( $welcome_lemmas ) > 0 ) { - $display_already_comment = true; - $something_to_do = true; - $there_are_new = true; - $this->writeInfo( " " . count( $welcome_lemmas ) . " new strings to translate" ); + $display_already_comment = true; + $something_to_do = true; + $there_are_new = true; $final_lemmas[ "POTSKY___NEW___POTSKY" ] = "POTSKY___NEW___POTSKY"; + $this->writeInfo( ' ' . ( $c = count( $welcome_lemmas ) ) . ' new string' . Tools::getPlural( $c ) . ' to translate' ); + foreach ( $welcome_lemmas as $key => $value ) { if ( $this->option( 'verbose' ) ) @@ -348,7 +391,7 @@ public function fire() { if ( $this->option( 'verbose' ) ) { - $this->writeLine( " " . count( $already_lemmas ) . " already translated strings" ); + $this->writeLine( ' ' . ( $c = count( $already_lemmas ) ) . ' already translated string' . Tools::getPlural( $c ) ); } $final_lemmas[ "POTSKY___OLD___POTSKY" ] = "POTSKY___OLD___POTSKY"; @@ -364,6 +407,8 @@ public function fire() /////////////////////////////// if ( count( $obsolete_lemmas ) > 0 ) { + $protected_already_included = false; + // Remove all dynamic fields foreach ( $obsolete_lemmas as $key => $value ) { @@ -371,31 +416,53 @@ public function fire() { if ( ( strpos( $key , '.' . $remove . '.' ) !== false ) || starts_with( $key , $remove . '.' ) ) { + if ( $this->option( 'verbose' ) ) + { + $this->writeLine( " " . $key . " is protected as a dynamic lemma" ); + } + unset( $obsolete_lemmas[ $key ] ); + + if ( $protected_already_included === false ) + { + $final_lemmas[ "POTSKY___PROTECTED___POTSKY" ] = "POTSKY___PROTECTED___POTSKY"; + $protected_already_included = true; + } + + // Given that this lemma is never obsolete, we need to send it back to the final lemma array + array_set( $final_lemmas , $key , $value ); } } } } + + ///////////////////////////////////// + // Fill the final lemmas array now // + ///////////////////////////////////// if ( count( $obsolete_lemmas ) > 0 ) { $display_already_comment = true; $something_to_do = true; - $this->writeComment( $this->option( 'no-obsolete' ) - ? " " . count( $obsolete_lemmas ) . " obsolete strings (will be deleted)" - : " " . count( $obsolete_lemmas ) . " obsolete strings (can be deleted manually in the generated file)" - ); - $final_lemmas[ "POTSKY___OBSOLETE___POTSKY" ] = "POTSKY___OBSOLETE___POTSKY"; - foreach ( $obsolete_lemmas as $key => $value ) + if ( $this->option( 'no-obsolete' ) ) { - if ( $this->option( 'verbose' ) ) - { - $this->writeLine( " " . $key . "" ); - } - if ( ! $this->option( 'no-obsolete' ) ) + $this->writeComment( " " . ( $c = count( $obsolete_lemmas ) ) . ' obsolete string' . Tools::getPlural( $c ) . ' (will be deleted)' ); + } + else + { + $this->writeComment( " " . ( $c = count( $obsolete_lemmas ) ) . ' obsolete string' . Tools::getPlural( $c ) . ' (can be deleted manually in the generated file)' ); + + $final_lemmas[ "POTSKY___OBSOLETE___POTSKY" ] = "POTSKY___OBSOLETE___POTSKY"; + + foreach ( $obsolete_lemmas as $key => $value ) { - array_set( $final_lemmas , $key , $value ); + if ( $this->option( 'verbose' ) ) + { + $this->writeLine( " " . $key . "" ); + } + + array_set( $final_lemmas , $obsolete_prefix . $key , $value ); } } } @@ -414,11 +481,13 @@ public function fire() array( "'POTSKY___NEW___POTSKY' => 'POTSKY___NEW___POTSKY'," , "'POTSKY___OLD___POTSKY' => 'POTSKY___OLD___POTSKY'," , + "'POTSKY___PROTECTED___POTSKY' => 'POTSKY___PROTECTED___POTSKY'," , "'POTSKY___OBSOLETE___POTSKY' => 'POTSKY___OBSOLETE___POTSKY'," , ) , array( '//============================== New strings to translate ==============================//' , ( $display_already_comment === true ) ? '//==================================== Translations ====================================//' : '' , + '//============================== Dynamic protected strings =============================//' , '//================================== Obsolete strings ==================================//' , ) , $content diff --git a/src/Potsky/LaravelLocalizationHelpers/Factory/Localization.php b/src/Potsky/LaravelLocalizationHelpers/Factory/Localization.php index a564801..3bd79b4 100644 --- a/src/Potsky/LaravelLocalizationHelpers/Factory/Localization.php +++ b/src/Potsky/LaravelLocalizationHelpers/Factory/Localization.php @@ -158,12 +158,14 @@ public function getLangPath( $lang_folder_path = null ) if ( file_exists( $path ) ) { return $path; + //@codeCoverageIgnoreStart } } $e = new Exception( '' , self::NO_LANG_FOLDER_FOUND_IN_THESE_PATHS ); $e->setParameter( $paths ); throw $e; + //@codeCoverageIgnoreEnd } else { @@ -450,6 +452,7 @@ public function deleteBackupFiles( $lang_folder_path , $days = 0 , $dryRun = fal { switch ( $e->getCode() ) { + //@codeCoverageIgnoreStart case self::NO_LANG_FOLDER_FOUND_IN_THESE_PATHS: $this->messageBag->writeError( "No lang folder found in these paths:" ); foreach ( $e->getParameter() as $path ) @@ -457,6 +460,7 @@ public function deleteBackupFiles( $lang_folder_path , $days = 0 , $dryRun = fal $this->messageBag->writeError( "- " . $path ); } break; + //@codeCoverageIgnoreEnd case self::NO_LANG_FOLDER_FOUND_IN_YOUR_CUSTOM_PATH: $this->messageBag->writeError( 'No lang folder found in your custom path: "' . $e->getParameter() . '"' ); diff --git a/src/config/config-laravel5.php b/src/config/config-laravel5.php index 0b13ad9..ba47134 100644 --- a/src/config/config-laravel5.php +++ b/src/config/config-laravel5.php @@ -118,6 +118,31 @@ ) , + /* + |-------------------------------------------------------------------------- + | Obsolete lemma prefix + |-------------------------------------------------------------------------- + | + | If you want to keep obsolete lemma in your lang file, they will be stored + | in a sub-array. If not stored in a sub-array, LLH will not be able to + | separate child lemma. + | + | eg : message.section.dog is used + | message.section.cat is not used anymore + | then : message.section.dog will be kept + | LLH:obsolete.message.section.cat will be generated + | + | : is used in the key because : is a special char in laravel lang lemma and + | : are automatically not scanned by LLH. + | + | Do not change this parameter between 2 commands launch because LLH will not + | be able to find obsolete lemma in the second pass and you will need to + | clean up obsolete lemma manually + | + */ + 'obsolete_array_key' => 'LLH:obsolete', + + /* |-------------------------------------------------------------------------- | Editor diff --git a/src/config/config.php b/src/config/config.php index e1f1c4f..f573014 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -119,6 +119,31 @@ ) , + /* + |-------------------------------------------------------------------------- + | Obsolete lemma prefix + |-------------------------------------------------------------------------- + | + | If you want to keep obsolete lemma in your lang file, they will be stored + | in a sub-array. If not stored in a sub-array, LLH will not be able to + | separate child lemma. + | + | eg : message.section.dog is used + | message.section.cat is not used anymore + | then : message.section.dog will be kept + | LLH:obsolete.message.section.cat will be generated + | + | : is used in the key because : is a special char in laravel lang lemma and + | : are automatically not scanned by LLH. + | + | Do not change this parameter between 2 commands launch because LLH will not + | be able to find obsolete lemma in the second pass and you will need to + | clean up obsolete lemma manually + | + */ + 'obsolete_array_key' => 'LLH:obsolete', + + /* |-------------------------------------------------------------------------- | Editor diff --git a/tests/fixes/Gh21Tests.php b/tests/fixes/Gh21Tests.php new file mode 100644 index 0000000..e684d45 --- /dev/null +++ b/tests/fixes/Gh21Tests.php @@ -0,0 +1,105 @@ + array( + 1 => array( + 'name' => 'First lady', + ), + 2 => array( + 'name' => 'Second to die', + ), + ), +);"; + + private static $defaultLangWithObsoleteContent = " array ( + 1 => array ( + 'name' => 'First lady', + ), + ), + 'LLH:obsolete' => array ( + 'section' => array ( + 2 => array ( + 'name' => 'Second to die', + ), + ), + ), +);"; + + + + /** + * Setup the test environment. + * + * - Remove all previous lang files before each test + * - Set custom configuration paths + */ + public function setUp() + { + parent::setUp(); + + self::$langFolder = self::MOCK_DIR_PATH . '/gh21/lang'; + self::$langFile = self::$langFolder . '/en/message.php'; + + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'lang_folder_path' , self::$langFolder ); + + // Set content in lang file + File::put( self::$langFile , self::$defaultLangContent ); + } + + + /** + * https://github.com/potsky/laravel-localization-helpers/issues/22 + */ + public function testObsoleteSubKeyRemoved() + { + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'folders' , self::MOCK_DIR_PATH . '/gh21/code' ); + + /** @noinspection PhpVoidFunctionResultUsedInspection */ + Artisan::call( 'localization:missing' , array( + '--no-interaction' => true , + '--no-backup' => true , + '--verbose' => true , + ) ); + + $this->assertContains( '1 obsolete string' , Artisan::output() ); + + $this->assertArrayHasKey( 'LLH:obsolete' , require( self::$langFile ) ); + } + + + /** + * https://github.com/potsky/laravel-localization-helpers/issues/22 + */ + public function testObsoleteAreKept() + { + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'folders' , self::MOCK_DIR_PATH . '/gh21/code' ); + + // Set content in lang file with obsolete lemma + File::put( self::$langFile , self::$defaultLangWithObsoleteContent ); + + /** @noinspection PhpVoidFunctionResultUsedInspection */ + Artisan::call( 'localization:missing' , array( + '--no-interaction' => true , + '--no-backup' => true , + ) ); + + $this->assertContains( '1 obsolete string' , Artisan::output() ); + + $lemmas = require( self::$langFile ); + + $this->assertArrayHasKey( 'LLH:obsolete' , $lemmas ); + $this->assertArrayNotHasKey( 'LLH:obsolete' , $lemmas['LLH:obsolete'] ); + } + +} diff --git a/tests/fixes/Gh22Tests.php b/tests/fixes/Gh22Tests.php new file mode 100644 index 0000000..bcb6d07 --- /dev/null +++ b/tests/fixes/Gh22Tests.php @@ -0,0 +1,120 @@ + 'My dog is rich' , + 'section' => array( + 1 => array( + 'name' => 'Niania', + ), + ), +);"; + + /** + * Setup the test environment. + * + * - Remove all previous lang files before each test + * - Set custom configuration paths + */ + public function setUp() + { + parent::setUp(); + + self::$langFolder = self::MOCK_DIR_PATH . '/gh22/lang'; + self::$langFile = self::$langFolder . '/en/message.php'; + + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'lang_folder_path' , self::$langFolder ); + + // Set content in lang file + File::put( self::$langFile , self::$defaultLangContent ); + } + + + /** + * https://github.com/potsky/laravel-localization-helpers/issues/22 + */ + public function testObsoleteKeyIsNotRemoved() + { + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'folders' , self::MOCK_DIR_PATH . '/gh22/phase1' ); + + /** @noinspection PhpVoidFunctionResultUsedInspection */ + Artisan::call( 'localization:missing' , array( + '--no-interaction' => true , + '--no-backup' => true , + ) ); + + $this->assertContains( '1 obsolete string' , Artisan::output() ); + + $lemmas = require( self::$langFile ); + + $this->assertArrayHasKey( 'LLH:obsolete' , $lemmas ); + $this->assertArrayHasKey( 'section' , $lemmas['LLH:obsolete'] ); + } + + + /** + * https://github.com/potsky/laravel-localization-helpers/issues/22 + */ + public function testObsoleteKeyIsRemovedWhenSettingOption() + { + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'folders' , self::MOCK_DIR_PATH . '/gh22/phase1' ); + + /** @noinspection PhpVoidFunctionResultUsedInspection */ + Artisan::call( 'localization:missing' , array( + '--no-interaction' => true , + '--no-backup' => true , + '--no-obsolete' => true , + ) ); + + $this->assertContains( '1 obsolete string' , Artisan::output() ); + + $lemmas = require( self::$langFile ); + + $this->assertArrayNotHasKey( 'LLH:obsolete' , $lemmas ); + } + + /** + * https://github.com/potsky/laravel-localization-helpers/issues/22 + */ + public function testDynamicFieldShouldNotBeObsoleteWhenNotAddingANewLemma() + { + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'folders' , self::MOCK_DIR_PATH . '/gh22/phase1' ); + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'never_obsolete_keys' , array( 'section' ) ); + + /** @noinspection PhpVoidFunctionResultUsedInspection */ + Artisan::call( 'localization:missing' , array( + '--no-interaction' => true , + '--no-backup' => true , + '--verbose' => true , + ) ); + + $this->assertEquals( self::$defaultLangContent , File::get( self::$langFile ) ); + } + + /** + * https://github.com/potsky/laravel-localization-helpers/issues/22 + */ + public function testDynamicFieldShouldNotBeObsoleteWhenAddingANewLemma() + { + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'folders' , self::MOCK_DIR_PATH . '/gh22/phase2' ); + Config::set( Localization::PREFIX_LARAVEL_CONFIG . 'never_obsolete_keys' , array( 'section' ) ); + + /** @noinspection PhpVoidFunctionResultUsedInspection */ + Artisan::call( 'localization:missing' , array( + '--no-interaction' => true , + '--no-backup' => true , + '--verbose' => true , + ) ); + + $this->assertArrayHasKey( 'section' , require( self::$langFile ) ); + } + +} diff --git a/tests/mock/gh21/code/trans.php b/tests/mock/gh21/code/trans.php new file mode 100644 index 0000000..988f154 --- /dev/null +++ b/tests/mock/gh21/code/trans.php @@ -0,0 +1,3 @@ + + array ( + 1 => + array ( + 'name' => 'First lady', + ), + ), + //================================== Obsolete strings ==================================// + 'LLH:obsolete' => + array ( + 'section' => + array ( + 2 => + array ( + 'name' => 'Second to die', + ), + ), + ), +); \ No newline at end of file diff --git a/tests/mock/gh22/lang/en/message.php b/tests/mock/gh22/lang/en/message.php new file mode 100644 index 0000000..e294802 --- /dev/null +++ b/tests/mock/gh22/lang/en/message.php @@ -0,0 +1,20 @@ + 'TODO: my cat is rich', + //==================================== Translations ====================================// + 'my dog is rich' => 'My dog is rich', + //============================== Dynamic protected strings =============================// + 'section' => + array ( + 1 => + array ( + 'name' => 'Niania', + ), + ), +); \ No newline at end of file diff --git a/tests/mock/gh22/phase1/trans.php b/tests/mock/gh22/phase1/trans.php new file mode 100644 index 0000000..de243f3 --- /dev/null +++ b/tests/mock/gh22/phase1/trans.php @@ -0,0 +1,8 @@ +