Freemarker Loop Variables in Translation Keys

November 2nd 2018 Freemarker Hippo CMS

Freemarker, the Hippo CMS template language, includes special directives for working with sequences. Additional built-ins can be used to get more information about the loop variable. However, I've noticed that they don't always work.

Let's start with the following contrived sample markup:

<#assign months=
    ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']>
<select id="month">
    <#list months as month>
        <option value="${month}">${month}</option>
    </#list>
</select>

It's a simple loop over a sequence of months without any built-ins. This will work as expected. It will return a select element with a list of months.

We can use the counter built-in to get the 1-based index of the item:

<#assign months=
    ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']>
<select id="month">
    <#list months as month>
        <option value="${month}">${month?counter}</option>
    </#list>
</select>

No problems so far. Of course, displaying month indexes instead of their names usually doesn't make sense. We'll use the index to localize the month names.

Hippo CMS provides an editor for dynamic resource bundles which can be accessed from the Freemarker templates. I used it to create resources for month names:

Month names in a dynamic resource bundle

I then tried to use interpolation to compose the correct resource key for each iteration:

<#assign months=
    ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']>
<select id="month">
    <#list months as month>
        <option value="${month}">
            <@fmt.message key="homepage.months.${month?counter}" />
        </option>
    </#list>
</select>

Freemarker didn't like it. The page generation failed with the following error

[WARNING] [talledLocalContainer] %d{HH:mm:ss} WARN [HstFreemarkerServlet] Syntax error in template "webfile:/freemarker/hippofreemarkerloops/homepage-main.ftl" in line 18, column 75:

[INFO] [talledLocalContainer] The left hand operand of ?counter must be a loop variable, but there's no loop variable in scope with this name: month. To see the stack trace, set 'org.hippoecm.hst.servlet.HstFreemarkerServlet' log-level to debug in log4j configuration or runtime via the logging servlet

The only explanation is that the counter built-in didn't recognize month as a loop variable. Interestingly enough, the variable had the correct value if I tried to use it without the built-in:

<#assign months=
    ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']>
<select id="month">
    <#list months as month>
        <option value="${month}">
            <@fmt.message key="homepage.months.${month}" />
        </option>
    </#list>
</select>

However, I didn't want to change the resource keys, so I continued looking for a workaround. Assigning the value returned by the built-in to another variable did the trick:

<#assign months=
    ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']>
<select id="month">
    <#list months as month>
        <#assign monthIndex=month?counter>
        <option value="${month}">
            <@fmt.message key="homepage.months.${monthIndex}" />
        </option>
    </#list>
</select>

In the end I found an even better solution:

<#assign months=
    ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']>
<select id="month">
    <#list months as month>
        <option value="${month}">
            <@fmt.message key="homepage.months." + month?counter />
        </option>
    </#list>
</select>

There's no need to use interpolation for composing the resource key since expressions can be assigned to the key attribute of the fmt.message tag.

Copyright
Creative Commons License