Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: type-safe messages #1649

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

GalacticHypernova
Copy link

@GalacticHypernova GalacticHypernova commented Nov 30, 2023

Closes #1124
Closes nuxt-modules/i18n#1753

This PR adds type safety for message translations when used with helpers like t().

The reason it's a breaking change is because it's more strict, which could bring some edge case bugs with non-existent messages.

It is a draft for now because there is much work I need to do to get it to work.

Benefits:

This allows for much safer and convenient locale message translation by providing autocomplete suggestions to make sure no typos are present. Essentially a very useful DX update.

Related: #1648
(Had to redo a certain part of the PR so decided to re-open it)

Proof-of-concept:

Smaller object:
image
Bigger object
image
image

Performance implications:

None. As with the proof of concept, we have tested (with @BobbieGoede) the performance on extremely large objects. (Tested with core.js package-lock.json file). The results were pretty much instant and no performance downgrade has been detected, view video in the attached folder below:
Video.zip (Video was too big and I couldn't get it to save a trimmed version, while testing with Bobbie I had experienced the same issue and had to send the same zip file, apologies in advance!)

Important note

I do not know how to generate unit tests for it, I'm still looking into it.

@GalacticHypernova
Copy link
Author

GalacticHypernova commented Nov 30, 2023

TODO: Allow for non-existent messages to go through. If done correctly, could prevent a breaking change.

Also yes, I will move the type to /types/, this is not final yet as I'm very early into the development of this feature.

@GalacticHypernova
Copy link
Author

GalacticHypernova commented Dec 1, 2023

@kazupon should this change VueMessageType or should this be added as an overload of t? Curious to hear your opinion on how to proceed with this. Both options have their advantages, and I would imagine backwards compatibility is preferred.

I was also thinking of making a "lazy loaded" version of this that would essentially only display autocompletions for the keys directly under the current scope, which would basically emulate object property intellisense in VSCode while being in a string. Would love to hear your thoughts on this as well!

@kazupon
Copy link
Member

kazupon commented Dec 2, 2023

Thank you for your contrivution!

First we would to confirm, I don't know from this PR what is the issue.
Could you first explanation on the details of the issue raised? 🙏

@kazupon kazupon added the Status: Need More Info Lacks enough info to make progress label Dec 2, 2023
@GalacticHypernova
Copy link
Author

GalacticHypernova commented Dec 2, 2023

@kazupon sure thing!
Basically, the current translation functions like t don't have type safety, because they accept a simple string or a number. This would allow autocomplete suggestions as well as type safety for those functions, prompting an error if no matching message is found. It's not really an issue, it's more of a DX improvement for developers with a ton of translations.

Also, just to clarify, it's in no way done, that's why it's still a draft and not even ready for review yet. There's still a lot for me to do to make it stable and then figure out how to generate tests for it.

@kazupon
Copy link
Member

kazupon commented Dec 4, 2023

@GalacticHypernova
Thank you for your explanation!

should this change VueMessageType or should this be added as an overload of t? Curious to hear your opinion on how to proceed with this. Both options have their advantages, and I would imagine backwards compatibility is preferred.

I am glad of your PR because I wanted to achieve fully type-safe message!
Please, let's go ahead! :)

I do not know how to generate unit tests for it, I'm still looking into it.

You can add type test to test-dts directory.

@GalacticHypernova
Copy link
Author

GalacticHypernova commented Dec 4, 2023

Of course!

You can add type test to test-dts directory.

I mean, this would require unit testing of type inference, rather than regular types. It partially relies directly on the typescript engine. Does vitest have something to deal with that?

Please, let's go ahead! :)

Could you please tell me where the messages themselves are stored (as in, the messages object from which you're reading the translations)? I'm seeing multiple potential places, but thought I'd ask you. Is it the C in the ComposerTranslation? Or is it the readonly messages in the composer?

Copy link
Member

kazupon commented Dec 7, 2023

I mean, this would require unit testing of type inference, rather than regular types. It partially relies directly on the typescript engine. Does vitest have something to deal with that?

Yeah, vitest has type testing.
You can read it at here
https://vitest.dev/guide/testing-types.html

vue-i18n has its own type check utils for type testing at test-dts, but you can replace it with vitest since it uses it.

Could you please tell me where the messages themselves are stored (as in, the messages object from which you're reading the translations)? I'm seeing multiple potential places, but thought I'd ask you. Is it the C in the ComposerTranslation? Or is it the readonly messages in the composer?

The messages themselves are stored in Compose's readonly messages, which is typed with the Messages passed in the Composer's generics.
ComposerTranslation is also typed with Composer's generics. ComposerTranslation is also typed with Composer's generics type, so that the t function can be used to complete the key.

@kazupon kazupon added good first issue Good for newcomers Status: PR Welcome Welcome to Pull Request Type: Improvement Includes backwards-compatible fixes 🍰 p2-nice-to-have Priority 2: nothing is broken but it's worth addressing typescript and removed Status: Need More Info Lacks enough info to make progress labels Dec 7, 2023 — with Volta.net
@GalacticHypernova
Copy link
Author

GalacticHypernova commented Dec 7, 2023

Thank you!
That means I'd probably be able to finish this PR's core functionality today, and then try the tests

@sagsagg
Copy link

sagsagg commented Dec 7, 2023

This is an awesome feature. But may I ask, Is this useful for JSON files?

@GalacticHypernova
Copy link
Author

@sagsagg I am planning to make it JSON compatible, yes. I'd have to see if json files get treated differently in terms of message parsing and adjust accordingly, but it is planned.

@GalacticHypernova
Copy link
Author

GalacticHypernova commented Dec 7, 2023

@kazupon does this make sense? From the tests that are already present the functionality appears to stay the same, still getting all the right messages. Would appreciate a review from you so that in case something went wrong I won't need to rewrite everything. If I get the green light from you I could start changing all the translation functions.

EDIT: Getting some weird behavior, investigating. It appears as though the tests fail with this one because it doesn't recognize the Locale when defining messages with locales in the composer (Locale is probably an empty string), leading to no autocomplete and defaulting to string. Either I'm missing something or I'm missing something. Does the locale not separate the messages in the composer?

EDIT 2: Upon further inspection, I realized the TranslationComposer has a Locales property. When changing it to Messages[Locales] the autocomplete works but TS errors saying Locales can't be used to index Messages. Can we allow it to error or should Locale be fixed? Perhaps we can extend ComposerTranslation from Composer or ComposerOptions? Although I don't quite know how that would affect messages from files.. This still needs a bit of experimentation.

Waiting to consult with you before proceeding on how we should integrate this.

@kazupon
Copy link
Member

kazupon commented Dec 21, 2023

@GalacticHypernova
Sorry my late reply

I realized the TranslationComposer has a Locales property. When changing it to Messages[Locales] the autocomplete works but TS errors saying Locales can't be used to index Messages. Can we allow it to error or should Locale be fixed? Perhaps we can extend ComposerTranslation from Composer or ComposerOptions? Although I don't quite know how that would affect messages from files.. This still needs a bit of experimentation.

Thank you for your contribution.

The value of Locales is passed via Composer and ComposerOptions. So can't be used to index Messages should not ignore the error because it will not be able to support the case where the type parameter is passed by that case. The type definition must be correctly supported so that all cases can be supported.

If can't be used to index Messages is causing wiht your implementation, you should add a type test to test-dts.
Adding it will avoid regression.

@GalacticHypernova
Copy link
Author

@kazupon
Alright, I'll add a type test 👍
There doesn't seem to be a regression from my current testing, but perhaps it's worth investigating why Locale is seen as an empty string. And if you have any idea for a different implementation of it I'd be happy to hear.

@GalacticHypernova
Copy link
Author

@kazupon
I've been looking into this a bit more, and I may have an idea that won't generate a type error, although I would like to discuss it with you regarding any possible performance impact it might bring, since i didn't get to test it enough like I did with my current idea. What is the type of ResourceKeys? If that's the thing that provides all the autocomplete options in the tests, it might be possible to "flatten" them into messages like I currently am trying with the actual Messages type.. but then again, I don't know how it will behave performance wise, so I'm gonna have to test it very carefully.

@Tzyito
Copy link

Tzyito commented Mar 5, 2024

Is this pr still going on? Look forward to

@GalacticHypernova
Copy link
Author

GalacticHypernova commented Mar 5, 2024

Is this pr still going on? Look forward to

Yea, it is still in progress. Sorry, was quite busy lately with personal life. This will likely take a little longer as there seems to be a limitation with a strictly typed solution which would mean a refactor is in order, and testing that will take a little bit. I'm just testing it locally and haven't pushed it yet.

@hongzzz
Copy link

hongzzz commented Mar 24, 2024

Looking forward to this feature 😄

@yarataliba
Copy link

Thank you for your contribution!

@GalacticHypernova
Copy link
Author

GalacticHypernova commented May 24, 2024

@kazupon Was thinking about this a little more. Obviously reading the messages from the files with a typed solution won't be possible, considering types can't access runtime objects (unless we can do somehow FlattenKeys<typeof import("path/to/file")>?). How about generating type definitions, like during postinstall when nuxt generates all the types? The files could be taken and provided the given types which could be used for $t (and the other translation methods).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers 🍰 p2-nice-to-have Priority 2: nothing is broken but it's worth addressing Status: PR Welcome Welcome to Pull Request Type: Improvement Includes backwards-compatible fixes typescript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Resource Keys completion does not work with global messages and $t $t() autocomplete support
6 participants