wuchale 0.2.0 → 0.3.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 +91 -32
- package/package.json +1 -1
- package/preprocess/gemini.js +17 -12
- package/preprocess/index.js +26 -23
- package/preprocess/prep.js +88 -47
package/README.md
CHANGED
|
@@ -26,6 +26,10 @@ simplicity in mind.
|
|
|
26
26
|
No extra imports or annotations. `wuchale` extracts and compiles everything
|
|
27
27
|
automatically. In the spirit of Svelte itself.
|
|
28
28
|
|
|
29
|
+
> They say "i18n is too costly to add later"
|
|
30
|
+
|
|
31
|
+
✨ **Not anymore**. wuchale brings i18n to existing Svelte projects — the UX-first way.
|
|
32
|
+
|
|
29
33
|
### 🧠 Compiler-Powered
|
|
30
34
|
|
|
31
35
|
Built on the Svelte compiler and powered by AST
|
|
@@ -127,7 +131,7 @@ Then finally you write your Svelte files naturally:
|
|
|
127
131
|
<script>
|
|
128
132
|
import WuchaleTrans, { wuchaleTrans } from 'wuchale/runtime.svelte'
|
|
129
133
|
</script>
|
|
130
|
-
<
|
|
134
|
+
<p>{wuchaleTrans(0)}</p> <!-- Extracted "Hello" as index 0 -->
|
|
131
135
|
```
|
|
132
136
|
|
|
133
137
|
Full example below.
|
|
@@ -236,30 +240,88 @@ This is what you import when you set it up above in `App.svelte`.
|
|
|
236
240
|
|
|
237
241
|
## Supported syntax
|
|
238
242
|
|
|
239
|
-
Text can be in three places
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
243
|
+
Text can be in three places and the probability of the text inside them being
|
|
244
|
+
intended is different for each of them. Therefore, a global heuristic function
|
|
245
|
+
is applied to check whether the text should be extracted depending on each
|
|
246
|
+
case, discussed below. And for specific granular control, comments can be used,
|
|
247
|
+
like for typescript: `@wc-ignore` and `@wc-include`.
|
|
248
|
+
|
|
249
|
+
### Markup
|
|
250
|
+
|
|
251
|
+
Markup means text that we write inside tags like in paragraphs. This is almost
|
|
252
|
+
always intended to be shown to the user. Therefore, the default global rule is
|
|
253
|
+
to extract all text inside the markup, with the ability to force ignore with a
|
|
254
|
+
comment:
|
|
255
|
+
|
|
256
|
+
```svelte
|
|
257
|
+
<p>This is extracted</p>
|
|
258
|
+
<!-- @wc-ignore -->
|
|
259
|
+
<p>This is ignored</p>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Attributes
|
|
263
|
+
|
|
264
|
+
Attributes are the text that are literally written like `class="this"`. Dynamic
|
|
265
|
+
attributes are not considered for this rule, instead they follow the script
|
|
266
|
+
rule below, because they are JS expressions. For text attributes, the default
|
|
267
|
+
rule is that all text starting with a capital letter is extracted.
|
|
268
|
+
Example:
|
|
269
|
+
|
|
270
|
+
```svelte
|
|
271
|
+
<p class="not-extracted" title="Extracted">
|
|
272
|
+
This is extracted
|
|
273
|
+
</p>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For ignoring or force-including, convert them to expressions and follow the
|
|
277
|
+
script rule below.
|
|
278
|
+
|
|
279
|
+
### Script
|
|
280
|
+
|
|
281
|
+
This includes all JS/TS code that is:
|
|
282
|
+
- Inside `<script>` tags
|
|
283
|
+
- Inside dynamic expressions inside the markup or attributes, anything in curly braces like `{call('this guy')}`
|
|
284
|
+
- In `*.svelte.[js|ts]` files.
|
|
285
|
+
|
|
286
|
+
The rule for this is that all strings and template strings that start with
|
|
287
|
+
capital letters are extracted. Additionally, if they are used inside the
|
|
288
|
+
`<script>` tags and in their own files (third case above), there is the
|
|
289
|
+
additional restriction that they must be inside a `$derived` variable
|
|
290
|
+
declaration. This is to make the behavior less magic and being more explicit.
|
|
291
|
+
Example:
|
|
292
|
+
|
|
293
|
+
```svelte
|
|
294
|
+
<script>
|
|
295
|
+
const a = 'Not extracted'
|
|
296
|
+
const b = $derived('not extracted either')
|
|
297
|
+
const c = $derived('This one is extracted')
|
|
298
|
+
const d = $derived(`This one as well ${a} - ${b}`)
|
|
299
|
+
const d = $derived(/* @wc-include */ `${a} - ${b} this is force extracted`)
|
|
300
|
+
</script>
|
|
301
|
+
|
|
302
|
+
<p class={/* @wc-ignore */ `Ignore${3}`} title={'Included'} >Normal text</p>
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Context?
|
|
307
|
+
|
|
308
|
+
Sometimes we need to have different translations that are the same text in the
|
|
309
|
+
source language. For that, the comment directive `@wc-context:` is provided and
|
|
310
|
+
they will be separate.
|
|
311
|
+
|
|
312
|
+
```svelte
|
|
313
|
+
<b>
|
|
314
|
+
<!-- @wc-context: machine -->
|
|
315
|
+
Maintenance
|
|
316
|
+
</b>
|
|
317
|
+
<i>Is different from</i>
|
|
318
|
+
<b>
|
|
319
|
+
<!-- @wc-context: beauty -->
|
|
320
|
+
Maintenance
|
|
321
|
+
</b>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Excuse my poor example choice.
|
|
263
325
|
|
|
264
326
|
## Plurals?
|
|
265
327
|
|
|
@@ -292,17 +354,14 @@ export const defaultOptions = {
|
|
|
292
354
|
```
|
|
293
355
|
|
|
294
356
|
While the others are self explanatory, the `heuristic` is a function that
|
|
295
|
-
decides what text to extract and what not to. The `defaultHeuristic`
|
|
296
|
-
implementation of the above rules, but you can roll your own and provide
|
|
297
|
-
here. The function should receive the following arguments:
|
|
357
|
+
globally decides what text to extract and what not to. The `defaultHeuristic`
|
|
358
|
+
is the implementation of the above rules, but you can roll your own and provide
|
|
359
|
+
it here. The function should receive the following arguments:
|
|
298
360
|
|
|
299
361
|
- `text`: The candidate text
|
|
300
362
|
- `scope`: Where the text is located, i.e. it can be one of `markup`, `script`, and `attribute`
|
|
301
363
|
|
|
302
|
-
And it should return
|
|
303
|
-
|
|
304
|
-
- `extract` (boolean): Whether to extract it or not
|
|
305
|
-
- `replace` (`string`): The string to replace it with. This is how you specify how to remove parts such as the prefixes above (`+` and `-`).
|
|
364
|
+
And it should return boolean to indicate whether to extract it.
|
|
306
365
|
|
|
307
366
|
## 🧹 Cleaning
|
|
308
367
|
|
package/package.json
CHANGED
package/preprocess/gemini.js
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
1
|
+
// $$ cd .. && npm run test
|
|
1
2
|
// $$ node %f
|
|
2
3
|
|
|
4
|
+
import PO from 'pofile'
|
|
5
|
+
|
|
3
6
|
const baseURL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key='
|
|
4
7
|
const h = {'Content-Type': 'application/json'}
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
|
-
* @param {
|
|
10
|
+
* @param {import('pofile').Item[]} fragments
|
|
8
11
|
* @param {string} sourceLocale
|
|
9
12
|
* @param {string} targetLocale
|
|
10
13
|
*/
|
|
11
14
|
function prepareData(fragments, sourceLocale, targetLocale) {
|
|
12
15
|
const instruction = `
|
|
13
|
-
You will be given
|
|
14
|
-
Translate each of the
|
|
16
|
+
You will be given the contents of a gettext .po file for a web app.
|
|
17
|
+
Translate each of the items from the source to the target language.
|
|
15
18
|
You have to find out the languages using their ISO 639-1 codes.
|
|
16
19
|
The source language is: ${sourceLocale}.
|
|
17
20
|
The target language is: ${targetLocale}.
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
You can read all of the information for the items including contexts,
|
|
22
|
+
comments and references to get the appropriate context about each item.
|
|
23
|
+
Provide the translated fragments in the in the same order, preserving
|
|
24
|
+
all placeholders.
|
|
20
25
|
The placeholder format is like the following examples:
|
|
21
26
|
- {0}: means arbitrary values.
|
|
22
27
|
- <0>something</0>: means something enclosed in some tags, like HTML tags
|
|
23
28
|
- <0/>: means a self closing tag, like in HTML
|
|
24
29
|
In all of the examples, 0 is an example for any integer.
|
|
25
30
|
`
|
|
31
|
+
const po = new PO()
|
|
32
|
+
po.items = fragments
|
|
26
33
|
return {
|
|
27
34
|
system_instruction: {
|
|
28
35
|
parts: [{ text: instruction }]
|
|
29
36
|
},
|
|
30
|
-
contents: [{parts: [{text:
|
|
37
|
+
contents: [{parts: [{text: po.toString()}]}]
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
|
|
@@ -43,18 +50,16 @@ function setupGemini(sourceLocale = 'en', targetLocale, apiKey) {
|
|
|
43
50
|
return
|
|
44
51
|
}
|
|
45
52
|
const url = `${baseURL}${apiKey}`
|
|
46
|
-
return async (/** @type {
|
|
53
|
+
return async (/** @type {import('pofile').Item[]} */ fragments) => {
|
|
47
54
|
const data = prepareData(fragments, sourceLocale, targetLocale)
|
|
48
55
|
const res = await fetch(url, {method: 'POST', headers: h, body: JSON.stringify(data)})
|
|
49
56
|
const json = await res.json()
|
|
50
57
|
const resText = json.candidates[0].content.parts[0].text
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
trans[fragments[i]] = text
|
|
58
|
+
for (const [i, item] of PO.parse(resText).items.entries()) {
|
|
59
|
+
if (item.msgstr[0]) {
|
|
60
|
+
fragments[i].msgstr = item.msgstr
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
|
-
return trans
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
|
package/preprocess/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import Preprocess, { defaultHeuristic, IndexTracker } from "./prep.js"
|
|
1
|
+
import Preprocess, { defaultHeuristic, IndexTracker, NestText } from "./prep.js"
|
|
2
2
|
import { parse } from "svelte/compiler"
|
|
3
3
|
import {writeFile} from 'node:fs/promises'
|
|
4
4
|
import compileTranslation from "./compile.js"
|
|
5
5
|
import setupGemini from "./gemini.js"
|
|
6
6
|
import PO from "pofile"
|
|
7
|
-
import { relative } from "node:path"
|
|
7
|
+
import { normalize, relative } from "node:path"
|
|
8
8
|
|
|
9
9
|
export const defaultOptions = {
|
|
10
10
|
sourceLocale: 'en',
|
|
@@ -35,7 +35,8 @@ async function loadPONoFail(filename) {
|
|
|
35
35
|
if (!item.msgstr[0]) {
|
|
36
36
|
untranslated++
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
const nTxt = new NestText(item.msgid, null, item.msgctxt)
|
|
39
|
+
translations[nTxt.toKey()] = item
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
res({translations, total, obsolete, untranslated})
|
|
@@ -102,12 +103,12 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
102
103
|
// startup compile
|
|
103
104
|
for (const loc of locales) {
|
|
104
105
|
compiled[loc] = []
|
|
105
|
-
for (const
|
|
106
|
-
const poItem = translations[loc][
|
|
106
|
+
for (const key in translations[loc]) {
|
|
107
|
+
const poItem = translations[loc][key]
|
|
107
108
|
if (forProduction) {
|
|
108
109
|
poItem.references = []
|
|
109
110
|
}
|
|
110
|
-
const index = indexTracker.get(
|
|
111
|
+
const index = indexTracker.get(key)
|
|
111
112
|
compiled[loc][index] = compileTranslation(poItem.msgstr[0], compiled[options.sourceLocale][index])
|
|
112
113
|
}
|
|
113
114
|
await writeFile(compiledFname[loc], JSON.stringify(compiled[loc], null, 2))
|
|
@@ -128,38 +129,40 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
128
129
|
for (const loc of locales) {
|
|
129
130
|
const newTxts = []
|
|
130
131
|
for (const nTxt of txts) {
|
|
131
|
-
|
|
132
|
-
let translated = translations[loc][
|
|
132
|
+
let key = nTxt.toKey()
|
|
133
|
+
let translated = translations[loc][key]
|
|
133
134
|
if (translated == null) {
|
|
134
135
|
translated = new PO.Item()
|
|
135
|
-
translated.msgid =
|
|
136
|
-
translations[loc][
|
|
136
|
+
translated.msgid = nTxt.toString()
|
|
137
|
+
translations[loc][key] = translated
|
|
138
|
+
}
|
|
139
|
+
if (nTxt.context) {
|
|
140
|
+
translated.msgctxt = nTxt.context
|
|
137
141
|
}
|
|
138
142
|
if (!translated.references.includes(filename)) {
|
|
139
143
|
translated.references.push(filename)
|
|
140
144
|
}
|
|
141
145
|
if (loc === options.sourceLocale) {
|
|
146
|
+
const txt = nTxt.toString()
|
|
142
147
|
if (translated.msgstr[0] !== txt) {
|
|
143
148
|
translated.msgstr = [txt]
|
|
144
|
-
newTxts.push(
|
|
149
|
+
newTxts.push(translated)
|
|
145
150
|
}
|
|
146
151
|
} else if (!translated.msgstr[0]) {
|
|
147
|
-
newTxts.push(
|
|
152
|
+
newTxts.push(translated)
|
|
148
153
|
}
|
|
149
154
|
}
|
|
150
155
|
if (loc !== options.sourceLocale && newTxts.length) {
|
|
151
156
|
const geminiT = setupGemini(options.sourceLocale, loc, options.geminiAPIKey)
|
|
152
157
|
if (geminiT) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
translations[loc][txt].msgstr = [gTrans[txt]]
|
|
156
|
-
}
|
|
158
|
+
console.info('Gemini translate', newTxts.length, 'items...')
|
|
159
|
+
await geminiT(newTxts) // will update because it's by reference
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
162
|
for (const nTxt of txts) {
|
|
160
|
-
const
|
|
161
|
-
const index = indexTracker.get(
|
|
162
|
-
compiled[loc][index] = compileTranslation(translations[loc][
|
|
163
|
+
const key = nTxt.toKey()
|
|
164
|
+
const index = indexTracker.get(key)
|
|
165
|
+
compiled[loc][index] = compileTranslation(translations[loc][key].msgstr[0], compiled[options.sourceLocale][index])
|
|
163
166
|
}
|
|
164
167
|
for (const [i, c] of compiled[loc].entries()) {
|
|
165
168
|
if (c == null) {
|
|
@@ -193,10 +196,10 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
193
196
|
* @param {string} id
|
|
194
197
|
*/
|
|
195
198
|
handler: async function(code, id) {
|
|
196
|
-
if (!id.startsWith(projectRoot)) {
|
|
199
|
+
if (!id.startsWith(projectRoot) || id.startsWith(normalize(projectRoot + '/node_modules'))) {
|
|
197
200
|
return
|
|
198
201
|
}
|
|
199
|
-
const isModule = id.endsWith('.svelte.js')
|
|
202
|
+
const isModule = id.endsWith('.svelte.js') || id.endsWith('.svelte.ts')
|
|
200
203
|
if (!id.endsWith('.svelte') && !isModule) {
|
|
201
204
|
return
|
|
202
205
|
}
|
|
@@ -223,8 +226,8 @@ export default async function wuchale(options = defaultOptions) {
|
|
|
223
226
|
return
|
|
224
227
|
}
|
|
225
228
|
for (const loc of locales) {
|
|
226
|
-
for (const
|
|
227
|
-
const poItem = translations[loc][
|
|
229
|
+
for (const key in translations[loc]) {
|
|
230
|
+
const poItem = translations[loc][key]
|
|
228
231
|
poItem.obsolete = poItem.references.length === 0
|
|
229
232
|
}
|
|
230
233
|
await savePO(translations[loc], translationsFname[loc])
|
package/preprocess/prep.js
CHANGED
|
@@ -8,7 +8,7 @@ const rtFunc = 'wuchaleTrans'
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @typedef {"script" | "markup" | "attribute"} TxtScope
|
|
11
|
-
* @typedef {(text: string, scope: TxtScope) =>
|
|
11
|
+
* @typedef {(text: string, scope: TxtScope) => boolean} HeuristicFunc
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -16,32 +16,29 @@ const rtFunc = 'wuchaleTrans'
|
|
|
16
16
|
*/
|
|
17
17
|
export function defaultHeuristic(text, scope = 'markup') {
|
|
18
18
|
if (scope === 'markup') {
|
|
19
|
-
|
|
20
|
-
return {extract: false, replace: text.slice(1).trim()}
|
|
21
|
-
}
|
|
22
|
-
return {extract: true, replace: text}
|
|
19
|
+
return true
|
|
23
20
|
}
|
|
24
21
|
// script and attribute
|
|
25
|
-
if (text.startsWith('+')) {
|
|
26
|
-
return {extract: true, replace: text.slice(1).trim()}
|
|
27
|
-
}
|
|
28
22
|
const range = 'AZ'
|
|
29
23
|
const startCode = text.charCodeAt(0)
|
|
30
|
-
|
|
31
|
-
return {extract: true, replace: text}
|
|
32
|
-
}
|
|
33
|
-
return {extract: false, replace: text}
|
|
24
|
+
return startCode >= range.charCodeAt(0) && startCode <= range.charCodeAt(1)
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
class NestText extends String {
|
|
27
|
+
export class NestText extends String {
|
|
37
28
|
/**
|
|
38
29
|
* @param {string} txt
|
|
39
30
|
* @param {TxtScope} scope
|
|
31
|
+
* @param {string | null} [context]
|
|
40
32
|
*/
|
|
41
|
-
constructor(txt, scope) {
|
|
33
|
+
constructor(txt, scope, context) {
|
|
42
34
|
super(txt)
|
|
43
35
|
this.scope = scope
|
|
36
|
+
/** @type {string} */
|
|
37
|
+
this.context = context ?? null
|
|
44
38
|
}
|
|
39
|
+
|
|
40
|
+
toKey = () => `${this.toString()}\n${this.context ?? ''}`.trim()
|
|
41
|
+
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
export class IndexTracker {
|
|
@@ -75,34 +72,32 @@ export default class Preprocess {
|
|
|
75
72
|
* @param {HeuristicFunc} heuristic
|
|
76
73
|
* @param {string} importFrom
|
|
77
74
|
*/
|
|
78
|
-
constructor(index, heuristic, importFrom) {
|
|
75
|
+
constructor(index, heuristic = defaultHeuristic, importFrom) {
|
|
79
76
|
this.index = index
|
|
80
77
|
this.importFrom = importFrom
|
|
81
78
|
this.heuristic = heuristic
|
|
82
79
|
this.content = ''
|
|
83
80
|
/** @type {MagicString} */
|
|
84
81
|
this.mstr = null
|
|
82
|
+
/** @type {boolean | null} */
|
|
83
|
+
this.forceInclude = null
|
|
84
|
+
/** @type {string} */
|
|
85
|
+
this.context = null
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
/**
|
|
88
|
-
* @param {number} start
|
|
89
|
-
* @param {number} end
|
|
90
89
|
* @param {string} text
|
|
91
90
|
* @param {TxtScope} scope
|
|
92
91
|
* @returns {Array<*> & {0: boolean, 1: NestText}}
|
|
93
92
|
*/
|
|
94
|
-
|
|
93
|
+
checkHeuristic = (text, scope) => {
|
|
95
94
|
text = text.replace(/\s+/g, ' ').trim()
|
|
96
95
|
if (text === '') {
|
|
97
96
|
// nothing to ask
|
|
98
97
|
return [false, null]
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!extract && text !== replace) {
|
|
103
|
-
this.mstr.update(start, end, replace)
|
|
104
|
-
}
|
|
105
|
-
return [extract, new NestText(replace, scope)]
|
|
99
|
+
const extract = this.forceInclude || this.heuristic(text, scope)
|
|
100
|
+
return [extract, new NestText(text, scope)]
|
|
106
101
|
}
|
|
107
102
|
|
|
108
103
|
// visitComment = () => []
|
|
@@ -118,11 +113,12 @@ export default class Preprocess {
|
|
|
118
113
|
return []
|
|
119
114
|
}
|
|
120
115
|
const { start, end } = node
|
|
121
|
-
const [pass, txt] = this.
|
|
116
|
+
const [pass, txt] = this.checkHeuristic(node.value, 'script')
|
|
122
117
|
if (!pass) {
|
|
123
118
|
return []
|
|
124
119
|
}
|
|
125
|
-
|
|
120
|
+
txt.context = this.context
|
|
121
|
+
this.mstr.update(start, end, `${rtFunc}(${this.index.get(txt.toKey())})`)
|
|
126
122
|
return [txt]
|
|
127
123
|
}
|
|
128
124
|
|
|
@@ -176,6 +172,17 @@ export default class Preprocess {
|
|
|
176
172
|
return txts
|
|
177
173
|
}
|
|
178
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @param {import('estree').BinaryExpression} node
|
|
177
|
+
* @returns {NestText[]}
|
|
178
|
+
*/
|
|
179
|
+
visitBinaryExpression = node => {
|
|
180
|
+
return [
|
|
181
|
+
...this.visit(node.left),
|
|
182
|
+
...this.visit(node.right),
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
|
|
179
186
|
/**
|
|
180
187
|
* @param {import('estree').VariableDeclaration} node
|
|
181
188
|
* @returns {NestText[]}
|
|
@@ -214,15 +221,15 @@ export default class Preprocess {
|
|
|
214
221
|
const quasi0 = node.quasis[0]
|
|
215
222
|
// @ts-ignore
|
|
216
223
|
const {start: start0, end: end0} = quasi0
|
|
217
|
-
const [pass,
|
|
224
|
+
const [pass, txt0] = this.checkHeuristic(quasi0.value.cooked, 'script')
|
|
218
225
|
if (!pass) {
|
|
219
226
|
return txts
|
|
220
227
|
}
|
|
221
|
-
let
|
|
228
|
+
let txt = txt0.toString()
|
|
222
229
|
for (const [i, expr] of node.expressions.entries()) {
|
|
223
230
|
txts.push(...this.visit(expr))
|
|
224
231
|
const quasi = node.quasis[i + 1]
|
|
225
|
-
|
|
232
|
+
txt += `{${i}}${quasi.value.cooked}`
|
|
226
233
|
// @ts-ignore
|
|
227
234
|
const {start, end} = quasi
|
|
228
235
|
this.mstr.remove(start - 1, end)
|
|
@@ -231,14 +238,15 @@ export default class Preprocess {
|
|
|
231
238
|
}
|
|
232
239
|
this.mstr.update(end, end + 2, ', ')
|
|
233
240
|
}
|
|
234
|
-
|
|
241
|
+
const nTxt = new NestText(txt, txt0.scope, this.context)
|
|
242
|
+
let repl = `${rtFunc}(${this.index.get(nTxt.toKey())}`
|
|
235
243
|
if (node.expressions.length) {
|
|
236
244
|
repl += ', '
|
|
237
245
|
}
|
|
238
246
|
this.mstr.update(start0 - 1, end0 + 2, repl)
|
|
239
247
|
// @ts-ignore
|
|
240
248
|
this.mstr.update(node.end - 1, node.end, ')')
|
|
241
|
-
txts.push(
|
|
249
|
+
txts.push(nTxt)
|
|
242
250
|
return txts
|
|
243
251
|
}
|
|
244
252
|
|
|
@@ -290,8 +298,7 @@ export default class Preprocess {
|
|
|
290
298
|
const textNodesToModify = {}
|
|
291
299
|
for (const [i, child] of node.fragment.nodes.entries()) {
|
|
292
300
|
if (child.type === 'Text') {
|
|
293
|
-
const
|
|
294
|
-
const [pass, modify] = this.modifyCheck(start, end, child.data, 'markup')
|
|
301
|
+
const [pass, modify] = this.checkHeuristic(child.data, 'markup')
|
|
295
302
|
if (pass) {
|
|
296
303
|
hasTextChild = true
|
|
297
304
|
textNodesToModify[i] = modify
|
|
@@ -374,7 +381,7 @@ export default class Preprocess {
|
|
|
374
381
|
if (!txt) {
|
|
375
382
|
return txts
|
|
376
383
|
}
|
|
377
|
-
const nTxt = new NestText(txt, 'markup')
|
|
384
|
+
const nTxt = new NestText(txt, 'markup', this.context)
|
|
378
385
|
txts.push(nTxt)
|
|
379
386
|
if (iTag > 0) {
|
|
380
387
|
const snippets = []
|
|
@@ -386,7 +393,7 @@ export default class Preprocess {
|
|
|
386
393
|
if (node.inCompoundText) {
|
|
387
394
|
begin += `ctx={ctx}`
|
|
388
395
|
} else {
|
|
389
|
-
begin += `id={${this.index.get(
|
|
396
|
+
begin += `id={${this.index.get(nTxt.toKey())}}`
|
|
390
397
|
}
|
|
391
398
|
let end = ' />\n'
|
|
392
399
|
if (iArg > 0) {
|
|
@@ -396,7 +403,7 @@ export default class Preprocess {
|
|
|
396
403
|
this.mstr.appendLeft(lastChildEnd, begin)
|
|
397
404
|
this.mstr.appendRight(lastChildEnd, end)
|
|
398
405
|
} else if (!node.inCompoundText) {
|
|
399
|
-
this.mstr.appendLeft(lastChildEnd, `{${rtFunc}(${this.index.get(
|
|
406
|
+
this.mstr.appendLeft(lastChildEnd, `{${rtFunc}(${this.index.get(nTxt.toKey())}, `)
|
|
400
407
|
this.mstr.appendRight(lastChildEnd, ')}')
|
|
401
408
|
}
|
|
402
409
|
return txts
|
|
@@ -409,12 +416,12 @@ export default class Preprocess {
|
|
|
409
416
|
* @returns {NestText[]}
|
|
410
417
|
*/
|
|
411
418
|
visitText = node => {
|
|
412
|
-
const
|
|
413
|
-
const [pass, txt] = this.modifyCheck(start, end, node.data, 'markup')
|
|
419
|
+
const [pass, txt] = this.checkHeuristic(node.data, 'markup')
|
|
414
420
|
if (!pass) {
|
|
415
421
|
return []
|
|
416
422
|
}
|
|
417
|
-
|
|
423
|
+
txt.context = this.context
|
|
424
|
+
this.mstr.update(node.start, node.end, `{${rtFunc}(${this.index.get(txt.toKey())})}`)
|
|
418
425
|
return [txt]
|
|
419
426
|
}
|
|
420
427
|
|
|
@@ -448,12 +455,13 @@ export default class Preprocess {
|
|
|
448
455
|
}
|
|
449
456
|
// Text
|
|
450
457
|
const {start, end} = value
|
|
451
|
-
const [pass, txt] = this.
|
|
458
|
+
const [pass, txt] = this.checkHeuristic(value.data, 'attribute')
|
|
452
459
|
if (!pass) {
|
|
453
460
|
continue
|
|
454
461
|
}
|
|
462
|
+
txt.context = this.context
|
|
455
463
|
txts.push(txt)
|
|
456
|
-
this.mstr.update(value.start, value.end, `{${rtFunc}(${this.index.get(txt.
|
|
464
|
+
this.mstr.update(value.start, value.end, `{${rtFunc}(${this.index.get(txt.toKey())})}`)
|
|
457
465
|
if (!`'"`.includes(this.content[start - 1])) {
|
|
458
466
|
continue
|
|
459
467
|
}
|
|
@@ -586,16 +594,49 @@ export default class Preprocess {
|
|
|
586
594
|
}
|
|
587
595
|
|
|
588
596
|
/**
|
|
589
|
-
* @param {
|
|
597
|
+
* @param {string} data
|
|
598
|
+
*/
|
|
599
|
+
processCommentDirectives = data => {
|
|
600
|
+
if (data === '@wc-ignore') {
|
|
601
|
+
this.forceInclude = false
|
|
602
|
+
}
|
|
603
|
+
if (data === '@wc-include') {
|
|
604
|
+
this.forceInclude = true
|
|
605
|
+
}
|
|
606
|
+
const ctxStart = '@wc-context:'
|
|
607
|
+
if (data.startsWith(ctxStart)) {
|
|
608
|
+
this.context = data.slice(ctxStart.length).trim()
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* @param {import("svelte/compiler").AST.SvelteNode & import('estree').BaseNode} node
|
|
590
614
|
* @returns {NestText[]}
|
|
591
615
|
*/
|
|
592
616
|
visit = node => {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
return
|
|
617
|
+
if (node.type === 'Comment') {
|
|
618
|
+
this.processCommentDirectives(node.data.trim())
|
|
619
|
+
return []
|
|
620
|
+
}
|
|
621
|
+
// for estree
|
|
622
|
+
for (const comment of node.leadingComments ?? []) {
|
|
623
|
+
this.processCommentDirectives(comment.value.trim())
|
|
624
|
+
}
|
|
625
|
+
let txts = []
|
|
626
|
+
if (this.forceInclude !== false) {
|
|
627
|
+
const methodName = `visit${node.type}`
|
|
628
|
+
if (methodName in this) {
|
|
629
|
+
txts = this[methodName](node)
|
|
630
|
+
}
|
|
596
631
|
}
|
|
597
|
-
|
|
598
|
-
|
|
632
|
+
this.forceInclude = null
|
|
633
|
+
if (this.context != null) {
|
|
634
|
+
for (const txt of txts) {
|
|
635
|
+
txt.context = this.context
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
this.context = null
|
|
639
|
+
return txts
|
|
599
640
|
}
|
|
600
641
|
|
|
601
642
|
/**
|