stringFormat for JS – a simple template function

Every time I start a web project I know there are a few tools I'm going to need. stringFormat is now a permanent part of that toolkit which I believe keeps my code tidy and makes it easier to handle localization on the clientside. What I call stringFormat (named after it's .NET equivalent) is certainly not an original idea nor is it particularly impressive code. There's no magic here. Despite it's simplicity I find it extremely useful and have used it on all my recent projects.

stringFormat

stringFormat accepts two or more arguments and returns a string. The first argument is the template. The rest are the strings we'll be applying to that template. It is used like this. (The function itself is at the bottom of this post.)

 s = stringFormat('{{0}} {{1}}{{2}}', 'Hello', 'world', '!');

 // returns
 // Hello world!

There are two common reasons I find myself using this function.

The first is to keep my code tidy. Recently I had a function which formatted a date string. (To avoid extra logic in this example let's assume we'd never span two months.) It could have looked like this:

 formatDate = function (monthName, startDate, endDate, fullYear) {
      return "Week of " + monthName + " " + startDate + " - " + endDate + ", " + fullYear;
 }

or

 formatDate = function (monthName, startDate, endDate, fullYear) {
      return stringFormat("Week of {{0}} {{1}} - {{2}}, {{3}}", monthName, startDate, endDate, fullYear);
 }

I find the second option much more palatable.

Now imagine that you need to change the format of this string to support another language. The first change we'll need to make is to remove the template from the logic and store it as a setting. Not only is this approach valuable for localization, it also helps you stay more organized by keeping your settings or constants in a single place. In my projects it often looks like this:

 // settings file
 var settings = {
      'weekRangeStringTemplate': 'Week of {{0}} {{1}} - {{2}}, {{3}}',
      'itemsPerPage': 4,
      'maxCommentLength': 250,
      ...
 }

 // logic file
 formatDate = function (monthName, startDate, endDate, fullYear) {
      return stringFormat(settings.weekRangeStringTemplate, monthName, startDate, endDate, fullYear);
 }

 // result
 // Week of December 02 - 08, 2012

If translations always had the same sentence structure the template part of this wouldn't be nearly as important. You could just store the 'Week of' string as a setting and switch it out as the language changed. Sometimes that is the case, especially if you are moving between similar languages. But often it is not. For example, according to Google translate the above phrase in Albanian looks like this: "Java e 02-08 dhjetor, 2012". (I don't speak Albanian, let's assume this is correct) Notice that in addition to the spelling changes the order has also changed. 'dhjetor', the translation for 'December' has moved and now appears after the dates. If you were hard-coding the string concatenation you'd need to branch your code:

 if (en) {
      s = "Week of " + monthName + " " + startDate + " - " + endDate + ", " + fullYear ":";
 } else if (sq) {
      s = "Jave e " + startDate + "-" + endDate + " " + monthName + ", " + fullYear ":";
 }

Obviously this is not sustainable. But if you have your template stored as a setting which is populated based on the user's language then there will be no branching required.

Your setting will be one of the following two values:

 'weekRangeStringTemplate': 'Week of {{0}} {{1}} - {{2}}, {{3}}'

or

 'weekRangeStringTemplate': 'Java e {{1}}-{{2}} {{0}}, {{3}}'

And your logic can remain exactly the same:

 formatDate = function (monthName, startDate, endDate, fullYear) {
      return stringFormat(settings.weekRangeStringTemplate, monthName, startDate, endDate, fullYear);
 }

That's pretty much it. Very simple, quite useful. There are an array of more powerful template scripts out there such as mustache, hogan and handlebars, but for the basic stuff, like what we've done above you won't need that much power.

Here is the function:

 stringFormat (str, arr) {
    a = (typeof arguments[1] === 'object') ? arr : Array.prototype.slice.call(arguments).slice(1);
    return str.replace(
        /\{{([0-9]+)\}}/g,
        function (_, index) { return a[index]; });
 }

I've also posted it on jsfiddle and along with the basic version there is a slight variation which accepts a JSON object instead of strings so you can use names as your template placeholders instead of integers.

Stories say it best.

Are you ready to make your workplace awesome? We're keen to hear what you have in mind.

Interested in learning more about the work we do?

Explore our culture and transformation services.

Our commitment to reconciliation

Learn how Habanero is responding to the Truth and Reconciliation Calls to Action as a settler-owned company operating on Indigenous territories across what is now called Canada.

Read about our commitment