Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

API CHANGE Removed $priority arguments from _t(), use module prioriti…

…es instead.

ENHANCEMENT Refactored i18nTextCollector collection logic alongside $priority removal, from regex to (slightly more maintainable) PHP tokenizer. Using var_export() for generating PHP, which auto-escapes strings more robustly.
ENHANCEMENT Refactored i18nTextCollector into pluggable writers (in preparation of new YML output format)
  • Loading branch information...
commit fca2c205b71e6bf304d1e085df7485a8dd2c8453 1 parent d44f6b3
@chillu chillu authored
View
62 docs/en/topics/i18n.md
@@ -118,7 +118,7 @@ The field tries to translate the date formats and locales into a format compatib
$field->setConfig('jslocale', 'de'); // jQuery UI only has a generic German localization
$field->setConfig('dateformat', 'dd. MMMM YYYY'); // will be transformed to 'dd. MM yy' for jQuery
-## Adapting modules for i18n
+## Translating text
Adapting a module to make it localizable is easy with SilverStripe. You just need to avoid hardcoding strings that are
language-dependent and use a translator function call instead.
@@ -130,59 +130,24 @@ language-dependent and use a translator function call instead.
echo _t("Namespace.Entity","This is a string");
-All strings passed through the _t() function will be collected in a separate language table (see "Collecting entities"
+All strings passed through the `_t()` function will be collected in a separate language table (see "Collecting entities"
below), which is the starting point for translations.
### The _t() function
-Here is the function prototype of this translator function
+The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional.
- :::php
- public function _t(string $entity [, string $string [, int $priority [, string $context]]]) {
-
-
-**$entity:** The first parameter is the identifier, and is composed by a namespace and an entity name, with a dot separating them.
-The main class name (i.e. the same one that the php name file) should usually be used as the namespace. This means that
-if we are coding in the file LeftAndMain.php, the namespace should be 'LeftAndMain', and therefore the complete first
-parameter would be 'LeftAndMain.ENTITY'. There is an exception to this rule. If you are using the same exactly string in two different files, for example in
-A.php and B.php, and the string in B.php will always be the same string that in A.php, then you can 'declare' this
-string in A.php with `_t('A.ENTITY','String that is used in A and B');`{php} and then in B.php simply write:
-`_t('A.ENTITY');`{php} In this way if somewhere in the future you need to modify this string, you just need to edit it
-in one file (A.php). Translators will also have to translate this string just once. Entity names are by convention written in uppercase. They have to be unique within their namespace, and its purpose is
-to serve as an identificator to this string, together with the namespace. Having an unique identificator for each string
-allows some features like change tracking. Therefore, a meaningful name is always welcomed, although not required. And
-also, that's why you shouldn't change an existing entity name in the code, unless you have a good reason to do it.
-
-**$string:** The second parameter is the string itself. It's not mandatory if you have set this same string in another place before
-(using the same class and entity). So you could write `_t('ClassName.HELLO',"Hello")` and later `_t('ClassName.HELLO')`.
-In fact, if you write the string in this second case, a warning will be issued when text-collecting to alert that you
-are redeclaring an entity.
-
-**$priority:** Priority parameter is an optional parameter and it can be used to set a translation priority. If a string is widely
-used, it should have a high priority (PR_HIGH), in this way translators will be able to prioritise the translation of
-this strings. If a string is extremely rarely shown, use PR_LOW. You can use PR_MEDIUM as well. Leaving this field blank
-will be interpretated as a "normal" priority (some less than PR_MEDIUM). Using priorities allows translators to benefit from the 80/20 rule when translating, since typically there is a reduced
-set of strings that are widely displayed, and a lot of more specific strings. Therefore, in a module with a considerable
-amount of strings, where partial translations can be expected, priorities will help to have translated the most
-displayed strings. If a string is in a class is inheritable, it's not recommended to establish a priority (we don't know about child
-behavior a priori).
-
-### Context
-
-Last parameter is context, it's also optional. Sometimes short phrases or words can have several translations depending
-upon where they are used, and Context serves as a way to tell translators more information about the string in these
-cases where translating can be difficult, due to lack of context or ambiguity.
-
-This context param can also be used with other situations where translation may need to know more than the original
-string, for example with sprintf '%' params inside the string, since you can tell translators about the meaning of this
-parameters.
+ * **$entity:** Unique identifier, composed by a namespace and an entity name, with a dot separating them. Both are arbitrary names, although by convention we use the name of the containing class or template. Use this identifier to reference the same translation elsewhere in your code.
+ * **$string:** (optional) The original language string to be translated. Only needs to be declared once, and gets picked up the [text collector](#collecting-text).
+ * **$string:** (optional) Natural language (particularly short phrases and individual words)
+are very context dependent. This parameter allows the developer to convey this information
+to the translator. Can also be used to explain `sprintf()` placeholders.
:::php
//Example 4: Using context to hint information about a parameter
sprintf(
_t('CMSMain.RESTORED',
"Restored '%s' successfully",
- PR_MEDIUM,
'Param %s is a title'
),
$title
@@ -255,10 +220,14 @@ If you want to run the text collector for just one module you can use the 'modul
**Note**: You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)).
</div>
-## Language tables in PHP
+## Language definitions
+
+Each module can have one language table per locale, stored by convention in the `lang/` subfolder.
+The translation is powered by `[Zend_Translate](http://framework.zend.com/manual/en/zend.translate.html)`,
+which supports different translation adapters, dealing with different storage formats.
-Each module can have one language table per locale. These tables are just PHP files with array notations. By convention,
-the files are stored in the /lang subfolder, and are named after their locale value, e.g. "en_US.php".
+In SilverStripe 2.x, there tables are just PHP files with array notations,
+stored based on their locale name (e.g. "en_US.php").
Example: framework/lang/en_US.php (extract)
@@ -266,7 +235,6 @@ Example: framework/lang/en_US.php (extract)
// ...
$lang['en_US']['ImageUploader']['ATTACH'] = array(
'Attach %s',
- PR_MEDIUM,
'Attach image/file'
);
$lang['en_US']['FileIFrameField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.';
View
13 i18n/i18n.php
@@ -1452,14 +1452,17 @@ public static function get_time_format() {
* the class name where this string is used and Entity identifies the string inside the namespace.
* @param string $string The original string itself. In a usual call this is a mandatory parameter, but if you are reusing a string which
* has already been "declared" (using another call to this function, with the same class and entity), you can omit it.
- * @param string $priority Optional parameter to set a translation priority. If a string is widely used, should have a high priority (PR_HIGH),
- * in this way translators will be able to prioritise this strings. If a string is rarely shown, you should use PR_LOW.
- * You can use PR_MEDIUM as well. Leaving this field blank will be interpretated as a "normal" priority (less than PR_MEDIUM).
* @param string $context If the string can be difficult to translate by any reason, you can help translators with some more info using this param
- *
* @return string The translated string, according to the currently set locale {@link i18n::set_locale()}
*/
- static function _t($entity, $string = "", $priority = 40, $context = "") {
+ static function _t($entity, $string = "", $context = "") {
+ if(is_numeric($context) && in_array($context, array(PR_LOW, PR_MEDIUM, PR_HIGH))) {
+ $context = func_get_arg(4);
+ Deprecation::notice(
+ '3.0',
+ 'The $priority argument to _t() is deprecated, please use module inclusion priorities instead'
+ );
+ }
// get current locale (either default or user preference)
$locale = i18n::get_locale();
$lang = i18n::get_lang_from_locale($locale);
View
348 i18n/i18nTextCollector.php
@@ -37,25 +37,33 @@ class i18nTextCollector extends Object {
* @todo Fully support changing of basePath through {@link SSViewer} and {@link ManifestBuilder}
*/
public $basePath;
-
+
+ public $baseSavePath;
+
/**
- * @var string $baseSavePath The directory base on which the collector should create new lang folders and files.
- * Usually the webroot set through {@link Director::baseFolder()}.
- * Can be overwritten for testing or export purposes.
- * @todo Fully support changing of baseSavePath through {@link SSViewer} and {@link ManifestBuilder}
+ * @var i18nTextCollector_Writer
*/
- public $baseSavePath;
+ protected $writer;
/**
* @param $locale
*/
function __construct($locale = null) {
- $this->defaultLocale = ($locale) ? $locale : i18n::default_locale();
+ $this->defaultLocale = ($locale) ? $locale : i18n::get_lang_from_locale(i18n::default_locale());
$this->basePath = Director::baseFolder();
$this->baseSavePath = Director::baseFolder();
parent::__construct();
}
+
+ public function setWriter($writer) {
+ $this->writer = $writer;
+ }
+
+ public function getWriter() {
+ if(!$this->writer) $this->writer = new i18nTextCollector_Writer_Php();
+ return $this->writer;
+ }
/**
* This is the main method to build the master string tables with the original strings.
@@ -139,10 +147,10 @@ public function run($restrictToModules = null) {
}
}
- // Write the generated master string tables
- $this->writeMasterStringFile($entitiesByModule);
-
- //Debug::message("Done!", false);
+ // Write each module language file
+ if($entitiesByModule) foreach($entitiesByModule as $module => $entities) {
+ $this->getWriter()->write($entities, $this->defaultLocale, $this->baseSavePath . '/' . $module);
+ }
}
/**
@@ -151,7 +159,7 @@ public function run($restrictToModules = null) {
* @param string $module Module's name or 'themes'
*/
protected function processModule($module) {
- $entitiesArr = array();
+ $entities = array();
//Debug::message("Processing Module '{$module}'", false);
@@ -166,8 +174,8 @@ protected function processModule($module) {
// exclude ss-templates, they're scanned separately
if(substr($filePath,-3) == 'php') {
$content = file_get_contents($filePath);
- $entitiesArr = array_merge($entitiesArr,(array)$this->collectFromCode($content, $module));
- $entitiesArr = array_merge($entitiesArr, (array)$this->collectFromEntityProviders($filePath, $module));
+ $entities = array_merge($entities,(array)$this->collectFromCode($content, $module));
+ $entities = array_merge($entities, (array)$this->collectFromEntityProviders($filePath, $module));
}
}
@@ -178,41 +186,69 @@ protected function processModule($module) {
$content = file_get_contents($filePath);
// templates use their filename as a namespace
$namespace = basename($filePath);
- $entitiesArr = array_merge($entitiesArr, (array)$this->collectFromTemplate($content, $module, $namespace));
+ $entities = array_merge($entities, (array)$this->collectFromTemplate($content, $module, $namespace));
}
}
// sort for easier lookup and comparison with translated files
- ksort($entitiesArr);
+ ksort($entities);
- return $entitiesArr;
+ return $entities;
}
public function collectFromCode($content, $module) {
- $entitiesArr = array();
-
- $regexRule = '#_t[[:space:]]*\(' .
- '[[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,' . // namespace.entity
- '[[:space:]]*(("([^"]|\\\")*"|\'([^\']|\\\\\')*\')' . // value
- '([[:space:]]*\\.[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))*)' . // concatenations
- '([[:space:]]*,[[:space:]]*[^,)]*)?([[:space:]]*,' . // priority (optional)
- '[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*' . // comment (optional)
- '\)#';
+ $entities = array();
- while (preg_match($regexRule, $content, $regs)) {
- $entitiesArr = array_merge($entitiesArr, (array)$this->entitySpecFromRegexMatches($regs));
-
- // remove parsed content to continue while() loop
- $content = str_replace($regs[0],"",$content);
+ $tokens = token_get_all("<?php\n" . $content);
+ $inTransFn = false;
+ $inConcat = false;
+ $currentEntity = array();
+ foreach($tokens as $token) {
+ if(is_array($token)) {
+ list($id, $text) = $token;
+ if($id == T_STRING && $text == '_t') {
+ // start definition
+ $inTransFn = true;
+ } elseif($inTransFn && $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);
+ } else {
+ $text = str_replace('\"', '"', $text);
+ $text = preg_replace('/^"/', '', $text);
+ $text = preg_replace('/"$/', '', $text);
+ }
+
+ if($inConcat) $currentEntity[count($currentEntity)-1] .= $text;
+ else $currentEntity[] = $text;
+ }
+ } elseif($inTransFn && $token == '.') {
+ $inConcat = true;
+ } elseif($inTransFn && $token == ',') {
+ $inConcat = false;
+ } elseif($inTransFn && $token == ')') {
+ // finalize definition
+ $inTransFn = false;
+ $inConcat = false;
+ $entity = array_shift($currentEntity);
+ $entities[$entity] = $currentEntity;
+ $currentEntity = array();
+ }
}
- ksort($entitiesArr);
+ foreach($entities as $entity => $spec) {
+ unset($entities[$entity]);
+ $entities[$this->normalizeEntity($entity, $module)] = $spec;
+ }
+ ksort($entities);
- return $entitiesArr;
+ return $entities;
}
- public function collectFromTemplate($content, $module, $fileName) {
- $entitiesArr = array();
+ public function collectFromTemplate($content, $fileName, $module) {
+ $entities = array();
// Search for included templates
preg_match_all('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', $content, $regs, PREG_SET_ORDER);
@@ -223,36 +259,32 @@ public function collectFromTemplate($content, $module, $fileName) {
if(!$filePath) $filePath = SSViewer::getTemplateFileByType($includeName, 'main');
if($filePath) {
$includeContent = file_get_contents($filePath);
- $entitiesArr = array_merge($entitiesArr,(array)$this->collectFromTemplate($includeContent, $module, $includeFileName));
+ $entities = array_merge($entities,(array)$this->collectFromTemplate($includeContent, $module, $includeFileName));
}
// @todo Will get massively confused if you include the includer -> infinite loop
}
- // @todo respect template tags (< % _t() % > instead of _t())
- $regexRule = '#_t[[:space:]]*\(' .
- '[[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,' . // namespace.entity
- '[[:space:]]*(("([^"]|\\\")*"|\'([^\']|\\\\\')*\')' . // value
- '([[:space:]]*\\.[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))*)' . // concatenations
- '([[:space:]]*,[[:space:]]*[^,)]*)?([[:space:]]*,' . // priority (optional)
- '[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*' . // comment (optional)
- '\)#';
+ // Collect in actual template
+ if(preg_match_all('/<%\s*(_t\(.*)%>/ms', $content, $matches)) {
+ foreach($matches as $match) {
+ $entities = array_merge($entities, $this->collectFromCode($match[0], $module));
+ }
+ }
- while (preg_match($regexRule,$content,$regs)) {
- $entitiesArr = array_merge($entitiesArr,(array)$this->entitySpecFromRegexMatches($regs, $fileName));
- // remove parsed content to continue while() loop
- $content = str_replace($regs[0],"",$content);
+ foreach($entities as $entity => $spec) {
+ unset($entities[$entity]);
+ $entities[$this->normalizeEntity($entity, $module)] = $spec;
}
+ ksort($entities);
- ksort($entitiesArr);
-
- return $entitiesArr;
+ return $entities;
}
/**
* @uses i18nEntityProvider
*/
function collectFromEntityProviders($filePath) {
- $entitiesArr = array();
+ $entities = array();
$classes = ClassInfo::classes_for_file($filePath);
if($classes) foreach($classes as $class) {
@@ -264,22 +296,20 @@ function collectFromEntityProviders($filePath) {
if($reflectionClass->isAbstract()) continue;
$obj = singleton($class);
- $entitiesArr = array_merge($entitiesArr,(array)$obj->provideI18nEntities());
+ $entities = array_merge($entities,(array)$obj->provideI18nEntities());
}
}
- ksort($entitiesArr);
-
- return $entitiesArr;
+ ksort($entities);
+ return $entities;
}
/**
- * @todo Fix regexes so the deletion of quotes, commas and newlines from wrong matches isn't necessary
+ * @param String $fullName
+ * @param String $_namespace
+ * @return String|FALSE
*/
- protected function entitySpecFromRegexMatches($regs, $_namespace = null) {
- // remove wrapping quotes
- $fullName = substr($regs[1],1,-1);
-
+ protected function normalizeEntity($fullName, $_namespace = null) {
// split fullname into entity parts
$entityParts = explode('.', $fullName);
if(count($entityParts) > 1) {
@@ -299,117 +329,10 @@ protected function entitySpecFromRegexMatches($regs, $_namespace = null) {
// through $db, which are detected by {@link collectFromEntityProviders}.
if(strpos('$', $entity) !== FALSE) return false;
- // remove wrapping quotes
- $value = !empty($regs[2]) ? substr($regs[2],1,-1) : null;
-
- $value = preg_replace("#([^\\\\])['\"][[:space:]]*\.[[:space:]]*['\"]#", '\\1', $value);
-
- // only escape quotes when wrapped in double quotes, to make them safe for insertion
- // into single-quoted PHP code. If they're wrapped in single quotes, the string should
- // be properly escaped already
- if(substr($regs[2],0,1) == '"') {
- // Double quotes don't need escaping
- $value = str_replace('\\"','"', $value);
- // But single quotes do
- $value = str_replace("'","\\'", $value);
- }
-
- // remove starting comma and any newlines
- $eol = PHP_EOL;
- $prio = !empty($regs[10]) ? trim(preg_replace("/$eol/", '', substr($regs[10],1))) : null;
-
- // remove wrapping quotes
- $comment = !empty($regs[12]) ? substr($regs[12],1,-1) : null;
-
- return array(
- "{$namespace}.{$entity}" => array(
- $value,
- $prio,
- $comment
- )
- );
+ return "{$namespace}.{$entity}";
}
- /**
- * Input for langArrayCodeForEntitySpec() should be suitable for insertion
- * into single-quoted strings, so needs to be escaped already.
- *
- * @param string $entity The entity name, e.g. CMSMain.BUTTONSAVE
- */
- public function langArrayCodeForEntitySpec($entityFullName, $entitySpec) {
- $php = '';
- $eol = PHP_EOL;
-
- $entityParts = explode('.', $entityFullName);
- if(count($entityParts) > 1) {
- // templates don't have a custom namespace
- $entity = array_pop($entityParts);
- // namespace might contain dots, so we implode back
- $namespace = implode('.',$entityParts);
- } else {
- user_error("i18nTextCollector::langArrayCodeForEntitySpec(): Wrong entity format for $entityFullName with values" . var_export($entitySpec, true), E_USER_WARNING);
- return false;
- }
-
- $value = $entitySpec[0];
- $prio = (isset($entitySpec[1])) ? addcslashes($entitySpec[1],'\'') : null;
- $comment = (isset($entitySpec[2])) ? addcslashes($entitySpec[2],'\'') : null;
-
- $php .= '$lang[\'' . $this->defaultLocale . '\'][\'' . $namespace . '\'][\'' . $entity . '\'] = ';
- if ($prio) {
- $php .= "array($eol\t'" . $value . "',$eol\t" . $prio;
- if ($comment) {
- $php .= ",$eol\t'" . $comment . '\'';
- }
- $php .= "$eol);";
- } else {
- $php .= '\'' . $value . '\';';
- }
- $php .= "$eol";
-
- return $php;
- }
- /**
- * Write the master string table of every processed module
- */
- protected function writeMasterStringFile($entitiesByModule) {
- // Write each module language file
- if($entitiesByModule) foreach($entitiesByModule as $module => $entities) {
- $php = '';
- $eol = PHP_EOL;
-
- // Create folder for lang files
- $langFolder = $this->baseSavePath . '/' . $module . '/lang';
- if(!file_exists($langFolder)) {
- Filesystem::makeFolder($langFolder, Filesystem::$folder_create_mask);
- touch($langFolder . '/_manifest_exclude');
- }
-
- // Open the English file and write the Master String Table
- $langFile = $langFolder . '/' . $this->defaultLocale . '.php';
- if($fh = fopen($langFile, "w")) {
- if($entities) foreach($entities as $fullName => $spec) {
- $php .= $this->langArrayCodeForEntitySpec($fullName, $spec);
- }
-
- // test for valid PHP syntax by eval'ing it
- try{
- eval($php);
- } catch(Exception $e) {
- user_error('i18nTextCollector->writeMasterStringFile(): Invalid PHP language file. Error: ' . $e->toString(), E_USER_ERROR);
- }
-
- fwrite($fh, "<"."?php{$eol}{$eol}global \$lang;{$eol}{$eol}" . $php . "{$eol}");
- fclose($fh);
-
- //Debug::message("Created file: $langFolder/" . $this->defaultLocale . ".php", false);
- } else {
- user_error("Cannot write language file! Please check permissions of $langFolder/" . $this->defaultLocale . ".php", E_USER_ERROR);
- }
- }
-
- }
/**
* Helper function that searches for potential files to be parsed
@@ -442,3 +365,90 @@ public function setDefaultLocale($locale) {
$this->defaultLocale = $locale;
}
}
+
+/**
+ * Allows serialization of entity definitions collected through {@link i18nTextCollector}
+ * into a persistent format, usually on the filesystem.
+ */
+interface i18nTextCollector_Writer {
+ /**
+ * @param Array $entities Map of entity names (incl. namespace) to an numeric array,
+ * with at least one element, the original string, and an optional second element, the context.
+ * @param String $locale
+ * @param String $path The directory base on which the collector should create new lang folders and files.
+ * Usually the webroot set through {@link Director::baseFolder()}. Can be overwritten for testing or export purposes.
+ * @return Boolean success
+ */
+ function write($entities, $locale, $path);
+}
+
+/**
+ * Legacy writer for 2.x style persistence.
+ */
+class i18nTextCollector_Writer_Php implements i18nTextCollector_Writer {
+
+ public function write($entities, $locale, $path) {
+ $php = '';
+ $eol = PHP_EOL;
+
+ // Create folder for lang files
+ $langFolder = $path . '/lang';
+ if(!file_exists($langFolder)) {
+ Filesystem::makeFolder($langFolder, Filesystem::$folder_create_mask);
+ touch($langFolder . '/_manifest_exclude');
+ }
+
+ // Open the English file and write the Master String Table
+ $langFile = $langFolder . '/' . $locale . '.php';
+ if($fh = fopen($langFile, "w")) {
+ if($entities) foreach($entities as $fullName => $spec) {
+ $php .= $this->langArrayCodeForEntitySpec($fullName, $spec, $locale);
+ }
+ // test for valid PHP syntax by eval'ing it
+ try{
+ eval($php);
+ } catch(Exception $e) {
+ throw new LogicException('i18nTextCollector->writeMasterStringFile(): Invalid PHP language file. Error: ' . $e->toString());
+ }
+
+ fwrite($fh, "<"."?php{$eol}{$eol}global \$lang;{$eol}{$eol}" . $php . "{$eol}");
+ fclose($fh);
+
+ } else {
+ throw new LogicException("Cannot write language file! Please check permissions of $langFolder/" . $locale . ".php");
+ }
+
+ return true;
+ }
+
+ /**
+ * Input for langArrayCodeForEntitySpec() should be suitable for insertion
+ * into single-quoted strings, so needs to be escaped already.
+ *
+ * @param string $entity The entity name, e.g. CMSMain.BUTTONSAVE
+ */
+ public function langArrayCodeForEntitySpec($entityFullName, $entitySpec, $locale) {
+ $php = '';
+ $eol = PHP_EOL;
+
+ $entityParts = explode('.', $entityFullName);
+ if(count($entityParts) > 1) {
+ // templates don't have a custom namespace
+ $entity = array_pop($entityParts);
+ // namespace might contain dots, so we implode back
+ $namespace = implode('.',$entityParts);
+ } else {
+ user_error("i18nTextCollector::langArrayCodeForEntitySpec(): Wrong entity format for $entityFullName with values" . var_export($entitySpec, true), E_USER_WARNING);
+ return false;
+ }
+
+ $value = $entitySpec[0];
+ $comment = (isset($entitySpec[1])) ? addcslashes($entitySpec[1],'\'') : null;
+
+ $php .= '$lang[\'' . $locale . '\'][\'' . $namespace . '\'][\'' . $entity . '\'] = ';
+ $php .= (count($entitySpec) == 1) ? var_export($entitySpec[0], true) : var_export($entitySpec, true);
+ $php .= ";$eol";
+
+ return $php;
+ }
+}
View
152 tests/i18n/i18nTextCollectorTest.php
@@ -52,7 +52,7 @@ function testConcatenationInEntityValues() {
'Line 1 and ' .
'Line \'2\' and ' .
'Line "3"',
-PR_MEDIUM,
+
'Comment'
);
@@ -64,8 +64,8 @@ function testConcatenationInEntityValues() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.CONCATENATED' => array("Line 1 and Line \\'2\\' and Line \"3\"",'PR_MEDIUM','Comment'),
- 'Test.CONCATENATED2' => array("Line \"4\" and Line 5",null,null)
+ 'Test.CONCATENATED' => array("Line 1 and Line '2' and Line \"3\"",'Comment'),
+ 'Test.CONCATENATED2' => array("Line \"4\" and Line 5")
)
);
}
@@ -78,7 +78,7 @@ function testCollectFromTemplateSimple() {
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
- 'Test.SINGLEQUOTE' => array('Single Quote',null,null)
+ 'Test.SINGLEQUOTE' => array('Single Quote')
)
);
@@ -88,7 +88,7 @@ function testCollectFromTemplateSimple() {
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
- 'Test.DOUBLEQUOTE' => array("Double Quote and Spaces", null, null)
+ 'Test.DOUBLEQUOTE' => array("Double Quote and Spaces")
)
);
@@ -98,7 +98,7 @@ function testCollectFromTemplateSimple() {
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
- 'Test.NOSEMICOLON' => array("No Semicolon", null, null)
+ 'Test.NOSEMICOLON' => array("No Semicolon")
)
);
}
@@ -115,7 +115,7 @@ function testCollectFromTemplateAdvanced() {
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
- 'Test.NEWLINES' => array("New Lines", null, null)
+ 'Test.NEWLINES' => array("New Lines")
)
);
@@ -123,14 +123,13 @@ function testCollectFromTemplateAdvanced() {
<% _t(
'Test.PRIOANDCOMMENT',
' Prio and Value with "Double Quotes"',
- PR_MEDIUM,
'Comment with "Double Quotes"'
) %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
- 'Test.PRIOANDCOMMENT' => array(' Prio and Value with "Double Quotes"','PR_MEDIUM','Comment with "Double Quotes"')
+ 'Test.PRIOANDCOMMENT' => array(' Prio and Value with "Double Quotes"','Comment with "Double Quotes"')
)
);
@@ -138,14 +137,14 @@ function testCollectFromTemplateAdvanced() {
<% _t(
'Test.PRIOANDCOMMENT',
" Prio and Value with 'Single Quotes'",
- PR_MEDIUM,
+
"Comment with 'Single Quotes'"
) %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
- 'Test.PRIOANDCOMMENT' => array(" Prio and Value with \'Single Quotes\'",'PR_MEDIUM',"Comment with 'Single Quotes'")
+ 'Test.PRIOANDCOMMENT' => array(" Prio and Value with 'Single Quotes'","Comment with 'Single Quotes'")
)
);
}
@@ -160,7 +159,7 @@ function testCollectFromCodeSimple() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.SINGLEQUOTE' => array('Single Quote',null,null)
+ 'Test.SINGLEQUOTE' => array('Single Quote')
)
);
@@ -170,7 +169,7 @@ function testCollectFromCodeSimple() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.DOUBLEQUOTE' => array("Double Quote and Spaces", null, null)
+ 'Test.DOUBLEQUOTE' => array("Double Quote and Spaces")
)
);
}
@@ -187,7 +186,7 @@ function testCollectFromCodeAdvanced() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.NEWLINES' => array("New Lines", null, null)
+ 'Test.NEWLINES' => array("New Lines")
)
);
@@ -195,14 +194,14 @@ function testCollectFromCodeAdvanced() {
_t(
'Test.PRIOANDCOMMENT',
' Value with "Double Quotes"',
- PR_MEDIUM,
+
'Comment with "Double Quotes"'
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.PRIOANDCOMMENT' => array(' Value with "Double Quotes"','PR_MEDIUM','Comment with "Double Quotes"')
+ 'Test.PRIOANDCOMMENT' => array(' Value with "Double Quotes"','Comment with "Double Quotes"')
)
);
@@ -210,14 +209,14 @@ function testCollectFromCodeAdvanced() {
_t(
'Test.PRIOANDCOMMENT',
" Value with 'Single Quotes'",
- PR_MEDIUM,
+
"Comment with 'Single Quotes'"
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.PRIOANDCOMMENT' => array(" Value with \'Single Quotes\'",'PR_MEDIUM',"Comment with 'Single Quotes'")
+ 'Test.PRIOANDCOMMENT' => array(" Value with 'Single Quotes'","Comment with 'Single Quotes'")
)
);
@@ -230,7 +229,7 @@ function testCollectFromCodeAdvanced() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.PRIOANDCOMMENT' => array("Value with \'Escaped Single Quotes\'",null,null)
+ 'Test.PRIOANDCOMMENT' => array("Value with 'Escaped Single Quotes'")
)
);
@@ -243,7 +242,7 @@ function testCollectFromCodeAdvanced() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.PRIOANDCOMMENT' => array("Doublequoted Value with \'Unescaped Single Quotes\'",null,null)
+ 'Test.PRIOANDCOMMENT' => array("Doublequoted Value with 'Unescaped Single Quotes'")
)
);
}
@@ -264,7 +263,7 @@ function testNewlinesInEntityValues() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.NEWLINESINGLEQUOTE' => array("Line 1{$eol}Line 2",null,null)
+ 'Test.NEWLINESINGLEQUOTE' => array("Line 1{$eol}Line 2")
)
);
@@ -278,7 +277,7 @@ function testNewlinesInEntityValues() {
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
- 'Test.NEWLINEDOUBLEQUOTE' => array("Line 1{$eol}Line 2",null,null)
+ 'Test.NEWLINEDOUBLEQUOTE' => array("Line 1{$eol}Line 2")
)
);
}
@@ -287,49 +286,46 @@ function testNewlinesInEntityValues() {
* Input for langArrayCodeForEntitySpec() should be suitable for insertion
* into single-quoted strings, so needs to be escaped already.
*/
- function testLangArrayCodeForEntity() {
- $c = new i18nTextCollector();
- $locale = $c->getDefaultLocale();
+ function testPhpWriterLangArrayCodeForEntity() {
+ $c = new i18nTextCollector_Writer_Php();
$this->assertEquals(
- $c->langArrayCodeForEntitySpec('Test.SIMPLE', array('Simple Value')),
- "\$lang['{$locale}']['Test']['SIMPLE'] = 'Simple Value';" . PHP_EOL
+ $c->langArrayCodeForEntitySpec('Test.SIMPLE', array('Simple Value'), 'en_US'),
+ "\$lang['en_US']['Test']['SIMPLE'] = 'Simple Value';" . PHP_EOL
);
$this->assertEquals(
// single quotes should be properly escaped by the parser already
- $c->langArrayCodeForEntitySpec('Test.ESCAPEDSINGLEQUOTES', array("Value with \'Escaped Single Quotes\'")),
- "\$lang['{$locale}']['Test']['ESCAPEDSINGLEQUOTES'] = 'Value with \'Escaped Single Quotes\'';" . PHP_EOL
+ $c->langArrayCodeForEntitySpec('Test.ESCAPEDSINGLEQUOTES', array("Value with 'Escaped Single Quotes'"), 'en_US'),
+ "\$lang['en_US']['Test']['ESCAPEDSINGLEQUOTES'] = 'Value with \'Escaped Single Quotes\'';" . PHP_EOL
);
$this->assertEquals(
- $c->langArrayCodeForEntitySpec('Test.DOUBLEQUOTES', array('Value with "Double Quotes"')),
- "\$lang['{$locale}']['Test']['DOUBLEQUOTES'] = 'Value with \"Double Quotes\"';" . PHP_EOL
+ $c->langArrayCodeForEntitySpec('Test.DOUBLEQUOTES', array('Value with "Double Quotes"'), 'en_US'),
+ "\$lang['en_US']['Test']['DOUBLEQUOTES'] = 'Value with \"Double Quotes\"';" . PHP_EOL
);
$php = <<<PHP
-\$lang['$locale']['Test']['PRIOANDCOMMENT'] = array(
- 'Value with \'Single Quotes\'',
- PR_MEDIUM,
- 'Comment with \'Single Quotes\''
+\$lang['en_US']['Test']['PRIOANDCOMMENT'] = array (
+ 0 => 'Value with \'Single Quotes\'',
+ 1 => 'Comment with \'Single Quotes\'',
);
PHP;
$this->assertEquals(
- $c->langArrayCodeForEntitySpec('Test.PRIOANDCOMMENT', array("Value with \'Single Quotes\'",'PR_MEDIUM',"Comment with 'Single Quotes'")),
+ $c->langArrayCodeForEntitySpec('Test.PRIOANDCOMMENT', array("Value with 'Single Quotes'","Comment with 'Single Quotes'"), 'en_US'),
$php
);
$php = <<<PHP
-\$lang['$locale']['Test']['PRIOANDCOMMENT'] = array(
- 'Value with "Double Quotes"',
- PR_MEDIUM,
- 'Comment with "Double Quotes"'
+\$lang['en_US']['Test']['PRIOANDCOMMENT'] = array (
+ 0 => 'Value with "Double Quotes"',
+ 1 => 'Comment with "Double Quotes"',
);
PHP;
$this->assertEquals(
- $c->langArrayCodeForEntitySpec('Test.PRIOANDCOMMENT', array('Value with "Double Quotes"','PR_MEDIUM','Comment with "Double Quotes"')),
+ $c->langArrayCodeForEntitySpec('Test.PRIOANDCOMMENT', array('Value with "Double Quotes"','Comment with "Double Quotes"'), 'en_US'),
$php
);
}
@@ -345,43 +341,43 @@ function testCollectFromIncludedTemplates() {
$this->assertArrayHasKey('i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE'],
- array('Layout Template no namespace', null, null)
+ array('Layout Template no namespace')
);
*/
$this->assertArrayHasKey('RandomNamespace.SPRINTFNONAMESPACE', $matches);
$this->assertEquals(
$matches['RandomNamespace.SPRINTFNONAMESPACE'],
- array('My replacement no namespace: %s', null, null)
+ array('My replacement no namespace: %s')
);
$this->assertArrayHasKey('i18nTestModule.LAYOUTTEMPLATE', $matches);
$this->assertEquals(
$matches['i18nTestModule.LAYOUTTEMPLATE'],
- array('Layout Template', null, null)
+ array('Layout Template')
);
$this->assertArrayHasKey('i18nTestModule.SPRINTFNAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestModule.SPRINTFNAMESPACE'],
- array('My replacement: %s', null, null)
+ array('My replacement: %s')
);
$this->assertArrayHasKey('i18nTestModule.WITHNAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestModule.WITHNAMESPACE'],
- array('Include Entity with Namespace', null, null)
+ array('Include Entity with Namespace')
);
$this->assertArrayHasKey('i18nTestModuleInclude.ss.NONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestModuleInclude.ss.NONAMESPACE'],
- array('Include Entity without Namespace', null, null)
+ array('Include Entity without Namespace')
);
$this->assertArrayHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE'],
- array('My include replacement: %s', null, null)
+ array('My include replacement: %s')
);
$this->assertArrayHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE'],
- array('My include replacement no namespace: %s', null, null)
+ array('My include replacement no namespace: %s')
);
}
@@ -397,48 +393,48 @@ function testCollectFromThemesTemplates() {
// all entities from i18nTestTheme1.ss
$this->assertEquals(
$matches['i18nTestTheme1.LAYOUTTEMPLATE'],
- array('Theme1 Layout Template', null, null)
+ array('Theme1 Layout Template')
);
$this->assertArrayHasKey('i18nTestTheme1.ss.LAYOUTTEMPLATENONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestTheme1.ss.LAYOUTTEMPLATENONAMESPACE'],
- array('Theme1 Layout Template no namespace', null, null)
+ array('Theme1 Layout Template no namespace')
);
$this->assertEquals(
$matches['i18nTestTheme1.SPRINTFNAMESPACE'],
- array('Theme1 My replacement: %s', null, null)
+ array('Theme1 My replacement: %s')
);
$this->assertArrayHasKey('i18nTestTheme1.ss.SPRINTFNONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestTheme1.ss.SPRINTFNONAMESPACE'],
- array('Theme1 My replacement no namespace: %s', null, null)
+ array('Theme1 My replacement no namespace: %s')
);
// all entities from i18nTestTheme1Include.ss
$this->assertEquals(
$matches['i18nTestTheme1Include.WITHNAMESPACE'],
- array('Theme1 Include Entity with Namespace', null, null)
+ array('Theme1 Include Entity with Namespace')
);
$this->assertArrayHasKey('i18nTestTheme1Include.ss.NONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestTheme1Include.ss.NONAMESPACE'],
- array('Theme1 Include Entity without Namespace', null, null)
+ array('Theme1 Include Entity without Namespace')
);
$this->assertEquals(
$matches['i18nTestTheme1Include.SPRINTFINCLUDENAMESPACE'],
- array('Theme1 My include replacement: %s', null, null)
+ array('Theme1 My include replacement: %s')
);
$this->assertArrayHasKey('i18nTestTheme1Include.ss.SPRINTFINCLUDENONAMESPACE', $matches);
$this->assertEquals(
$matches['i18nTestTheme1Include.ss.SPRINTFINCLUDENONAMESPACE'],
- array('Theme1 My include replacement no namespace: %s', null, null)
+ array('Theme1 My include replacement no namespace: %s')
);
SSViewer::set_theme($theme);
@@ -451,6 +447,7 @@ function testCollectFromFilesystemAndWriteMasterTables() {
i18n::set_default_locale('en_US');
$c = new i18nTextCollector();
+ $c->setWriter(new i18nTextCollector_Writer_Php());
$c->basePath = $this->alternateBasePath;
$c->baseSavePath = $this->alternateBaseSavePath;
@@ -465,31 +462,30 @@ function testCollectFromFilesystemAndWriteMasterTables() {
$moduleLangFileContent = file_get_contents($moduleLangFile);
$this->assertContains(
- "\$lang['en_US']['i18nTestModule']['ADDITION'] = 'Addition';",
+ "\$lang['en']['i18nTestModule']['ADDITION'] = 'Addition';",
$moduleLangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestModule']['ENTITY'] = array(
- 'Entity with \"Double Quotes\"',
- PR_LOW,
- 'Comment for entity'
+ "\$lang['en']['i18nTestModule']['ENTITY'] = array (
+ 0 => 'Entity with \"Double Quotes\"',
+ 1 => 'Comment for entity',
);",
$moduleLangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestModule']['MAINTEMPLATE'] = 'Main Template';",
+ "\$lang['en']['i18nTestModule']['MAINTEMPLATE'] = 'Main Template';",
$moduleLangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestModule']['OTHERENTITY'] = 'Other Entity';",
+ "\$lang['en']['i18nTestModule']['OTHERENTITY'] = 'Other Entity';",
$moduleLangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestModule']['WITHNAMESPACE'] = 'Include Entity with Namespace';",
+ "\$lang['en']['i18nTestModule']['WITHNAMESPACE'] = 'Include Entity with Namespace';",
$moduleLangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestModuleInclude.ss']['NONAMESPACE'] = 'Include Entity without Namespace';",
+ "\$lang['en']['i18nTestModuleInclude.ss']['NONAMESPACE'] = 'Include Entity without Namespace';",
$moduleLangFileContent
);
@@ -501,11 +497,11 @@ function testCollectFromFilesystemAndWriteMasterTables() {
);
$otherModuleLangFileContent = file_get_contents($otherModuleLangFile);
$this->assertContains(
- "\$lang['en_US']['i18nOtherModule']['ENTITY'] = 'Other Module Entity';",
+ "\$lang['en']['i18nOtherModule']['ENTITY'] = 'Other Module Entity';",
$otherModuleLangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nOtherModule']['MAINTEMPLATE'] = 'Main Template Other Module';",
+ "\$lang['en']['i18nOtherModule']['MAINTEMPLATE'] = 'Main Template Other Module';",
$otherModuleLangFileContent
);
@@ -517,40 +513,40 @@ function testCollectFromFilesystemAndWriteMasterTables() {
);
$theme1LangFileContent = file_get_contents($theme1LangFile);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1']['MAINTEMPLATE'] = 'Theme1 Main Template';",
+ "\$lang['en']['i18nTestTheme1']['MAINTEMPLATE'] = 'Theme1 Main Template';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1']['LAYOUTTEMPLATE'] = 'Theme1 Layout Template';",
+ "\$lang['en']['i18nTestTheme1']['LAYOUTTEMPLATE'] = 'Theme1 Layout Template';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1']['SPRINTFNAMESPACE'] = 'Theme1 My replacement: %s';",
+ "\$lang['en']['i18nTestTheme1']['SPRINTFNAMESPACE'] = 'Theme1 My replacement: %s';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1.ss']['LAYOUTTEMPLATENONAMESPACE'] = 'Theme1 Layout Template no namespace';",
+ "\$lang['en']['i18nTestTheme1.ss']['LAYOUTTEMPLATENONAMESPACE'] = 'Theme1 Layout Template no namespace';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1.ss']['SPRINTFNONAMESPACE'] = 'Theme1 My replacement no namespace: %s';",
+ "\$lang['en']['i18nTestTheme1.ss']['SPRINTFNONAMESPACE'] = 'Theme1 My replacement no namespace: %s';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1Include']['SPRINTFINCLUDENAMESPACE'] = 'Theme1 My include replacement: %s';",
+ "\$lang['en']['i18nTestTheme1Include']['SPRINTFINCLUDENAMESPACE'] = 'Theme1 My include replacement: %s';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1Include']['WITHNAMESPACE'] = 'Theme1 Include Entity with Namespace';",
+ "\$lang['en']['i18nTestTheme1Include']['WITHNAMESPACE'] = 'Theme1 Include Entity with Namespace';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1Include.ss']['NONAMESPACE'] = 'Theme1 Include Entity without Namespace';",
+ "\$lang['en']['i18nTestTheme1Include.ss']['NONAMESPACE'] = 'Theme1 Include Entity without Namespace';",
$theme1LangFileContent
);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme1Include.ss']['SPRINTFINCLUDENONAMESPACE'] = 'Theme1 My include replacement no namespace: %s';",
+ "\$lang['en']['i18nTestTheme1Include.ss']['SPRINTFINCLUDENONAMESPACE'] = 'Theme1 My include replacement no namespace: %s';",
$theme1LangFileContent
);
@@ -562,7 +558,7 @@ function testCollectFromFilesystemAndWriteMasterTables() {
);
$theme2LangFileContent = file_get_contents($theme2LangFile);
$this->assertContains(
- "\$lang['en_US']['i18nTestTheme2']['MAINTEMPLATE'] = 'Theme2 Main Template';",
+ "\$lang['en']['i18nTestTheme2']['MAINTEMPLATE'] = 'Theme2 Main Template';",
$theme2LangFileContent
);
Please sign in to comment.
Something went wrong with that request. Please try again.