Issues

ZF-1116: Proposed new convenience function: stranslate()

Description

Zend_Translate could easily support something like:

NEW way:


// in a view:
<?= $this->escape($translator->stranslate($string, $locale, $param1, $param2, $param3)); ?>
<?= $this->escape($translator->stranslate("Mr. %1\$s has an appointment on %2\$s in %3\$s.", $locale, $param1, $param2, $param3)); ?>

Different languages will need to reorder the sequence of the parameters due to differences in grammar. The order of the parameters defines the numerical position assignments for these parameters (i.e. $param1 is '%1\$s', $param2 is '%2\$s', etc.).

OLD way:


// in a view:
<?= $this->escape(sprintf($translator->translate($string, $locale), $param1, $param2, $param3)); ?>

For implementation tricks, see: * http://php.net/func_get_args * http://php.net/vsprintf

Comments

I know we already discussed a closely related idea, but this variant might avoid the concerns voiced previously.

This is already integrated...

See the latest documentation.

The following ways are supported:


$trans->translate('mystring', array('%1\$d' => '2007', '%1\$s' => 'date', '%2\$s' => 'other'), $locale);
$trans->translate('mystring', $locale);
$trans->translate('mystring');

The useage of sprintf or printf is not needed and this is already documented.

I see you just committed the change described above.

However, I believe the method prototype for translate() and _() as just committed in changeset 4189 is confusing, not intuitive, and not tool/IDE friendly. For example, in order to specify a locale, I would have to insert null value for the second operand of translate().

http://framework.zend.com/fisheye/browse/… http://framework.zend.com/fisheye/browse/…

No... Standard behaviour is to set the locale within Zend_Translate with "setLocale()"...

But ok... I just wanted to simplify things for our users. I will erase this feature as it seems not to be accepted by devteam.

Sorry for being too fast with implementations.

I continue to believe there is a chance the devteam/community might accept the proposed {{stranslate()}} method, as presented in the description of this issue.

Thomas, you are forever adding, and subsequently removing, features. ;-)

Why not make {{setLocale()}} the only way of declaring the locale? It seems to me that most people are not going to be jumping back and forth and printing German, Japanese, and English side by side on the same page.

Then, {{translate()}} can work as I suggested in [ZF-1096], minus {{$locale}}:


print $translator->translate('my string');
// or
print $translator->translate('my string', array($paramter1, $parameter2, $etc));

Have parameters be defined in order, as with {{sprintf}}, and every parameter must be declared (even if just a {{null}} value).

Easy.

I like the idea of having a setter for the object's locale property.

Why list the parameters inside an array, instead of: ```

For example, the implementation of the function will know that $parameter1 is the first parameter, and can then substitute it for '%1\s'.

@Gavin:

We can not only accept a list of parameters like you proposed because within an translation string there can not only be a %x\$s... Translation Strings are often declared like this:


('%1\$d lines of %1\$s', 23, 'documentation');

What I mean is that not only strings should be supported but also all other tokens like digit or char. And this can only be done if we accept key-value pairs and default to string if no key (integer) is given.

@Matthew: Yes, you are right... but it's not often that a new included feature is killed two hours after it's commit. ;-)

@All: The complete I18N API makes it possible to have the locale as optional LAST parameter. And within all I18N classes the locale can be set with setLocale() for all future uses.

My implementation does wether brake the API nor make problems with users. The documentation describes the usage of setLocale/setLanguage and does not mention the optional locale parameter which seems to be a problem in your two opinions.

Even Zend_Date makes use of the optional locale parameter as third or even fourth parameter. Why does this not make problems ? Because setLocale is proposed to be used for standard workflow.

The implementation as already done and reverted by me is no problem in my opinion and would completly fit within the API.

{quote} The complete I18N API makes it possible to have the locale as optional LAST parameter. And within all I18N classes the locale can be set with setLocale() for all future uses.

My implementation does wether [neither?] brake the API nor make problems with users. The documentation describes the usage of setLocale/setLanguage and does not mention the optional locale parameter which seems to be a problem in your two opinions.

Even Zend_Date makes use of the optional locale parameter as third or even fourth parameter. Why does this not make problems ? Because setLocale is proposed to be used for standard workflow.

The implementation as already done and reverted by me is no problem in my opinion and would completly fit within the API. {quote}

I don't quite follow your comment. I don't think the optional {{$locale}} parameter is necessarily worthwhile to keep in this particular case. Can you explain why it is? The second of three arguments being optional based on context has precedent in PHP itself, but I wouldn't exactly call that a thumbs up for that approach. :-) But it sounds like you do that already with existing components like {{Zend_Date}}, so I suppose it wouldn't be out of the ordinary.

Actually the API works like this


$date->set($value, $type = null, $locale = null);

The documentation describes the usage the following way


$date->setLocale('de');
$date->set('März', Zend_Date::MONTH);

// optionally overriding the general settings just for this operation
$date->set('March', Zend_Date::MONTH, 'en');

This is how the complete I18N API works... all locale settings can be optionally set to override the general settings. Actually the documentation describes translation-handling like this:


$trans->setLocale('de');
$trans->translate('My string');

// optionally overriding the general settings just for this operation
$trans->translate('My string', 'fr');

So if we cancel the optional locale parameter for this particular case it does no longer fit within the API. We should then strip all optional locale parameters for the complete API, but I think they are quite usefull.

Having the locale as second parameter is not acceptable. Stripping the locale from Zend_Translate would mean to strip it within the complete API.

Actually all optional parameters can be forgotten and the function works the same way


$date->set('March', Zend_Date::MONTH, 'en');
$date->set('March', Zend_Date::MONTH);
$date->set('March', 'en');

The third case is what gavin meant with non tool friendly because it's possible to forget the middle parameter and the function works like it should do... But a tool will display param1, param2 OPTIONAL, param3 OPTIONAL And a user will then do


$trans->translate('My string', null, 'de');

if he is in need of this optional parameter. Anyway, I see no problems with this approach because the language is always OPTIONAL as every example and the documentation describes only the use of setLocale() for setting the language. Therefor I do not accept the "non-tool-friendly".

Btw: {quote}The second of three arguments being optional based on context has precedent in PHP itself, but I wouldn't exactly call that a thumbs up for that approach.{quote} But there are 2 optional parameters... not only the array but also the locale itself is optional and should only be used in special cases. It is more common that the array will be used than the third optional parameter, the locale. There are much functions in PHP which do not only accept one optional parameter but also two or more. This is no precedence.

The ZF strives for extreme simplicity.

Is "%1\$d lines of %1\$s" the simplest way of expressing two place holders?

Why can't we infer types from the arguments supplied by the user?

This has nothing to do with simplest way... this is how translators work.

The problem is that you can not say if the $s is the first or the second parameter. In some languages you have to switch the location and then you have the problem.

And if the translator has more types integrated than $s we could not work with it... This would mean that this is not possible with the framework. There are 7 Integer types avaiable for sprintf which actualy many work with, and which we would not support in the case that we only accept strings '$s'. As all of these types are integer we would have no chance of detecting the type automatically by supplied arguments.

When we make it possible to work with array-keys we have another advantage:


// working only with strings the standard way
$trans->translate('%1\$s : %2\$s', array('Dates', '2006'));

// working with more than strings
$trans->translate('$s : $d', array('$s' => 'Dates', '$d' => 2006));

// working with self defined tags
$trans->translate('My name is Thomas, I am 8.5 years old', array('Thomas' => $myname, '8.5' => $myage));

The third option as described here has the advantage that the original string is much more readable as only having %1\$s included within the code. And this is what I wanted to work within the ZF.

Ok, so you are saying that translators must choose the types to use with each conversion specification for each phrase in each language?

For example, there is a one character difference in the following two examples (the conversion specification "$d" is changed to "$e").


// English:
<?= $this->escape($translator->stranslate("Hello! Mr. %1\$s has %2\$d oranges at %3\$s.", $locale, $param1, $param2, $param3)); ?>

// Vietnamese:
<?= $this->escape($translator->stranslate("Chao' Anh! %2\$e tra'i cam o*? %3\$s cu?a Anh.", $locale, $param1, $param2, $param3)); ?>

Would you provide some example use cases showing why a translator must choose different conversion specifications for the same phrase expressed in a different language? My example above is contrived and and does not need dynamic selection of different conversion specifiers, since the quantity should be expressed using Zend_Locale_Format::toNumber() and not using the "%e" or "%d" conversion specifier for sprintf() as shown below:


Zend_Locale_Format::toNumber($param2, array('locale' => $locale));

// English:
<?= $this->escape($translator->stranslate("Hello! Mr. %1 has %2 oranges at %3.", $param1, $param2, $param3)); ?>

// Vietnamese:
<?= $this->escape($translator->stranslate("Chao' Anh! %2 tra'i cam o*? %3 cu?a Anh %1.", $param1, $param2, $param3)); ?>

Regarding a "locale" parameter, the parameter can specify the source locale of input data, or the locale of output data. I think code is more readable when the "output" locale is specified by setting a property on the object (e.g. "{{$translator->setLocale($locale);}}", before calling translate().

Perhaps if you posted a patch that supports the second example above, and the "self defined tags", then more community members could experiment with this new feature and give more feedback? If the function only returns a string (no echo/print), then nobody can object to the function "printing data to the output".

{quote}Ok, so you are saying that translators must choose the types to use with each conversion specification for each phrase in each language?{quote}

No... I did not say that. The search-value has only be given if it's not %x\$s. This is the standard identifier for strings. Using the standard (strings) no search-value has to be given.

Your example is quite irritating as $e is no avaiable type identifier.

{quote}Would you provide some example use cases showing why a translator must choose different conversion specifications for the same phrase expressed in a different language?{quote}


$translator->translate("Binary $b is added to $d", array(100, 200));

In one language the translation could be "Zu $d wird der Binärwert $b addiert".

So now there is no way to produce a correct translation.

The problem is that you can not define only to accept %x identifiers as this would mean to exclude existing code not to be used with Zend_Translate. Having the identifier if needed solves the problem


$translator->translate("Binary $b is added to $d", array('$b' => 100, '$d' => 200));

{quote}Regarding a "locale" parameter, the parameter can specify the source locale of input data, or the locale of output data. I think code is more readable when the "output" locale is specified by setting a property on the object (e.g. "$translator->setLocale($locale);", before calling translate().{quote}

As already written, the language can already be set by using the setLocale() function. This is already described within the documentation. When Zend_Translate brakes the API by deleting the optional locale parameter we would have to change the complete I18N API for all other classes. This would mean to erase the optional locale parameter for all classes. It is no option to have one class which is not API conform. This is not acceptable.

{quote}Perhaps if you posted a patch that supports the second example above, and the "self defined tags", then more community members could experiment with this new feature and give more feedback? If the function only returns a string (no echo/print), then nobody can object to the function "printing data to the output".{quote} There was already a implementation which had this included. SVN 4189. It was not a complete implementation as it worked only with set key but it displayed what I wanted to integrate. It already, did not print, but only return a string.

But this was not acceptable as you wrote in you second reply so I had to revert the already made change.

The "type specifiers" are listed here, include "e": http://www.php.net/sprintf

Ok, now I see an example showing the use case you are concerned about. Repeating the type information (both in the array and in the format string) is not DRY. Regardless, I think this is an edge case, and will not occur frequently. The ZF normally provides simple ways to solve the most common use cases. If needed, the ZF sometimes supports additional edge cases using additional methods. Perhaps we can all work together to provide an extremely simple method to solve the common, simple use cases, and a more complex method for edge cases?

There is a difference between setLocale() setting a static class variable and an instance property. I think the example I gave (as written above) works fine, without including an optional $locale parameter. Why would "the complete I18N API for all other classes" need to change, if the following is used?


$translator->setLocale($someLocale);
$translator->translate($string, $param1, $param2, $param3);

{quote}There was already a implementation which had this included. SVN 4189. It was not a complete implementation as it worked only with set key but it displayed what I wanted to integrate. It already, did not print, but only return a string.

But this was not acceptable as you wrote in you second reply so I had to revert the already made change.{quote}

I did not say this, and the patch in 4189 did not "supports the second example above" (the one using Zend_Locale_Format::toNumber() ).

{quote}The "type specifiers" are listed here, include "e":{quote} My documentation from 21.Nov.2006. It includes PHP 5.1.4 which is the minimum requirement for ZF and does not mention this identifier. But this has nothing to do with this issue.

{quote}Repeating the type information (both in the array and in the format string) is not DRY{quote} We are not repeating the type information... we are defining the tokens which have to be changed. This could also be strings like 'Thomas' as mentioned above.

This would be a real benefit for users not familiar with sprinft tokens. %1\$s would therefor only be the token to search for... not a way to change the given content to a binary representation or scientific formatting.

We would therefor not integrate the sprintf function itself because it changes the content which should be added. We would only change the expected tokens with the given values.

Of course the sprintf function could be used as actually with no loss of information or problems.

{quote}There is a difference between setLocale() setting a static class variable and an instance property.{quote} setLocale set's a instance property because you could have several translation objects with each set to it's own language. This is the same behaviour as Zend_Date. Why is this a problem ??

{quote}Perhaps we can all work together to provide an extremely simple method to solve the common, simple use cases, and a more complex method for edge cases?{quote} What is the problem with the actual proposed method which can be used simple or also complex if needed ???


// case 1: standard
$x->translate('My string');

// case 2: only string parameters
$x->translate('My string %1\$s %2\$s', array('value1', 'value2'));

// case 3: special tags which can not automatically be detected
$x->translate('My string $e $d', array('$e' => 'value1', '$d' => 'value2'));

// case 4: self defined tags
$x->translate('My string with %date% and %name%', array('%date%' => $mydate, '%name%' => $myname));

// case 5: standard with locale overriding actual settings for this string
$x->translate('My string', null, 'de');

// case 6: only string parameters with locale overriding actual settings for this string
$x->translate('My string %1\$s %2\$s', array('value1', 'value2'), 'de');

// case 7: special tags which can not automatically be detected with locale
$x->translate('My string $e $d', array('$e' => 'value1', '$d' => 'value2'), 'de');

// case 8: self defined tags with locale overriding actual settings
$x->translate('My string with %date% and %name%', array('%date%' => $mydate, '%name%' => $myname), 'de');

This would support all possible cases easy and also complex.

{quote}the patch in 4189 did not "supports the second example above"{quote} As I said before the implementation was not finished. And having 3 optional parameters for 3 tokens is also not acceptable. It does not fit within the API. This is the same as with the locale parameter.

As several people have mentioned the framework is inconsistent with a couple of it's classes. I am not willing to add inconsistency myself to the I18N core only because it's easier to implement.