sveltejs/sapper

support i18n or allow to the Store contain functions

Closed this issue ยท 18 comments

tevel commented

it seems like sapper does not support i18n...

I mean possibility to add language files and easily translate string in templates as well as in javascript code. e.t.c: this.$t('home.wellcome') will be translated to 'Welcome guest' from file /lang/en-US.json.

Is support of i18n planned?

tevel commented

well, I added to the store function t() to translate the text with parameters
everything works,
but now I can't reuse STORE from server on the client
because it contains function.

my store is not POJO

so whats is the right solution for translation?

  1. find another correct way for translate functions in templates and javascript code
  2. or use Svelte/Store but let's consider using functions in the Store.

is it possible to modify devalue to ignore functions instead of throwing exception?
middleware.ts.js line 320:
function try_serialize(data) { try { return devalue(data); } catch (err) { return null; } }

tevel commented

I liked the second way: using global functions in the Store
because it useful for many purposes: my own API calls e.t.c.

Please let's solve the problem transferring Store state from server to client and IGNORING functions instead of dropping off entire the state.

I think the correct approach here is to make your store be an instance of a subclass of Store, and to put your methods on the prototype/class and not as data on the store. Then when Sapper calls store.get() it just gets the data - and it will not get the methods, which would always be the same, and aren't really part of the store's state. Something like a string specifying the currently-selected language is a good candidate for living in the store's state however.

tevel commented

I tried, this approach is not useful because I can't access these methods in template!

I'm using something like store.set({ t: (...args) => i18next.t(...args) }); on the client. Why can't you use a function on the client? Perhaps I missed something.

tevel commented

yes, I'm doing exactly the same!
but I'm doing this also on the server side.

and when Sapper trying to send the state from the server to the client it fails.
because Sapper is using devalue to get string from state.
devalue requires no functions and objects inside the state.

To solve this problem temporarily I did:

  1. in my own Store object I added method get to override the Svelte's method
  2. in this method I check if it called from Sapper middleware I return entire state except my function.
    it works but begging for better solution.

// patch to hide function t in the state
get() {
let res = super.get.apply(this, arguments)
if (arguments.length < 1) {
const stackTrace = new Error().stack
if (stackTrace.indexOf('middleware.ts') >= 0) {
res = Object.assign({}, res)
// console.log('Patch: t function removed from Store')
delete res.t
}
}
return res
}

it works fine.
state successfully transfered from server to client in index.html

I have been using the hack suggested by @tevel to support i18n, but it just broke in the release of 0.19.1 because. Apparently middleware.ts had changed to middleware.js. I think it would be nice with a more robust solution, for use cases like this.

libraries like https://github.com/kaisermann/svelte-i18n/ is also not usable i sapper projects because of this issue.

tevel commented

thanks for update, I will not upgrade to 0.19.1 then

You can update, you just need to change 'middleware.ts' to 'middleware.js'

Unfortunately this hack is broken again with the move to having everything in __sapper__/server.js. Maybe instead of ignoring functions in the serialising we could have two functions for getting data from the store? One for use when rendering templates that can include helper functions and another for serialisation, so we can remove these functions. Another option would be to allow the store to serialise itself then this can be overridden by anyone who wants to.

@superafroman the stackTrace.indexOf('middleware.ts') >= 0 condition is simply a way among many others to detect that you are server-side. What you can do is to subclass the Store class (to overwrite the get method) directly in your server.js file. That way you do not need the above condition. It is working great for me.

@laurentpayot My understanding was the check was to see if get() was being called in order to serialize the data (though that was just an assumption!). If this isn't the case why bother adding functions to the store at all on the server? You could get away with just adding them in client.js?

My problem is I need these functions available in my templates for server-side rendering, so I only want to remove them when the store is being serialized for sharing with the client.

get itself does not do the serialization, for what I understand.
The idea behind the clever @tevel's hack is that in your templates get is always called with an argument. But it is called without argument to get the whole store just before serialization. That's where you remove the translation function (t or _ in case of svelte-i18n).

Maybe this is something that has change as well, but get() doesn't take arguments - it simply returns this._state. So there's no way to tell if the function is being called to look up a property or for serialisation.

You're right get should be used without arguments. The fact is that arguments.length is used in this special case, apparently for a good reason. I'm too busy to look in the internals for the reason why, but it works ^^

Ah, OK - I've figured out my problem. In fact neither the middleware.ts or arguments.length checks are required. The problem I had was I was effectively calling delete this._state._ to remove the i18n functions - obviously removing the functions from state altogether rather than just from the result. Overriding get() to the following has solved it for me.

get() {
  const res = { ...this._state };
  delete res._;
  return res;
}

I just tried it and the arguments.length check is indeed not required. Makes sense.

pngwn commented

Closing in favour of #576.