hilla icon indicating copy to clipboard operation
hilla copied to clipboard

[i18n] Add support for HMR when changing translations

Open sissbruecker opened this issue 11 months ago • 3 comments

When the developer changes translations in a properties file, or Copilot writes a new translation string into a property file, the frontend should update immediately and render the updated translation without a page reload.

The current approach is to serve translation strings from the server at runtime, where the server looks up translations from a resource bundle packaged into the Java application. That means just changing a properties file would have no effect on the frontend. The developer would also have to recompile the Java app which updates the resource bundle and triggers a restart of the Spring app, which would then also trigger a reload of the page in the browser. Ideally our solution would notice changes to property files and then update translations in the frontend automatically, without requiring a page reload.

Two options come to mind:

  1. In development mode, the server request handler does not use the compiled resource bundle, but loads the contents of the property files from the file system. We would then add a watcher for the property files on the file system, somehow use the Vite dev server to push a notice to the browser that those files have changed, which in turn causes the I18n instance to reload translations from the server.
  2. We don't load translations from the server at all, but instead compile the property files into Javascript modules as part of the Vite build. The I18n instance would use dynamic imports to load these JS modules. By using JS imports we can reuse the Vite HMR mechanism that automatically updates these modules when they are recompiled.

sissbruecker avatar Mar 06 '24 07:03 sissbruecker

Prototype for a Vite plugin that watches translation properties files and sends an HMR update to the browser when those change:

import chokidar, { type FSWatcher } from "chokidar";

let translationsWatcher: FSWatcher;
const translationsHmrPlugin: Plugin = {
  name: "translations-hmr",
  configureServer(server) {
    translationsWatcher = chokidar
      .watch("./src/main/resources/vaadin-i18n/*.properties")
      .on("all", () => {
        server.ws.send({
          type: "custom",
          event: "translations-update",
        });
      });
  },
  buildEnd() {
    translationsWatcher.close();
  },
};

The I18n implementation in the client could then listen for the event and reload translations for the current language:

if (import.meta.hot) {
  import.meta.hot.on('translations-update', () => {
    // reload translations
  });
}

sissbruecker avatar Mar 06 '24 15:03 sissbruecker

Is this really a problem we should solve? None of the copilot features for Java can work properly without jrebel or hotswapagent

edit: okay, updating the UI still needs some event

Artur- avatar Mar 06 '24 15:03 Artur-

I think we should, requiring a Java hot swap solution would be a weird requirement when working on React views when everything else there (views, theme) already works with HMR.

It also doesn't seem to be too difficult to solve. We'll need to make the server work with the property files instead of the resource bundle in dev mode, and then add the Vite plugin to vite.generated.ts. The hard-coded path to the translations is a bit sketchy. Maybe there's a better solution to this, but it seems like a good start at least.

sissbruecker avatar Mar 07 '24 07:03 sissbruecker