wuchale 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -94
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin/compile.js.map +1 -0
- package/dist/plugin/gemini.d.ts +28 -0
- package/dist/plugin/gemini.js +102 -0
- package/dist/plugin/gemini.js.map +1 -0
- package/dist/plugin/index.d.ts +53 -0
- package/dist/plugin/index.js +294 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/{preprocess → plugin}/prep.d.ts +7 -9
- package/dist/{preprocess → plugin}/prep.js +17 -38
- package/dist/plugin/prep.js.map +1 -0
- package/package.json +21 -11
- package/dist/preprocess/compile.js.map +0 -1
- package/dist/preprocess/gemini.d.ts +0 -4
- package/dist/preprocess/gemini.js +0 -55
- package/dist/preprocess/gemini.js.map +0 -1
- package/dist/preprocess/index.d.ts +0 -48
- package/dist/preprocess/index.js +0 -230
- package/dist/preprocess/index.js.map +0 -1
- package/dist/preprocess/prep.js.map +0 -1
- /package/dist/{preprocess → plugin}/compile.d.ts +0 -0
- /package/dist/{preprocess → plugin}/compile.js +0 -0
package/README.md
CHANGED
|
@@ -50,9 +50,9 @@ Messages are compiled into arrays with index-based lookups. Runtime only
|
|
|
50
50
|
concatenates and renders — no regex, replace, or complex logic. And the
|
|
51
51
|
compiled bundles are as small as possible, they don't even have keys.
|
|
52
52
|
|
|
53
|
-
### 🔁 HMR & Dev Translations Live updates during development.
|
|
53
|
+
### 🔁 Optional HMR & Dev Translations Live updates during development.
|
|
54
54
|
|
|
55
|
-
Translation files and source changes trigger updates instantly — including
|
|
55
|
+
Translation files and source changes can trigger updates instantly — including
|
|
56
56
|
optional Gemini-based auto-translation. This means you can write the code in
|
|
57
57
|
English and have it shown in another language in the browser while in dev mode.
|
|
58
58
|
|
|
@@ -69,12 +69,12 @@ some existing solutions.
|
|
|
69
69
|
|
|
70
70
|
### ✨ Ready for Svelte 5
|
|
71
71
|
|
|
72
|
-
Works with Svelte 5's new architecture and snippets. Future-proof
|
|
73
|
-
integrated
|
|
72
|
+
Works with Svelte 5's new architecture with runes and snippets. Future-proof
|
|
73
|
+
and tightly integrated
|
|
74
74
|
|
|
75
75
|
## 🚀 Getting Started
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
### Installation
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
80
|
npm install wuchale
|
|
@@ -97,80 +97,138 @@ export default {
|
|
|
97
97
|
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
then set it up in your main component. Assuming `/src/App.svelte`:
|
|
100
|
+
### Configuration
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
<script>
|
|
105
|
-
import {setTranslations} from 'wuchale/runtime.svelte.js'
|
|
102
|
+
To configure `wuchale`, you can do one of:
|
|
106
103
|
|
|
107
|
-
|
|
104
|
+
- Pass an object `wuchale()` in your `vite.config.js` `vite.config.ts`
|
|
105
|
+
- Create a `wuchale.config.js` file that exports the config object as `default` in your project root directory.
|
|
108
106
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
But the latter is recommended as it is also read when using the CLI command to extract items.
|
|
108
|
+
|
|
109
|
+
The config object should look like the following (the default):
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
export const defaultOptions: Config = {
|
|
113
|
+
sourceLocale: 'en',
|
|
114
|
+
otherLocales: [],
|
|
115
|
+
localesDir: './src/locales',
|
|
116
|
+
srcDirs: ['src'],
|
|
117
|
+
heuristic: defaultHeuristic,
|
|
118
|
+
hmr: true,
|
|
119
|
+
geminiAPIKey: 'env',
|
|
120
|
+
}
|
|
120
121
|
```
|
|
121
122
|
|
|
122
|
-
Note that you
|
|
123
|
-
|
|
124
|
-
lazy loading (like this example) or you can import it directly and it will be
|
|
125
|
-
bundled by Vite. After that, you notify `wuchale` to set it as the current one.
|
|
123
|
+
Note that you have to provide `otherLocales`, otherwise it doesn't have any
|
|
124
|
+
effect.
|
|
126
125
|
|
|
127
|
-
|
|
126
|
+
While the others are self explanatory, the `heuristic` is a function that
|
|
127
|
+
globally decides what text to extract and what not to. The `defaultHeuristic`
|
|
128
|
+
is the implementation of the default rules explained below, but you can roll
|
|
129
|
+
your own and provide it here. The function should receive the following
|
|
130
|
+
arguments:
|
|
128
131
|
|
|
129
|
-
|
|
132
|
+
- `text`: The candidate text
|
|
133
|
+
- `scope`: Where the text is located, i.e. it can be one of `markup`, `script`, and `attribute`
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
<p>Hello</p>
|
|
135
|
+
And it should return boolean to indicate whether to extract it.
|
|
133
136
|
|
|
134
|
-
|
|
137
|
+
The `hmr` can be used to turn off the live updates during dev. It will disable
|
|
138
|
+
all text extraction from source files and modification of the `.po` files as
|
|
139
|
+
you modify the source files. This may be desired because the language files are
|
|
140
|
+
imported by the top components and frequent modification of the language files
|
|
141
|
+
can trigger big updates of the DOM, which may cause states not depending on the
|
|
142
|
+
URL to be lost. If you choose to disable the `hmr` extraction, you can still
|
|
143
|
+
extract (and translate with Gemini) using the CLI command.
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
### Register CLI
|
|
146
|
+
|
|
147
|
+
You must add entries to run the CLI from `npm`.
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
// package.json
|
|
151
|
+
{
|
|
152
|
+
// ...
|
|
153
|
+
"scripts": {
|
|
154
|
+
// ...
|
|
155
|
+
"extract": "wuchale",
|
|
156
|
+
"clean": "wuchale --clean"
|
|
157
|
+
},
|
|
158
|
+
// ...
|
|
159
|
+
}
|
|
140
160
|
```
|
|
141
161
|
|
|
142
|
-
|
|
162
|
+
You can read more about the CLI below
|
|
143
163
|
|
|
144
|
-
|
|
164
|
+
### Setup
|
|
145
165
|
|
|
146
|
-
|
|
166
|
+
Create `/src/locales/` (or the directory you set up in the configuration,
|
|
167
|
+
relative to the projects root) if it doesn't exist, and then set it up in your
|
|
168
|
+
main component. How you set it up depends on whether you use SvelteKit or not.
|
|
147
169
|
|
|
148
|
-
|
|
170
|
+
#### SvelteKit
|
|
149
171
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
1. If Gemini integration is enabled, it fetches translations automatically.
|
|
154
|
-
1. Messages are compiled and written
|
|
155
|
-
1. In dev mode: Vite picks the write and does HMR during dev
|
|
156
|
-
1. In production mode: unused messages are marked obsolete
|
|
157
|
-
1. On next run, obsolete ones are purged unless reused.
|
|
158
|
-
1. Final build contains only minified catalogs and the runtime.
|
|
172
|
+
If you use SvelteKit you likely use SSR/SSG too. And this is how you make
|
|
173
|
+
`wuchale` work with SSR/SSG. If you instead want to use normal client side
|
|
174
|
+
state for the locale, feel free to adapt the method explained below for Svelte.
|
|
159
175
|
|
|
160
|
-
|
|
176
|
+
You have to put the following in yout top load function. Taking
|
|
177
|
+
the default template as an example, the main load function would be inside
|
|
178
|
+
`src/routes/+page.js`. Have the following content in it (TypeScript):
|
|
161
179
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- Includes source references and obsolete flags for cleaning.
|
|
180
|
+
```typescript
|
|
181
|
+
import { setTranslations } from 'wuchale/runtime.svelte.js'
|
|
165
182
|
|
|
166
|
-
|
|
183
|
+
interface Params {
|
|
184
|
+
url: URL,
|
|
185
|
+
}
|
|
167
186
|
|
|
168
|
-
|
|
169
|
-
|
|
187
|
+
export async function load({ url }: Params): Promise<{}> {
|
|
188
|
+
const locale = url.searchParams.get('locale') ?? 'es'
|
|
189
|
+
// IMPORTANT! The path should be relative to the current file
|
|
190
|
+
const mod = await import(`../locales/${locale}.js`)
|
|
191
|
+
setTranslations(mod.default)
|
|
192
|
+
return {}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
170
195
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
196
|
+
What it does is it makes the locale dependent on the URL search param `locale`
|
|
197
|
+
and loads the appropriate language js based on it. You can now adapt it to
|
|
198
|
+
any state such as the `[locale]` slug in the URL path based on your own needs.
|
|
199
|
+
|
|
200
|
+
Now you can start the dev server and see it in action. You can change the URL
|
|
201
|
+
search params like `?locale=es` (if you set up `es` in `otherLocales`)
|
|
202
|
+
|
|
203
|
+
#### Svelte
|
|
204
|
+
|
|
205
|
+
For Svelte you can set up lazy loading and code splitting (recommended). Taking
|
|
206
|
+
the default template as an example, the main component is located in
|
|
207
|
+
`src/App.svelte`.
|
|
208
|
+
|
|
209
|
+
```svelte
|
|
210
|
+
<script>
|
|
211
|
+
import {setTranslations} from 'wuchale/runtime.svelte.js'
|
|
212
|
+
|
|
213
|
+
let locale = $state('en')
|
|
214
|
+
|
|
215
|
+
$effect.pre(() => {
|
|
216
|
+
// IMPORTANT! The path should be relative to the current file (vite restriction).
|
|
217
|
+
import(`../locales/${locale}.js`).then(mod => {
|
|
218
|
+
setTranslations(mod.default)
|
|
219
|
+
})
|
|
220
|
+
// but this only applies if you want to do lazy loading.
|
|
221
|
+
// Otherwise you can do an absolute import
|
|
222
|
+
})
|
|
223
|
+
</script>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Note that you manage the state of which locale is active and how to download
|
|
227
|
+
the compiled `.js`. This is to allow maximum flexibility, meaning you can use
|
|
228
|
+
lazy loading (like this example) or you can import it directly and it will be
|
|
229
|
+
bundled by Vite. After that, you notify `wuchale` to set it as the current one.
|
|
230
|
+
|
|
231
|
+
Then finally you write your Svelte files naturally.
|
|
174
232
|
|
|
175
233
|
## 🧪 Example
|
|
176
234
|
|
|
@@ -188,7 +246,8 @@ Output:
|
|
|
188
246
|
|
|
189
247
|
```svelte
|
|
190
248
|
<script>
|
|
191
|
-
import
|
|
249
|
+
import {wuchaleTrans} from "wuchale/runtime.svelte.js"
|
|
250
|
+
import WuchaleTrans from "wuchale/runtime.svelte"
|
|
192
251
|
</script>
|
|
193
252
|
|
|
194
253
|
<p>{wuchaleTrans(0)}</p>
|
|
@@ -228,8 +287,8 @@ msgstr "Hola {0}"
|
|
|
228
287
|
|
|
229
288
|
Which is then automatically compiled to:
|
|
230
289
|
|
|
231
|
-
```
|
|
232
|
-
[
|
|
290
|
+
```javascript
|
|
291
|
+
export default [
|
|
233
292
|
"¡Hola!",
|
|
234
293
|
[
|
|
235
294
|
"Hola ",
|
|
@@ -241,7 +300,36 @@ Which is then automatically compiled to:
|
|
|
241
300
|
]
|
|
242
301
|
```
|
|
243
302
|
|
|
244
|
-
This is what
|
|
303
|
+
This is what is included in the imported module above in `+page.ts` or `App.svelte`.
|
|
304
|
+
|
|
305
|
+
## 📦 How It Works
|
|
306
|
+
|
|
307
|
+
### Process
|
|
308
|
+
|
|
309
|
+

|
|
310
|
+
|
|
311
|
+
1. All text nodes are extracted using AST traversal.
|
|
312
|
+
1. Replaced with index-based lookups `wuchaleTrans(n)`, which is minifiable for production builds.
|
|
313
|
+
1. Catalog is updated
|
|
314
|
+
1. If Gemini integration is enabled, it fetches translations automatically.
|
|
315
|
+
1. Messages are compiled
|
|
316
|
+
1. In dev mode: Vite picks the write and does HMR during dev
|
|
317
|
+
1. Final build contains only minified catalogs and the runtime.
|
|
318
|
+
|
|
319
|
+
### Catalogs:
|
|
320
|
+
|
|
321
|
+
- Stored as PO files (.po).
|
|
322
|
+
- Compatible with tools like Poedit or translation.io.
|
|
323
|
+
- Includes source references and obsolete flags for cleaning.
|
|
324
|
+
|
|
325
|
+
## 🌐 Gemini Integration (optional)
|
|
326
|
+
|
|
327
|
+
To enable the Gemini live translation, set the environment variable
|
|
328
|
+
`GEMINI_API_KEY` to your API key beforehand. The integration is:
|
|
329
|
+
|
|
330
|
+
- Rate-limit friendly (bundles messages to be translated into one request)
|
|
331
|
+
- Only translates new/changed messages
|
|
332
|
+
- Keeps original source intact
|
|
245
333
|
|
|
246
334
|
## Supported syntax
|
|
247
335
|
|
|
@@ -343,30 +431,13 @@ necessary to have a separate plurals support because you can do something like:
|
|
|
343
431
|
And they will be extracted separately. You can also make a reusable function
|
|
344
432
|
yourself.
|
|
345
433
|
|
|
346
|
-
##
|
|
347
|
-
|
|
348
|
-
To configure `wuchale`, you pass an object that looks like the following (the
|
|
349
|
-
default) to `wuchale()` in your `vite.config.js` `vite.config.ts`:
|
|
350
|
-
|
|
351
|
-
```javascript
|
|
352
|
-
export const defaultOptions: Options = {
|
|
353
|
-
sourceLocale: 'en',
|
|
354
|
-
otherLocales: [],
|
|
355
|
-
localesDir: './src/locales',
|
|
356
|
-
heuristic: defaultHeuristic,
|
|
357
|
-
geminiAPIKey: 'env',
|
|
358
|
-
}
|
|
359
|
-
```
|
|
434
|
+
## CLI
|
|
360
435
|
|
|
361
|
-
|
|
362
|
-
globally decides what text to extract and what not to. The `defaultHeuristic`
|
|
363
|
-
is the implementation of the above rules, but you can roll your own and provide
|
|
364
|
-
it here. The function should receive the following arguments:
|
|
365
|
-
|
|
366
|
-
- `text`: The candidate text
|
|
367
|
-
- `scope`: Where the text is located, i.e. it can be one of `markup`, `script`, and `attribute`
|
|
436
|
+
A simple command line interface is also provided, just `wuchale` with an optional `--clean` argument.
|
|
368
437
|
|
|
369
|
-
|
|
438
|
+
By default, it looks inside all svelte sources and extracts new texts into the
|
|
439
|
+
`.po` files. If the `--clean` argument is provided, it additionally removes
|
|
440
|
+
unused texts from the `.po` files.
|
|
370
441
|
|
|
371
442
|
## Files management
|
|
372
443
|
|
|
@@ -383,17 +454,12 @@ This is a `gettext` file that is used to exchange the text fragments with transl
|
|
|
383
454
|
**Note**: You should commit these files, they are where the translated
|
|
384
455
|
fragments are stored and are the source of truth for the translations.
|
|
385
456
|
|
|
386
|
-
### Compiled `.
|
|
387
|
-
|
|
388
|
-
This is the compiled version of the `.po` file. It is generated at the startup
|
|
389
|
-
of the dev server and at build time. As it has to be generated every time, you
|
|
390
|
-
should consider it as a temp file and therefore you should **not** commit it.
|
|
391
|
-
|
|
392
|
-
## 🧹 Cleaning
|
|
457
|
+
### Compiled data in `.js` file
|
|
393
458
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
459
|
+
This is the module that imports the compiled version of the `.po` file. It is
|
|
460
|
+
generated at the startup of the dev server and at build time. As can be
|
|
461
|
+
generated every time, you should consider it as a temp file and therefore you
|
|
462
|
+
should not commit it.
|
|
397
463
|
|
|
398
464
|
## 🧪 Tests
|
|
399
465
|
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import createPlugin from "./plugin/index.js";
|
|
3
|
+
let clean = false;
|
|
4
|
+
if (process.argv[2] === '--clean') {
|
|
5
|
+
clean = true;
|
|
6
|
+
}
|
|
7
|
+
const plugin = await createPlugin();
|
|
8
|
+
await plugin.configResolved({ env: { EXTRACT: true }, root: process.cwd() });
|
|
9
|
+
console.log('Extracting...');
|
|
10
|
+
if (clean) {
|
|
11
|
+
for (const loc of plugin.locales) {
|
|
12
|
+
for (const item of Object.values(plugin._translations[loc])) {
|
|
13
|
+
item.references = [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
await plugin.directExtract();
|
|
18
|
+
if (clean) {
|
|
19
|
+
for (const loc of plugin.locales) {
|
|
20
|
+
for (const [key, item] of Object.entries(plugin._translations[loc])) {
|
|
21
|
+
if (item.references.length === 0) {
|
|
22
|
+
delete plugin._translations[loc][key];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
plugin.afterExtract(loc);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
console.log('Extraction finished.');
|
|
29
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,YAAY,MAAM,mBAAmB,CAAA;AAE5C,IAAI,KAAK,GAAG,KAAK,CAAA;AACjB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;IAChC,KAAK,GAAG,IAAI,CAAA;AAChB,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAA;AACnC,MAAM,MAAM,CAAC,cAAc,CAAC,EAAC,GAAG,EAAE,EAAC,OAAO,EAAE,IAAI,EAAC,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,EAAC,CAAC,CAAA;AAExE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;AAE5B,IAAI,KAAK,EAAE,CAAC;IACR,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC1D,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;QACxB,CAAC;IACL,CAAC;AACL,CAAC;AAED,MAAM,MAAM,CAAC,aAAa,EAAE,CAAA;AAE5B,IAAI,KAAK,EAAE,CAAC;IACR,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAClE,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,OAAO,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;YACzC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;AACL,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type HeuristicFunc } from "./plugin/prep.js";
|
|
2
|
+
export interface Config {
|
|
3
|
+
sourceLocale?: string;
|
|
4
|
+
otherLocales?: string[];
|
|
5
|
+
localesDir?: string;
|
|
6
|
+
srcDirs?: string[];
|
|
7
|
+
heuristic?: HeuristicFunc;
|
|
8
|
+
hmr?: boolean;
|
|
9
|
+
geminiAPIKey?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const defaultOptions: Config;
|
|
12
|
+
export declare function getOptions(codeOptions?: Config): Promise<Config>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defaultHeuristic } from "./plugin/prep.js";
|
|
2
|
+
export const defaultOptions = {
|
|
3
|
+
sourceLocale: 'en',
|
|
4
|
+
otherLocales: [],
|
|
5
|
+
localesDir: './src/locales',
|
|
6
|
+
srcDirs: ['src'],
|
|
7
|
+
heuristic: defaultHeuristic,
|
|
8
|
+
hmr: true,
|
|
9
|
+
geminiAPIKey: 'env',
|
|
10
|
+
};
|
|
11
|
+
function mergeOptions(fromOpt, toOpt) {
|
|
12
|
+
for (const key of Object.keys(fromOpt)) {
|
|
13
|
+
toOpt[key] = fromOpt[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function getOptions(codeOptions = {}) {
|
|
17
|
+
const options = defaultOptions;
|
|
18
|
+
try {
|
|
19
|
+
const module = await import(process.cwd() + '/wuchale.config.js');
|
|
20
|
+
mergeOptions(module.default, options);
|
|
21
|
+
}
|
|
22
|
+
catch { }
|
|
23
|
+
mergeOptions(codeOptions, options);
|
|
24
|
+
return options;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAsB,MAAM,kBAAkB,CAAA;AAYvE,MAAM,CAAC,MAAM,cAAc,GAAW;IAClC,YAAY,EAAE,IAAI;IAClB,YAAY,EAAE,EAAE;IAChB,UAAU,EAAE,eAAe;IAC3B,OAAO,EAAE,CAAC,KAAK,CAAC;IAChB,SAAS,EAAE,gBAAgB;IAC3B,GAAG,EAAE,IAAI;IACT,YAAY,EAAE,KAAK;CACtB,CAAA;AAED,SAAS,YAAY,CAAC,OAAe,EAAE,KAAa;IAChD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,cAAsB,EAAE;IACrD,MAAM,OAAO,GAAW,cAAc,CAAA;IACtC,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,oBAAoB,CAAC,CAAA;QACjE,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IACV,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;IAClC,OAAO,OAAO,CAAA;AAClB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import plugin from "./
|
|
1
|
+
import plugin from "./plugin/index.js";
|
|
2
2
|
export declare const wuchale: typeof plugin;
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,mBAAmB,CAAA;AAEtC,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/plugin/compile.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAOvC,SAAS,gBAAgB,CAAC,IAAgG;IACtH,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACvB,CAAC;QACD,OAAO,EAAE,CAAA;IACb,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QAChC,OAAO,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5C,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC5B,OAAO,CAAC;gBACJ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC1B,GAAG,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC;aACrC,CAAC,CAAA;IACN,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,EAAE,CAAA;QAChB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAA;QAC1C,CAAC;QACD,OAAO,KAAK,CAAA;IAChB,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAA;IAC3C,OAAO,EAAE,CAAA;AACb,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,IAAY,EAAE,QAA0B;IAC/E,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,QAAQ,CAAA;IACnB,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAA;IACf,CAAC;IACD,+CAA+C;IAC/C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAA;IAC/C,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;QACzC,OAAO,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClB,OAAO,QAAQ,CAAA;IACnB,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import PO from 'pofile';
|
|
2
|
+
export type ItemType = InstanceType<typeof PO.Item>;
|
|
3
|
+
export default class GeminiQueue {
|
|
4
|
+
batches: ItemType[][];
|
|
5
|
+
running: Promise<void> | null;
|
|
6
|
+
sourceLocale: string;
|
|
7
|
+
targetLocale: string;
|
|
8
|
+
url: string;
|
|
9
|
+
instruction: string;
|
|
10
|
+
onComplete: () => Promise<void>;
|
|
11
|
+
constructor(sourceLocale: string, targetLocale: string, apiKey: string | null, onComplete: () => Promise<void>);
|
|
12
|
+
prepareData(fragments: ItemType[]): {
|
|
13
|
+
system_instruction: {
|
|
14
|
+
parts: {
|
|
15
|
+
text: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
contents: {
|
|
19
|
+
parts: {
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
translate(fragments: ItemType[]): Promise<void>;
|
|
25
|
+
getBatches(): Generator<ItemType[], void, unknown>;
|
|
26
|
+
run(): Promise<void>;
|
|
27
|
+
add(items: ItemType[]): boolean;
|
|
28
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// $$ cd .. && npm run test
|
|
2
|
+
// $$ node %f
|
|
3
|
+
import PO from 'pofile';
|
|
4
|
+
const baseURL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=';
|
|
5
|
+
const h = { 'Content-Type': 'application/json' };
|
|
6
|
+
function codeStandard(locale) {
|
|
7
|
+
return `ISO 639-${locale.length === 2 ? 1 : 3}`;
|
|
8
|
+
}
|
|
9
|
+
// implements a queue for a sequential translation useful for vite's transform during dev
|
|
10
|
+
// as vite can do async transform
|
|
11
|
+
export default class GeminiQueue {
|
|
12
|
+
batches = [];
|
|
13
|
+
running = null;
|
|
14
|
+
sourceLocale;
|
|
15
|
+
targetLocale;
|
|
16
|
+
url;
|
|
17
|
+
instruction;
|
|
18
|
+
onComplete;
|
|
19
|
+
constructor(sourceLocale, targetLocale, apiKey, onComplete) {
|
|
20
|
+
if (apiKey === 'env') {
|
|
21
|
+
apiKey = process.env.GEMINI_API_KEY;
|
|
22
|
+
}
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.sourceLocale = sourceLocale;
|
|
27
|
+
this.targetLocale = targetLocale;
|
|
28
|
+
this.url = `${baseURL}${apiKey}`;
|
|
29
|
+
this.instruction = `
|
|
30
|
+
You will be given the contents of a gettext .po file for a web app.
|
|
31
|
+
Translate each of the items from the source to the target language.
|
|
32
|
+
The source language ${codeStandard(this.sourceLocale)} code is: ${this.sourceLocale}.
|
|
33
|
+
The target language ${codeStandard(this.targetLocale)} code is: ${this.targetLocale}.
|
|
34
|
+
You can read all of the information for the items including contexts,
|
|
35
|
+
comments and references to get the appropriate context about each item.
|
|
36
|
+
Provide the same content with the only difference being that the
|
|
37
|
+
empty msgstr quotes should be filled with the appropriate translations,
|
|
38
|
+
preserving all placeholders.
|
|
39
|
+
The placeholder format is like the following examples:
|
|
40
|
+
- {0}: means arbitrary values.
|
|
41
|
+
- <0>something</0>: means something enclosed in some tags, like HTML tags
|
|
42
|
+
- <0/>: means a self closing tag, like in HTML
|
|
43
|
+
In all of the examples, 0 is an example for any integer.
|
|
44
|
+
`;
|
|
45
|
+
this.onComplete = onComplete;
|
|
46
|
+
}
|
|
47
|
+
prepareData(fragments) {
|
|
48
|
+
const po = new PO();
|
|
49
|
+
po.items = fragments;
|
|
50
|
+
return {
|
|
51
|
+
system_instruction: {
|
|
52
|
+
parts: [{ text: this.instruction }]
|
|
53
|
+
},
|
|
54
|
+
contents: [{ parts: [{ text: po.toString() }] }]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async translate(fragments) {
|
|
58
|
+
const data = this.prepareData(fragments);
|
|
59
|
+
const res = await fetch(this.url, { method: 'POST', headers: h, body: JSON.stringify(data) });
|
|
60
|
+
const json = await res.json();
|
|
61
|
+
if (json.error) {
|
|
62
|
+
console.error('Gemini error', json.error.code, json.error.message);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const resText = json.candidates[0]?.content.parts[0].text;
|
|
66
|
+
for (const [i, item] of PO.parse(resText).items.entries()) {
|
|
67
|
+
if (item.msgstr[0]) {
|
|
68
|
+
fragments[i].msgstr = item.msgstr;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
*getBatches() {
|
|
73
|
+
while (this.batches.length > 0) {
|
|
74
|
+
yield this.batches.pop(); // order doesn't matter, because they are given by ref
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async run() {
|
|
78
|
+
for (const batch of this.getBatches()) {
|
|
79
|
+
await this.translate(batch);
|
|
80
|
+
}
|
|
81
|
+
await this.onComplete();
|
|
82
|
+
this.running = null;
|
|
83
|
+
}
|
|
84
|
+
add(items) {
|
|
85
|
+
if (!this.url) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let newRequest = false;
|
|
89
|
+
if (this.batches.length > 0) {
|
|
90
|
+
this.batches[0].push(...items);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.batches.push(items);
|
|
94
|
+
newRequest = true;
|
|
95
|
+
}
|
|
96
|
+
if (!this.running) {
|
|
97
|
+
this.running = this.run();
|
|
98
|
+
}
|
|
99
|
+
return newRequest;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/plugin/gemini.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CompiledFragment } from "./compile.js";
|
|
2
|
+
import { type ItemType } from "./gemini.js";
|
|
3
|
+
import { type Config as Config } from "../config.js";
|
|
4
|
+
type ViteHotUpdateCTX = {
|
|
5
|
+
file: string;
|
|
6
|
+
server: {
|
|
7
|
+
moduleGraph: {
|
|
8
|
+
getModuleById: Function;
|
|
9
|
+
invalidateModule: Function;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
timestamp: number;
|
|
13
|
+
};
|
|
14
|
+
declare class Plugin {
|
|
15
|
+
#private;
|
|
16
|
+
name: string;
|
|
17
|
+
locales: string[];
|
|
18
|
+
_translations: {
|
|
19
|
+
[loc: string]: {
|
|
20
|
+
[key: string]: ItemType;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
_compiled: {
|
|
24
|
+
[locale: string]: (CompiledFragment | number)[];
|
|
25
|
+
};
|
|
26
|
+
transform: {
|
|
27
|
+
order: 'pre';
|
|
28
|
+
handler: any;
|
|
29
|
+
};
|
|
30
|
+
constructor();
|
|
31
|
+
init: (configRaw: Config) => Promise<void>;
|
|
32
|
+
directExtract: () => Promise<void>;
|
|
33
|
+
afterExtract: (loc: string) => Promise<void>;
|
|
34
|
+
configResolved: (config: {
|
|
35
|
+
env: {
|
|
36
|
+
PROD?: boolean;
|
|
37
|
+
EXTRACT?: boolean;
|
|
38
|
+
};
|
|
39
|
+
root: string;
|
|
40
|
+
}) => Promise<void>;
|
|
41
|
+
handleHotUpdate: (ctx: ViteHotUpdateCTX) => Promise<any[]>;
|
|
42
|
+
resolveId: (source: string) => string;
|
|
43
|
+
load: (id: string) => string;
|
|
44
|
+
transformHandler: (code: string, id: string) => Promise<{
|
|
45
|
+
code?: undefined;
|
|
46
|
+
map?: undefined;
|
|
47
|
+
} | {
|
|
48
|
+
code: string;
|
|
49
|
+
map: import("magic-string").SourceMap;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
export default function wuchale(config?: Config): Promise<Plugin>;
|
|
53
|
+
export {};
|