I appreciate this one is only a warning, and that there may be security reasons for insisting on the rule
This rule is at odds with object-oriented encapsulation:
WARNING | Only string literals should be passed to t() where possible
Example: in my educational OOE = Object Oriented Examples tutorial module (sandbox https://drupal.org/sandbox/webel/2120905) I have an interface IBlock and a class DefaultBlock that encapsulates a Drupal block. It has a private variable for the block $info, and this is used (with a lot of other stuff) to help build a Drupal block array that is then passes off to Drupal core:
class DefaultBlock implements IBlock {
/**
* From hook_block_info():
* 'The human-readable administrative name of the block.'
*
* 'This is used to identify the block on administration screens,
* and is not displayed to non-administrative users.'
*
* @var string
*/
private $info;
public function __construct($delta, $info) {
..
$this->info = $info;
}
/**
* Builds and returns a Drupal array compatible with hook_block_info().
*
* @return array
* A Drupal array compatible with hook_block_info().
*/
public function get() {
$out = array();
$out['delta'] = $this->delta;
$out['info'] = t($this->info);
// Coder: WARNING | Only string literals should be passed to t() ..
if (!empty($this->cache)) {
$out['cache'] = $this->cache;
}
if (!empty($this->properties)) {
$out['properties'] = $this->properties;
}
if (!empty($this->weight)) {
$out['weight'] = $this->weight;
}
if (!empty($this->status)) {
$out['status'] = $this->status;
}
if (!empty($this->region)) {
$out['region'] = $this->region->getName();
}
if (!empty($this->visibility)) {
$out['visibility'] = $this->visibility;
}
$pages = $this->getPages();
if (!empty($pages)) {
$out['pages'] = $pages;
}
return $out;
}
Coder triggers on this line:
$out['info'] = t($this->info);
Use of string literals is in such cases at odds with object-oriented encapsulation.
That said, I don't currently have any ideas how best to handle the above safely.
Comments
Comment #1
webel CreditAttribution: webel commentedComment #2
yukare CreditAttribution: yukare commentedThis is because http://localize.drupal.org, as the tools used for translation need that the string is inside the function to parse it. If a string is a constant or a variable, it will be translated only if the same string is present anywhere on drupal.
Comment #3
webel CreditAttribution: webel commented@yukare wrote:
Thanks for explanation.
Comment #4
klausiPassing variables directly to t() is wrong 98% of the time, that's why we have the warning. New contributors make this mistake a lot. Instead, you should use t() where you define the string literal. If it is purely user provided content, then it is recommended to look into the i18n module and its helper functions.
So I don't think we should remove this sniff, although it can throw false positives from time to time.
Comment #5
webel CreditAttribution: webel commentedUnless you are using object-oriented programming with encapsulation, as per my example, and then it's right 98% of the time.
Which prevents a Class constructor or method of a Class applying translation on injected strings as policy; it puts the onus on the developer to always remember to do it before or as one passes arguments to a constructor or method of a class, instead of letting the class do it for you (every time, automatically, as a policy).
See for example: RenderT a renderer Class that always translates its markup.
And what about the case where the string to be translated is constructed dynamically ?
I have left his as minor, but please don't just close it until you can provide an argument that actually addresses my example convincingly.
Comment #6
yukare CreditAttribution: yukare commentedWhen you pass the string, you pass inside t():
Or when you create it:
Comment #7
webel CreditAttribution: webel commented@yukare
Good, yes, examples (or counter examples if you disagree with mine) !
We are on the same page concerning this one, but you can see that your examples require the developer to always apply the policy (remember to execute t() on a literal at the point of string creation), which is different from it being applied as automatic policy within a rendering Class.
The WARNING says "where possible":
The fact is that if the string is encapsulated within a Class that applies translation as an internal policy (or according to an option flag as many of my renderer classes do) it's not "possible". I will just have to ignore this warning.
Comment #8
klausiAnother problem is that automatic translation extraction tools cannot populate localize.drupal.org with expressions such as t($variable). They can only find t('Some string literal value that should be translated'). And yes, developers must use t() or D8's translation service where they define their user facing strings.
So whenever you have a class that applies translation as a policy you are doing it wrong in 98% of cases, because the translatable strings cannot be discovered.
Comment #9
webel CreditAttribution: webel commented@klausi Thanks for feedback, agree closed.
You wrote:
"wrong" for Drupal's current translation discovery strategy, not "wrong" for OO encapsulation and the DRY (Don't Repeat Yourself) principle.
Am exploring ways to leverage placeholders:
- Dynamic strings with placeholders
- Localization API
- t()
Comment #10
webel CreditAttribution: webel commentedI have only reopened this (now as a support request) to record the results of my investigation. Please close again on reading.
How to encapsulate and share Drupal-translatable strings and markup
I have explored various ways of handling valid Drupal-translatable strings in a way that can be shared across an application, so that the Don't Repeat Yourself (DRY) principle is respected.
For the sake of this illustration I have a class DemoCommon that should be usable by external client classes.
It will offer a marked up string that explains that one can hover over links with a mouse to obtain more info, which string should be translatable.
Firstly, some things that don't work and why:
This was the initial point of this issue post. The Drupal translation registry strategy
does not see the encapsulated shared string in t(self::MARKUP_TIP_HOVER_LINKS).
This also does not work, applying t() in the initialiser:
Because PHP const can't be result of calculation this fails, however note that PHP5.6 will apparently offer more initialisation options.
Just for the record, trying to make a static class variable act as a const in PHP does not work:
Unlike Java, PHP does not support final on static class variables.
One also can't initialise a static class variable with t() already:
I now show 3 strategies that work, with various pros and cons.
One can post-initialize a private static class variable like this (adapted from http://stackoverflow.com/questions/693691/how-to-initialize-static-varia...):
This has the disadvantage that one can only safely use the private shared translated string internally (perhaps value adding before returning via another public method). This is because one can't in PHP make the static class variable final, and if it is made public it is not safe against external manipulation.
Another simpler approach is to use a public method that simply translates the string and returns it:
This can then easily be used externally as DemoCommon::tMarkupTipHoverLinks().
However, the following with lazy init gives better performance if it is used frequently:
This will be populated on first access as DemoCommon::tMarkupTipHoverLinksLazy().
As far as I can tell, all 3 solutions above are compatible with the Drupal localisation registry strategy (searching for t('string literal') in code) while also respecting the DRY principle (avoiding error prone repetition of the string and/or the translation of the string).
Any feedback welcome.
Comment #11
klausiI personally think that this is over-engineering just to conform to some interpretation of the DRY principle. Adding this kind of complexity just to not repeat a translatable sentence is not worth it in my opinion. One could argue this violates the KISS principle. But since we very seldom repeat translatable sentences anyway this will not be a problem for most developers.
All approaches you presented look fine to me if you really want to avoid repeating a translatable sentence. Thanks for the thorough research!
Comment #12
webel CreditAttribution: webel commentedJust feedback. I am changing this to Closed (works as designed) in the sense that the Drupal translation registry discovery process is by design not compatible with OO encapsulation of reusable text. I can dream up a number of other strategies that are, including sluggish real-time registration of text for translation, but I accept that I am going to have to live with it using one of the techniques I demonstrate at #10.