@npm-questionpro/wick-ui-i18n 0.8.0 → 0.10.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/index.js +80 -163
- package/package.json +1 -1
- package/src/debug.js +80 -0
- package/src/processor.js +153 -0
- package/src/transform.js +224 -0
- package/src/transformJSXTextWithEntities.js +127 -0
- package/src/transformTemplateLiteral.js +90 -0
- package/src/transformWtCalls.js +136 -0
- package/wickuii18n.test.js +528 -10
package/index.js
CHANGED
|
@@ -1,181 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"WuMenuIcon",
|
|
21
|
-
"WuScrollArea",
|
|
22
|
-
"WuDrawer",
|
|
23
|
-
"WuLoader",
|
|
24
|
-
"WuContentEditor",
|
|
25
|
-
].concat(options.ignoreComponents || []),
|
|
26
|
-
);
|
|
27
|
-
this.dictionary = new Map();
|
|
28
|
-
this.debugEnabled = options.debug || false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
log(...args) {
|
|
32
|
-
if (this.debugEnabled) console.log("[wick-i18n]", ...args);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
record(key, text, file) {
|
|
36
|
-
if (this.dictionary.has(key) && this.dictionary.get(key) !== text) {
|
|
37
|
-
console.warn(
|
|
38
|
-
`[wick-i18n] Collision in ${file}\nKey: "${key}"\nNew: "${text}"`,
|
|
39
|
-
);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
this.dictionary.set(key, text);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
shouldTranslate(path) {
|
|
46
|
-
let isIgnored = false;
|
|
47
|
-
let targetFound = false;
|
|
48
|
-
|
|
49
|
-
path.findParent((p) => {
|
|
50
|
-
if (!p.isJSXElement()) return false;
|
|
51
|
-
|
|
52
|
-
const name =
|
|
53
|
-
p.node.openingElement.name.name ||
|
|
54
|
-
p.node.openingElement.name.property?.name;
|
|
55
|
-
const attrs = p.node.openingElement.attributes || [];
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
attrs.some((a) =>
|
|
59
|
-
["data-skip", "data-i18n-skip"].includes(a.name?.name),
|
|
60
|
-
)
|
|
61
|
-
) {
|
|
62
|
-
isIgnored = true;
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview wick-ui-i18n — Vite plugin that automatically wraps static JSX
|
|
3
|
+
* text inside Wick UI components with `<WuTranslate>` at build/dev time and
|
|
4
|
+
* emits a `wick-ui-i18n.json` translation dictionary.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // vite.config.js
|
|
8
|
+
* import wickI18n from '@npm-questionpro/wick-ui-i18n';
|
|
9
|
+
*
|
|
10
|
+
* export default {
|
|
11
|
+
* plugins: [
|
|
12
|
+
* wickI18n({
|
|
13
|
+
* components: ['MyWidget'],
|
|
14
|
+
* ignoreComponents: ['MyRawHtml'],
|
|
15
|
+
* debug: true,
|
|
16
|
+
* }),
|
|
17
|
+
* ],
|
|
18
|
+
* };
|
|
19
|
+
*/
|
|
65
20
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
21
|
+
import { createFilter } from "vite";
|
|
22
|
+
import { TranslationProcessor } from "./src/processor.js";
|
|
23
|
+
import { transformFile } from "./src/transform.js";
|
|
24
|
+
import { printReport } from "./src/debug.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} WickI18nOptions
|
|
28
|
+
* @property {string[]} [components] - Extra component names that trigger translation.
|
|
29
|
+
* @property {string[]} [ignoreComponents] - Component names to exclude from translation.
|
|
30
|
+
* @property {string|string[]|RegExp} [excludeFiles] - Files to skip (passed as `exclude` to Vite's createFilter).
|
|
31
|
+
* @property {boolean} [debug] - Log transform activity to the console.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Vite plugin factory for wick-ui-i18n.
|
|
36
|
+
*
|
|
37
|
+
* @param {WickI18nOptions} [options]
|
|
38
|
+
* @returns {import('vite').Plugin}
|
|
39
|
+
*/
|
|
40
|
+
export default function wickuiI18nPlugin(options = {}) {
|
|
41
|
+
const processor = new TranslationProcessor({
|
|
42
|
+
components: options.components || [],
|
|
43
|
+
ignoreComponents: options.ignoreComponents,
|
|
44
|
+
debug: options.debug,
|
|
45
|
+
});
|
|
87
46
|
|
|
88
|
-
|
|
89
|
-
const parent = path.findParent((p) => p.isJSXElement());
|
|
90
|
-
const attr = parent?.node.openingElement.attributes.find(
|
|
91
|
-
(a) => a.name?.name === "data-i18n-key",
|
|
92
|
-
);
|
|
93
|
-
if (!attr) return null;
|
|
94
|
-
return attr.value.type === "StringLiteral"
|
|
95
|
-
? attr.value.value
|
|
96
|
-
: attr.value.expression?.value;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
47
|
+
const filter = createFilter([/\.(jsx|tsx)$/], options.excludeFiles);
|
|
99
48
|
|
|
100
|
-
|
|
101
|
-
const processor = new TranslationProcessor(options);
|
|
102
|
-
const filter = createFilter(options.include || [/\.(jsx|tsx)$/]);
|
|
49
|
+
let base = "/";
|
|
103
50
|
|
|
104
51
|
return {
|
|
105
52
|
name: "wick-ui-i18n",
|
|
106
53
|
enforce: "pre",
|
|
107
54
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
let [needsImport, hasImport] = [false, false];
|
|
117
|
-
|
|
118
|
-
const handleCapture = (path, text, start, end) => {
|
|
119
|
-
const cleanText = text.trim();
|
|
120
|
-
if (!cleanText || !processor.shouldTranslate(path)) return;
|
|
121
|
-
|
|
122
|
-
const key = processor.getExplicitKey(path) || cleanText;
|
|
123
|
-
processor.record(key, cleanText, id);
|
|
124
|
-
|
|
125
|
-
const original = ms.slice(start, end);
|
|
126
|
-
ms.overwrite(
|
|
127
|
-
start,
|
|
128
|
-
end,
|
|
129
|
-
`<WuTranslate __i18nKey=${JSON.stringify(key)}></WuTranslate>`,
|
|
130
|
-
);
|
|
131
|
-
needsImport = true;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
traverse(ast, {
|
|
135
|
-
ImportDeclaration(path) {
|
|
136
|
-
if (path.node.source.value.includes("wick-ui-lib")) {
|
|
137
|
-
hasImport = path.node.specifiers.some(
|
|
138
|
-
(s) => s.imported?.name === "WuTranslate",
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
JSXText(path) {
|
|
143
|
-
const text = path.node.value;
|
|
144
|
-
const trimmed = text.trim();
|
|
145
|
-
const start = path.node.start + text.indexOf(trimmed);
|
|
146
|
-
handleCapture(path, trimmed, start, start + trimmed.length);
|
|
147
|
-
},
|
|
148
|
-
JSXExpressionContainer(path) {
|
|
149
|
-
const expr = path.node.expression;
|
|
150
|
-
let text = null;
|
|
151
|
-
if (expr.type === "StringLiteral") text = expr.value;
|
|
152
|
-
else if (
|
|
153
|
-
expr.type === "TemplateLiteral" &&
|
|
154
|
-
!expr.expressions.length
|
|
155
|
-
) {
|
|
156
|
-
text = expr.quasis[0].value.cooked;
|
|
157
|
-
}
|
|
158
|
-
if (text) handleCapture(path, text, path.node.start, path.node.end);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (needsImport && !hasImport) {
|
|
163
|
-
ms.prepend(
|
|
164
|
-
`import { WuTranslate } from '@npm-questionpro/wick-ui-lib';\n`,
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return needsImport
|
|
169
|
-
? { code: ms.toString(), map: ms.generateMap({ hires: true }) }
|
|
170
|
-
: null;
|
|
55
|
+
/**
|
|
56
|
+
* Capture the resolved base so the dev-server middleware path matches
|
|
57
|
+
* what `import.meta.env.BASE_URL` resolves to in the consumer app.
|
|
58
|
+
*
|
|
59
|
+
* @param {import('vite').ResolvedConfig} resolvedConfig
|
|
60
|
+
*/
|
|
61
|
+
configResolved(resolvedConfig) {
|
|
62
|
+
base = resolvedConfig.base;
|
|
171
63
|
},
|
|
172
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Clear state on every build so stale keys don't accumulate across
|
|
67
|
+
* watch-mode rebuilds.
|
|
68
|
+
*/
|
|
173
69
|
buildStart() {
|
|
174
70
|
processor.dictionary.clear();
|
|
71
|
+
processor.entries = [];
|
|
175
72
|
},
|
|
176
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Transform a single file: skip fast if it has no "Wu" tokens, then run
|
|
76
|
+
* the full AST rewrite.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} code
|
|
79
|
+
* @param {string} id
|
|
80
|
+
*/
|
|
81
|
+
transform(code, id) {
|
|
82
|
+
if (!filter(id) || (!code.includes("Wu") && !/\bwt\(/.test(code))) return null;
|
|
83
|
+
return transformFile(code, id, processor);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Expose the collected dictionary at `GET /wick-ui-i18n.json` during dev.
|
|
88
|
+
*
|
|
89
|
+
* @param {import('vite').ViteDevServer} server
|
|
90
|
+
*/
|
|
177
91
|
configureServer(server) {
|
|
178
|
-
server.middlewares.use(
|
|
92
|
+
server.middlewares.use(`${base}wick-ui-i18n.json`, (_req, res) => {
|
|
179
93
|
res.setHeader("Content-Type", "application/json");
|
|
180
94
|
res.end(
|
|
181
95
|
JSON.stringify(Object.fromEntries(processor.dictionary), null, 2),
|
|
@@ -183,6 +97,7 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
183
97
|
});
|
|
184
98
|
},
|
|
185
99
|
|
|
100
|
+
/** Emit the translation dictionary as a build asset and print debug table. */
|
|
186
101
|
generateBundle() {
|
|
187
102
|
this.emitFile({
|
|
188
103
|
type: "asset",
|
|
@@ -193,6 +108,8 @@ export default function wickuiI18nPlugin(options = {}) {
|
|
|
193
108
|
2,
|
|
194
109
|
),
|
|
195
110
|
});
|
|
111
|
+
|
|
112
|
+
printReport(processor.entries);
|
|
196
113
|
},
|
|
197
114
|
};
|
|
198
115
|
}
|
package/package.json
CHANGED
package/src/debug.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Build-time debug reporter — prints a formatted table of every
|
|
3
|
+
* captured translation entry (text, source file, JSX component tree) to stdout
|
|
4
|
+
* after the bundle is generated.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { basename } from "node:path";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Walk up the Babel path and collect JSX element names from outermost inward.
|
|
11
|
+
*
|
|
12
|
+
* @param {import('@babel/traverse').NodePath} path
|
|
13
|
+
* @returns {string} e.g. `"WuProvider > div > WuButton"`
|
|
14
|
+
*/
|
|
15
|
+
export function getComponentTree(path) {
|
|
16
|
+
const parts = [];
|
|
17
|
+
path.findParent((p) => {
|
|
18
|
+
if (p.isJSXElement()) {
|
|
19
|
+
const name =
|
|
20
|
+
p.node.openingElement.name.name ||
|
|
21
|
+
p.node.openingElement.name.property?.name;
|
|
22
|
+
if (name) parts.unshift(name);
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
});
|
|
26
|
+
return parts.join(" > ") || "(root)";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} DebugEntry
|
|
31
|
+
* @property {string} key - i18n key used in WuTranslate.
|
|
32
|
+
* @property {string} text - Original source text.
|
|
33
|
+
* @property {string} file - Absolute path of the source file.
|
|
34
|
+
* @property {string} componentTree - Ancestor JSX chain, outermost first.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Print a box-drawing ASCII table of all captured translation entries.
|
|
39
|
+
* Columns are auto-sized to content.
|
|
40
|
+
*
|
|
41
|
+
* @param {DebugEntry[]} entries
|
|
42
|
+
*/
|
|
43
|
+
export function printReport(entries) {
|
|
44
|
+
if (!entries.length) {
|
|
45
|
+
console.log("\n[wick-i18n] No translations captured.\n");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const headers = ["Text", "File", "Component Tree"];
|
|
50
|
+
|
|
51
|
+
const rows = entries.map((e) => [e.text, basename(e.file), e.componentTree]);
|
|
52
|
+
|
|
53
|
+
const cols = headers.length;
|
|
54
|
+
const widths = headers.map((h, i) =>
|
|
55
|
+
Math.max(h.length, ...rows.map((r) => r[i].length)),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const pad = (str, w) => str.padEnd(w);
|
|
59
|
+
const sep = (l, m, r, fill) =>
|
|
60
|
+
l + widths.map((w) => fill.repeat(w + 2)).join(m) + r;
|
|
61
|
+
|
|
62
|
+
const top = sep("┌", "┬", "┐", "─");
|
|
63
|
+
const mid = sep("├", "┼", "┤", "─");
|
|
64
|
+
const bottom = sep("└", "┴", "┘", "─");
|
|
65
|
+
const row = (cells) =>
|
|
66
|
+
"│" + cells.map((c, i) => ` ${pad(c, widths[i])} `).join("│") + "│";
|
|
67
|
+
|
|
68
|
+
const lines = [
|
|
69
|
+
"",
|
|
70
|
+
`[wick-i18n] Build report — ${entries.length} translation(s) captured`,
|
|
71
|
+
top,
|
|
72
|
+
row(headers),
|
|
73
|
+
mid,
|
|
74
|
+
...rows.map(row),
|
|
75
|
+
bottom,
|
|
76
|
+
"",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
console.log(lines.join("\n"));
|
|
80
|
+
}
|
package/src/processor.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TranslationProcessor — maintains the translation dictionary and
|
|
3
|
+
* decides which JSX nodes should be wrapped with WuTranslate.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Components always excluded from translation regardless of user config. */
|
|
7
|
+
const DEFAULT_IGNORE = [
|
|
8
|
+
"WuIcon",
|
|
9
|
+
"WuTranslateProvider",
|
|
10
|
+
"WuHelpButton",
|
|
11
|
+
"WuActivityLog",
|
|
12
|
+
"WuAppHeader",
|
|
13
|
+
"WuAPpHeadeMenu",
|
|
14
|
+
"WuCopyToClipboard",
|
|
15
|
+
"WuMenuIcon",
|
|
16
|
+
"WuScrollArea",
|
|
17
|
+
"WuDrawer",
|
|
18
|
+
"WuLoader",
|
|
19
|
+
"WuContentEditor",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export class TranslationProcessor {
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {string[]} options.components - Component names that trigger translation.
|
|
26
|
+
* @param {string[]} [options.ignoreComponents] - Extra components to exclude.
|
|
27
|
+
* @param {boolean} [options.debug] - Enable verbose logging.
|
|
28
|
+
*/
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.components = new Set(options.components);
|
|
31
|
+
this.ignoreComponents = new Set(
|
|
32
|
+
DEFAULT_IGNORE.concat(options.ignoreComponents || []),
|
|
33
|
+
);
|
|
34
|
+
/** @type {Map<string, string>} key → original text */
|
|
35
|
+
this.dictionary = new Map();
|
|
36
|
+
/** @type {import('./debug.js').DebugEntry[]} */
|
|
37
|
+
this.entries = [];
|
|
38
|
+
this.debugEnabled = options.debug || false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Conditional logger — only emits when `debug: true`.
|
|
43
|
+
* @param {...unknown} args
|
|
44
|
+
*/
|
|
45
|
+
log(...args) {
|
|
46
|
+
if (this.debugEnabled) console.log("[wick-i18n]", ...args);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Store a key→text pair and append a debug entry.
|
|
51
|
+
* Warns on collision (same key, different text).
|
|
52
|
+
*
|
|
53
|
+
* @param {string} key
|
|
54
|
+
* @param {string} text
|
|
55
|
+
* @param {string} file - Source file path.
|
|
56
|
+
* @param {string} componentTree - JSX ancestor chain for the debug report.
|
|
57
|
+
*/
|
|
58
|
+
record(key, text, file, componentTree) {
|
|
59
|
+
if (this.dictionary.has(key) && this.dictionary.get(key) !== text) {
|
|
60
|
+
console.warn(
|
|
61
|
+
`[wick-i18n] Collision in ${file}\nKey: "${key}"\nNew: "${text}"`,
|
|
62
|
+
);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.dictionary.set(key, text);
|
|
66
|
+
this.entries.push({ key, text, file, componentTree });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Walk up the JSX tree from `path` to determine if the node is inside a
|
|
71
|
+
* component that should be translated.
|
|
72
|
+
*
|
|
73
|
+
* Rules (first match wins, walking outward):
|
|
74
|
+
* - `data-skip` / `data-i18n-skip` attr → ignored
|
|
75
|
+
* - component in `ignoreComponents` → ignored
|
|
76
|
+
* - `data-i18n-wrapper` attr OR component starts with "Wu" / is in `components` → translate
|
|
77
|
+
*
|
|
78
|
+
* @param {import('@babel/traverse').NodePath} path
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
shouldTranslate(path) {
|
|
82
|
+
let isIgnored = false;
|
|
83
|
+
let targetFound = false;
|
|
84
|
+
|
|
85
|
+
path.findParent((p) => {
|
|
86
|
+
if (!p.isJSXElement()) return false;
|
|
87
|
+
|
|
88
|
+
const name =
|
|
89
|
+
p.node.openingElement.name.name ||
|
|
90
|
+
p.node.openingElement.name.property?.name;
|
|
91
|
+
const attrs = p.node.openingElement.attributes || [];
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
attrs.some((a) =>
|
|
95
|
+
["data-skip", "data-i18n-skip"].includes(a.name?.name),
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
isIgnored = true;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.ignoreComponents.has(name)) {
|
|
103
|
+
isIgnored = true;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hasWrapper = attrs.some(
|
|
108
|
+
(a) => a.name?.name === "data-i18n-wrapper",
|
|
109
|
+
);
|
|
110
|
+
const isTarget = this.components.has(name) || name?.startsWith("Wu");
|
|
111
|
+
|
|
112
|
+
if (hasWrapper || isTarget) {
|
|
113
|
+
targetFound = true;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return targetFound && !isIgnored;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Return the explicit i18n key from the nearest ancestor JSX element's
|
|
125
|
+
* `data-i18n-key` attribute, or `null` if absent.
|
|
126
|
+
*
|
|
127
|
+
* @param {import('@babel/traverse').NodePath} path
|
|
128
|
+
* @returns {string|null}
|
|
129
|
+
*/
|
|
130
|
+
getExplicitKey(path) {
|
|
131
|
+
let result = null;
|
|
132
|
+
path.findParent((p) => {
|
|
133
|
+
if (!p.isJSXElement()) return false;
|
|
134
|
+
const attr = p.node.openingElement.attributes.find(
|
|
135
|
+
(a) => a.name?.name === "data-i18n-key",
|
|
136
|
+
);
|
|
137
|
+
if (!attr) return false;
|
|
138
|
+
const val =
|
|
139
|
+
attr.value.type === "StringLiteral"
|
|
140
|
+
? attr.value.value
|
|
141
|
+
: attr.value.expression?.value;
|
|
142
|
+
if (!val) {
|
|
143
|
+
console.warn(
|
|
144
|
+
`[wick-i18n] data-i18n-key on <${p.node.openingElement.name.name}> is dynamic or empty — falling back to text content.`,
|
|
145
|
+
);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
result = val;
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
}
|