Chris Ruppel
August 15, 2013

CSS generated content is cool. You can make those little triangles everyone seems to love, but its real purpose is to let you add presentational words that would otherwise be a pain to generate in markup for some situations.

a:before {
  content: 'Download item';

It can do other neat things like output the contents of another attribute on your element. This feature was originally created to allow print stylesheets to display URLs inline for printed documents:

a:after {
  content: ' (' attr(href) ')';

But what happens when you need to bring multilingual into the mix? We occasionally build multilingual sites, and typically there is translatable content at every level of the stack: Drupal nodes, template files, CSS, JS, you name it. When I was originally tasked with solving this it seemed like a big problem, but it turns out to be relatively simple.

My initial thought was to use the language attribute of html tag and make individual, static content properties, but that leads to code bloat as you accommodate each new language. Furthermore, in Drupal’s case, it leaves your strings stranded in CSS and inaccessible to translators which have an interface built into Drupal’s admin UI.

Solution: Dynamically generate attributes

We had a client who wanted the links to be big CTAs accompanied by the instructions “Download item.” So instead of hardcoding the English words into my stylesheet, I added a data-attribute into the DOM via JavaScript (you could also do this in Drupal by creating or modifying the appropriate field.tpl.php, but I did some other things with JS so it was simpler to keep it all in one file).

$('.my-field').attr('data-cta-msg', Drupal.t('Download item'));

Notice Drupal.t in there? t is for translate, and it is the standard mechanism for localizing JS-powered UI components in Drupal 6, Drupal 7, and Drupal 8. There’s a bit of background magic that happens, but in a nutshell once a page containing your new Drupal.t() string has been loaded, you can access it in the Translate interface in the Drupal admin UI:

Drupal 7 interface for translating non-content/UI strings

Now you can grab a translator, add the appropriate text, and your stylesheet will always supply the correct translation in your CSS generated content! Look at the minor modification to CSS content property (compare to the very first example in this article)

a:before {
  content: attr(data-cta-msg);

And here’s the result in English and Arabic respectively:

English download button with CSS generated content

Arabic download button with CSS generated content

Update: Down in the comments Dan Mouyard pointed out how any CSS generated content is less accessible than real markup. Just a heads up for general use of CSS content that is intended to be read aloud on the page, not just the multilingual method described in this article.


Good point and yes! In fact I was relieved that it wasn’t a problem while developing this solution. I ran into trouble while attempting to get the content to display, but it ended up being that does not automatically modify the DOM to include your new data-attributes. Only jQuery.attr(‘data-foo’) adds the attribute to the DOM.

Yes, I’m familiar with’s weirdness :) But that’s offtopic, really.

So, CSS’s content: attr(data-something) is auto-updating then? Whenever the referred attribute’s value is updated, CSS’s rendered content is also updated? That’s … very interesting :)

Hello Chris,

This article is worth reading. You have shared here excellent information. I will share this precious post with my all friends. It will be helpful for them.

Hariot Marks
drupal developer

This is an interesting solution, nicely done! But what makes you choose client-end rather than processing this via t() on the template level? How much of a bloat would that be? Why I am asking, is because the solution you are suggesting, will not be very SEO friendly, since it goes through javascript and css.

The honest answer is that I couldn’t find the right magical incantation to override the tpl. I had a pre-existing JS file doing other stuff on the page, so I used JS. I made a casual mention of this in the post, and that really was the deciding factor in choosing my method.

CSS generated content is client-side anyway, so there’s absolutely no SEO benefit to supplying this data-attribute via PHP.

Alex has a valid point about just outputting the correct markup through the t() but i usually use css content: for decorative and embellishment purposes. I think that was Chris’ use-case as well (icomoon font items and simple prepended oneworders). With that in mind its less overhead and a little easier to maintain I think.

Regardless its an interesting use of attr() for multilingual support (we’ve previously only used it for things like dynamic font character substitution and the like. Ill be keeping this one in the toolbag for outr next multilingual project. Thanks for sharing.

PS That is a very picky homepage field in this comment. it took me a couple attempts. Maybe I can I introduce you to the link module ;).

This is ingenious!

The catch, however, is that CSS generated content is not available to assistive technology. Unfortunately, this technique isn’t accessible if you’re generating text content that should be read.

Thanks for the heads-up, Dan. That’s a very important distinction to note so I have updated the post to mention that any CSS generated content is not as accessible as real text within markup.

Luckily the general approach to multilingual would remain the same, the main difference being that you’d use $.prepend() or something similar to insert another DOM node instead of $.attr() plus the CSS content sttribute.

That’s exactly what I was thinking… Technically, it’s a nice solution, but it may not always be semantically correct. In your example, the “download” label is not just decoration: it tells people what to expect when they click the button. This information should not be hidden from people who read your content without using your stylesheet. The same can be said about people who do not have javascript enabled.

Nice! I had no idea I could manage this without modules.. my life has just become a little more easier :) CSS generated content is neat stuff. Bw, Tu.

Chris Ruppel
August 15, 2013
Chris Ruppel is a frontend developer who makes websites load fast and shrink on your phone. He currently lives in the beautiful town of Freiburg, Germany.