This was suggested as part of the #361597: CRUD API for locale source and locale target strings issue and also by various people in the discussion at http://groups.drupal.org/node/154394. Basically, abstracting out the formatting code from t() allows coders who need to write single language modules for clients (for sites that will never use English) to use this same functionality in their strings, without going through the whole loop of the translation system.

If abstracting this out of t() is deemed a performance problem, we can still copy it in t() and have it as its own function as well. It is currently implemented as the same code in t(), st() and Drupal.t(). This patch touches t() and Drupal.t(). st() is not changed because even though st() does include theme.inc, it does not actually use any theming to render the placeholder, which is due to the possibly missing theme system I guess. Sounds like it is best left as-is for now.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

KarenS’s picture

I think this is an excellent idea. This is useful functionality that shouldn't be limited to or dependent on the translation system.

pwolanin’s picture

So I often use t() even on custom things since the substitution is helpful. Do we really want to encourage people to avoid t()?

What is the problem being solved? If no localization is happening is there any performance penalty for using t() simply for replacement?

Gábor Hojtsy’s picture

People discuss this at the linked thread at http://groups.drupal.org/node/154394. There are modules built for markets where English is not among the supported languages. They do not have resources to make up English original strings. If they use t(), their strings will end up in the database for translation assumed to be English. This is both counter-productive for performance and goes against logic. We can either support them to at least use secure practices with this function or let them make up their own custom stuff each time. This is not about discouraging use of t() and I don't think Drupal core or drupal.org contributed modules will use this function much if ever. This is about supporting some uses cases from http://groups.drupal.org/node/154394 without going the whole way of externalizing strings that many people (including you) did not like.

pwolanin’s picture

Ok, so the use case is:

1) Site is mono-lingual using localization features already (non-English).
2) Custom code needs to do secure token substitution on non-English UI strings.

That makes more sense, thanks.

wojtha’s picture

Cool, you wrote the patch first, Gábor :-) ... exactly what I need ... +1.

Is it worth for contrib to use the drupal_format_string in D6 or D7? ... since there is probably no chance to get it in D7 now, right?

plach’s picture

Status: Needs review » Needs work

If I understood webchick right, there is a remote possibility for this to be backported to D7. Anyway:

+++ b/misc/drupal.js
@@ -150,24 +182,7 @@ Drupal.t = function (str, args) {
-    }
+    Drupal.replaceString(str, args);
   }
   return str;

This should be:

str = Drupal.replaceString(str, args);

otherwise we get no string replacement. Tests do not cover this, that's why the bot is green.

Powered by Dreditor.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
6.36 KB

Right, patch updated. Thanks!

wojtha’s picture

Tests do not cover this, that's why the bot is green.

I think we need to cover this with test and I think we need extend the tests too ... IMO there should be unit test for the drupal_replace_string and functional for Drupal.replaceString to not break the string replacement in the future again. What do you think?

Gábor Hojtsy’s picture

There is no support for running tests on Javascript code at the moment in the Drupal QA system. We could (probably should) expand tests to the drupal_replace_string() function. We can probably just copy/reuse some tests from t().

sun’s picture

  1. drupal_replace_string() is a bit too lengthy of a name, IMHO. Why not simply drupal_string()?
  2. I think we need to solve this in a larger scope, see http://groups.drupal.org/node/160704
Gábor Hojtsy’s picture

1. Well, I just picked this up as it was before on the linked issue. I think drupal_string() can be good as well, it is still far away from looking like "in the family" of t(). Not sure if we need that to happen.
2. Yes, I think it would be great to get this one in and continue the refactoring on higher levels. I assume your discussion will need to take its paces and we can ensure this is in in the meantime :)

sun’s picture

Status: Needs review » Needs work

On a second thought, let's use a name that's consistent with other core formatting functions:

format_string()

With that name, I'd agree we can move on independently here.

Gábor Hojtsy’s picture

Well, I was thinking about this too, and think "format" is a poor name for these functions, but we can definitely use that and get renamed later in an API cleanup :)

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
6.33 KB

There you go format_string() and formatString().

sun’s picture

Status: Needs review » Reviewed & tested by the community
plach’s picture

Issue tags: +Needs committer feedback, +Needs backport to D7

Let's see if this can be backported to D7.

sun’s picture

Status: Reviewed & tested by the community » Needs work

Sorry to be a pain:

+++ b/includes/bootstrap.inc
@@ -1223,26 +1215,47 @@ function t($string, array $args = array(), array $options = array()) {
+ *     drupal_placeholder(), which shows up as <em>emphasized</em> text.
+ */
+function format_string($string, $args) {

Missing phpDoc suffix:

*
* @see t()
* @ingroup sanitization

18 days to next Drupal core point release.

Gábor Hojtsy’s picture

Ah, do you mean we remove the existing See format_string() and see Drupal.formatString() and convert them to @see as well at the end of the phpdoc as well? (Trying to avoid playing ping-pong for quite a while which can easily happen).

sun’s picture

Sorry if #17 was too sparse. Let me clarify:

+++ b/includes/bootstrap.inc
@@ -1223,26 +1215,47 @@ function t($string, array $args = array(), array $options = array()) {
+/**
+ * Replace placeholders with sanitized values in a string.
+ *
+ * @param $string
+ *   A string containing placeholders.
+ * @param $args
+ *   An associative array of replacements to make. Occurrences in $string of
+ *   any key in $args are replaced with the corresponding value, after
+ *   sanitization. The sanitization function depends on the first character of
+ *   the key:
+ *   - !variable: Inserted as is. Use this for text that has already been
+ *     sanitized.
+ *   - @variable: Escaped to HTML using check_plain(). Use this for anything
+ *     displayed on a page on the site.
+ *   - %variable: Escaped as a placeholder for user-submitted content using
+ *     drupal_placeholder(), which shows up as <em>emphasized</em> text.
+ */
+function format_string($string, $args) {

I don't see an "existing" @see here...?

As this is a sanitization function, api.d.o should output it in the corresponding group.

And since people might mistakenly use format_string() instead of t() without knowing about t(), format_string() should at least have a final @see pointer to t().

18 days to next Drupal core point release.

Gábor Hojtsy’s picture

I meant the See's which are not @see's should be converted, and moved down to the end of phpdoc no? I'm not clear what should be done with inline "see information" notes that we have on t() and Drupal.t() now for the replacement formats.

sun’s picture

Nope, the others are fine, as they are in the context/description of a @param. The @param itself does not duplicate the description of format_string() [which makes sense], so we need to point the reader to the docs that describe the @param within its description; not in a completely off-context location. A @directive cannot contain sub-@directives; i.e, @see cannot be used within @param.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
8.7 KB

All righto. Here is a new patch with your suggested additions. Other changes:

- I've also added the @see and @ingroup in JS, although JS does not seem to be parsed for api.drupal.org it is the same code there
- I looked for but could not find any tests for t()'s sanitization/replacement behavior. Yeah. So I wrote my own, and put format_string() to the test while we were there.
- While running the tests some bugs were found. Namely the $args on format_string() was not optional before and it was not typed to an array.

The relevant tests now pass on my machine. I think this covers the test requirement for this, expands testing on t() and otherwise includes all suggestions from above.

The only thing I'm not sure about in this patch is the em placeholder test equivalent check. Looks like hardcoding a specific Drupalism there, but without calling out to the theme system and therefore making the test almost use the same code as the one tested I don't think we can do much better.

Let's get this in, can we?!

sun’s picture

+++ b/includes/bootstrap.inc
@@ -1290,26 +1282,50 @@ function t($string, array $args = array(), array $options = array()) {
+ * @see Drupal.t()
+ * @ingroup sanitization
+ */
+function format_string($string, array $args = array()) {

The @see of the PHP format_string() should point to t(), not to the JavaScript Drupal.t().

15 days to next Drupal core point release.

Gábor Hojtsy’s picture

Haha, good catch. Fixed that. Thanks.

sun’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs committer feedback +API addition

Thanks!

Dries’s picture

Version: 8.x-dev » 7.x-dev

Committed to 8.x. Thanks. Moving to 7.x-dev as this could be a candidate for backporting.

webchick’s picture

Version: 7.x-dev » 8.x-dev
Status: Reviewed & tested by the community » Needs work

Interesting patch. I'm fine with backporting this to D7; looks to be a harmless API addition and we're finally below our bug count thresholds.

If I can be allowed to be a bit pedantic for a minute though, the function name "format_string()" doesn't remotely give any indication at all that this is what that function will do. It's also non-standard when you view it in terms the rest of the format_* functions, whose names are very descriptive:

format_date	includes/common.inc	Formats a date, using a date type or a custom date format string.
format_interval	includes/common.inc	Format a time interval with the requested granularity.
format_plural	includes/common.inc	Format a string containing a count of items.
format_rss_channel	includes/common.inc	Formats an RSS channel.
format_rss_item	includes/common.inc	Format a single RSS item.
format_size	includes/common.inc	Generate a string representation for the given byte count.
format_username	includes/common.inc	Format a username.

Can we do format_placeholders() or format_placeholder_string() instead, or similar? Because unlike what Gábor says in #13, we can't actually change this function name willy-nilly in D7 once we introduce it.

sun’s picture

IMO, format_string() is perfectly ok and sufficiently describes what it is for.

Most of our format_*() functions are actually following PHP's native formatting functions (e.g., number_format(), date_format()). Whereas there is no comparable equivalent for format_string() in PHP -- the closest functions are strtr(), str_replace(), and sprintf() - and out of those, only the latter partially sanitizes the input (but still not to the extent of format_string()).

format_placeholders() would be wrong, because it's not formatting placeholders, but a string. format_tokens() would clash with token API, and would also have aforementioned issue.

format_placeholder_string() and alike is an absolute no-go. We already did a huge mistake by introducing overly verbose function names with drupal_get_nested_array_value() - or whatever it is called - and its siblings - the function names are so long and verbose that it's kinda impossible to remember them (unless you use them daily). Likewise, you put one of these into your code and the line immediately exceeds 80 chars (decreasing readability big time). This totally defeats the purpose of general utility functions.

The only real alternatives I can see would be s() and f() - but I'm fairly sure we don't want to go down that rabbit hole.

Gábor Hojtsy’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Reviewed & tested by the community

No other feedback, so moving back to D7.

webchick’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Needs backport to D7 +Needs change record

Yeah, ok. :\

Committed and pushed to 7.x. This could probably use a change notice.

Gábor Hojtsy’s picture

We previously used the change notices to mark changed stuff not cool new stuff. That was the thinking behind not submitting one yet. Does this new system cover cool new things too?

webchick’s picture

Now that it's not a complete pain in the ass to make these, I think they should, yeah. It'd certainly be useful for D7 at any rate, because it's a new capability that contrib modules have available that they didn't in 7.8 and below.

Gábor Hojtsy’s picture

Status: Needs work » Fixed
Issue tags: -Needs change record

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Gábor Hojtsy’s picture

Issue tags: +language-ui

Tagging for the UI translation leg of D8MI.