-
-
Notifications
You must be signed in to change notification settings - Fork 246
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
Automatic tree-shaking of messages #1
Comments
This comment was marked as outdated.
This comment was marked as outdated.
For very performance critical apps, it might make sense to use components that make use of |
This comment was marked as outdated.
This comment was marked as outdated.
Thanks @amannn for sharing this with me. For those who might be interested, we built a similar Babel plugin solution that works on Next.js: https://github.com/Avansai/next-multilingual Of course, as called out, one of the drawbacks is that all locales are loaded in the same bundle which increases the size. I am planning to try to find a solution as we are adding an SWC version of the plugin. |
@yashvesikar just shared a POC for a webpack plugin that extracts messages based on page entry points with me: https://github.com/yashvesikar/next-intl-translation-bundler-example I'm still mind blown 🤯 AFAICT what we'd have to do (assuming we'd implement this for the
The other part is that this is a webpack plugin, no idea if these will be supported by Turbopack or if we'd have to implement it in Rust instead (if Turbopack even supports plugins at this point). Edit: An improved version of the webpack plugin that supports the App Router was implemented in https://github.com/jantimon/next-poc-i18n-split. Another related project: https://lingui.dev/guides/message-extraction#dependency-tree-crawling-experimental |
@amannn Unfortunately, I don't have much knowledge of the
// src/components/my-component/i18n/en-US.json
{"title": "Title in English", "other": "Some other Key that won't be bundled"}
// src/components/my-component/i18n/fr.json
{"title": "Title in French"}
// src/components/my-component/i18n/index.ts
// loader can check that the keys match the ones in the main locale json
let translations = {title: "title"} as const
// src/components/my-component/my-component.tsx
import { translations } from "/.i18n
export const MyComponent() => {
// by modifying the useTranslations hook we can make t(key) have key be typeof translations
const t = useTranslations(translations);
return(
<div>{t(translations.title)}</div>
)
}
Some known limitations are that the Webpack parser is very limited and fickle. It's not a great way to traverse the AST to get all the keys, but since it's a single toolchain it is nice. I don't have any experience in SWC or TurboPack parsers but I have a hard time imagining they are worse than Webpack. There are a few more things but I will leave it here for now. Sorry for the wall of text, happy that this may be useful to the project :) |
Hey @yashvesikar, thanks a lot for chiming in here and sharing your thoughts in detail!
That's an interesting one, I think @nbouvrette is exploring something similar to this. At least for
Good point! That's something that's working fairly well currently, I'd have to investigate this for sure.
😆 Well, I've read that Turbopack is adding support for Webpack loaders, I'm not sure what the story for plugins is though. This topic is generally a bit futuristic at this point and Turbopack is still quite experimental from what I know. At least for the time being, a webpack plugin would definitely be the way to go.
Hmm, I just console.log'd the entry points—and I can confirm the part with the pages being entry points! But e.g. if Generally I'm currently still quite busy with the RSC integration for |
Great to see I'm not the only one wanting to solve this problem. I don't know if you guys saw but I split the next-multilingual logic that dynamically loads message modules into this package: https://github.com/Avansai/messages-modules It uses Babel to dynamically inject the message in their local scope which is pretty neat but one optimization I would like to fix is that it loads all locales and not just the one being viewed... which of course would end up causing performance issues in the long run. But I think it's still better than a huge message file containing messages that are not even used The big problem ahead is that from what I understood the If you guys are interested to team up, I would be more than happy to get some help. Otherwise, I will most likely work on this later this year (Q3?) which should also give extra time to make the |
Maybe something like like this can help to omit loading all translation files at ones, esp. the caching strategy is interesting here one variant of the server part. https://github.com/QuiiBz/next-international/blob/v2/packages/next-international/src/app/server/create-get-scoped-i18n.ts Maybe this helps |
In
The current plan for this feature is to collect namespaces and keys with a compiler! |
I apologize for the misunderstanding in my earlier message about scoped translations - I should have read the documentation more thoroughly. Scoped translations help streamline code by reducing redundancy. getI18n vs getScopedI18n
About the last sentence...
In which context, the issue about tree shaking or how next-intl currently handles internals? Thx! |
No worries! Yep, I agree that scoping translations is helpful. next-intl is heavily based on that too! 🙂 |
Is there any progress? |
@stramel Not yet, no. I was generally a bit busy with other tasks recently, but I'm planning to pick up this topic again early next year to hopefully introduce this in the v4 range. The current state is that there's a promising proof of concept (see #1 (comment)) that seems to show that this can be done. I'd like to explore this topic in combination with #962 since these topics are related. I'll keep this issue updated once I have more. Thanks everyone for showing so much interest in this feature, I absolutely know this is an important feature for many of you! |
Messages can either be consumed in Server- or Client Components.
The former is recommended in the docs, as it has both performance benefits as well as a simpler model in general since there is only a single environment where code executes. However, for highly interactive apps, using messages on the client side is totally fine too. The situation gets a bit hairy when you need to split messages based on usage in Client Components (e.g. by page, or a mix of RSC / RCC).
Ideally, we'd provide more help with handing off translations to Client Components.
Ideas:
(1) User-defined namespaces
This is already possible (and shown in the docs), but unfortunately not so maintainable. For single components it can be fine however, so when you have many small interactive leaf components, this is quite ok.
(2) Manually composed namespaces
This worked well for the Pages Router and was documented, but unfortunately doesn't work with RSC.
Apollo has a similar situation with static fragments: apollographql/apollo-client-nextjs#27
A potential solution could be to use a build-time macro that is evaluated separately.
(3) Compiler-based approach
See #1 (comment). Similar to fragment hoisting in Relay, if the collection of namespaces happens at build time, we could provide this list to Server Components. This seems to be the best option.
Design-wise this requires analyzing the namespace that is passed to
useTranslations
. Furthermore, a nice-to-have would be to analyze which keys are passed tot
(if all calls are statically known).Current progress:
See also: Dependency tree crawling
The text was updated successfully, but these errors were encountered: