Freemarker Loop Variables in Translation Keys
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:
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.