Thymeleaf Teil 2 mit Internationalisierung in Spring
Im 2. Teil meines Spring MVC mit Thymeleaf Artikels geht es um: html Formulare und JavaScript in Templates, Wiederverwendung gleicher html-Blöcke in Thymeleaf und der Internationalisierung (I18N) von Spring Web-Anwendungen.
Spring Model-View-Controller mit Thymeleaf und POJOs |
Den 1. Teil meines Spring MVC mit Thymeleaf Artikels findet ihr hier und auf YouTube:
Den kompletten Code zu Teil 1 & 2 gibt es hier bei GitHub:
https://github.com/elmar-brauch/thymeleaf
Wiederverwendung von gemeinsamen html Blöcken
Das berühmte Clean Code Prinzip (Clean Code von Robert Martin) "Don't repeat yourself" gilt nicht nur für Java-Code. Es gilt für alle selbst geschriebenen Artefakte.
Wenn wir für 10 verschiedene html Seiten Thymeleaf Templates erstellt haben, sollten wir unbedingt vermeiden per Copy & Paste den Header, Footer oder ähnliches überall zu duplizieren. Damit wir es nicht an 10 Stellen anpassen müssen, wenn entsprechend neue Anforderungen kommen.
Thymeleaf bietet zur Vermeidung von Copy & Paste das Inkludieren von Fragmenten an. Fragmente werden genauso wie andere Thymeleaf Templates in html-Form abgespeichert. Sie befinden sich zusammen mit den anderen html-Dateien im src/main/resources/templates Verzeichnis. In der Fragment-Datei common_head.html habe ich das html head-Tag definiert, weil es für alle Seiten gleich ist:<!DOCTYPE HTML><html xmlns:th="http://www.thymeleaf.org"><head th:fragment="head"> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,
initial-scale=1, shrink-to-fit=no" /> <title th:text="#{title.head}" /> <link rel="stylesheet" th:href="@{/css/main.css}" /> <script type="text/javascript" th:src="@{/js/main.js}"></script></head>- Damit eine Gruppe html-Tags von anderen Thymeleaf Templates als Fragment inkludiert werden kann, muss das Fragment in der Datei (hier common_head.html) definiert werden. Dazu habe ich das head-Tag mit dem Thymeleaf Ausdruck th:fragment="head" als Fragment gekennzeichnet. "head" ist der Name des Fragments. Alle Tags innerhalb des head-Tags sind damit Teil des wiederverwendbaren Fragments.
- Alle anderen hier verwendeten Thymeleaf Ausdrücke wurden schon in meinem ersten Blog-Post zu Thymeleaf vorgestellt, siehe dazu: spring-mvc-thymeleaf.html
Die Inkludierung eines Fragments in ein anderes Template geht so (Ausschnitt der Datei item-create.html):
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head th:replace="~{common_head :: head}"> /> <body>...
th:replace ist der Thymeleaf Ausdruck, um ein Tag durch ein Fragment zu ersetzen. "~{common_head :: head}" gibt den Namen der Template Datei (common_head) an, welche das Fragment head enthält. ~{...} ist die zu verwendende Klammer für Thymeleaf Fragment-Referenzen.
Eine Alternative zu Fragmenten sind Templates. Diese sind aber eine Erweiterung bzw. ein Dialekt für Thymeleaf und benötigen daher zusätzliche Maven Dependencies. Ich möchte sie daher hier erst mal nicht weiter vorstellen und verweise euch daher auf:
https://github.com/ultraq/thymeleaf-layout-dialect
Wenn wir für 10 verschiedene html Seiten Thymeleaf Templates erstellt haben, sollten wir unbedingt vermeiden per Copy & Paste den Header, Footer oder ähnliches überall zu duplizieren. Damit wir es nicht an 10 Stellen anpassen müssen, wenn entsprechend neue Anforderungen kommen.
initial-scale=1, shrink-to-fit=no" />
- Damit eine Gruppe html-Tags von anderen Thymeleaf Templates als Fragment inkludiert werden kann, muss das Fragment in der Datei (hier common_head.html) definiert werden. Dazu habe ich das head-Tag mit dem Thymeleaf Ausdruck th:fragment="head" als Fragment gekennzeichnet. "head" ist der Name des Fragments. Alle Tags innerhalb des head-Tags sind damit Teil des wiederverwendbaren Fragments.
- Alle anderen hier verwendeten Thymeleaf Ausdrücke wurden schon in meinem ersten Blog-Post zu Thymeleaf vorgestellt, siehe dazu: spring-mvc-thymeleaf.html
https://github.com/ultraq/thymeleaf-layout-dialect
Mit Thymeleaf ins MVC Modell schreiben
th:action="@{/item}"
https://wiki.selfhtml.org/wiki/HTML
Thymeleaf und JavaScript
- Mit th:inline="javascript" wird dem Thymeleaf Template mitgeteilt, dass sich im script-Tag inline JavaScript Code befindet.
- In diesem inline JavaScript Code können wir nun mit den bekannten Thymeleaf Ausdrücken auf das Modell zugreifen. ${items[0].name} liest das Attribute name aus dem ersten Element der items Liste. Damit der Wert des Attributes name richtig escaped in den JavaScript Code geschrieben wird, werden die eckigen Klammern doppelt um den Ausdruck geschrieben, also [[...]].
- Die JavaScript Methode popup wurde in der separaten JavaScript Datei des 1. Teils definiert. Sie enthält keine Besonderheiten, also nur normales JavaScript.
Texte in Konfiguration auslagern
I18N per Spring Konvention
- src/main/resources/messages_de.properties
button.create-item=Erstellentitle.item-list=Ding Sammlungtitle.item-create=Ding Erzeugung... - src/main/resources/messages_en.properties
button.create-item=Createtitle.item-list=Item Collectiontitle.item-create=Item Creation
...
- Deutsche Sprache:
curl -H "Accept-Language: de" http://localhost:8080 - Englische Sprache:
curl -H "Accept-Language: en" http://localhost:8080
In den Thymeleaf Templates müssen dazu keine Anpassungen gemacht werden.
Sprachauswahl durch den Client
- Bean Definitionen mit @Bean und @Configuration hatte ich schon vorgestellt, siehe kernkonzepte-von-spring-beans-und.html
- Die LocaleResolver Bean speichert den vom Benutzer ausgewählten Locale, also die Sprache und den Ländercode (in der Demo arbeite ich aber nur mit der Sprache). Konkret wird hier ein SessionLocaleResolver instanziiert, der die Sprachauswahl in der Browser Session zwischen Client und Server hält.
- Die LocaleChangeInterceptor Bean ist ein Interceptor, welcher bei jedem Request die Sprache in der LocaleResolver Bean ändern kann. Das tut der Interceptor genau dann, wenn der Request einen Request Parameter mit dem Namen "lang" hat. Dann wird der Wert des Parameters als neue Locale im LocaleResolver hinterlegt.
Was ein Interceptor genau ist, könnt ihr z.B. hier nachlesen Pro Spring 5 Buch. - Damit der Interceptor sich in die Spring Request Verarbeitung einklinkt, muss er als Interceptor in der InterceptorRegistry registiert werden. Das machen wir, indem wir unsere @Configuration Klasse das Interface WebMvcConfigurer implementieren lassen und die Registrierung in der überschriebenen Methode addInterceptors vornehmen.
- Deutsche Sprache: http://localhost:8080?lang=de
- Englische Sprache: http://localhost:8080?lang=en
Ihr könnt z.B. die zuvor gezeigten Elemente verwenden, um einen Button zu erstellen, der einen Request mit Parameter "lang=de" oder "lang=en" an den Server schickt.
Fazit & Ausblick
https://www.thymeleaf.org/
Kommentare
Mein Blog ist nicht mit Spring und Thymeleaf gemacht.
Ich verwende den Blog-Service von Goolge, siehe https://de.wikipedia.org/wiki/Blogger.com.
Die 110 Sprachen kommen von Google Translate. Google Translate ist als Plugin einfach eingebunden.
Im Enterprise Kontext will man aber meist eigene Übersetzungen selbst formulieren und nicht automatisch generierte Übersetzungen von Google Translate verwenden. Das kann man dann mit Spring und den message.properties Dateien machen, so wie es hier gezeigt ist.