[FEATURE] i18n
CI / build-and-test (pull_request) Successful in 1m14s

This commit is contained in:
Pit Friedrich
2026-07-19 21:15:23 +02:00
parent 9316b83f82
commit 9114ea9800
9 changed files with 431 additions and 65 deletions
@@ -6,38 +6,42 @@ import com.example.components.LineChart;
import com.example.components.PieChart;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Route;
import java.util.List;
@Route("")
@PageTitle("Dashboard")
public class DashboardView extends VerticalLayout {
public class DashboardView extends VerticalLayout implements HasDynamicTitle {
public DashboardView() {
addClassName("dialect-content");
List<String> months = List.of(
getTranslation("month.jan"), getTranslation("month.feb"),
getTranslation("month.mar"), getTranslation("month.apr"),
getTranslation("month.may"), getTranslation("month.jun"));
LineChart lineChart = new LineChart();
lineChart.setWidthFull();
lineChart.setHeight("400px");
lineChart.setData("Umsatz 2026",
lineChart.setData(getTranslation("chart.revenueSeries"),
List.of(30.0, 40.0, 35.0, 50.0, 49.0, 60.0),
List.of("Jan", "Feb", "Mär", "Apr", "Mai", "Jun"));
months);
lineChart.addPointClickListener(e -> {
Notification.show("Serie %d, Punkt %d"
.formatted(e.getSeriesIndex(), e.getDataPointIndex()));
Notification.show(getTranslation("chart.pointClick",
e.getSeriesIndex(), e.getDataPointIndex()));
});
BarChart barChart = new BarChart();
barChart.setWidthFull();
barChart.setHeight("400px");
barChart.setData("Umsatz 2026",
barChart.setData(getTranslation("chart.revenueSeries"),
List.of(30.0, 40.0, 35.0, 50.0, 49.0, 60.0),
List.of("Jan", "Feb", "Mär", "Apr", "Mai", "Jun"));
months);
barChart.addPointClickListener(e -> {
Notification.show("Serie %d, Punkt %d"
.formatted(e.getSeriesIndex(), e.getDataPointIndex()));
Notification.show(getTranslation("chart.pointClick",
e.getSeriesIndex(), e.getDataPointIndex()));
});
PieChart pieChart = new PieChart();
@@ -45,13 +49,19 @@ public class DashboardView extends VerticalLayout {
pieChart.setHeight("400px");
pieChart.setData(
List.of(30.0, 40.0, 35.0, 50.0),
List.of("Nord", "Süd", "Ost", "West"));
List.of(getTranslation("region.north"), getTranslation("region.south"),
getTranslation("region.east"), getTranslation("region.west")));
pieChart.addPointClickListener(e -> {
Notification.show("Slice %d".formatted(e.getDataPointIndex()));
Notification.show(getTranslation("chart.sliceClick", e.getDataPointIndex()));
});
add(new Card("Umsatz-Entwicklung", lineChart),
new Card("Umsatz nach Monat", barChart),
new Card("Umsatz nach Region", pieChart));
add(new Card(getTranslation("card.revenueTrend"), lineChart),
new Card(getTranslation("card.revenueByMonth"), barChart),
new Card(getTranslation("card.revenueByRegion"), pieChart));
}
@Override
public String getPageTitle() {
return getTranslation("page.dashboard");
}
}
+41 -33
View File
@@ -20,7 +20,7 @@ import com.vaadin.flow.component.timepicker.TimePicker;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.validator.EmailValidator;
import com.vaadin.flow.data.validator.IntegerRangeValidator;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Route;
import java.text.DateFormatSymbols;
@@ -33,30 +33,34 @@ import java.util.Locale;
* to {@link Registration} via {@link Binder}.
*/
@Route("formular")
@PageTitle("Formular")
public class FormView extends VerticalLayout {
public class FormView extends VerticalLayout implements HasDynamicTitle {
private final Binder<Registration> binder = new Binder<>(Registration.class);
public FormView() {
addClassName("dialect-content");
add(new Card("Registrierung", buildForm()));
add(new Card(getTranslation("card.registration"), buildForm()));
//<theme-editor-local-classname>
addClassName("form-view-vertical-layout-1");
}
@Override
public String getPageTitle() {
return getTranslation("page.form");
}
private Component buildForm() {
TextField vorname = new TextField("Vorname");
TextField vorname = new TextField(getTranslation("form.firstName"));
vorname.setAutofocus(true);
TextField nachname = new TextField("Nachname");
EmailField email = new EmailField("E-Mail");
PasswordField passwort = new PasswordField("Passwort");
TextField nachname = new TextField(getTranslation("form.lastName"));
EmailField email = new EmailField(getTranslation("form.email"));
PasswordField passwort = new PasswordField(getTranslation("form.password"));
//<theme-editor-local-classname>
passwort.addClassName("form-view-password-field-1");
IntegerField alter = new IntegerField("Alter");
IntegerField alter = new IntegerField(getTranslation("form.age"));
alter.setReadOnly(true);
BigDecimalField gehalt = new BigDecimalField("Gehalt (€)");
DatePicker geburtsdatum = new DatePicker("Geburtsdatum");
BigDecimalField gehalt = new BigDecimalField(getTranslation("form.salary"));
DatePicker geburtsdatum = new DatePicker(getTranslation("form.birthdate"));
geburtsdatum.setLocale(Locale.GERMANY);
DateFormatSymbols symbols = new DateFormatSymbols(Locale.GERMANY);
@@ -69,44 +73,48 @@ vorname.setAutofocus(true);
.setWeekdays(wochentage)
.setWeekdaysShort(wochentageKurz)
.setFirstDayOfWeek(1)
.setToday("Heute")
.setCancel("Abbrechen"));
TimePicker uhrzeit = new TimePicker("Bevorzugte Uhrzeit");
.setToday(getTranslation("form.today"))
.setCancel(getTranslation("form.cancel")));
TimePicker uhrzeit = new TimePicker(getTranslation("form.time"));
ComboBox<String> land = new ComboBox<>("Land");
land.setItems("Deutschland", "Österreich", "Schweiz", "Frankreich", "Niederlande");
ComboBox<String> land = new ComboBox<>(getTranslation("form.country"));
land.setItems(getTranslation("country.de"), getTranslation("country.at"),
getTranslation("country.ch"), getTranslation("country.fr"), getTranslation("country.nl"));
Select<String> abteilung = new Select<>();
abteilung.setLabel("Abteilung");
abteilung.setItems("Vertrieb", "Marketing", "Entwicklung", "Support", "Geschäftsführung");
abteilung.setLabel(getTranslation("form.department"));
abteilung.setItems(getTranslation("dept.sales"), getTranslation("dept.marketing"),
getTranslation("dept.dev"), getTranslation("dept.support"), getTranslation("dept.management"));
CheckboxGroup<String> interessen = new CheckboxGroup<>();
interessen.setLabel("Interessen");
interessen.setItems("Newsletter-Themen", "Webinare", "Produkt-Updates", "Events");
interessen.setLabel(getTranslation("form.interests"));
interessen.setItems(getTranslation("interest.newsletter"), getTranslation("interest.webinars"),
getTranslation("interest.updates"), getTranslation("interest.events"));
RadioButtonGroup<String> geschlecht = new RadioButtonGroup<>();
geschlecht.setLabel("Anrede");
geschlecht.setItems("Frau", "Herr", "Divers", "Keine Angabe");
geschlecht.setLabel(getTranslation("form.salutation"));
geschlecht.setItems(getTranslation("salutation.mrs"), getTranslation("salutation.mr"),
getTranslation("salutation.diverse"), getTranslation("salutation.none"));
Checkbox newsletter = new Checkbox("Newsletter abonnieren");
Checkbox newsletter = new Checkbox(getTranslation("form.newsletter"));
TextArea bemerkung = new TextArea("Bemerkung");
TextArea bemerkung = new TextArea(getTranslation("form.remark"));
bemerkung.setMaxLength(500);
binder.forField(vorname)
.asRequired("Vorname wird benötigt")
.asRequired(getTranslation("valid.firstNameRequired"))
.bind(Registration::getVorname, Registration::setVorname);
binder.forField(nachname)
.asRequired("Nachname wird benötigt")
.asRequired(getTranslation("valid.lastNameRequired"))
.bind(Registration::getNachname, Registration::setNachname);
binder.forField(email)
.asRequired("E-Mail wird benötigt")
.withValidator(new EmailValidator("Ungültige E-Mail-Adresse"))
.asRequired(getTranslation("valid.emailRequired"))
.withValidator(new EmailValidator(getTranslation("valid.emailInvalid")))
.bind(Registration::getEmail, Registration::setEmail);
binder.forField(passwort)
.bind(Registration::getPasswort, Registration::setPasswort);
binder.forField(alter)
.withValidator(new IntegerRangeValidator("Alter muss zwischen 0 und 120 liegen", 0, 120))
.withValidator(new IntegerRangeValidator(getTranslation("valid.ageRange"), 0, 120))
.bind(Registration::getAlter, Registration::setAlter);
binder.forField(gehalt)
.bind(Registration::getGehalt, Registration::setGehalt);
@@ -143,20 +151,20 @@ vorname.setAutofocus(true);
formLayout.setColspan(bemerkung, 2);
formLayout.add(interessen, geschlecht, newsletter, bemerkung);
Button speichern = new Button("Speichern", e -> {
Button speichern = new Button(getTranslation("form.save"), e -> {
Registration ziel = new Registration();
if (binder.writeBeanIfValid(ziel)) {
Notification erfolg = Notification.show(
"Gespeichert: %s %s".formatted(ziel.getVorname(), ziel.getNachname()));
getTranslation("notify.saved", ziel.getVorname(), ziel.getNachname()));
erfolg.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
} else {
Notification fehler = Notification.show("Bitte Eingaben prüfen");
Notification fehler = Notification.show(getTranslation("notify.checkInput"));
fehler.addThemeVariants(NotificationVariant.LUMO_ERROR);
}
});
speichern.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Button zuruecksetzen = new Button("Zurücksetzen",
Button zuruecksetzen = new Button(getTranslation("form.reset"),
e -> binder.readBean(new Registration()));
HorizontalLayout buttons = new HorizontalLayout(speichern, zuruecksetzen);
@@ -1,15 +1,21 @@
package com.example.views;
import com.example.js.CommonJS;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.menubar.MenuBar;
import com.vaadin.flow.component.menubar.MenuBarVariant;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.Layout;
import com.vaadin.flow.server.VaadinSession;
import java.util.Locale;
@Layout
public class MainLayout extends AppLayout {
@@ -19,32 +25,53 @@ public class MainLayout extends AppLayout {
public MainLayout() {
DrawerToggle toggle = new DrawerToggle();
Span title = new Span("Chart App");
Span title = new Span(getTranslation("app.title"));
title.addClassName("dialect-title");
Span spacer = new Span();
spacer.getStyle().set("flex-grow", "1");
MenuBar languageMenu = buildLanguageMenu();
themeToggle.addThemeVariants(ButtonVariant.TERTIARY, ButtonVariant.LUMO_ICON);
themeToggle.setAriaLabel("Farbschema wechseln");
themeToggle.setAriaLabel(getTranslation("action.themeToggle"));
themeToggle.addClickListener(e ->
getElement().executeJs(CommonJS.TOGGLE_THEME_JS).then(String.class, this::applyIconFor));
addToNavbar(toggle, title, spacer, themeToggle);
addToNavbar(toggle, title, spacer, languageMenu, themeToggle);
getElement().executeJs(CommonJS.INIT_THEME_JS).then(String.class, this::applyIconFor);
SideNav nav = new SideNav();
nav.addItem(new SideNavItem("Dashboard", DashboardView.class,
nav.addItem(new SideNavItem(getTranslation("nav.dashboard"), DashboardView.class,
VaadinIcon.DASHBOARD.create()));
nav.addItem(new SideNavItem("Formular", FormView.class,
nav.addItem(new SideNavItem(getTranslation("nav.form"), FormView.class,
VaadinIcon.FORM.create()));
nav.addItem(new SideNavItem("Tabelle", TableView.class,
nav.addItem(new SideNavItem(getTranslation("nav.table"), TableView.class,
VaadinIcon.TABLE.create()));
addToDrawer(nav);
}
private MenuBar buildLanguageMenu() {
MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
var root = menuBar.addItem(VaadinIcon.GLOBE_WIRE.create());
root.setAriaLabel(getTranslation("action.language"));
root.getSubMenu().addItem(getTranslation("lang.de"), e -> switchLanguage(Locale.GERMAN));
root.getSubMenu().addItem(getTranslation("lang.en"), e -> switchLanguage(Locale.ENGLISH));
root.getSubMenu().addItem(getTranslation("lang.es"), e -> switchLanguage(Locale.of("es")));
return menuBar;
}
private void switchLanguage(Locale locale) {
VaadinSession.getCurrent().setLocale(locale);
UI.getCurrent().getPage().reload();
}
private void applyIconFor(String theme) {
themeToggle.setIcon("dark".equals(theme) ? VaadinIcon.SUN_O.create() : VaadinIcon.MOON.create());
}
+13 -9
View File
@@ -7,7 +7,7 @@ import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Route;
import java.text.NumberFormat;
@@ -19,8 +19,7 @@ import java.util.Locale;
* live text filter across all columns.
*/
@Route("tabelle")
@PageTitle("Tabelle")
public class TableView extends VerticalLayout {
public class TableView extends VerticalLayout implements HasDynamicTitle {
private record Mitarbeiter(String name, String abteilung, String stadt, double gehalt) {
}
@@ -45,7 +44,7 @@ public class TableView extends VerticalLayout {
addClassName("dialect-content");
TextField suche = new TextField();
suche.setPlaceholder("Suchen…");
suche.setPlaceholder(getTranslation("table.search"));
suche.setPrefixComponent(VaadinIcon.SEARCH.create());
suche.setClearButtonVisible(true);
suche.setValueChangeMode(ValueChangeMode.EAGER);
@@ -53,10 +52,10 @@ public class TableView extends VerticalLayout {
Grid<Mitarbeiter> grid = new Grid<>();
grid.setWidthFull();
grid.addColumn(Mitarbeiter::name).setHeader("Name").setSortable(true);
grid.addColumn(Mitarbeiter::abteilung).setHeader("Abteilung").setSortable(true);
grid.addColumn(Mitarbeiter::stadt).setHeader("Stadt").setSortable(true);
grid.addColumn(m -> GEHALT_FORMAT.format(m.gehalt())).setHeader("Gehalt (€)").setSortable(true);
grid.addColumn(Mitarbeiter::name).setHeader(getTranslation("col.name")).setSortable(true);
grid.addColumn(Mitarbeiter::abteilung).setHeader(getTranslation("col.department")).setSortable(true);
grid.addColumn(Mitarbeiter::stadt).setHeader(getTranslation("col.city")).setSortable(true);
grid.addColumn(m -> GEHALT_FORMAT.format(m.gehalt())).setHeader(getTranslation("col.salary")).setSortable(true);
GridListDataView<Mitarbeiter> dataView = grid.setItems(MITARBEITER);
suche.addValueChangeListener(e -> dataView.refreshAll());
@@ -66,7 +65,12 @@ public class TableView extends VerticalLayout {
content.setPadding(false);
content.setSpacing(true);
add(new Card("Mitarbeiter", content));
add(new Card(getTranslation("card.employees"), content));
}
@Override
public String getPageTitle() {
return getTranslation("page.table");
}
private static boolean matches(Mitarbeiter mitarbeiter, String filter) {