{"id":19168457,"url":"https://github.com/vaadin-developer/i18n-page-title-for-flow","last_synced_at":"2025-08-11T06:34:59.201Z","repository":{"id":57739937,"uuid":"181714930","full_name":"vaadin-developer/i18n-page-title-for-flow","owner":"vaadin-developer","description":null,"archived":false,"fork":false,"pushed_at":"2020-03-29T14:16:00.000Z","size":46,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-07-29T09:51:09.752Z","etag":null,"topics":["flow","i18n","internationalization","ruppert","sven","vaadin"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vaadin-developer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-16T15:16:16.000Z","updated_at":"2020-02-01T13:28:48.000Z","dependencies_parsed_at":"2022-08-30T10:50:28.392Z","dependency_job_id":null,"html_url":"https://github.com/vaadin-developer/i18n-page-title-for-flow","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/vaadin-developer/i18n-page-title-for-flow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fi18n-page-title-for-flow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fi18n-page-title-for-flow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fi18n-page-title-for-flow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fi18n-page-title-for-flow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vaadin-developer","download_url":"https://codeload.github.com/vaadin-developer/i18n-page-title-for-flow/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fi18n-page-title-for-flow/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269842562,"owners_count":24484097,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flow","i18n","internationalization","ruppert","sven","vaadin"],"created_at":"2024-11-09T09:42:45.727Z","updated_at":"2025-08-11T06:34:59.173Z","avatar_url":"https://github.com/vaadin-developer.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ccenter\u003e\n\u003ca href=\"https://vaadin.com\"\u003e\n \u003cimg src=\"https://vaadin.com/images/hero-reindeer.svg\" width=\"200\" height=\"200\" /\u003e\u003c/a\u003e\n\u003c/center\u003e\n\n# Vaadin V10 app with I18N Page Title, dynamically created\n\nWhat I want to show in this example is, how you could deal with \na dynamic page title per view (or time or whatever)\nthat will handle your browser Locale as well.\n\n## the Implementation and Usage\nThe solution should :\n\n* be based on message bundles\n* not be inside inheritance\n* based on Annotations\n* be easy to extend\n* be able to change the language during runtime\n\n### The developer / user view\nMostly it is an excellent approach to develop a solution for a developer \nfrom the perspective of a developer.\nHere it means, what should a developer see if he/she have to use your solution.\n\nThe developer will see this Annotation.\nThree things can be defined here. \n\n* The message key that will be used to resolve the message based on the actual Locale\n* A default value the will be used, if no corresponding resource key was found neither fallback language is provided \n* Definition of the message formatter, default Formatter will only return the translated key.\n\n\n```java\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface I18NPageTitle {\n  String messageKey() default \"\";\n  String defaultValue() default \"\";\n  Class\u003c ? extends TitleFormatter\u003e formatter() default DefaultTitleFormatter.class;\n}\n```\n\nThe default usage should look like the following one.\n\n```java\n@Route(View003.VIEW_003)\n@I18NPageTitle(messageKey = \"view.title\")\npublic class View003 extends Composite\u003cDiv\u003e implements HasLogger {\n  public static final String VIEW_003 = \"view003\";\n}\n```\n\nNow we need a way to resolve the final message and the right point in time to set the title.\nHere we could use the following interfaces.\n\n* VaadinServiceInitListener, \n* UIInitListener, \n* BeforeEnterListener\n\nWith these interfaces we can hook into the life cycle of a view. At this time slots, we have all the information's we need. \nThe Annotation to get the message key and the locale of the current request.\n\nThe class that is implementing all these interfaces is called **I18NPageTitleEngine**\n\n```java\npublic class I18NPageTitleEngine \n       implements VaadinServiceInitListener, \n                  UIInitListener, \n                  BeforeEnterListener, \n                  HasLogger {\n\n  public static final String ERROR_MSG_NO_LOCALE = \"no locale provided and i18nProvider #getProvidedLocales()# list is empty !! \";\n  public static final String ERROR_MSG_NO_ANNOTATION = \"no annotation found at class \";\n\n  @Override\n  public void beforeEnter(BeforeEnterEvent event) {\n    Class\u003c?\u003e navigationTarget = event.getNavigationTarget();\n    I18NPageTitle annotation = navigationTarget.getAnnotation(I18NPageTitle.class);\n    match(\n        matchCase(() -\u003e success(annotation.messageKey())) ,\n        matchCase(() -\u003e annotation == null ,\n                  () -\u003e failure(ERROR_MSG_NO_ANNOTATION + navigationTarget.getName())) ,\n        matchCase(() -\u003e annotation.messageKey().isEmpty() ,\n                  () -\u003e success(annotation.defaultValue()))\n    )\n        .ifPresentOrElse(\n            msgKey -\u003e {\n              final I18NProvider i18NProvider = VaadinService\n                  .getCurrent()\n                  .getInstantiator()\n                  .getI18NProvider();\n              final Locale locale = event.getUI().getLocale();\n              final List\u003cLocale\u003e providedLocales = i18NProvider.getProvidedLocales();\n              match(\n                  matchCase(() -\u003e success(providedLocales.get(0))) ,\n                  matchCase(() -\u003e locale == null \u0026\u0026 providedLocales.isEmpty() ,\n                            () -\u003e failure(ERROR_MSG_NO_LOCALE + i18NProvider.getClass().getName())) ,\n                  matchCase(() -\u003e locale == null ,\n                            () -\u003e success(providedLocales.get(0))) ,\n                  matchCase(() -\u003e providedLocales.contains(locale) ,\n                            () -\u003e success(locale))\n              ).ifPresentOrElse(\n                  finalLocale -\u003e ((CheckedFunction\u003cClass\u003c? extends TitleFormatter\u003e, TitleFormatter\u003e) f -\u003e f.getDeclaredConstructor().newInstance())\n                      .apply(annotation.formatter())\n                      .ifPresentOrElse(\n                          formatter -\u003e formatter\n                              .apply(i18NProvider , finalLocale , msgKey).\n                                  ifPresentOrElse(title -\u003e UI.getCurrent()\n                                                             .getPage()\n                                                             .setTitle(title) ,\n                                                  failed -\u003e logger().info(failed)) ,\n                          failed -\u003e logger().info(failed)) ,\n                  failed -\u003e logger().info(failed));\n            }\n            , failed -\u003e logger().info(failed));\n  }\n\n  @Override\n  public void uiInit(UIInitEvent event) {\n    final UI ui = event.getUI();\n    ui.addBeforeEnterListener(this);\n    //addListener(ui, PermissionsChangedEvent.class, e -\u003e ui.getPage().reload());\n  }\n\n  @Override\n  public void serviceInit(ServiceInitEvent event) {\n    event\n        .getSource()\n        .addUIInitListener(this);\n  }\n}\n```\nThe method with the name **beforeEnter** is the critical part. Here you can see how the key is resolved.\nHowever, there is one new thing.  Let´s have a look at the following lines.\n\n```java\n              final I18NProvider i18NProvider = VaadinService\n                  .getCurrent()\n                  .getInstantiator()\n                  .getOrCreate(VaadinI18NProvider.class);\n```\n\nThis few lines are introducing a new thing, that is available in Vaadin 10.\nThe interface **I18NProvider** is used to implement a mechanism for the internationalisation \nof Vaadin applications.\n\nThe interface is simple and with only two methods to implement.\n\n```java\npublic interface I18NProvider extends Serializable {\n    List\u003cLocale\u003e getProvidedLocales();\n    String getTranslation(String key, Locale locale, Object... params);\n}\n```\n\nThe first one should give back the list of Locales that could be handled from this implementation.\nThe second method is used to translate the message key. \nIn this method, the handling of a default translation or better the switch into a default language should be handled. Missing keys can be handled differently. Some developers are throwing an exception, but I prefer to return the key itself, \ntogether with the locale from the original request. \nThis information is mostly better to use as a stack trace.\n\nThe solution that is bundled with this demo can handle the Locales EN ad DE, the fallback will be the locale EN.\nThe implementation is not dealing with reloads of message bundles during runtime or other features that are needed for professional environments.\n\n```java\npublic class VaadinI18NProvider implements I18NProvider, HasLogger {\n\n  public VaadinI18NProvider() {\n    logger().info(\"VaadinI18NProvider was found..\");\n  }\n\n  public static final String RESOURCE_BUNDLE_NAME = \"vaadinapp\";\n\n  private static final ResourceBundle RESOURCE_BUNDLE_EN = getBundle(RESOURCE_BUNDLE_NAME , ENGLISH);\n  private static final ResourceBundle RESOURCE_BUNDLE_DE = getBundle(RESOURCE_BUNDLE_NAME , GERMAN);\n\n\n  @Override\n  public List\u003cLocale\u003e getProvidedLocales() {\n    logger().info(\"VaadinI18NProvider getProvidedLocales..\");\n    return List.of(ENGLISH ,\n                   GERMAN);\n  }\n\n  @Override\n  public String getTranslation(String key , Locale locale , Object... params) {\n//    logger().info(\"VaadinI18NProvider getTranslation.. key : \" + key + \" - \" + locale);\n    return match(\n        matchCase(() -\u003e success(RESOURCE_BUNDLE_EN)) ,\n        matchCase(() -\u003e GERMAN.equals(locale) , () -\u003e success(RESOURCE_BUNDLE_DE)) ,\n        matchCase(() -\u003e ENGLISH.equals(locale) , () -\u003e success(RESOURCE_BUNDLE_EN))\n    )\n        .map(resourceBundle -\u003e {\n          if (! resourceBundle.containsKey(key))\n            logger().info(\"missing ressource key (i18n) \" + key);\n\n          return (resourceBundle.containsKey(key)) ? resourceBundle.getString(key) : key;\n\n        })\n        .getOrElse(() -\u003e key + \" - \" + locale);\n  }\n}\n```\nThe Interface **I18NProvider** is implemented for example by the abstract class **Component**.\nHaving this in mind, we are now using the same mechanism for the page title as well as inside a Component. \n\nThe last thing you should not forget is the activation of the **I18NProvider** implementation itself.\nThere are several ways you can use; I am using a simple approach inside the primary method that will start my app itself.\n\n```setProperty(\"vaadin.i18n.provider\", VaadinI18NProvider.class.getName());```\n\n\n\n```java\npublic class BasicTestUIRunner {\n  private BasicTestUIRunner() {\n  }\n\n  public static void main(String[] args) {\n    setProperty(\"vaadin.i18n.provider\", VaadinI18NProvider.class.getName());\n    \n    new Meecrowave(new Meecrowave.Builder() {\n      {\n//        randomHttpPort();\n        setHttpPort(8080);\n        setTomcatScanning(true);\n        setTomcatAutoSetup(false);\n        setHttp2(true);\n      }\n    })\n        .bake()\n        .await();\n  }\n}\n```\n\nThe Vaadin documentation will give you more detailed information´s about this.\n\nThe last step for today is the activation of our **I18NPageTitleEngine**\nThis activation is done inside the file with the name **com.vaadin.flow.server.VaadinServiceInitListener**\nyou have to create inside the folder  **META-INF/services** \nThe only line we have to add is the fully qualified name of our class.\n\n```\norg.rapidpm.vaadin.api.i18.I18NPageTitleEngine\n```\n\nIf you have questions or something to discuss..  ping me via\nemail [mailto::sven.ruppert@gmail.com](sven.ruppert@gmail.com)\nor via Twitter : [https://twitter.com/SvenRuppert](@SvenRuppert)\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaadin-developer%2Fi18n-page-title-for-flow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvaadin-developer%2Fi18n-page-title-for-flow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaadin-developer%2Fi18n-page-title-for-flow/lists"}