Open timofei-iatsenko opened 12 months ago
@thekip Thank you for the spending your time reviewing the plugin!
To use macro it indeed requires an additional compilation step. However there no issue with using macro with Vite using babel-macro or SWC. We have a couple working examples in the repo.
Yeah, at first I wanted to use lingui's provided macro as it is since it seems very feature complete, but I wasn't able make it work with Sveltekit + Vite, which I think is due to the incompatible module format (babel-plugin-macros
is not exporting an ESM-compatible module which both SK and Vite expects).
Considering that the recommended approach to build Svelte apps is to use SK + Vite, and they don't use Babel internally to compile the code, I tried to emulate as much of the macro's functionality as possible in this plugin, both through the extraction process and through the runtime code. In particular, converting message declarations to id is done both statically during extraction, and during runtime to generate the key for looking up the compiled catalog.
However, I agree with your observations that there is only so much that can be done without the compiler approach. I was able to implement the basic features without doing the macro approach of preprocessing the code directly, but I started stumbling upon the implementation when trying to implement the more advanced cases like nested plurals, since the runtime side of the code are unable to get the name of the variable being passed in to the placeholders like the name in t`Hello {name}`
example you gave.
So it's very beneficial to make macro work with svelte. I'm not particularly familiar with Svelte, but i can offer my help to achieve this from compiler / ast point of view. I quickly checked svelte compiler options, and see that it supports "preprocess" param. I believe that could be useful to implement full macro support into Svelte code and template blocks.
Thanks for offering to help, it is greatly appreciated! I think that is correct, we can use the preprocess function exposed by the compiler to create a preprocessor to be called before or after the default vitePreprocess.
I don't have much experience with code replacement, I initially thought I'll need to replace the existing ast nodes with new nodes that I'll need to generate on my own to make this work. However, I see the docs' example to make modifications to the code is to handle the code as string and use a package called magic-string
to make modifications and generate a source map automatically. Perhaps this way is simpler?
However, I see the docs' example to make modifications to the code is to handle the code as string and use a package called magic-string to make modifications and generate a source map automatically. Perhaps this way is simpler?
I believe they use magic string approach for the sake of simplicity. Doing real-world scenarios on the code is not possible using simple string replace mechanisms. So you need in preprocess function parse the string into ast, do some changes and the stringify AST back.
Actually i could help with this, what i really need is couple of examples, something like input -> output.
In the begining it could be prototyped with babel and JS for the sake of simplicity and development speed. Then it could be ported to the SWC + Rust to make this additional compile step as fast as possible.
Got it, thanks for the pointers! With the plan to switch to the compiler approach, I went back to the drawing board and reconsidered some of the design decisions. With the current version I made some deviations from Lingui's JS macro syntax to differentiate usages in different places due to them all being executed on runtime. With the compiler approach, I think it's possible to closely follow Lingui's JS macros syntax 🎉
The current version has 6 functions:
$t
and $plural
to make them reactive when the locale changesgt
and gPlural
msg
and msgPlural
I'm thinking instead of providing Svelte stores and make every piece of message in the component reactive, it's going to be much simpler to force rerender of the entire tree when the user requests a locale change with a key
block on the root of the tree, that will use the current locale as the value. With this approach, the store and non-store syntax can be combined, and the compiler approach will make msgPlural
unnecessary too.
I think we can start with basic form t
syntax. It will be the same syntax on both Svelte and JS/TS files and look very similar to Lingu's macro, save for the module name to import from:
import { t } from "svelte-i18n-lingui";
const message = t`Hello World`;
// ↓ ↓ ↓ ↓ ↓ ↓
import { i18n } from "@lingui/core";
const message = i18n._(
/*i18n*/ {
id: "mY42CM",
message: "Hello World",
}
);
Could you show me how the compilation above should be written? I'll then try implementing it either with babel or with a Vite plugin (I had issues with importing babel-plugin-macros
before, Vite complains that it is a CommonJS plugin).
Could you show me how the compilation above should be written? I'll then try implementing it either with babel or with a Vite plugin (I had issues with importing babel-plugin-macros before, Vite complains that it is a CommonJS plugin).
I believe that Svelte with theirs custom extension for files has the same problems with https://www.npmjs.com/package/vite-plugin-babel-macros as Vue integration. I once investigated and proposed my custom solution here. You can use this as example to start from.
But still, abilities of macro-babel-plugin is very limited. I think it would not be possible to make comprehensive Svelte implementation with it. For Svelte you would need to write custom transformations.
The anatomy of lingui macro is
For svelte integration i think we could drop babel-macro-plugin and find interesting nodes by our own then pass these nodes to existing macro code to reuse it.
Unfortunately, lingui macro code is not modular, so you will not able to re-use parts from it in your custom integration, but this is TBA, for PoC you can start from copy-pasting it.
Also if i were you i would play with svelte compiler, put console.log or use debugger to see what exactly you get in this preprocess step and how you can use it.
If you want we can connect in the Discord and i could guide you thru the process so you will understand it better.
You could also check other svelte preprocess plugins to pickup some ideas This one for instance: https://github.com/l-portet/svelte-switch-case/blob/master/src/index.ts
Hi @HenryLie, thanks for taking time to bring support for Svelte into Lingui. I quickly reviewed the docs and code and have some questions / suggestions:
To use macro it indeed requires an additional compilation step. However there no issue with using macro with Vite using babel-macro or SWC. We have a couple working examples in the repo.
Using macro has few main advantages over "native" approach:
t`Hello {name}`
context
,defaultMessage
and generate id.plural
.So it's very beneficial to make macro work with svelte. I'm not particularly familiar with Svelte, but i can offer my help to achieve this from compiler / ast point of view.
I quickly checked svelte compiler options, and see that it supports "preprocess" param. I believe that could be useful to implement full macro support into Svelte code and template blocks.