Teil 2: Lost in Translation?!

Backend meets Frontend Reloaded: I18N – Mehrsprachige Log-in-Seiten

Sven Ruppert

© Shutterstock / HelenField & bkf (modifiziert)

Vaadin 10 ist nun schon einige Zeit verfügbar und immer mehr Projekte beginnen mit dieser Version oder mit der entsprechenden Migration auf die neue Version. In diesem Tutorial werden wir uns mit Vaadin 10 beschäftigen und den Fokus dabei auf langlebige Projekte legen. In Teil 2 geht es darum, ein Grundgerüst für eine vollständige Anwendung zu erstellen, dazu gehören unter anderem mehrsprachige Log-in Screens.

Rückblick

Im letzten Teil der Serie haben wir uns damit beschäftigt, einen Log-in Screen zu erstellen:

Da dieser nun soweit in den Grundzügen vorhanden ist, kommen wir in diesem Teil der Serie dazu, das Grundgerüst einer vollständigen Anwendung zu erstellen. Dazu werden wir uns in den nächsten Teilen verschiedene Wege ansehen, die man zu diesem Zwecke einschlagen kann.

Die Anforderungen

Eine Anwendung hat in einem Unternehmen oftmals einen Satz an Anforderungen, die man vermutlich immer wieder vorfinden wird. Dazu zählt schon die erste Bedingung, das nur ein User die Anwendung verwenden soll, der über ein Set passender Anmeldeinformationen verfügt. Wie man im ersten Screenshot sehen kann, fehlen zum Beispiel noch die Bezeichner an den Komponenten. Hier sind wir gleich bei der ersten Fragestellung: Soll mehr als eine Sprache unterstützt werden? I18N ist hier das Schlüsselwort, das einem entgegenspringt.

Größere Anwendungen haben bestimmte generische Elemente, die das grundsätzliche Aussehen der Anwendung bestimmen. Wir werden uns mit dem Thema Layout im Bereich der Strukturelemente, nicht mit dem Styling der Farben oder ähnlichem beschäftigen.

Eine weitere Anforderung wird sich aus der Art und Weise ergeben, mit der ein Benutzer sich in der Anwendung bewegen soll. Hier sprechen wir von der Definition einer Menüstruktur, wie auch immer diese visuell dargestellt werden soll. Zudem gibt es immer eine Art von Berechtigungssystem innerhalb einer Anwendung, das dann ebenfalls Auswirkungen auf die Navigationsstruktur und Navigationsmöglichkeiten hat. Diese Punkte sollen uns erst einmal reichen und mit ihnen werden uns zunächst ausgibig beschäftigen.

Anforderungen:

  • I18N
  • Layouting
  • Menüstrukturen
  • Berechtigungen

I18N – Die ersten Gedanken

Beginnen wir nun mit dem Punkt I18N, also der Art und Weise, wie in der Anwendung mit mehr als einer Sprache umgegangen werden soll. Hierzu ein paar Grundüberlegungen:

Alles, was in für Menschen lesbarer Form bei/auf grafischen Elementen stehen soll (etwa die Beschriftung auf einem Button), muss in einer neutralen Form definiert werden. Hierfür kommen Ressourcenschlüssel zum Einsatz. Diese müssen zu einem vordefinierten Zeitpunkt in die jeweilige Zielsprache aufgelöst werden. Der Zeitpunkt ist ausschlaggebend dafür, welche Informationen einem dann zur Verfügung stehen und für welche Zielsprache sich entschieden wird. Wenn die gewünschte Zielsprache nicht vorhanden ist oder nicht unterstützt wird, muss auf eine alternative Sprache umgeschaltet werden. Auch hier gibt es verschiedene Lösungsmöglichkeiten, nicht nur das Umschalten auf eine allgemeine Default-Sprache. Wichtig für die Bereitstellung und Pflege der verschiedenen Sprachen ist ebenfalls, die Bereitstellung auf technischer Ebene der jeweiligen Sprachen. Nicht zu vergessen natürlich die Möglichkeit, zur Laufzeit weitere Sprachen hinzuzufügen, vorhandene Sprachen zu korrigieren und zu erweitern bzw. reduzieren oder Sprachen vollständig zu entfernen.

Das sind schon jede Menge Anforderungen, allerdings gibt es noch weitere Anforderungen, auf die wir in diesem Teil nicht eingehen werden. Hierzu zählen u.a. die grafischen Auswirkungen, die sich bei dem Wechsel einer Sprache ergeben können. Gemeint sind Sprachen, die von oben nach unten orientiert sind oder von rechts nach links. Wir konzentrieren uns hier auf Sprachen die von links nach rechts orientiert sind.

I18N und Vaadin

Vaadin hat in Flow einen grundlegenden Mechanismus integriert, der es einem Entwickler sehr leicht macht, ihn seinen Bedürfnissen anzupassen. Der Grundmechanismus ist im Interface I18NProvider definiert, dieses besteht lediglich aus zwei Methoden.

public interface I18NProvider extends Serializable {

    List<Locale> getProvidedLocales();

    String getTranslation(String key, Locale locale, Object... params);
}

Die erste Methode gibt eine Liste von den derzeit unterstützten Sprachen zurück. Bei der zweiten Methode handelt es sich um die Übersetzung selbst. Angefordert wird eine Übersetzung basierend auf einem Schlüssel, einer Zielsprache und einer beliebigen Menge an Zusatzattributen.

I18NProvider – HelloWorld

Kommen wir zu einem I18N Hello-World-Programm. Der einfachste Fall ist das Vorhandensein einer einzigen Sprache. Um diesen Mechanismus nun initial zu implementieren, erstellen wir eine Klasse I18NProviderImpl. Die Implementierung selbst ist sehr geradlinig: Nachdem ein implements I18NProvider hinzugefügt worden ist, können wir mit der Implementierung der beiden Methoden beginnen. Als erstes kommt die Methode getProvidedLocales dran. Wir liefern dafür lediglich ein Set mit genau einem Element zurück, einem Locale ENGLISH.

public class I18NProviderImpl implements I18NProvider {

  @Override
  public List<Locale> getProvidedLocales() {
    return List.of(Locale.ENGLISH);
  }

  @Override
  public String getTranslation(String s , Locale locale , Object... objects) {
    return null;
  }
}

Kommen wir nun zu der Implementierung der Methode getTranslation. Für den ersten Test reicht eine interne Map<String, String>, die mit den notwendigen Schlüssel/Wert-Paaren versehen wird. Der Zugriff erfolgt innerhalb der Methode getTranslation. Immer dann, wenn es den angefragten Schlüssel nicht gibt, soll der Schlüssel selbst zurückgegeben werden. So erkennt man recht schnell, ob noch Werte fehlen.

public class I18NProviderImpl implements I18NProvider, HasLogger {

  private static final Map<String, String> translations = new HashMap<>();

  @Override
  public List<Locale> getProvidedLocales() {
    return List.of(Locale.ENGLISH);
  }

  @Override
  public String getTranslation(String s , Locale locale , Object... objects) {
    return translations.getOrDefault(s , s);
  }
}

Nun haben wir eine mögliche Implementierung für das Interface I18NProvider. Allerdings fehlen noch zwei grundlegende Dinge: Zum einen muss die Implementierung aktiviert werden und zum anderen muss die Verwendung des I18NProvider in der Anwendung erfolgen.

Die Verwendung selbst ist schnell realisiert. Die Klasse Component stellt einem schon die Methode getTranslation(String key) zur Verfügung und leitet diese Anfrage dann an den aktuell aktivierten I18NProvider durch. Wenn man diesen als Ausgangspunkt für weitere Nachforschungen nimmt, so gelangt man nach ein paar Klicks durch die Quelltexte von Flow zu der folgenden Methode in der Klasse DefaultInstantiator:

 /**
     * Get the I18NProvider property from the session configurator or try to
     * load it from application.properties property file.
     *
     * @return I18NProvider parameter or null if not found
     */
    private String getI18NProviderProperty() {
        DeploymentConfiguration deploymentConfiguration = service
                .getDeploymentConfiguration();
        if (deploymentConfiguration == null) {
            return null;
        }
        return deploymentConfiguration
                .getStringProperty(Constants.I18N_PROVIDER, null);
    }

Die letzte Zeile gibt an, wie nach der aktiven Implementierung bei der Verwendung des DefaultInstantiator gesucht wird. Die Property vaadin.i18n.provider wird verwendet, um den Klassennamen der Implementierung zu erhalten. Diese Property kann man nun auf verschiedene Arten setzen, z. B. beim Aufruf der JVM selbst oder in der web.xml als WebInit-Parameter für das Servlet. Oder man geht den ganz einfachen Weg und setzt diese Property schlicht und ergreifend beim Start der Anwendung in der main-Methode.

System.setProperty("vaadin.i18n.provider" , I18NProviderImpl.class.getName());

Damit kann jetzt auf die Implementierung zugegriffen werden.

Kommen wir nun zu den Definitionen der Schlüssel/Wert-Paare: In der ersten trivialen Implementierung setzen wir diese Wertepaare einfach innerhalt eines static-Blocks in der Implementierung des I18NProviders; in der Implementierung unserer Log-in View werden die jeweiligen Schlüssel dann beim dem Setzen der Textinhalte verwendet.

 //I18N
    username.setPlaceholder(getTranslation("login.username.placeholder"));
    password.setPlaceholder(getTranslation("login.password.placeholder"));
    rememberMe.setLabel(getTranslation("login.rememberme.label"));
    btnLogin.setText(getTranslation("login.button.ok.text"));
    btnCancel.setText(getTranslation("login.button.cancel.text"));

 

public class I18NProviderImpl implements I18NProvider, HasLogger {

  private static final Map<String, String> translations = new HashMap<>();

  static {
    translations.put("login.username.placeholder" , "username");
    translations.put("login.password.placeholder" , "password");
    translations.put("login.rememberme.label" , "remember Me");
    translations.put("login.button.ok.text" , "OK");
    translations.put("login.button.cancel.text" , "Cancel");
  }

  @Override
  public List<Locale> getProvidedLocales() {
    return List.of(Locale.ENGLISH);
  }

  @Override
  public String getTranslation(String s , Locale locale , Object... objects) {
    return translations.getOrDefault(s , s);
  }
}

Fazit

Wir haben in diesem Teil der Serie eine erste Implementierung des I18NProviders aktiviert und verwendet. Im nächsten Teil werden wir uns ansehen, wie man dies nun auf mehrere Sprachen ausweitet und dafür eine praxistaugliche Implementierung begutachten. Das Beispiel zum vorliegenden Serienteil ist wie immer auf GitHub zu finden.

Wer Fragen und Anmerkungen hat meldet sich am besten per Twitter an @SvenRuppert oder direkt per Mail an sven.ruppert@gmail.com

Happy Coding!

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Hinterlasse einen Kommentar

3 Kommentare auf "Backend meets Frontend Reloaded: I18N – Mehrsprachige Log-in-Seiten"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Thorsten Göckeler
Gast

Schöner Artikel, auch wenn jetzt auf die „praxistaugliche Implementierung“ gewartet werden darf, die sicherlich Hinweise gibt, wie der Provider mit den Standard-Bordmitteln von Java verheiratet wird, oder?

Bis dahin könnte die Zeile 10 im letzten Listing etwas Aufmerksamkeit vertragen: translations.put(„login.button.cancel.text“ , „OK“); // please replace with „Cancel“

Dominik Mohilo
Mitglied

Hallo Thorsten,

Listing wurde korrigiert 🙂 Vielen Dank für den Hinweis!

Liebe Grüße,
Dominik

Sven Ruppert
Gast

Servus Thorsten,
erst einmal Danke für den Hinweis im Listing. Im Quelltext ist es schon korrigiert gewesen.. nur hier noch nicht.
bzgl der Implementierung.. da hilft wohl nur gespanntes warten 😉