diff --git a/admin/code/GroupImportForm.php b/admin/code/GroupImportForm.php index c084aeecc..850fe7e75 100644 --- a/admin/code/GroupImportForm.php +++ b/admin/code/GroupImportForm.php @@ -70,17 +70,17 @@ class GroupImportForm extends Form { // result message $msgArr = array(); - if($result->CreatedCount()) $msgArr[] = sprintf( - _t('GroupImportForm.ResultCreated', 'Created %d groups'), - $result->CreatedCount() + if($result->CreatedCount()) $msgArr[] = _t( + 'GroupImportForm.ResultCreated', 'Created {count} groups', + array('count' => $result->CreatedCount()) ); - if($result->UpdatedCount()) $msgArr[] = sprintf( - _t('GroupImportForm.ResultUpdated', 'Updated %d groups'), - $result->UpdatedCount() + if($result->UpdatedCount()) $msgArr[] = _t( + 'GroupImportForm.ResultUpdated', 'Updated %d groups', + array('count' => $result->UpdatedCount()) ); - if($result->DeletedCount()) $msgArr[] = sprintf( - _t('GroupImportForm.ResultDeleted', 'Deleted %d groups'), - $result->DeletedCount() + if($result->DeletedCount()) $msgArr[] = _t( + 'GroupImportForm.ResultDeleted', 'Deleted %d groups', + array('count' => $result->DeletedCount()) ); $msg = ($msgArr) ? implode(',', $msgArr) : _t('MemberImportForm.ResultNone', 'No changes'); diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index e0ec7e4d4..a394f5fc1 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -1299,12 +1299,12 @@ class LeftAndMain extends Controller implements PermissionProvider { $title = _t("{$class}.MENUTITLE", LeftAndMain::menu_title_for_class($class)); $perms["CMS_ACCESS_" . $class] = array( - 'name' => sprintf(_t( + 'name' => _t( 'CMSMain.ACCESS', - "Access to '%s' section", - - "Item in permission selection identifying the admin section. Example: Access to 'Files & Images'" - ), $title, null), + "Access to '{title}' section", + "Item in permission selection identifying the admin section. Example: Access to 'Files & Images'", + array('title' => $title) + ), 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access') ); } diff --git a/admin/code/MemberImportForm.php b/admin/code/MemberImportForm.php index 1b15692ea..bb6274738 100644 --- a/admin/code/MemberImportForm.php +++ b/admin/code/MemberImportForm.php @@ -75,17 +75,17 @@ class MemberImportForm extends Form { // result message $msgArr = array(); - if($result->CreatedCount()) $msgArr[] = sprintf( - _t('MemberImportForm.ResultCreated', 'Created %d members'), - $result->CreatedCount() + if($result->CreatedCount()) $msgArr[] = _t( + 'MemberImportForm.ResultCreated', 'Created {count} members', + array('count' => $result->CreatedCount()) ); - if($result->UpdatedCount()) $msgArr[] = sprintf( - _t('MemberImportForm.ResultUpdated', 'Updated %d members'), - $result->UpdatedCount() + if($result->UpdatedCount()) $msgArr[] = _t( + 'MemberImportForm.ResultUpdated', 'Updated {count} members', + array('count' => $result->UpdatedCount()) ); - if($result->DeletedCount()) $msgArr[] = sprintf( - _t('MemberImportForm.ResultDeleted', 'Deleted %d members'), - $result->DeletedCount() + if($result->DeletedCount()) $msgArr[] = _t( + 'MemberImportForm.ResultDeleted', 'Deleted %d members', + array('count' => $result->DeletedCount()) ); $msg = ($msgArr) ? implode(',', $msgArr) : _t('MemberImportForm.ResultNone', 'No changes'); diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index d55a3f1e4..e19dfbbc4 100644 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -387,17 +387,17 @@ abstract class ModelAdmin extends LeftAndMain { $results = $loader->load($_FILES['_CsvFile']['tmp_name']); $message = ''; - if($results->CreatedCount()) $message .= sprintf( - _t('ModelAdmin.IMPORTEDRECORDS', "Imported %s records."), - $results->CreatedCount() + if($results->CreatedCount()) $message .= _t( + 'ModelAdmin.IMPORTEDRECORDS', "Imported {count} records.", + array('count' => $results->CreatedCount()) ); - if($results->UpdatedCount()) $message .= sprintf( - _t('ModelAdmin.UPDATEDRECORDS', "Updated %s records."), - $results->UpdatedCount() + if($results->UpdatedCount()) $message .= _t( + 'ModelAdmin.UPDATEDRECORDS', "Updated {count} records.", + array('count' => $results->UpdatedCount()) ); - if($results->DeletedCount()) $message .= sprintf( - _t('ModelAdmin.DELETEDRECORDS', "Deleted %s records."), - $results->DeletedCount() + if($results->DeletedCount()) $message .= _t( + 'ModelAdmin.DELETEDRECORDS', "Deleted {count} records.", + array('count' => $results->DeletedCount()) ); if(!$results->CreatedCount() && !$results->UpdatedCount()) $message .= _t('ModelAdmin.NOIMPORT', "Nothing to import"); diff --git a/admin/code/SecurityAdmin.php b/admin/code/SecurityAdmin.php index 4e0690d90..c43d4d798 100755 --- a/admin/code/SecurityAdmin.php +++ b/admin/code/SecurityAdmin.php @@ -219,7 +219,7 @@ class SecurityAdmin extends LeftAndMain implements PermissionProvider { $title = _t("SecurityAdmin.MENUTITLE", LeftAndMain::menu_title_for_class($this->class)); return array( "CMS_ACCESS_SecurityAdmin" => array( - 'name' => sprintf(_t('CMSMain.ACCESS', "Access to '%s' section"), $title), + 'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)), 'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'), 'help' => _t( 'SecurityAdmin.ACCESS_HELP', diff --git a/core/Core.php b/core/Core.php index a340abeff..d2e25d102 100644 --- a/core/Core.php +++ b/core/Core.php @@ -386,8 +386,8 @@ function stripslashes_recursively(&$array) { /** * @see i18n::_t() */ -function _t($entity, $string = "", $context = "") { - return i18n::_t($entity, $string, $context); +function _t($entity, $string = "", $context = "", $injection = "") { + return i18n::_t($entity, $string, $context, $injection); } /** diff --git a/docs/en/changelogs/3.0.0.md b/docs/en/changelogs/3.0.0.md index 3757dbc93..63c9b1fac 100644 --- a/docs/en/changelogs/3.0.0.md +++ b/docs/en/changelogs/3.0.0.md @@ -455,13 +455,36 @@ and was rarely used in practice - so we moved it to a "[homepagefordomain](https ### New syntax for translatable _t functions [i18n-t]### -You can now call the _t() function in both templates and code with a namespace and string to translate, as well as a -comment and injection array. Note that the proxity arguement to _t is no longer supported. +You can now call the `_t()` function in both templates and code with a namespace and string to translate, as well as a comment and injection array. The new syntax supports injecting variables into the translation. For example: :::php - _t('i18nTestModule.INJECTIONS2', "Hello {name} {greeting}", array("name"=>"Paul", "greeting"=>"good you are here")); + _t( + 'i18nTestModule.INJECTIONS2', + "Hello {name} {greeting}", + array("name"=>"Paul", "greeting"=>"good you are here") + ); + +We've written the injection logic in a way that keeps backwards compatible with +existing translations. This means that you can migrate from `sprintf()` to the new injection +API incrementally. The following to "mixed usage" examples still work, although they +don't get the advantage of flexible ordering in substitutions. + + :::php + _t( + 'i18nTestModule.INJECTIONS2', + "Hello {name} {greeting}", + array("Paul", "good you are here") + ); + _t( + 'i18nTestModule.INJECTIONS2', + "Hello %s, %s", + array("name"=>"Paul", "greeting"=>"good you are here") + ); + +Of course, you can keep using `sprintf()` for variable substitution in your own code. + ### Default translation source in YML instead of PHP $lang array, using Zend_Translate {#zend-translate} diff --git a/docs/en/topics/i18n.md b/docs/en/topics/i18n.md index ab16c430c..fd61ff134 100644 --- a/docs/en/topics/i18n.md +++ b/docs/en/topics/i18n.md @@ -180,9 +180,10 @@ Therefore, the following would be a valid use in templates: Using SS templating variables in the translatable string (e.g. $Author, $Date..) is not currently supported. -### Injection-support +### Injection Support -Variable injection in _t allows us to dynamically replace parts of a translated string, e.g. by a username or a page-title. +Variable injection in `_t()` allows us to dynamically replace parts of a translated string, e.g. by a username or a page-title. The named parameters also allow flexible ordering of placeholders, +which might vary depending on the used language. :::php // in PHP-file @@ -196,6 +197,10 @@ Variable injection in _t allows us to dynamically replace parts of a translated // in SS-template ($Name must be available in the current template-scope) <%t MYPROJECT.INJECTIONS "Hello {name} {greeting}" name="$Name" greeting="good to see you" %> +Note that you can still use `sprintf()` wrapped around a `_t()` call +for your substitutions. In contrast to `sprintf()`, our API has a more translator friendly +placeholder syntax, as well as more graceful fallback if not all placeholders are found +(an outdated translation with less placeholders will throw a notice rather than a fatal error). ## Collecting text diff --git a/filesystem/Upload.php b/filesystem/Upload.php index 31339dcec..7af603fb2 100644 --- a/filesystem/Upload.php +++ b/filesystem/Upload.php @@ -496,28 +496,22 @@ class Upload_Validator { if(!$this->isValidSize()) { $ext = (isset($pathInfo['extension'])) ? $pathInfo['extension'] : ''; $arg = File::format_size($this->getAllowedMaxFileSize($ext)); - $this->errors[] = sprintf( - _t( - 'File.TOOLARGE', - 'Filesize is too large, maximum %s allowed.', - - 'Argument 1: Filesize (e.g. 1MB)' - ), - $arg + $this->errors[] = _t( + 'File.TOOLARGE', + 'Filesize is too large, maximum {size} allowed.', + 'Argument 1: Filesize (e.g. 1MB)', + array('size' => $arg) ); return false; } // extension validation if(!$this->isValidExtension()) { - $this->errors[] = sprintf( - _t( - 'File.INVALIDEXTENSION', - 'Extension is not allowed (valid: %s)', - - 'Argument 1: Comma-separated list of valid extensions' - ), - wordwrap(implode(', ', $this->allowedExtensions)) + $this->errors[] = _t( + 'File.INVALIDEXTENSION', + 'Extension is not allowed (valid: {extensions})', + 'Argument 1: Comma-separated list of valid extensions', + array('extensions' => wordwrap(implode(', ', $this->allowedExtensions))) ); return false; } diff --git a/forms/ComplexTableField.php b/forms/ComplexTableField.php index ddb6d112f..ec04fd7aa 100644 --- a/forms/ComplexTableField.php +++ b/forms/ComplexTableField.php @@ -485,12 +485,11 @@ JS; $editLink = Controller::join_links($this->Link(), 'item/' . $childData->ID . '/edit'); - $message = sprintf( - _t('ComplexTableField.SUCCESSADD', 'Added %s %s %s'), - $childData->singular_name(), - '' . $childData->Title . '', - $closeLink + $message = _t( + 'ComplexTableField.SUCCESSADD2', 'Added {name}', + array('name' => $childData->singular_name()) ); + $message .= '' . $childData->Title . '' . $closeLink; $form->sessionMessage($message, 'good'); diff --git a/forms/ConfirmedPasswordField.php b/forms/ConfirmedPasswordField.php index 889b3342a..213150b8b 100644 --- a/forms/ConfirmedPasswordField.php +++ b/forms/ConfirmedPasswordField.php @@ -247,13 +247,25 @@ class ConfirmedPasswordField extends FormField { if(($this->minLength || $this->maxLength)) { if($this->minLength && $this->maxLength) { $limit = "{{$this->minLength},{$this->maxLength}}"; - $errorMsg = sprintf(_t('ConfirmedPasswordField.BETWEEN', 'Passwords must be %s to %s characters long.'), $this->minLength, $this->maxLength); + $errorMsg = _t( + 'ConfirmedPasswordField.BETWEEN', + 'Passwords must be {min} to {max} characters long.', + array('min' => $this->minLength, 'max' => $this->maxLength) + ); } elseif($this->minLength) { $limit = "{{$this->minLength}}.*"; - $errorMsg = sprintf(_t('ConfirmedPasswordField.ATLEAST', 'Passwords must be at least %s characters long.'), $this->minLength); + $errorMsg = _t( + 'ConfirmedPasswordField.ATLEAST', + 'Passwords must be at least {min} characters long.', + array('min' => $this->minLength) + ); } elseif($this->maxLength) { $limit = "{0,{$this->maxLength}}"; - $errorMsg = sprintf(_t('ConfirmedPasswordField.MAXIMUM', 'Passwords must be at most %s characters long.'), $this->maxLength); + $errorMsg = _t( + 'ConfirmedPasswordField.MAXIMUM', + 'Passwords must be at most {max} characters long.', + array('max' => $this->maxLength) + ); } $limitRegex = '/^.' . $limit . '$/'; if(!empty($value) && !preg_match($limitRegex,$value)) { diff --git a/forms/CreditCardField.php b/forms/CreditCardField.php index bcf246095..e8f8e83ed 100644 --- a/forms/CreditCardField.php +++ b/forms/CreditCardField.php @@ -51,9 +51,10 @@ class CreditCardField extends TextField { } $validator->validationError( $this->name, - sprintf( - _t('Form.VALIDATIONCREDITNUMBER', "Please ensure you have entered the %s credit card number correctly."), - $number + _t( + 'Form.VALIDATIONCREDITNUMBER', + "Please ensure you have entered the {number} credit card number correctly.", + array('number' => $number) ), "validation", false diff --git a/forms/DateField.php b/forms/DateField.php index b352c6c2c..4d609abf9 100644 --- a/forms/DateField.php +++ b/forms/DateField.php @@ -310,9 +310,9 @@ class DateField extends TextField { if(!$valid) { $validator->validationError( $this->name, - sprintf( - _t('DateField.VALIDDATEFORMAT2', "Please enter a valid date format (%s)."), - $this->getConfig('dateformat') + _t( + 'DateField.VALIDDATEFORMAT2', "Please enter a valid date format ({format}).", + array('format' => $this->getConfig('dateformat')) ), "validation", false @@ -331,9 +331,9 @@ class DateField extends TextField { if(!$this->valueObj->isLater($minDate) && !$this->valueObj->equals($minDate)) { $validator->validationError( $this->name, - sprintf( - _t('DateField.VALIDDATEMINDATE', "Your date has to be newer or matching the minimum allowed date (%s)"), - $minDate->toString($this->getConfig('dateformat')) + _t( + 'DateField.VALIDDATEMINDATE', "Your date has to be newer or matching the minimum allowed date ({date})", + array('date' => $minDate->toString($this->getConfig('dateformat'))) ), "validation", false @@ -351,9 +351,9 @@ class DateField extends TextField { if(!$this->valueObj->isEarlier($maxDate) && !$this->valueObj->equals($maxDate)) { $validator->validationError( $this->name, - sprintf( - _t('DateField.VALIDDATEMAXDATE', "Your date has to be older or matching the maximum allowed date (%s)"), - $maxDate->toString($this->getConfig('dateformat')) + _t( + 'DateField.VALIDDATEMAXDATE', "Your date has to be older or matching the maximum allowed date ({date})", + array('date' => $maxDate->toString($this->getConfig('dateformat'))) ), "validation", false diff --git a/forms/FileIFrameField.php b/forms/FileIFrameField.php index a5b651352..cbf469dec 100644 --- a/forms/FileIFrameField.php +++ b/forms/FileIFrameField.php @@ -92,9 +92,11 @@ class FileIFrameField extends FileField { ) ); } else { - return sprintf(_t ( - 'FileIFrameField.ATTACHONCESAVED', '%ss can be attached once you have saved the record for the first time.' - ), $this->FileTypeName()); + return _t( + 'FileIFrameField.ATTACHONCESAVED', + '{type}s can be attached once you have saved the record for the first time.', + array('type' => $this->FileTypeName()) + ); } } @@ -130,9 +132,9 @@ class FileIFrameField extends FileField { $selectFile = _t('FileIFrameField.FROMFILESTORE', 'From the File Store'); if($this->AttachedFile() && $this->AttachedFile()->ID) { - $title = sprintf(_t('FileIFrameField.REPLACE', 'Replace %s'), $this->FileTypeName()); + $title = _t('FileIFrameField.REPLACE', 'Replace {type}', array('type' => $this->FileTypeName())); } else { - $title = sprintf(_t('FileIFrameField.ATTACH', 'Attach %s'), $this->FileTypeName()); + $title = _t('FileIFrameField.ATTACH', 'Attach {type}', array('type' => $this->FileTypeName())); } $fileSources = array(); @@ -235,7 +237,7 @@ class FileIFrameField extends FileField { ), new FieldList ( $deleteButton = new FormAction ( - 'delete', sprintf(_t('FileIFrameField.DELETE', 'Delete %s'), $this->FileTypeName()) + 'delete', _t('FileIFrameField.DELETE', 'Delete {type}', array('type' => $this->FileTypeName())) ) ) ); diff --git a/forms/NumericField.php b/forms/NumericField.php index 872d9cbdf..218d777d4 100644 --- a/forms/NumericField.php +++ b/forms/NumericField.php @@ -16,9 +16,9 @@ class NumericField extends TextField{ if($this->value && !is_numeric(trim($this->value))){ $validator->validationError( $this->name, - sprintf( - _t('NumericField.VALIDATION', "'%s' is not a number, only numbers can be accepted for this field"), - $this->value + _t( + 'NumericField.VALIDATION', "'{value}' is not a number, only numbers can be accepted for this field", + array('value' => $this->value) ), "validation" ); diff --git a/forms/TimeField.php b/forms/TimeField.php index 38b096d4a..330223793 100644 --- a/forms/TimeField.php +++ b/forms/TimeField.php @@ -143,9 +143,9 @@ class TimeField extends TextField { if(!Zend_Date::isDate($this->value, $this->getConfig('timeformat'), $this->locale)) { $validator->validationError( $this->name, - sprintf( - _t('TimeField.VALIDATEFORMAT', "Please enter a valid time format (%s)"), - $this->getConfig('timeformat') + _t( + 'TimeField.VALIDATEFORMAT', "Please enter a valid time format ({format})", + array('format' => $this->getConfig('timeformat')) ), "validation", false diff --git a/forms/UploadField.php b/forms/UploadField.php index e23f89944..e8dcd4aca 100644 --- a/forms/UploadField.php +++ b/forms/UploadField.php @@ -348,23 +348,26 @@ class UploadField extends FileField { if (count($this->getValidator()->getAllowedExtensions())) { $allowedExtensions = $this->getValidator()->getAllowedExtensions(); $config['acceptFileTypes'] = '(\.|\/)(' . implode('|', $allowedExtensions) . ')$'; - $config['errorMessages']['acceptFileTypes'] = sprintf(_t( + $config['errorMessages']['acceptFileTypes'] = _t( 'File.INVALIDEXTENSION', - 'Extension is not allowed (valid: %s)' - ), wordwrap(implode(', ', $allowedExtensions))); + 'Extension is not allowed (valid: {extensions})', + array('extensions' => wordwrap(implode(', ', $allowedExtensions))) + ); } if ($this->getValidator()->getAllowedMaxFileSize()) { $config['maxFileSize'] = $this->getValidator()->getAllowedMaxFileSize(); - $config['errorMessages']['maxFileSize'] = sprintf(_t( + $config['errorMessages']['maxFileSize'] = _t( 'File.TOOLARGE', - 'Filesize is too large, maximum %s allowed.' - ), File::format_size($config['maxFileSize'])); + 'Filesize is too large, maximum {size} allowed.', + array('size' => File::format_size($config['maxFileSize'])) + ); } if ($config['maxNumberOfFiles'] > 1) { - $config['errorMessages']['maxNumberOfFiles'] = sprintf(_t( + $config['errorMessages']['maxNumberOfFiles'] = _t( 'UploadField.MAXNUMBEROFFILES', - 'Max number of %s file(s) exceeded.' - ), $config['maxNumberOfFiles']); + 'Max number of {count} file(s) exceeded.', + array('count' => $config['maxNumberOfFiles']) + ); } $configOverwrite = array(); if (is_numeric($config['maxNumberOfFiles']) && $this->getItems()->count()) { @@ -459,10 +462,11 @@ class UploadField extends FileField { // Report the constraint violation. if ($tooManyFiles) { if(!$this->getConfig('allowedMaxFileNumber')) $this->setConfig('allowedMaxFileNumber', 1); - $return['error'] = sprintf(_t( + $return['error'] = _t( 'UploadField.MAXNUMBEROFFILES', - 'Max number of %s file(s) exceeded.' - ), $this->getConfig('allowedMaxFileNumber')); + 'Max number of {count} file(s) exceeded.', + array('count' => $this->getConfig('allowedMaxFileNumber')) + ); } } diff --git a/forms/gridfield/GridFieldAddExistingAutocompleter.php b/forms/gridfield/GridFieldAddExistingAutocompleter.php index a9f1f4629..1f4cfe036 100755 --- a/forms/gridfield/GridFieldAddExistingAutocompleter.php +++ b/forms/gridfield/GridFieldAddExistingAutocompleter.php @@ -251,15 +251,15 @@ class GridFieldAddExistingAutocompleter implements GridField_HTMLProvider, GridF if($label) $labels[] = $label; } if($labels) { - return sprintf( - _t('GridField.PlaceHolderWithLabels', 'Find %s by %s', 'Find by '), - singleton($dataClass)->plural_name(), - implode(', ', $labels) + return _t( + 'GridField.PlaceHolderWithLabels', + 'Find {type} by {name}', + array('type' => singleton($dataClass)->plural_name(), 'name' => implode(', ', $labels)) ); } else { - return sprintf( - _t('GridField.PlaceHolder', 'Find %s', 'Find '), - singleton($dataClass)->plural_name() + return _t( + 'GridField.PlaceHolder', 'Find {type}', + array('type' => singleton($dataClass)->plural_name()) ); } } diff --git a/i18n/i18n.php b/i18n/i18n.php index 4e8460a06..c350797f6 100644 --- a/i18n/i18n.php +++ b/i18n/i18n.php @@ -1488,7 +1488,7 @@ class i18n extends Object implements TemplateGlobalProvider { $translatorsByPrio = self::$translators; if(!$translatorsByPrio) $translatorsByPrio = self::get_translators(); - $returnValue = $string; // Fall back to default string argument + $returnValue = (is_string($string)) ? $string : ''; // Fall back to default string argument foreach($translatorsByPrio as $priority => $translators) { foreach($translators as $name => $translator) { @@ -1512,9 +1512,36 @@ class i18n extends Object implements TemplateGlobalProvider { } // inject the variables from injectionArray (if present) - if ($injectionArray && count($injectionArray) > 0) { - foreach($injectionArray as $variable => $injection) { - $returnValue = str_replace('{'.$variable.'}', $injection, $returnValue); + if($injectionArray) { + $regex = '/\{[\w\d]*\}/i'; + if(!preg_match($regex, $returnValue)) { + // Legacy mode: If no injection placeholders are found, + // replace sprintf placeholders in fixed order. + $returnValue = vsprintf($returnValue, array_values($injectionArray)); + } else if(!ArrayLib::is_associative($injectionArray)) { + // Legacy mode: If injection placeholders are found, + // but parameters are passed without names, replace them in fixed order. + $returnValue = preg_replace_callback( + $regex, + function($matches) use(&$injectionArray) { + return $injectionArray ? array_shift($injectionArray) : ''; + }, + $returnValue + ); + } else { + // Standard placeholder replacement with named injections and variable order. + foreach($injectionArray as $variable => $injection) { + $placeholder = '{'.$variable.'}'; + $returnValue = str_replace($placeholder, $injection, $returnValue, $count); + if(!$count) { + SS_Log::log(sprintf( + "Couldn't find placeholder '%s' in translation string '%s' (id: '%s')", + $placeholder, + $returnValue, + $entity + ), SS_Log::NOTICE); + } + } } } diff --git a/i18n/i18nTextCollector.php b/i18n/i18nTextCollector.php index d4a054523..fb5503830 100644 --- a/i18n/i18nTextCollector.php +++ b/i18n/i18nTextCollector.php @@ -162,6 +162,7 @@ class i18nTextCollector extends Object { //Debug::message("Processing Module '{$module}'", false); // Search for calls in code files if these exists + $fileList = array(); if(is_dir("$this->basePath/$module/code")) { $fileList = $this->getFilesRecursive("$this->basePath/$module/code"); } else if($module == FRAMEWORK_DIR || substr($module, 0, 7) == 'themes/') { diff --git a/lang/en.yml b/lang/en.yml index 24a67903f..751121af7 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -55,7 +55,7 @@ en: NO: No YES: Yes CMSMain: - ACCESS: 'Access to ''%s'' section' + ACCESS: 'Access to ''{title}'' section' ACCESSALLINTERFACES: 'Access to all CMS sections' ACCESSALLINTERFACESHELP: 'Overrules more specific access settings.' SAVE: Save @@ -72,7 +72,7 @@ en: YES: Yes ComplexTableField: CLOSEPOPUP: 'Close Popup' - SUCCESSADD: 'Added %s %s %s' + SUCCESSADD2: 'Added {name}' SUCCESSEDIT: 'Saved %s %s %s' SUCCESSEDIT2: 'Deleted %s %s' ComplexTableField.ss: @@ -83,9 +83,9 @@ en: NEXT: Next PREVIOUS: Previous ConfirmedPasswordField: - ATLEAST: 'Passwords must be at least %s characters long.' - BETWEEN: 'Passwords must be %s to %s characters long.' - MAXIMUM: 'Passwords must be at most %s characters long.' + ATLEAST: 'Passwords must be at least {min} characters long.' + BETWEEN: 'Passwords must be {min} to {max} characters long.' + MAXIMUM: 'Passwords must be at most {max} characters long.' SHOWONCLICKTITLE: 'Change Password' CreditCardField: FIRST: first @@ -108,16 +108,16 @@ en: MONTHS: ' months' SEC: ' sec' SECS: ' secs' - TIMEDIFFAGO: '%s ago' - TIMEDIFFIN: 'in %s' + TIMEDIFFAGO: '{difference} ago' + TIMEDIFFIN: 'in {difference}' YEAR: ' year' YEARS: ' years' DateField: NOTSET: 'not set' TODAY: today - VALIDDATEFORMAT2: 'Please enter a valid date format (%s).' - VALIDDATEMAXDATE: 'Your date has to be older or matching the maximum allowed date (%s)' - VALIDDATEMINDATE: 'Your date has to be newer or matching the minimum allowed date (%s)' + VALIDDATEFORMAT2: 'Please enter a valid date format ({format}).' + VALIDDATEMAXDATE: 'Your date has to be older or matching the maximum allowed date ({date})' + VALIDDATEMINDATE: 'Your date has to be newer or matching the minimum allowed date ({date})' DropdownField: CHOOSE: (Choose) EmailField: @@ -130,24 +130,24 @@ en: File: Content: Content Filename: Filename - INVALIDEXTENSION: 'Extension is not allowed (valid: %s)' + INVALIDEXTENSION: 'Extension is not allowed (valid: {extensions})' NOFILESIZE: 'Filesize is zero bytes.' NOVALIDUPLOAD: 'File is not a valid upload' Name: Name PLURALNAME: Files SINGULARNAME: File - TOOLARGE: 'Filesize is too large, maximum %s allowed.' + TOOLARGE: 'Filesize is too large, maximum {size} allowed.' Title: Title FileIFrameField: - ATTACH: 'Attach %s' - ATTACHONCESAVED: '%ss can be attached once you have saved the record for the first time.' - DELETE: 'Delete %s' + ATTACH: 'Attach {type}' + ATTACHONCESAVED: '{type}s can be attached once you have saved the record for the first time.' + DELETE: 'Delete {type}' DISALLOWEDFILETYPE: 'This filetype is not allowed to be uploaded' FILE: File FROMCOMPUTER: 'From your Computer' FROMFILESTORE: 'From the File Store' NOSOURCE: 'Please select a source file to attach' - REPLACE: 'Replace %s' + REPLACE: 'Replace {type}' FileIFrameField_iframe.ss: TITLE: 'Image Uploading Iframe' ForgotPasswordEmail.ss: @@ -157,8 +157,7 @@ en: TEXT3: for Form: FIELDISREQUIRED: '%s is required' - VALIDATIONCREDITNUMBER: 'Please ensure you have entered the %s credit card number correctly.' - VALIDATIONFAILED: 'Validation failed' + VALIDATIONCREDITNUMBER: 'Please ensure you have entered the {number} credit card number correctly.' VALIDATIONNOTUNIQUE: 'The value entered is not unique' VALIDATIONPASSWORDSDONTMATCH: 'Passwords don''t match' VALIDATIONPASSWORDSNOTEMPTY: 'Passwords can''t be empty' @@ -179,8 +178,8 @@ en: NoItemsFound: 'No items found' PRINTEDAT: 'Printed at' PRINTEDBY: 'Printed by' - PlaceHolder: 'Find %s' - PlaceHolderWithLabels: 'Find %s by %s' + PlaceHolder: 'Find {type}' + PlaceHolderWithLabels: 'Find {type} by {name}' RelationSearch: 'Relation search' ResetFilter: Reset GridFieldAction_Delete: @@ -209,16 +208,17 @@ en: GroupImportForm: Help1: 'Import one or more groups in CSV format (comma-separated values). Show advanced usage' Help2: "\n Advanced usage\n \n Allowed columns: %s\n Existing groups are matched by their unique Code value, and updated with any new values from the imported file\n Group hierarchies can be created by using a ParentCode column.\n Permission codes can be assigned by the PermissionCode column. Existing permission codes are not cleared.\n \n" - ResultCreated: 'Created %d groups' + ResultCreated: 'Created {count} groups' ResultDeleted: 'Deleted %d groups' ResultUpdated: 'Updated %d groups' Hierarchy: - InfiniteLoopNotAllowed: 'Infinite loop found within the "%s" hierarchy. Please change the parent to resolve this' + InfiniteLoopNotAllowed: 'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this' HtmlEditorField: ANCHORVALUE: Anchor BUTTONINSERT: Insert BUTTONINSERTLINK: 'Insert link' BUTTONREMOVELINK: 'Remove link' + BUTTONUpdate: Update CAPTIONTEXT: 'Caption text' CSSCLASS: 'Alignment / style' CSSCLASSCENTER: 'Centered, on its own.' @@ -233,12 +233,12 @@ en: FROMCMS: 'From the CMS' FROMCOMPUTER: 'From your computer' Find: Find - IMAGE: 'Insert Image' IMAGEALTTEXT: 'Alternative text (alt) - shown if image cannot be displayed' IMAGEDIMENSIONS: Dimensions IMAGEHEIGHTPX: Height IMAGETITLE: 'Title text (tooltip) - for additional information about the image' IMAGEWIDTHPX: Width + INSERTIMAGE: 'Insert Image' LINK: 'Insert Link' LINKANCHOR: 'Anchor on this page' LINKDESCR: 'Link description' @@ -250,6 +250,7 @@ en: LINKTO: 'Link to' PAGE: Page URL: URL + UpdateIMAGE: 'Update Image' ImageField: IMAGE: Image Image_iframe.ss: @@ -270,6 +271,7 @@ en: IP: 'IP Address' Status: Status Member: + ADDGROUP: 'Add group' ADDRESS: Address BUTTONCHANGEPASSWORD: 'Change Password' BUTTONLOGIN: 'Log in' @@ -293,8 +295,8 @@ en: FIRSTNAME: 'First Name' GREETING: Welcome INTERFACELANG: 'Interface Language' - INVALIDNEWPASSWORD: 'We couldn''t accept that password: %s' - LOGGEDINAS: 'You''re logged in as %s.' + INVALIDNEWPASSWORD: 'We couldn''t accept that password: {password}' + LOGGEDINAS: 'You''re logged in as {name}.' MOBILE: Mobile NAME: Name NEWPASSWORD: 'New Password' @@ -308,8 +310,8 @@ en: SUBJECTPASSWORDRESET: 'Your password reset link' SURNAME: Surname VALIDATIONMEMBEREXISTS: 'A member already exists with the same %s' - ValidationIdentifierFailed: 'Can''t overwrite existing member #%d with identical identifier (%s = %s))' - WELCOMEBACK: 'Welcome Back, %s' + ValidationIdentifierFailed: 'Can''t overwrite existing member #{id} with identical identifier ({name} = {value}))' + WELCOMEBACK: 'Welcome Back, {firstname}' YOUROLDPASSWORD: 'Your old password' belongs_many_many_Groups: Groups db_LastVisited: 'Last Visited Date' @@ -343,37 +345,37 @@ en: MemberImportForm: Help1: 'Import users in CSV format (comma-separated values). Show advanced usage' Help2: "\n Advanced usage\n \n Allowed columns: %s\n Existing users are matched by their unique Code property, and updated with any new values from the imported file.\n Groups can be assigned by the Groups column. Groups are identified by their Code property, multiple groups can be separated by comma. Existing group memberships are not cleared.\n \n" - ResultCreated: 'Created %d members' + ResultCreated: 'Created {count} members' ResultDeleted: 'Deleted %d members' ResultNone: 'No changes' - ResultUpdated: 'Updated %d members' + ResultUpdated: 'Updated {count} members' MemberTableField: 'APPLY FILTER': 'Apply Filter' ModelAdmin: DELETE: Delete - DELETEDRECORDS: 'Deleted %s records.' + DELETEDRECORDS: 'Deleted {count} records.' IMPORT: 'Import from CSV' - IMPORTEDRECORDS: 'Imported %s records.' + IMPORTEDRECORDS: 'Imported {count} records.' NOCSVFILE: 'Please browse for a CSV file to import' NOIMPORT: 'Nothing to import' RESET: Reset - UPDATEDRECORDS: 'Updated %s records.' + UPDATEDRECORDS: 'Updated {count} records.' MoneyField: FIELDLABELAMOUNT: Amount FIELDLABELCURRENCY: Currency NullableField: IsNullLabel: 'Is Null' NumericField: - VALIDATION: '''%s'' is not a number, only numbers can be accepted for this field' + VALIDATION: '''{value}'' is not a number, only numbers can be accepted for this field' Permission: AdminGroup: Administrator CMS_ACCESS_CATEGORY: 'CMS Access' FULLADMINRIGHTS: 'Full administrative rights' FULLADMINRIGHTS_HELP: 'Implies and overrules all other assigned permissions.' PermissionCheckboxSetField: - AssignedTo: 'assigned to "%s"' - FromGroup: 'inherited from group "%s"' - FromRole: 'inherited from role "%s"' + AssignedTo: 'assigned to "{title}"' + FromGroup: 'inherited from group "{title}"' + FromRole: 'inherited from role "{title}"' FromRoleOnGroup: 'inherited from role "%s" on group "%s"' Permissions: PERMISSIONS_CATEGORY: 'Roles and access permissions' @@ -391,10 +393,10 @@ en: LOGGEDOUT: 'You have been logged out. If you would like to log in again, enter your credentials below.' LOGIN: 'Log in' NOTEPAGESECURED: 'That page is secured. Enter your credentials below and we will send you right along.' - NOTERESETLINKINVALID: 'The password reset link is invalid or expired.You can request a new one here or change your password after you logged in.' + NOTERESETLINKINVALID: 'The password reset link is invalid or expired.You can request a new one here or change your password after you logged in.' NOTERESETPASSWORD: 'Enter your e-mail address and we will send you a link with which you can reset your password' - PASSWORDSENTHEADER: 'Password reset link sent to ''%s''' - PASSWORDSENTTEXT: 'Thank you! A reset link has been sent to ''%s'', provided an account exists for this email address.' + PASSWORDSENTHEADER: 'Password reset link sent to ''{email}''' + PASSWORDSENTTEXT: 'Thank you! A reset link has been sent to ''{email}'', provided an account exists for this email address.' SecurityAdmin: ACCESS_HELP: 'Allow viewing, adding and editing users, as well as assigning permissions and roles to them.' APPLY_ROLES: 'Apply roles to groups' @@ -440,7 +442,7 @@ en: TextareaField_Readonly.ss: NONE: none TimeField: - VALIDATEFORMAT: 'Please enter a valid time format (%s)' + VALIDATEFORMAT: 'Please enter a valid time format ({format})' ToggleCompositeField.ss: HIDE: Hide SHOW: Show @@ -459,7 +461,7 @@ en: FIELDNOTSET: 'File information not found' FROMCOMPUTER: 'From your computer' FROMFILES: 'From files' - MAXNUMBEROFFILES: 'Max number of %s file(s) exceeded.' + MAXNUMBEROFFILES: 'Max number of {count} file(s) exceeded.' REMOVEERROR: 'Error removing file' REMOVEINFO: 'Remove this file from here, but do not delete it from the file store' STARTALL: 'Start all' diff --git a/model/Hierarchy.php b/model/Hierarchy.php index 48ad13889..dbec83d1f 100644 --- a/model/Hierarchy.php +++ b/model/Hierarchy.php @@ -44,13 +44,11 @@ class Hierarchy extends DataExtension { if ($node->ParentID==$this->owner->ID) { // Hierarchy is looping. $validationResult->error( - sprintf( - _t( - 'Hierarchy.InfiniteLoopNotAllowed', - 'Infinite loop found within the "%s" hierarchy. Please change the parent to resolve this', - 'First argument is the class that makes up the hierarchy.' - ), - $this->owner->class + _t( + 'Hierarchy.InfiniteLoopNotAllowed', + 'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this', + 'First argument is the class that makes up the hierarchy.', + array('type' => $this->owner->class) ), 'INFINITE_LOOP' ); diff --git a/model/fieldtypes/Date.php b/model/fieldtypes/Date.php index 7de0cca95..269c93df8 100644 --- a/model/fieldtypes/Date.php +++ b/model/fieldtypes/Date.php @@ -191,24 +191,18 @@ class Date extends DBField { function Ago() { if($this->value) { if(strtotime($this->value) == time() || time() > strtotime($this->value)) { - return sprintf( - _t( - 'Date.TIMEDIFFAGO', - "%s ago", - - 'Natural language time difference, e.g. 2 hours ago' - ), - $this->TimeDiff() + return _t( + 'Date.TIMEDIFFAGO', + "{difference} ago", + 'Natural language time difference, e.g. 2 hours ago', + array('difference' => $this->TimeDiff()) ); } else { - return sprintf( - _t( - 'Date.TIMEDIFFIN', - "in %s", - - 'Natural language time difference, e.g. in 2 hours' - ), - $this->TimeDiff() + return _t( + 'Date.TIMEDIFFIN', + "in {difference}", + 'Natural language time difference, e.g. in 2 hours', + array('difference' => $this->TimeDiff()) ); } } diff --git a/security/ChangePasswordForm.php b/security/ChangePasswordForm.php index cb218e6f0..53b21b394 100644 --- a/security/ChangePasswordForm.php +++ b/security/ChangePasswordForm.php @@ -117,7 +117,11 @@ class ChangePasswordForm extends Form { } else { $this->clearMessage(); $this->sessionMessage( - sprintf(_t('Member.INVALIDNEWPASSWORD', "We couldn't accept that password: %s"), nl2br("\n".$isValid->starredList())), + _t( + 'Member.INVALIDNEWPASSWORD', + "We couldn't accept that password: {password}", + array('password' => nl2br("\n".$isValid->starredList())) + ), "bad" ); Director::redirectBack(); diff --git a/security/Member.php b/security/Member.php index a7774f981..0558c28d6 100644 --- a/security/Member.php +++ b/security/Member.php @@ -626,16 +626,15 @@ class Member extends DataObject implements TemplateGlobalProvider { ) ); if($existingRecord) { - throw new ValidationException(new ValidationResult(false, sprintf( - _t( - 'Member.ValidationIdentifierFailed', - 'Can\'t overwrite existing member #%d with identical identifier (%s = %s))', - - 'The values in brackets show a fieldname mapped to a value, usually denoting an existing email address' - ), - $existingRecord->ID, - $identifierField, - $this->$identifierField + throw new ValidationException(new ValidationResult(false, _t( + 'Member.ValidationIdentifierFailed', + 'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))', + 'The values in brackets show a fieldname mapped to a value, usually denoting an existing email address', + array( + 'id' => $existingRecord->ID, + 'name' => $identifierField, + 'value' => $this->$identifierField + ) ))); } } @@ -1624,12 +1623,10 @@ class Member_Validator extends RequiredFields { $uniqueField = $this->form->dataFieldByName($identifierField); $this->validationError( $uniqueField->id(), - sprintf( - _t( - 'Member.VALIDATIONMEMBEREXISTS', - 'A member already exists with the same %s' - ), - strtolower($identifierField) + _t( + 'Member.VALIDATIONMEMBEREXISTS', + 'A member already exists with the same %s', + array('identifier' => strtolower($identifierField)) ), 'required' ); diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index 0299e181f..538824453 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -105,7 +105,11 @@ JS protected function getMessageFromSession() { parent::getMessageFromSession(); if(($member = Member::currentUser()) && !Session::get('MemberLoginForm.force_message')) { - $this->message = sprintf(_t('Member.LOGGEDINAS', "You're logged in as %s."), $member->{$this->loggedInAsField}); + $this->message = _t( + 'Member.LOGGEDINAS', + "You're logged in as {name}.", + array('name' => $member->{$this->loggedInAsField}) + ); } Session::set('MemberLoginForm.force_message', false); } @@ -198,7 +202,7 @@ JS } Session::set('Security.Message.message', - sprintf(_t('Member.WELCOMEBACK', "Welcome Back, %s"), $firstname) + _t('Member.WELCOMEBACK', "Welcome Back, {firstname}", array('firstname' => $firstname)) ); Session::set("Security.Message.type", "good"); } diff --git a/security/PermissionCheckboxSetField.php b/security/PermissionCheckboxSetField.php index 4f37bb854..03f639242 100644 --- a/security/PermissionCheckboxSetField.php +++ b/security/PermissionCheckboxSetField.php @@ -96,9 +96,9 @@ class PermissionCheckboxSetField extends FormField { $relationMethod = $this->name; foreach($record->$relationMethod() as $permission) { if(!isset($uninheritedCodes[$permission->Code])) $uninheritedCodes[$permission->Code] = array(); - $uninheritedCodes[$permission->Code][] = sprintf( - _t('PermissionCheckboxSetField.AssignedTo', 'assigned to "%s"'), - $record->Title + $uninheritedCodes[$permission->Code][] = _t( + 'PermissionCheckboxSetField.AssignedTo', 'assigned to "{title}"', + array('title' => $record->Title) ); } @@ -110,14 +110,11 @@ class PermissionCheckboxSetField extends FormField { foreach($record->Roles() as $role) { foreach($role->Codes() as $code) { if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array(); - $inheritedCodes[$code->Code][] = sprintf( - _t( - 'PermissionCheckboxSetField.FromRole', - 'inherited from role "%s"', - - 'A permission inherited from a certain permission role' - ), - $role->Title + $inheritedCodes[$code->Code][] = _t( + 'PermissionCheckboxSetField.FromRole', + 'inherited from role "{title}"', + 'A permission inherited from a certain permission role', + array('title' => $role->Title) ); } } @@ -132,15 +129,11 @@ class PermissionCheckboxSetField extends FormField { if ($role->Codes()) { foreach($role->Codes() as $code) { if (!isset($inheritedCodes[$code->Code])) $inheritedCodes[$code->Code] = array(); - $inheritedCodes[$code->Code][] = sprintf( - _t( - 'PermissionCheckboxSetField.FromRoleOnGroup', - 'inherited from role "%s" on group "%s"', - - 'A permission inherited from a role on a certain group' - ), - $role->Title, - $parent->Title + $inheritedCodes[$code->Code][] = _t( + 'PermissionCheckboxSetField.FromRoleOnGroup', + 'inherited from role "%s" on group "%s"', + 'A permission inherited from a role on a certain group', + array('roletitle' => $role->Title, 'grouptitle' => $parent->Title) ); } } @@ -149,14 +142,11 @@ class PermissionCheckboxSetField extends FormField { foreach($parent->Permissions() as $permission) { if (!isset($inheritedCodes[$permission->Code])) $inheritedCodes[$permission->Code] = array(); $inheritedCodes[$permission->Code][] = - sprintf( - _t( - 'PermissionCheckboxSetField.FromGroup', - 'inherited from group "%s"', - - 'A permission inherited from a certain group' - ), - $parent->Title + _t( + 'PermissionCheckboxSetField.FromGroup', + 'inherited from group "{title}"', + 'A permission inherited from a certain group', + array('title' => $parent->Title) ); } } diff --git a/security/Security.php b/security/Security.php index 29765ec96..1d07d7318 100644 --- a/security/Security.php +++ b/security/Security.php @@ -494,10 +494,10 @@ class Security extends Controller { $email = Convert::raw2xml(rawurldecode($request->param('ID')) . '.' . $request->getExtension()); $customisedController = $controller->customise(array( - 'Title' => sprintf(_t('Security.PASSWORDSENTHEADER', "Password reset link sent to '%s'"), $email), + 'Title' => _t('Security.PASSWORDSENTHEADER', "Password reset link sent to '{email}'", array('email' => $email)), 'Content' => "" . - sprintf(_t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '%s', provided an account exists for this email address."), $email) . + _t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '{email}', provided an account exists for this email address.", array('email' => $email)) . "", 'Email' => $email )); @@ -571,12 +571,10 @@ class Security extends Controller { if(isset($_REQUEST['h'])) { $customisedController = $controller->customise( array('Content' => - sprintf( - _t('Security.NOTERESETLINKINVALID', - 'The password reset link is invalid or expired.You can request a new one here or change your password after you logged in.' - ), - $this->Link('lostpassword'), - $this->link('login') + _t( + 'Security.NOTERESETLINKINVALID', + 'The password reset link is invalid or expired.You can request a new one here or change your password after you logged in.', + array('link1' => $this->Link('lostpassword'), 'link2' => $this->link('login')) ) ) ); diff --git a/tests/filesystem/UploadTest.php b/tests/filesystem/UploadTest.php index 8816d00b5..d3f112384 100644 --- a/tests/filesystem/UploadTest.php +++ b/tests/filesystem/UploadTest.php @@ -333,28 +333,22 @@ class UploadTest_Validator extends Upload_Validator implements TestOnly { if(!$this->isValidSize()) { $ext = (isset($pathInfo['extension'])) ? $pathInfo['extension'] : ''; $arg = File::format_size($this->getAllowedMaxFileSize($ext)); - $this->errors[] = sprintf( - _t( - 'File.TOOLARGE', - 'Filesize is too large, maximum %s allowed.', - - 'Argument 1: Filesize (e.g. 1MB)' - ), - $arg + $this->errors[] = _t( + 'File.TOOLARGE', + 'Filesize is too large, maximum {size} allowed.', + 'Argument 1: Filesize (e.g. 1MB)', + array('size' => $arg) ); return false; } // extension validation if(!$this->isValidExtension()) { - $this->errors[] = sprintf( - _t( - 'File.INVALIDEXTENSION', - 'Extension is not allowed (valid: %s)', - - 'Argument 1: Comma-separated list of valid extensions' - ), - implode(',', $this->allowedExtensions) + $this->errors[] = _t( + 'File.INVALIDEXTENSION', + 'Extension is not allowed (valid: {extensions})', + 'Argument 1: Comma-separated list of valid extensions', + array('extensions' => implode(',', $this->allowedExtensions)) ); return false; } diff --git a/tests/i18n/i18nTest.php b/tests/i18n/i18nTest.php index 7c5a40c84..ca0ac6392 100644 --- a/tests/i18n/i18nTest.php +++ b/tests/i18n/i18nTest.php @@ -251,7 +251,8 @@ class i18nTest extends SapphireTest { i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.NEWMETHODSIG' => 'TRANS New _t method signature test', - 'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}' + 'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}', + 'i18nTestModule.INJECTIONSLEGACY' => 'TRANS Hello %s %s. But it is late, %s', ), 'en_US'); $entity = "i18nTestModule.INJECTIONS"; @@ -287,6 +288,24 @@ class i18nTest extends SapphireTest { $translated, "Testing a translation with just entity and injection array" ); + $translated = i18n::_t( + 'i18nTestModule.INJECTIONSLEGACY', // has %s placeholders + array("name"=>"Cat", "greeting2"=>"meow", "goodbye"=>"meow") + ); + $this->assertContains( + "TRANS Hello Cat meow. But it is late, meow", + $translated, "Testing sprintf placeholders with named injections" + ); + + $translated = i18n::_t( + 'i18nTestModule.INJECTIONS', // has {name} placeholders + array("Cat", "meow", "meow") + ); + $this->assertContains( + "TRANS Hello Cat meow. But it is late, meow", + $translated, "Testing named injection placeholders with unnamed injections" + ); + i18n::set_locale($oldLocale); }
Import one or more groups in CSV format (comma-separated values). Show advanced usage
Import users in CSV format (comma-separated values). Show advanced usage
The password reset link is invalid or expired.
You can request a new one here or change your password after you logged in.
" . - sprintf(_t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '%s', provided an account exists for this email address."), $email) . + _t('Security.PASSWORDSENTTEXT', "Thank you! A reset link has been sent to '{email}', provided an account exists for this email address.", array('email' => $email)) . "