A convenient way to translate dynamic text in applications is to use placeholders, like so:
$message = 'Hello I am {firstname}';
$translation = Yii::t($message, array('{firstname}', 'John'));
But I want it to be some more dynamicly and just pass a complete person object (just for example) and the user of the application is free to use whatever he feels comfortable of the person object.
So, given the following object:
$customer = new tstCustomer();
$customer->firstname = 'testfirst';
$customer->lastname = 'testlast';
$customer->email = 'testemail';
class tstCustomer
{
public $firstname;
public $lastname;
public $email;
public function __construct(){}
}
I want my user to be able to create a translatable string like this:
$message = '{greetings} I am {customer->firstname} {customer->lastname} and my email address is {customer->email} {customer->notexisting}.';
I only have to pass the $customer
object now, instead of all kinds of separate properties (and their possible placeholders). This solution also gives the possibility of object traversing (but is it a problem?).
echo translate($message, array('{greetings}' => 'Hello World', '{customer}' => $customer));
// Output:
// Hello World I am testfirst testlast and my email address is testemail .
I’ve written the following function to accomplish above:
function translation($message, $placeholders = array())
{
// catch all notices/warnings/errors due to non existent attributes
$backupHandler = set_error_handler('strtrErrorHandler');
foreach($placeholders as $placeholder => $value)
{
if(gettype($value) === 'string')
{
$message = strtr($message, array($placeholder => $value));
continue;
}
if(gettype($value) !== 'object')
continue;
$trimmed_placeholder = trim($placeholder, '{}');
$regex = '/\{'.$trimmed_placeholder.'[\-\>[\w]*]*\}/';
$matches = array();
preg_match_all($regex, $message, $matches);
// $matches should look like:
// array(1) { [0]=> array(3) { [0]=> string(21) "{customer->firstname}" [1]=> string(20) "{customer->lastname}" [2]=> string(17) "{customer->email}" } }
if(!isset($matches[0]))
continue;
foreach($matches[0] as $match)
{
$stringpath = trim($match, '{}');
$traversal = substr($stringpath, strlen($trimmed_placeholder)+2);
try
{
$message = strtr($message, array($match => $value->{$traversal}));
}
catch(Exception $e)
{
// replace the placeholder with empty string
$message = strtr($message, array($match => ''));
}
}
} // foreach $placeholders
set_error_handler($backupHandler);
return $message;
}
// catch all and convert to a catchable Exception
function strtrErrorHandler($severity, $message, $filename, $lineno) {
Yii::log($message, 'error', 'backend.test.index');
throw new Exception('');
}
Now, my questions:
- How safe will this be? (I hope object traversing is limited like this)
- Can this be accomplished better in terms of safety?
- Can this be accomplished better in terms of performance?
- Any other thoughts?
My original post on SO which is getting very low number of views: https://stackoverflow.com/questions/55635119/convert-placeholders-in-a-string-to-the-values-of-objects-property-how-safe-is