hilla icon indicating copy to clipboard operation
hilla copied to clipboard

[i18n] Hilla React I18n solution

Open sissbruecker opened this issue 1 year ago • 2 comments

This is an epic for implementing a first version of an I18n solution for Hilla React.

This initial version should cover:

  • Automatically detect a sensible initial language for the client
  • Allow developers to configure an explicit language to override auto-detection
  • Load translations from the Java backend
  • Provide a way to block app rendering, or show a loading placeholder, until translations are loaded
  • Translate strings, including support for basic placeholder replacement
  • Change language on the fly
  • Ensure React components re-render when language changes
  • Allow running global side-effects outside of React components when language changes

We want to base the implementation on signals, which allows reactive updates in React components, as well as in global side-effects, when translations are loaded or the language changes.

The following prototype provides a basic functional implementation and outline of the API: https://github.com/sissbruecker/hilla-i18n-client-prototype

Related issues:

  • https://github.com/vaadin/hilla/issues/2107
  • https://github.com/vaadin/hilla/issues/2102
  • https://github.com/vaadin/hilla/issues/2103
  • https://github.com/vaadin/hilla/issues/2108
  • https://github.com/vaadin/hilla/issues/2104
  • https://github.com/vaadin/hilla/issues/2105

sissbruecker avatar Feb 22 '24 16:02 sissbruecker

I have been using this for a while and seems to work very well. Also hotswap etc. The only thing I think is missing is a way frontend/React can use the locale set in backend. I am using the cookie now as a workaround, or could use an endpoint. Maybe: i18n.configure({useVaadinServerLocale : true}).then(root.render(createElement(App)));

Saw some samples of configuration. I think it could be as simple as this:

index.tsx. Copy if the auto-generated and only added modified the last line added i18n.configure()

function App() {
  return <DialogProvider><RouterProvider router={router}/></DialogProvider>;
}

const outlet = document.getElementById('outlet')!;
let root = (outlet as any)._root ?? createRoot(outlet);
(outlet as any)._root = root;
i18n.configure().then(root.render(createElement(App)));

Last line...

@SpringComponent
class ServiceInitListener : VaadinServiceInitListener {
    override fun serviceInit(serviceInitEvent: ServiceInitEvent) {
        serviceInitEvent.source.addUIInitListener { uiInitEvent ->
            initLanguage(uiInitEvent.ui)
        }
    }

    private fun initLanguage(ui: UI) {
        var localeCookie: Cookie? = null
        VaadinRequest.getCurrent().cookies?.let {
            localeCookie = it.firstOrNull { cookie -> LOCALE_COOKIE_KEY == cookie.name }
        }
        val locale = localeCookie?.let { lc ->
            if (lc.value != "") Locale.forLanguageTag(lc.value) else null
        } ?: VaadinService.getCurrentRequest().locale
        ui.locale = if (LanguageTranslationUtil.isNorwegian(locale)) LocaleUtil.NO else LocaleUtil.EN
        //Need to save it for frontend language translation
        LanguageTranslationUtil.setVaadinLanguageSettingsCookieForFrontend(ui.locale)
    }
}
fun setVaadinLanguageSettingsCookieForFrontend(locale: Locale?) {
        VaadinService.getCurrentResponse().addCookie(Cookie("vaadinLanguageSettings", "{%22language%22:%22${locale?.toLanguageTag()}%22}")) //For frontend
    }

sveinnetnordic avatar Feb 14 '25 08:02 sveinnetnordic

Removed this one from the roadmap board in favor of the high-level PRD issue: https://github.com/vaadin/platform/issues/7468

Legioth avatar Mar 31 '25 08:03 Legioth