Vor kurzem habe ich mich mit Grails beschäftigt. Eigentlich eine ziemlich nettes Framework, mit dem man schnell einen Prototyp programmieren kann. Für meinen Geschmack war die Unterstützung für Rich UI allerdings etwas dürftig, doch das ist kein Problem, denn in diese Lücke springen in Grails die sogenannten Plugins.
In meinem Fall habe ich mich für das Grails-UI Plugin entschieden. Dieses Plugin bietet eine schöne Integration von YUI und YUI wiederum bietet ein umfangreiches Set an Rich UI Komponenten.
Das Plugin war schnell installiert und die Integration in Grails war auch relativ leicht umzusetzen. Vor allem das Tag <gui:dataTable> kam bei mir stark zum Einsatz. Irgendwann allerdings kam ich zu dem Punkt, wo ich mich mit Internationalisierung auseinandersetzen musste und dieses Problem war leider nur sehr dürftig beschrieben. Aus diesem Grund will ich heute kurz aufzeigen was man hierfür alles tun muss.
i18n für YUI
Um YUI zu internationalisieren muss man ein wenig in der offiziellen Doku suchen. Leider habe ich kein durchgängiges Konzept erkennen können. Teilweise müssen einfach statische JS Variablen umdefiniert werden, teilweise muss eine Variable im prototype überschrieben werden und teilweise müssen sog. Data Formatter definiert werden, die z.B. eine Datumszelle entsprechend formatieren.
YAHOO.widget.Calendar.DEFAULT_CONFIG.NAV.value = true;
YAHOO.widget.Calendar.DEFAULT_CONFIG.CLOSE.value = "Schlie\u00DFen"
// Correct formats for Germany: dd.mm.yyyy, dd.mm, mm.yyyy
YAHOO.widget.Calendar.DEFAULT_CONFIG.DATE_FIELD_DELIMITER.value = ".";
YAHOO.widget.Calendar.DEFAULT_CONFIG.MDY_DAY_POSITION.value = 1;
YAHOO.widget.Calendar.DEFAULT_CONFIG.MDY_MONTH_POSITION.value = 2;
YAHOO.widget.Calendar.DEFAULT_CONFIG.MDY_YEAR_POSITION.value = 3;
YAHOO.widget.Calendar.DEFAULT_CONFIG.MD_DAY_POSITION.value = 1;
YAHOO.widget.Calendar.DEFAULT_CONFIG.MD_MONTH_POSITION.value = 2;
// Date labels for German locale
YAHOO.widget.Calendar.DEFAULT_CONFIG.MONTHS_SHORT.value = ["Jan", "Feb", "M\u00E4r", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
YAHOO.widget.Calendar.DEFAULT_CONFIG.MONTHS_LONG.value = ["Januar", "Februar", "M\u00E4rz", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
YAHOO.widget.Calendar.DEFAULT_CONFIG.WEEKDAYS_1CHAR.value = ["S", "M", "D", "M", "D", "F", "S"];
YAHOO.widget.Calendar.DEFAULT_CONFIG.WEEKDAYS_SHORT.value = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
YAHOO.widget.Calendar.DEFAULT_CONFIG.WEEKDAYS_MEDIUM.value = ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam"];
YAHOO.widget.Calendar.DEFAULT_CONFIG.WEEKDAYS_LONG.value = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
// Start the week on a Monday (Sunday == 0)
YAHOO.widget.Calendar.DEFAULT_CONFIG.START_WEEKDAY.value = 1;
//CalendarNavigator show up if clicked on the current month
YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG.strings.submit = "OK"
YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG.strings.cancel = "Abbr."
YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG.strings.invalidYear = "Das Jahr muss eine Zahl sein."
YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG.strings.month = "Monat"
YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG.strings.year = "Jahr"
YAHOO.widget.BaseCellEditor.prototype.LABEL_SAVE = "OK"
YAHOO.widget.BaseCellEditor.prototype.LABEL_CANCEL = "Abbr."
/*
* BUGFIX for YUI < 2.9
* If a date cell contains a null value the cell editor calendar does not show up.
* Till we're not using YUI 2.9 and above this is the fix for it.
*
* http://yuilibrary.com/projects/yui2/ticket/2529117
*/
YAHOO.widget.DateCellEditor.prototype.resetForm = function() {
var value = this.value || new Date();
var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
this.calendar.cfg.setProperty("selected",selectedValue,false);
this.calendar.render();
};
YAHOO.widget.DataTable.Formatter.date = function(elCell, oRecord, oColumn, oData) {
var oDate = oData;
if(oDate instanceof Date) {
elCell.innerHTML = YAHOO.util.Date.format(oDate, { format: "%d.%m.%Y"});
} else {
elCell.innerHTML = '';
}
};
Damit hat man zumindest den reinen YUI DataTable umgestellt. Der große Teil ist hier einfach die Übersetzung der englischen Strings in die jeweilige Zielsprache. Ein kleinerer Teil ist die Verwendung eines speziellen DateFormatters, damit in der Zelle selbst ein deutsches Format verwendet wird, und zum Schluss noch ein Bugfix für YUI selbst, denn das Grails Plugin verwendet momentan noch YUI in der Version 2.8. In dieser Version ist es unmöglich für eine leere Datums Tabellenzelle einen neuen Wert zu definieren. Sollte das Grails Plugin irgendwann YUI die Version 2.9 verwenden entfällt dieser Bugfix.
i18n für Grails
Hat man YUI nun eine andere Sprache beigebracht, muss man sich jetzt noch um den Grails Teil kümmern. Folgender Template Code demonstriert dies kurz.
<!-- http://jira.grails.org/browse/GPUI-250 -->
<g:set var="i18nfirstName" value="${message(code: 'user.firstName.label', default: 'First Name')}"/>
<g:set var="i18nlastName" value="${message(code: 'user.lastName.label', default: 'Last Name')}" />
<g:set var="i18ndate" value="${message(code: 'user.date.label', default: 'Date')}" />
<g:set var="i18nfirstPage" value="${message(code: 'disy.paginator.firstPage.label', default: 'First Page')}" />
<g:set var="i18npreviousPage" value="${message(code: 'disy.paginator.previousPage.label', default: 'Previous Page')}" />
<g:set var="i18nnextPage" value="${message(code: 'disy.paginator.nextPage.label', default: 'Next Page')}" />
<g:set var="i18nlastPage" value="${message(code: 'disy.paginator.lastPage.label', default: 'Last Page')}" />
<gui:dataTable
id="myList"
selectionMode="standard"
draggableColumns="true"
sortedBy="lastName"
columnDefs="[
[id:'Id',
hidden: true ],
[lastName: i18nlastName,
formatter: 'text' ,
editor:[controller:'myController', action:'tableChange', onSuccess:'successCallback', onFailure:'failureCallback'],
sortable:true,
resizeable: true],
[firstName: i18nfirstName,
formatter: 'text' ,
editor:[controller:'myController', action:'tableChange', onSuccess:'successCallback', onFailure:'failureCallback'],
sortable:true,
resizeable: true],
[date: i18ndate,
formatter: 'date',
editor:[controller:'myController', action:'tableChange', onSuccess:'successCallback', onFailure:'failureCallback'],
sortable:true,
resizeable: true]]"
allowExclusiveSort='true'
controller="myController" action="listJSON"
rowsPerPage="30"
MSG_EMPTY="${message(code: 'dataTable.empty.label', default: 'No records found.')}"
MSG_ERROR="${message(code: 'dataTable.error.label', default: 'Data error.')}"
MSG_LOADING="${message(code: 'dataTable.loading.label', default: 'Loading...')}"
paginatorConfig="[
firstPageLinkLabel: '<<',
firstPageLinkTitle: i18nfirstPage,
previousPageLinkLabel: '<',
previousPageLinkTitle: i18npreviousPage,
nextPageLinkLabel: '>',
nextPageLinkTitle: i18nnextPage,
lastPageLinkLabel: '>>',
lastPageLinkTitle: i18nlastPage
]" />
HIerbei fällt folgendes auf:
- Der Pager wird als paginatorConfig umdefiniert, damit dieser auch in der gewünschten Zielsprache ist.
- Die Default Texte für einen leeren DataTable (MSG_EMPTY, MSG_ERROR, MSG_LOADING) werden ebenfalls definiert.
- Alle Zellenüberschriften werden zuvor als Grails Variable angelegt und verwendet. Dies ist leider noch ein Feature Request für gui:dataTable und vielleicht wird dies in absehbarer Zeit umgesetzt.
Damit hat man nun einen DataTable, der fast fertig ist. Fast deswegen, weil die übermittelten Werte des YUI DataTable auf Controller Seite noch formatiert werden müssen. Werden neue Datumswerte ausgewählt, dann überträgt YUI dies in einem bestimmten DateFormat, welches wir auf Grails Seite bei einem AJAX Update entsprechend in unser Date parsen müssen.
//Parse param
if (params.field == 'date') {
SimpleDateFormat formatter = new java.text.SimpleDateFormat("E MMM dd yyyy HH:mm:ss ZZZZZZZ", Locale.ROOT);
myObject."$params.field" = formatter.fomrmat(params.newValue)
}
Haben wir all diese Schritte entsprechend umgesetzt, sollten wir nun in der Lage sein einen internationalisierten gui:dataTable zu verwenden.