cibernox / svelte-intl-precompile

I18n library for Svelte.js that analyzes your keys at build time for max performance and minimal footprint
https://svelte-intl-precompile.com
ISC License
274 stars 13 forks source link

Changing language doesn't cause reactive updating when immutable compiler option enabled #44

Closed robertadamsonsmith closed 2 years ago

robertadamsonsmith commented 2 years ago

I have a svelte project where the user can change language without reloading the page. To do this, $locale is simply set to a new value in response to a button click. This works as expected, unless the immutable compiler option is enabled, in which case changing $locale doesn't trigger any reactive updating.

The existing $format function is defined as:

export const $format = /*@__PURE__*/ derived([$locale, $dictionary], () => formatMessage);

When $locale is altered, because the derived store value hasn't changed (it is still a reference to the same function), the runtime assumes that nothing has changed, and so no reactive updating should be triggered.

As a work-around, I've implemented my own version of this function to use instead, which simply wraps formatMessage in an anonymous function which will then be different every time it is checked. It wouldn't say it is the neatest solution, but it works:

export const $format = derived([$locale, $dictionary], () => (...args)=>formatMessage(...args));

The other format functions (formatTime etc.) have the same issue.

cibernox commented 2 years ago

That's interesting, TBH I never turned on the immutable option on the compiler. If I'm right that works at a component level, right? I mean, you can enable that only on some component, and those components are the one that would fall out of sync when the locale changes.

Probably making $format return a new function for every instance of $t is somewhat wasteful. Two ideas come to mind to solve this:

Create a special function to be used only on components that are immutable.

Something like $immutableFormat. I don't love it because it forces you to use a different API depending on how you compile the component. Yuck!

Limit allocations by caching format functions

const FORMATS_CACHE = {};
export const $format = /*@__PURE__*/ derived([$locale, $dictionary], () => FORMATS_CACHE[$locale] || FORMATS_CACHE[$locale] = formatMessage.bind($locale)); // Using bind is probably neat, not required tho.

How do you feel about each one?

robertadamsonsmith commented 2 years ago

You can enable the immutable option on a per component basis, but you can also (as I do) enable it globally.

I'm not sure what the best implementation is. I would certainly avoid using a separate store. In my somewhat wasteful implementation, the anonymous function will only be recreated when the $locale of $dictionary stores are altered, which I can't imagine happens enough to be worth optimising - without benchmarking I couldn't say for sure though.

This might overlap somewhat with #39 , so I might have more thoughts on it once things have resolved there.

cibernox commented 2 years ago

@robertadamsonsmith version published just now (0.9.1) should work with the immutable compiler option enabled. Now I'm going to investigate how to best deal with SSR race conditions

cibernox commented 2 years ago

I'm going to close this. Please confirm that it works in your app.