diff --git a/core/SSViewer.php b/core/SSViewer.php
index 2befda7e3..4f22c4167 100644
--- a/core/SSViewer.php
+++ b/core/SSViewer.php
@@ -433,14 +433,20 @@ class SSViewer extends Object {
$content = ereg_replace('<' . '% +else +%' . '>', ' } else { ?>', $content);
$content = ereg_replace('<' . '% +end_if +%' . '>', ' } ?>', $content);
- // i18n
+ // i18n - get filename of currently parsed template
+ // CAUTION: No spaces allowed between arguments for all i18n calls!
ereg('.*[\/](.*)',$template,$path);
- $content = ereg_replace('<' . '% +_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '= _t(\''. $path[1] . '.\\2\\3\'\\4) ?>', $content);
-
- // i18n - sprintf => "sprintf(_t(...),$argument)"
- // CAUTION: No spaces allowed between arguments!
- $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>', '= sprintf(_t(\''. $path[1] . '.\\2\\3\'\\4),\\6) ?>', $content);
+ // i18n _t(...) - with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
+ $content = ereg_replace('<' . '% +_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '= _t(\''. $path[1] . '.\\2\\3\'\\4) ?>', $content);
+ // i18n _t(...)
+ $content = ereg_replace('<' . '% +_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\) +%' . '>', '= _t(\'\\2\\3\'\\4) ?>', $content);
+
+ // i18n sprintf(_t(...),$argument) with entity only (no dots in namespace), meaning the current template filename will be added as a namespace
+ $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\.\']*)\'|"([^\."]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>', '= sprintf(_t(\''. $path[1] . '.\\2\\3\'\\4),\\6) ?>', $content);
+ // i18n sprintf(_t(...),$argument)
+ $content = ereg_replace('<' . '% +sprintf\(_t\((\'([^\']*)\'|"([^"]*)")(([^)]|\)[^ ]|\) +[^% ])*)\),\<\?= +([^\?]*) +\?\>) +%' . '>', '= sprintf(_t(\'\\2\\3\'\\4),\\6) ?>', $content);
+
// isnt valid html? !?
$content = ereg_replace('<' . '% +base_tag +%' . '>', '', $content);
diff --git a/core/i18n.php b/core/i18n.php
index 0678f6675..a10154487 100755
--- a/core/i18n.php
+++ b/core/i18n.php
@@ -1,8 +1,24 @@
template variable.
+ *
+ * Usage PHP:
+ *
+ * _t('MyNamespace.MYENTITY', 'My default natural language value');
+ * _t('MyNamespace.MYENTITY', 'My default natural language value', PR_MEDIUM, 'My explanatory context');
+ * sprintf(_t('MyNamespace.MYENTITY', 'Counting %s things'), 42);
+ *
+ *
+ * Usage Templates:
+ *
+ * <% _t('MyNamespace.MYENTITY', 'My default natural language value') %>
+ * <% sprintf(_t('MyNamespace.MYENTITY','Counting %s things'),$ThingsCount) %>
+ *
+ *
+ * Usage Javascript (see sapphire/javascript/i18n.js):
+ *
+ * ss.i18n._t('MyEntity.MyNamespace','My default natural language value');
+ *
*
* File-based i18n-translations always have a "locale" (e.g. 'en_US').
* Common language names (e.g. 'en') are mainly used in {Translatable} for
diff --git a/tests/i18n/_fakewebroot/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss b/tests/i18n/_fakewebroot/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss
index 3fa5151a9..93187c01f 100644
--- a/tests/i18n/_fakewebroot/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss
+++ b/tests/i18n/_fakewebroot/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss
@@ -1,2 +1,4 @@
<% _t("i18nTestModule.WITHNAMESPACE", 'Include Entity with Namespace') %>
-<% _t("NONAMESPACE", 'Include Entity without Namespace') %>
\ No newline at end of file
+<% _t("NONAMESPACE", 'Include Entity without Namespace') %>
+<% sprintf(_t('i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE','My include replacement: %s'),$TestProperty) %>
+<% sprintf(_t('SPRINTFINCLUDENONAMESPACE','My include replacement no namespace: %s'),$TestProperty) %>
\ No newline at end of file
diff --git a/tests/i18n/_fakewebroot/i18ntestmodule/templates/Layout/i18nTestModule.ss b/tests/i18n/_fakewebroot/i18ntestmodule/templates/Layout/i18nTestModule.ss
index 1930114e4..1ec3bbcaf 100644
--- a/tests/i18n/_fakewebroot/i18ntestmodule/templates/Layout/i18nTestModule.ss
+++ b/tests/i18n/_fakewebroot/i18ntestmodule/templates/Layout/i18nTestModule.ss
@@ -1,2 +1,5 @@
-<% _t('i18nTestModule.LAYOUTTEMPLATE',"Layout Template")%>
+<% _t('i18nTestModule.LAYOUTTEMPLATE',"Layout Template") %>
+<% _t('LAYOUTTEMPLATENONAMESPACE',"Layout Template no namespace") %>
+<% sprintf(_t('i18nTestModule.SPRINTFNAMESPACE','My replacement: %s'),$TestProperty) %>
+<% sprintf(_t('SPRINTFNONAMESPACE','My replacement no namespace: %s'),$TestProperty) %>
<% include i18nTestModuleInclude %>
\ No newline at end of file
diff --git a/tests/i18n/_fakewebroot/i18ntestmodule/templates/i18nTestModule.ss b/tests/i18n/_fakewebroot/i18ntestmodule/templates/i18nTestModule.ss
index bf2fbc702..5cc818354 100644
--- a/tests/i18n/_fakewebroot/i18ntestmodule/templates/i18nTestModule.ss
+++ b/tests/i18n/_fakewebroot/i18ntestmodule/templates/i18nTestModule.ss
@@ -1,2 +1,3 @@
-<% _t('i18nTestModule.MAINTEMPLATE',"Main Template")%>
+<% _t('i18nTestModule.MAINTEMPLATE',"Main Template") %>
+$Layout
lonely _t() call that should be ignored
\ No newline at end of file
diff --git a/tests/i18n/i18nTest.php b/tests/i18n/i18nTest.php
index b7cab5668..ebd28d309 100644
--- a/tests/i18n/i18nTest.php
+++ b/tests/i18n/i18nTest.php
@@ -4,6 +4,64 @@
* @subpackage tests
*/
class i18nTest extends SapphireTest {
+
+ /**
+ * @var string $tmpBasePath Used to write language files.
+ * We don't want to store them inside sapphire (or in any web-accessible place)
+ * in case something goes wrong with the file parsing.
+ */
+ protected $alternateBaseSavePath;
+
+ /**
+ * @var string $alternateBasePath Fake webroot with a single module
+ * /i18ntestmodule which contains some files with _t() calls.
+ */
+ protected $alternateBasePath;
+
+ function setUp() {
+ parent::setUp();
+
+ $this->alternateBasePath = Director::baseFolder() . "/sapphire/tests/i18n/_fakewebroot";
+ $this->alternateBaseSavePath = TEMP_FOLDER . '/i18nTextCollectorTest_webroot';
+ FileSystem::makeFolder($this->alternateBaseSavePath);
+
+ // SSViewer and ManifestBuilder don't support different webroots, hence we set the paths manually
+ global $_CLASS_MANIFEST;
+ $_CLASS_MANIFEST['i18nTestModule'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
+ $_CLASS_MANIFEST['i18nTestModule_Addition'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
+ $_CLASS_MANIFEST['i18nTestModuleDecorator'] = $this->alternateBasePath . '/i18nothermodule/code/i18nTestModuleDecorator.php';
+
+ global $_ALL_CLASSES;
+ $_ALL_CLASSES['parents']['i18nTestModule'] = array('DataObject'=>'DataObject','Object'=>'Object');
+ $_ALL_CLASSES['parents']['i18nTestModule_Addition'] = array('Object'=>'Object');
+ $_ALL_CLASSES['parents']['i18nTestModuleDecorator'] = array('DataObjectDecorator'=>'DataObjectDecorator','Object'=>'Object');
+
+ global $_TEMPLATE_MANIFEST;
+ $_TEMPLATE_MANIFEST['i18nTestModule.ss'] = array(
+ 'main' => $this->alternateBasePath . '/i18ntestmodule/templates/i18nTestModule.ss',
+ 'Layout' => $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss',
+ );
+ $_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss'] = array(
+ 'Includes' => $this->alternateBasePath . '/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss',
+ );
+ $_TEMPLATE_MANIFEST['i18nTestModule.ss'] = array(
+ 'main' => $this->alternateBasePath . '/i18ntestmodule/templates/i18nTestModule.ss',
+ 'Layout' => $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss',
+ );
+ }
+
+ function tearDown() {
+ //FileSystem::removeFolder($this->tmpBasePath);
+
+ global $_CLASS_MANIFEST;
+ unset($_CLASS_MANIFEST['i18nTestModule']);
+ unset($_CLASS_MANIFEST['i18nTestModule_Addition']);
+
+ global $_TEMPLATE_MANIFEST;
+ unset($_TEMPLATE_MANIFEST['i18nTestModule.ss']);
+ unset($_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss']);
+ }
+
function testGetExistingTranslations() {
$translations = i18n::get_existing_translations();
$this->assertTrue(isset($translations['en_US']), 'Checking for en_US translation');
@@ -64,6 +122,78 @@ class i18nTest extends SapphireTest {
);
}
+ function testTemplateTranslation() {
+ global $lang;
+ $oldLocale = i18n::get_locale();
+
+ i18n::set_locale('en_US');
+ $lang['en_US']['i18nTestModule']['MAINTEMPLATE'] = 'Main Template';
+ $lang['en_US']['i18nTestModule.ss']['SPRINTFNONAMESPACE'] = 'My replacement no namespace: %s';
+ $lang['en_US']['i18nTestModule']['LAYOUTTEMPLATE'] = 'Layout Template';
+ $lang['en_US']['i18nTestModule.ss']['LAYOUTTEMPLATENONAMESPACE'] = 'Layout Template no namespace';
+ $lang['en_US']['i18nTestModule']['SPRINTFNAMESPACE'] = 'My replacement: %s';
+ $lang['en_US']['i18nTestModule']['WITHNAMESPACE'] = 'Include Entity with Namespace';
+ $lang['en_US']['i18nTestModule.ss']['NONAMESPACE'] = 'Include Entity without Namespace';
+ $lang['en_US']['i18nTestModuleInclude.ss']['SPRINTFINCLUDENAMESPACE'] = 'My include replacement: %s';
+ $lang['en_US']['i18nTestModule.ss']['SPRINTFINCLUDENONAMESPACE'] = 'My include replacement no namespace: %s';
+ $viewer = new SSViewer('i18nTestModule');
+ $parsedHtml = $viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')));
+ $this->assertContains(
+ "Layout Template\n",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "Layout Template no namespace\n",
+ $parsedHtml
+ );
+
+ i18n::set_locale('de_DE');
+ $lang['de_DE']['i18nTestModule']['MAINTEMPLATE'] = 'TRANS Main Template';
+ $lang['de_DE']['i18nTestModule.ss']['SPRINTFNONAMESPACE'] = 'TRANS My replacement no namespace: %s';
+ $lang['de_DE']['i18nTestModule']['LAYOUTTEMPLATE'] = 'TRANS Layout Template';
+ $lang['de_DE']['i18nTestModule.ss']['LAYOUTTEMPLATENONAMESPACE'] = 'TRANS Layout Template no namespace';
+ $lang['de_DE']['i18nTestModule']['SPRINTFNAMESPACE'] = 'TRANS My replacement: %s';
+ $lang['de_DE']['i18nTestModule']['WITHNAMESPACE'] = 'TRANS Include Entity with Namespace';
+ $lang['de_DE']['i18nTestModule.ss']['NONAMESPACE'] = 'TRANS Include Entity without Namespace';
+ $lang['de_DE']['i18nTestModuleInclude.ss']['SPRINTFINCLUDENAMESPACE'] = 'TRANS My include replacement: %s';
+ $lang['de_DE']['i18nTestModule.ss']['SPRINTFINCLUDENONAMESPACE'] = 'TRANS My include replacement no namespace: %s';
+ $viewer = new SSViewer('i18nTestModule');
+ $parsedHtml = $viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')));
+ $this->assertContains(
+ "TRANS Main Template\n",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS Layout Template\n",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS Layout Template no namespace",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS My replacement: TestPropertyValue",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS Include Entity with Namespace",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS Include Entity without Namespace",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS My include replacement: TestPropertyValue",
+ $parsedHtml
+ );
+ $this->assertContains(
+ "TRANS My include replacement no namespace: TestPropertyValue",
+ $parsedHtml
+ );
+
+ i18n::set_locale($oldLocale);
+ }
}
class i18nTest_DataObject extends DataObject implements TestOnly {
diff --git a/tests/i18n/i18nTextCollectorTest.php b/tests/i18n/i18nTextCollectorTest.php
index 44f377049..8655c2ca0 100644
--- a/tests/i18n/i18nTextCollectorTest.php
+++ b/tests/i18n/i18nTextCollectorTest.php
@@ -330,13 +330,49 @@ PHP;
$templateFilePath = $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss';
$html = file_get_contents($templateFilePath);
+ $matches = $c->collectFromTemplate($html, 'mymodule', 'RandomNamespace');
+
+ /*
+ $this->assertArrayHasKey('i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE', $matches);
$this->assertEquals(
- $c->collectFromTemplate($html, 'mymodule', 'RandomNamespace'),
- array(
- 'i18nTestModule.WITHNAMESPACE' => array('Include Entity with Namespace', null, null),
- 'i18nTestModuleInclude.ss.NONAMESPACE' => array('Include Entity without Namespace', null, null),
- 'i18nTestModule.LAYOUTTEMPLATE' => array('Layout Template', null, null),
- )
+ $matches['i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE'],
+ array('Layout Template no namespace', null, null)
+ );
+ */
+ $this->assertArrayHasKey('RandomNamespace.SPRINTFNONAMESPACE', $matches);
+ $this->assertEquals(
+ $matches['RandomNamespace.SPRINTFNONAMESPACE'],
+ array('My replacement no namespace: %s', null, null)
+ );
+ $this->assertArrayHasKey('i18nTestModule.LAYOUTTEMPLATE', $matches);
+ $this->assertEquals(
+ $matches['i18nTestModule.LAYOUTTEMPLATE'],
+ array('Layout Template', null, null)
+ );
+ $this->assertArrayHasKey('i18nTestModule.SPRINTFNAMESPACE', $matches);
+ $this->assertEquals(
+ $matches['i18nTestModule.SPRINTFNAMESPACE'],
+ array('My replacement: %s', null, null)
+ );
+ $this->assertArrayHasKey('i18nTestModule.WITHNAMESPACE', $matches);
+ $this->assertEquals(
+ $matches['i18nTestModule.WITHNAMESPACE'],
+ array('Include Entity with Namespace', null, null)
+ );
+ $this->assertArrayHasKey('i18nTestModuleInclude.ss.NONAMESPACE', $matches);
+ $this->assertEquals(
+ $matches['i18nTestModuleInclude.ss.NONAMESPACE'],
+ array('Include Entity without Namespace', null, null)
+ );
+ $this->assertArrayHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE', $matches);
+ $this->assertEquals(
+ $matches['i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE'],
+ array('My include replacement: %s', null, null)
+ );
+ $this->assertArrayHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE', $matches);
+ $this->assertEquals(
+ $matches['i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE'],
+ array('My include replacement no namespace: %s', null, null)
);
}