hilla
hilla copied to clipboard
[i18n] Hilla React I18n solution
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
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
}
Removed this one from the roadmap board in favor of the high-level PRD issue: https://github.com/vaadin/platform/issues/7468