diff --git a/src/i18n/TextCollection/Parser.php b/src/i18n/TextCollection/Parser.php index 43b9df3a9..21eda9185 100644 --- a/src/i18n/TextCollection/Parser.php +++ b/src/i18n/TextCollection/Parser.php @@ -60,7 +60,8 @@ class Parser extends SSTemplateParser public function Translate_Entity(&$res, $sub) { - $this->currentEntityKey = $sub['text']; // key + // Collapse escaped slashes + $this->currentEntityKey = str_replace('\\\\', '\\', $sub['text']); // key } public function Translate_Default(&$res, $sub) diff --git a/src/i18n/TextCollection/i18nTextCollector.php b/src/i18n/TextCollection/i18nTextCollector.php index e44aedc27..cb4a3b495 100644 --- a/src/i18n/TextCollection/i18nTextCollector.php +++ b/src/i18n/TextCollection/i18nTextCollector.php @@ -2,6 +2,7 @@ namespace SilverStripe\i18n\TextCollection; +use LogicException; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Manifest\ClassLoader; @@ -618,14 +619,24 @@ class i18nTextCollector // Check text if ($id == T_CONSTANT_ENCAPSED_STRING) { // Fixed quoting escapes, and remove leading/trailing quotes - if (preg_match('/^\'/', $text)) { - $text = str_replace("\\'", "'", $text); - $text = preg_replace('/^\'/', '', $text); - $text = preg_replace('/\'$/', '', $text); + if (preg_match('/^\'(?.*)\'$/s', $text, $matches)) { + $text = preg_replace_callback( + '/\\\\([\\\\\'])/s', // only \ and ' + function ($input) { + return stripcslashes($input[0]); + }, + $matches['text'] + ); + } elseif (preg_match('/^\"(?.*)\"$/s', $text, $matches)) { + $text = preg_replace_callback( + '/\\\\([nrtvf\\\\$"]|[0-7]{1,3}|\x[0-9A-Fa-f]{1,2})/s', // rich replacement + function ($input) { + return stripcslashes($input[0]); + }, + $matches['text'] + ); } else { - $text = str_replace('\"', '"', $text); - $text = preg_replace('/^"/', '', $text); - $text = preg_replace('/"$/', '', $text); + throw new LogicException("Invalid string escape: " .$text); } } elseif ($id === T_CLASS_C) { // Evaluate __CLASS__ . '.KEY' concatenation diff --git a/tests/php/i18n/i18nTextCollectorTest.php b/tests/php/i18n/i18nTextCollectorTest.php index f6292f889..e30cf777a 100644 --- a/tests/php/i18n/i18nTextCollectorTest.php +++ b/tests/php/i18n/i18nTextCollectorTest.php @@ -86,7 +86,8 @@ PHP; <%t i18nTestModule.INJECTIONS_3 name="Cat" greeting='meow' goodbye="meow" %> <%t i18nTestModule.INJECTIONS_4 name=\$absoluteBaseURL greeting=\$get_locale goodbye="global calls" %> <%t i18nTestModule.INJECTIONS_9 "An item|{count} items" is "Test Pluralisation" count=4 %> -<%t SilverStripe\TestModule\i18nTestModule.INJECTIONS_10 "This string is namespaced" %> +<%t SilverStripe\\TestModule\\i18nTestModule.INJECTIONS_10 "This string is namespaced" %> +<%t SilverStripe\\\\TestModule\\\\i18nTestModule.INJECTIONS_11 "Escaped namespaced string" %> SS; $c->collectFromTemplate($html, null, $mymodule); @@ -105,7 +106,8 @@ SS; 'other' => '{count} items', 'comment' => 'Test Pluralisation' ], - 'SilverStripe\\TestModule\\i18nTestModule.INJECTIONS_10' => 'This string is namespaced' + 'SilverStripe\\TestModule\\i18nTestModule.INJECTIONS_10' => 'This string is namespaced', + 'SilverStripe\\TestModule\\i18nTestModule.INJECTIONS_11' => 'Escaped namespaced string' ], $c->collectFromTemplate($html, null, $mymodule) ); @@ -328,10 +330,26 @@ class MyClass extends Base implements SomeService { 'New Lines' ); } + public function getAnotherString() { + return _t( + 'SilverStripe\\\\Framework\\\\MyClass.ANOTHER_STRING', + 'Slash=\\\\, Quote=\\'' + ); + } + public function getDoubleQuotedString() { + return _t( + "SilverStripe\\\\Framework\\\\MyClass.DOUBLE_STRING", + "Slash=\\\\, Quote=\\"" + ); + } } PHP; $this->assertEquals( - [ 'SilverStripe\\Framework\\Core\\MyClass.NEWLINES' => "New Lines" ], + [ + 'SilverStripe\\Framework\\Core\\MyClass.NEWLINES' => "New Lines", + 'SilverStripe\\Framework\\MyClass.ANOTHER_STRING' => 'Slash=\\, Quote=\'', + 'SilverStripe\\Framework\\MyClass.DOUBLE_STRING' => 'Slash=\\, Quote="' + ], $c->collectFromCode($php, null, $mymodule) ); }