-
Notifications
You must be signed in to change notification settings - Fork 13
Step 11 internationalizing
Purpose: in this step we will see how to prepare your application to support different languages.
cd ~/devel/apps
git clone git://github.com/opensas/play-demo.git
cd play-demo
git checkout origin/11-internationalizing
Internationalizing your app can be a very tedious task. Fortunately play! framework has several tools that can help us achieve this goal.
First you should configure your languages in application.conf, like this
conf/application.conf
# i18n
# ~~~~~
# Define locales used by your application.
# You can then place localized messages in conf/messages.{locale} files
application.langs=en,es
Then you should create a default messages file and a messages.xx file for each supported language.
this is our messages file
conf/messages
# You can specialize this file for each language.
# For example, for French create a messages.fr file
#
event.title=jugAR Events
event.name=Event
event.name.tip=Enter the event's description.
event.type=Type
event.type.tip=Select the event type.
event.place=Place
event.place.tip=Enter the event's place.
event.date=Date & time
event.date.tip=Enter event's date and time. Formato: mm-dd-aaaa hh:mm.
event.noEvents=No events in sight...
event.button.newEvent=new event
event.button.loadFromYaml=load from yaml file
event.button.admin=admin
event.form.updateEvent=Update event
event.form.createEvent=Create a new event
event.form.subtitle=Enter next events information
event.button.saveEvent=Save changes
event.button.cancel=Cancel
topbar.title=jugArgentina Events
topbar.about=about...
topbar.jugARHomepage=jugAR homepage
topbar.linkedin=follow us at linkedIn
topbar.signin=signin
topbar.signout=signout
footer.backHome=Back home
footer.forkMe=Fork me at
footer.appTitle=Play! demo application for jugAR
footer.appName=Play demo application
header.countDown=Only %s to
login.legend=Hi!<br>You haven't authenticated yourself yet, use any of the buttons below to log in.
dateHelper.year=year
dateHelper.years=years
dateHelper.month=month
dateHelper.months=months
dateHelper.day=day
dateHelper.days=days
dateHelper.hour=hour
dateHelper.hours=hours
dateHelper.minute=minute
dateHelper.minutes=minutes
dateHelper.second=second
dateHelper.seconds=seconds
And this is our messages.es file with the spanish translation
# You can specialize this file for each language.
# For example, for French create a messages.fr file
#
event.title=Eventos de jugAR
event.name=Evento
event.name.tip=Ingrese la descripción del evento.
event.type=Tipo
event.type.tip=Seleccione el tipo de evento.
event.place=Lugar
event.place.tip=Seleccione el lugar del evento.
event.date=Fecha y hora
event.date.tip=Ingrese la fecha y hora del evento. Formato: dd-mm-aaaa hh:mm.
event.noEvents=¡No hay ningún evento a la vista!
event.button.newEvent=nuevo evento
event.button.loadFromYaml=cargar desde archivo yaml
event.button.admin=administrar eventos
event.form.updateEvent=Modificar evento
event.form.createEvent=Crear un nuevo evento
event.form.subtitle=Ingrese la información de los próximos eventos
event.button.saveEvent=Guardar cambios
event.button.cancel=Cancelar
topbar.title=Eventos de jugArgentina
topbar.about=acerca de...
topbar.jugARHomepage=página de jugAR
topbar.linkedin=seguinos en linkedIn
topbar.signin=registrate
topbar.signout=salir
footer.backHome=Volver a la página principal
footer.forkMe=Mirá el código en
footer.appTitle=Aplicación de prueba para jugAR
footer.appName=Play demo application
header.countDown=Faltan %s para
login.legend=¡Hola!<br>Todavía no te registraste. Usá cualquiera de los botones para ingresar.
dateHelper.year=año
dateHelper.years=años
dateHelper.month=mes
dateHelper.months=meses
dateHelper.day=día
dateHelper.days=días
dateHelper.hour=hora
dateHelper.hours=horas
dateHelper.minute=minuto
dateHelper.minutes=minutos
dateHelper.second=segundo
dateHelper.seconds=segundos
Now when you are refering to any of those strings, you have to use the &{'key'} syntax, and play will lookup that text in the message file corresponding to your language. For example, have a look at the nextEvent template:
views/Application/nextEvent.html
<p class="lead">
#{if nextEvent}
<span class="event-countdown">&{'header.countDown', nextEvent.countDown()}</span><br />
<span class="next-event">${nextEvent.name}</span><br />
#{/if}
#{else}
<span class="no-events">&{'event.noEvents'}</span><br />
#{/else}
</p>
This is a special example of message with parameters. See the definition of the header.countDown message:
conf/messages
[...]
header.countDown=Only %s to
[...]
Now you should go thru all the app and replace the hardcoded messages by references to entries in the messages file.
Of course that is easier to start working like that from the beginning.
It's really easy to add message support to our input tag, just use the &{} operator on label and messages, like this:
views/tags/input.html
<div class="${_fieldClass}${_errorClass}">
<label for="${_name}">&{_label}</label>
<div class="${_controlClass}">
#{if _body}
#{doBody /}
#{/if}
#{else}
<input class="${_inputClass}${_errorClass}" id="${_id}" name="${_name}" size="30" type="text" value="${_value}"/>
#{/else}
#{if _isError}<span class="${_errorMessageClass}">#{error _name /}</span>#{/if}
#{if _help}<div class="${_helpClass}">&{_help}</div>#{/if}
</div>
</div> <!-- /clearfix -->
If there's no message matching the content of the &{} tag, play will display the text itself, so you can call the input tag like this:
#{input name:'event.name', label:'event.name', help:'event.name.tip' /}
or directly entering the text
#{input name:'event.name', label:'Event name', help:'Enter the name of the event' /}
You can access the messages collection with plain java code using play.i18n.Messages collection. You can also get the selected language with the play.i18n.Messages.Lang.get() method. It's useful to access it from templates, so we'll add the following lines to our main template:
views/main.html
%{
messages = play.i18n.Messages;
lang = play.i18n.Lang.get();
%}
[...]
Unfortunately we can use import statements on groovy templates. That's why we declare the messages variable.
And a few lines latter we'll have to use it to include the js localization files for the date-time picker
views/main.html
[...]
<script src="@{'/public/javascripts/jquery-ui-1.8.16.custom.min.js'}"></script>
<script src="@{'/public/javascripts/jquery-ui-timepicker-addon.js'}"></script>
<script src="@{'/public/javascripts/localization/jquery.ui.timepicker-' + lang + '.js'}"></script>
<script src="@{'/public/javascripts/localization/jquery.ui.datepicker-' + lang + '.js'}"></script>
#{get 'moreScripts' /}
</head>
[...]
In the application.conf file we will specify the date format for each supported locale
conf/application.conf
# i18n
# ~~~~~
# Define locales used by your application.
# You can then place localized messages in conf/messages.{locale} files
application.langs=en,es
# Date format
# ~~~~~
date.format=MM-dd-yyyy
date.format.en=MM-dd-yyyy HH:mm
date.format.es=dd-MM-yyyy HH:mm
Then in the templates, we will use the .format() method to format each date according to the corresponding format:
views/list.html
[...]
<td>${event.type.name}</td>
<td>${event.place}</td>
<td>${event?.date?.format()}</td>
[...]
views/form.html
[...]
#{input name:'event.date', label:'event.date',
value:event.date?.format(),
help:'event.date.tip'
/}
[...]
We will also use the lang variable in form.html template. to tell the date picker which format to use
views/form.html
[...]
<script type="text/javascript">
$.datepicker.setDefaults($.datepicker.regional['${lang}']);
$('#event\\.date').datetimepicker( {
hour: 18,
[...]
The dateDiff helper function retrieved the time between two dates in a human readable fashion. We just have to get the translation of each item (year, month, day, hour, minute, second) from the corresponding message file. Notice how we also deal with the singular and plural form of each word.
package lib.utils;
import java.util.Date;
import org.joda.time.Period;
import play.i18n.Messages;
public class DateHelper {
public static String dateDiff(final Date begin, final Date end) {
final Period p = new Period(begin.getTime(), end.getTime());
String message =
addTime(p.getYears(), "dateHelper.year") +
addTime(p.getMonths(), "dateHelper.month") +
addTime(p.getDays() + (p.getWeeks()*7), "dateHelper.day") +
addTime(p.getHours(), "dateHelper.hour") +
addTime(p.getMinutes(), "dateHelper.minute") +
addTime(p.getSeconds(), "dateHelper.second");
message = message.replaceAll(", $", "");
return message;
}
private static String addTime(final Integer time, final String period, final String periods) {
if (time==0) return "";
String periodLocalized = Messages.get(period);
String periodsLocalized = Messages.get(periods);
return String.valueOf(time) + " " + (time==1 ? periodLocalized : periodsLocalized) + ", ";
}
private static String addTime(final Integer time, final String period) {
return addTime(time, period, period + "s");
}
// non instantiable class
private DateHelper() {
throw new AssertionError();
}
}
To test it, just open a browser and set it's language to spanish / english to see the changes. In firefox you have to click on Edit, Preferences, Content, and Choose your prefered language for shoing pages. On Chrome click on Preferences, Under the Hood, Languages and Spell-checker Settings and select the prefered languages.
More info on internationalizing your play! framework applications:
How to localize play framework applications
Chapter on Internationalization from play documentation
Internationalizing play YABE application example
Setting the date format in application.conf
Now we are going to Step 12 - testing it all.