ultimate-jekyll-manager 0.0.39 → 0.0.41
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 +1 -0
- package/dist/commands/audit.js +1 -1
- package/dist/commands/translation.js +1 -1
- package/dist/defaults/dist/_layouts/placeholder/test/translation.md +8 -0
- package/dist/gulp/tasks/audit.js +2 -2
- package/dist/gulp/tasks/defaults.js +1 -1
- package/dist/gulp/tasks/developmentRebuild.js +1 -1
- package/dist/gulp/tasks/distribute.js +1 -1
- package/dist/gulp/tasks/imagemin.js +1 -1
- package/dist/gulp/tasks/jekyll.js +1 -4
- package/dist/gulp/tasks/sass.js +1 -1
- package/dist/gulp/tasks/translation.js +139 -45
- package/dist/gulp/tasks/utils/collectTextNodes.js +37 -8
- package/dist/gulp/tasks/webpack.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/commands/audit.js
CHANGED
|
@@ -11,5 +11,5 @@ module.exports = async function (options) {
|
|
|
11
11
|
// Log
|
|
12
12
|
logger.log(`Starting audit...`);
|
|
13
13
|
|
|
14
|
-
await execute('
|
|
14
|
+
await execute('UJ_AUDIT_FORCE=true bundle exec npm run gulp -- audit', { log: true })
|
|
15
15
|
};
|
|
@@ -11,5 +11,5 @@ module.exports = async function (options) {
|
|
|
11
11
|
// Log
|
|
12
12
|
logger.log(`Starting translation...`);
|
|
13
13
|
|
|
14
|
-
await execute('
|
|
14
|
+
await execute('UJ_TRANSLATION_FORCE=true bundle exec npm run gulp -- translation', { log: true })
|
|
15
15
|
};
|
|
@@ -44,3 +44,11 @@ By vising {{ brand }}, you agree to comply.
|
|
|
44
44
|
- Account page: [/account](/account)
|
|
45
45
|
- Admin page: [/admin](/admin)
|
|
46
46
|
- Admin sub-page: [/admin/subpage](/admin/subpage)
|
|
47
|
+
|
|
48
|
+
## This is an input
|
|
49
|
+
<div class="form-group">
|
|
50
|
+
<input type="email" name="slap_honey" class="form-control" placeholder="Your Email">
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
## This button has a title
|
|
54
|
+
<a href="https://itwcreativeworks.com" class="btn btn-primary" title="Visit ITW Creative Works">Visit ITW Creative Works</a>
|
package/dist/gulp/tasks/audit.js
CHANGED
|
@@ -35,8 +35,8 @@ async function audit(complete) {
|
|
|
35
35
|
// Log
|
|
36
36
|
logger.log('Starting...');
|
|
37
37
|
|
|
38
|
-
// Quit if NOT in build mode and
|
|
39
|
-
if (!Manager.isBuildMode() && process.env.
|
|
38
|
+
// Quit if NOT in build mode and UJ_AUDIT_FORCE is not true
|
|
39
|
+
if (!Manager.isBuildMode() && process.env.UJ_AUDIT_FORCE !== 'true') {
|
|
40
40
|
logger.log('Skipping audit in development mode');
|
|
41
41
|
return complete();
|
|
42
42
|
}
|
|
@@ -232,7 +232,7 @@ function defaultsWatcher(complete) {
|
|
|
232
232
|
// Watch for changes
|
|
233
233
|
watch(input, { delay: delay, dot: true }, defaults)
|
|
234
234
|
.on('change', (path) => {
|
|
235
|
-
logger.log(`[watcher] File ${path}
|
|
235
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
// Complete
|
|
@@ -91,7 +91,7 @@ function developmentRebuildWatcher(complete) {
|
|
|
91
91
|
// Watch for changes
|
|
92
92
|
watch(input, { delay: delay, dot: true }, developmentRebuild)
|
|
93
93
|
.on('change', (path) => {
|
|
94
|
-
logger.log(`[watcher] File ${path}
|
|
94
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
// Complete
|
|
@@ -102,7 +102,7 @@ function distributeWatcher(complete) {
|
|
|
102
102
|
// Watch for changes
|
|
103
103
|
watch(input, { delay: delay, dot: true }, distribute)
|
|
104
104
|
.on('change', (path) => {
|
|
105
|
-
logger.log(`[watcher] File ${path}
|
|
105
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
// Complete
|
|
@@ -130,7 +130,7 @@ function imageminWatcher(complete) {
|
|
|
130
130
|
// Watch for changes
|
|
131
131
|
watch(input, { delay: delay, dot: true }, imagemin)
|
|
132
132
|
.on('change', (path) => {
|
|
133
|
-
logger.log(`[watcher] File ${path}
|
|
133
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
// Complete
|
|
@@ -79,9 +79,6 @@ async function jekyll(complete) {
|
|
|
79
79
|
// Run buildpost hook
|
|
80
80
|
await hook('build:post', index);
|
|
81
81
|
|
|
82
|
-
// QUIT
|
|
83
|
-
// QUIT
|
|
84
|
-
|
|
85
82
|
// Log
|
|
86
83
|
logger.log('Finished!');
|
|
87
84
|
|
|
@@ -113,7 +110,7 @@ function jekyllWatcher(complete) {
|
|
|
113
110
|
// Watch for changes
|
|
114
111
|
watch(input, { delay: delay, dot: true }, jekyll)
|
|
115
112
|
.on('change', (path) => {
|
|
116
|
-
logger.log(`[watcher] File ${path}
|
|
113
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
117
114
|
|
|
118
115
|
// Check if changed file is a .rb file
|
|
119
116
|
if (path.endsWith('.rb')) {
|
package/dist/gulp/tasks/sass.js
CHANGED
|
@@ -113,7 +113,7 @@ function sassWatcher(complete) {
|
|
|
113
113
|
// Watch for changes
|
|
114
114
|
watch(input, { delay: delay, dot: true }, sass)
|
|
115
115
|
.on('change', (path) => {
|
|
116
|
-
logger.log(`[watcher] File ${path}
|
|
116
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
// Complete
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Libraries
|
|
2
2
|
const Manager = new (require('../../build.js'));
|
|
3
3
|
const logger = Manager.logger('translation');
|
|
4
|
-
const { series } = require('gulp');
|
|
4
|
+
const { series, watch } = require('gulp');
|
|
5
5
|
const glob = require('glob').globSync;
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const fetch = require('wonderful-fetch');
|
|
@@ -9,7 +9,7 @@ const jetpack = require('fs-jetpack');
|
|
|
9
9
|
const cheerio = require('cheerio');
|
|
10
10
|
const crypto = require('crypto');
|
|
11
11
|
const yaml = require('js-yaml');
|
|
12
|
-
const { execute, wait } = require('node-powertools');
|
|
12
|
+
const { execute, wait, template } = require('node-powertools');
|
|
13
13
|
const { Octokit } = require('@octokit/rest')
|
|
14
14
|
const AdmZip = require('adm-zip') // npm install adm-zip
|
|
15
15
|
|
|
@@ -38,13 +38,29 @@ const RECHECK_DAYS = 0;
|
|
|
38
38
|
// const AI_MODEL = 'gpt-4.1-nano';
|
|
39
39
|
const AI_MODEL = 'gpt-4.1-mini';
|
|
40
40
|
const TRANSLATION_BRANCH = 'uj-translations';
|
|
41
|
-
const LOUD = false;
|
|
42
|
-
|
|
41
|
+
// const LOUD = false;
|
|
42
|
+
const LOUD = process.env.UJ_LOUD_LOGS === 'true';
|
|
43
|
+
const CONTROL = 'UJ-TRANSLATION-CONTROL';
|
|
43
44
|
|
|
44
45
|
const TRANSLATION_DELAY_MS = 500; // wait between each translation
|
|
45
46
|
const TRANSLATION_BATCH_SIZE = 25; // wait longer every N translations
|
|
46
47
|
const TRANSLATION_BATCH_DELAY_MS = 10000; // longer wait after batch
|
|
47
48
|
|
|
49
|
+
// Prompt
|
|
50
|
+
const SYSTEM_PROMPT = `
|
|
51
|
+
You are a professional translator.
|
|
52
|
+
Translate the provided content, preserving all original formatting, HTML structure, metadata, and links.
|
|
53
|
+
Do not explain anything — just return the translated content.
|
|
54
|
+
The content is TAGGED with [0]...[/0], etc. to mark the text. You MUST KEEP THESE TAGS IN PLACE IN YOUR RESPONSE and OPEN ([0]) and CLOSE ([/0]) them PROPERLY.
|
|
55
|
+
|
|
56
|
+
DO NOT translate the following elements (but still keep them in place):
|
|
57
|
+
- URLs or other non-text elements.
|
|
58
|
+
- the brand name ({brand}).
|
|
59
|
+
- the control tag (${CONTROL}).
|
|
60
|
+
|
|
61
|
+
Translate to {lang}
|
|
62
|
+
`;
|
|
63
|
+
|
|
48
64
|
// Variables
|
|
49
65
|
let octokit;
|
|
50
66
|
|
|
@@ -61,11 +77,8 @@ async function translation(complete) {
|
|
|
61
77
|
// Log
|
|
62
78
|
logger.log('Starting...');
|
|
63
79
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Quit if NOT in build mode and UJ_FORCE_TRANSLATION is not true
|
|
68
|
-
if (!Manager.isBuildMode() && process.env.UJ_FORCE_TRANSLATION !== 'true') {
|
|
80
|
+
// Quit if NOT in build mode and UJ_TRANSLATION_FORCE is not true
|
|
81
|
+
if (!Manager.isBuildMode() && process.env.UJ_TRANSLATION_FORCE !== 'true') {
|
|
69
82
|
logger.log('Skipping translation in development mode');
|
|
70
83
|
return complete();
|
|
71
84
|
}
|
|
@@ -103,6 +116,34 @@ async function translation(complete) {
|
|
|
103
116
|
return complete();
|
|
104
117
|
};
|
|
105
118
|
|
|
119
|
+
// TODO: Currently this does not work because it will run an infinite loop
|
|
120
|
+
function translationWatcher(complete) {
|
|
121
|
+
// Quit if in build mode
|
|
122
|
+
if (Manager.isBuildMode()) {
|
|
123
|
+
logger.log('[watcher] Skipping watcher in build mode');
|
|
124
|
+
return complete();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Log
|
|
128
|
+
logger.log('[watcher] Watching for changes...');
|
|
129
|
+
|
|
130
|
+
// Get ignored pages
|
|
131
|
+
const ignoredPages = getIgnoredPages();
|
|
132
|
+
const ignore = [
|
|
133
|
+
...ignoredPages.files.map(key => `_site/${key}.html`),
|
|
134
|
+
...ignoredPages.folders.map(folder => `_site/${folder}/**/*`)
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
// Watch for changes
|
|
138
|
+
watch(input, { delay: delay, ...getGlobOptions(), }, translation)
|
|
139
|
+
.on('change', (path) => {
|
|
140
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Complete
|
|
144
|
+
return complete();
|
|
145
|
+
}
|
|
146
|
+
|
|
106
147
|
// Default Task
|
|
107
148
|
module.exports = series(translation);
|
|
108
149
|
|
|
@@ -110,7 +151,6 @@ module.exports = series(translation);
|
|
|
110
151
|
async function processTranslation() {
|
|
111
152
|
const enabled = config?.translation?.enabled !== false;
|
|
112
153
|
const languages = config?.translation?.languages || [];
|
|
113
|
-
const ignoredPages = getIgnoredPages();
|
|
114
154
|
const updatedFiles = new Set();
|
|
115
155
|
|
|
116
156
|
// Quit if translation is disabled or no languages are configured
|
|
@@ -135,13 +175,7 @@ async function processTranslation() {
|
|
|
135
175
|
// }
|
|
136
176
|
|
|
137
177
|
// Get files
|
|
138
|
-
const allFiles = glob(input,
|
|
139
|
-
nodir: true,
|
|
140
|
-
ignore: [
|
|
141
|
-
...ignoredPages.files.map(key => `_site/${key}.html`),
|
|
142
|
-
...ignoredPages.folders.map(folder => `_site/${folder}/**/*`)
|
|
143
|
-
]
|
|
144
|
-
});
|
|
178
|
+
const allFiles = glob(input, getGlobOptions());
|
|
145
179
|
|
|
146
180
|
// Log
|
|
147
181
|
logger.log(`Translating ${allFiles.length} files for ${languages.length} supported languages: ${languages.join(', ')}`);
|
|
@@ -153,6 +187,8 @@ async function processTranslation() {
|
|
|
153
187
|
skipped: new Set(),
|
|
154
188
|
}
|
|
155
189
|
};
|
|
190
|
+
const promptHash = crypto.createHash('sha256').update(SYSTEM_PROMPT).digest('hex');
|
|
191
|
+
|
|
156
192
|
for (const lang of languages) {
|
|
157
193
|
const metaPath = path.join(CACHE_DIR, lang, 'meta.json');
|
|
158
194
|
let meta = {};
|
|
@@ -163,6 +199,15 @@ async function processTranslation() {
|
|
|
163
199
|
logger.warn(`⚠️ Failed to parse meta for [${lang}], starting fresh`);
|
|
164
200
|
}
|
|
165
201
|
}
|
|
202
|
+
|
|
203
|
+
// Check if the promptHash matches; if not, invalidate the cache
|
|
204
|
+
if (meta.prompt?.hash !== promptHash) {
|
|
205
|
+
logger.warn(`⚠️ Prompt hash mismatch for [${lang}]. Invalidating cache.`);
|
|
206
|
+
meta = {};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Store the current promptHash in the meta file
|
|
210
|
+
meta.prompt = { hash: promptHash };
|
|
166
211
|
metas[lang] = { meta, path: metaPath, skipped: new Set() };
|
|
167
212
|
}
|
|
168
213
|
|
|
@@ -173,9 +218,16 @@ async function processTranslation() {
|
|
|
173
218
|
for (const filePath of allFiles) {
|
|
174
219
|
// Get relative path and original HTML
|
|
175
220
|
const relativePath = filePath.replace(/^_site[\\/]/, '');
|
|
176
|
-
|
|
221
|
+
let originalHtml = jetpack.read(filePath);
|
|
177
222
|
const $ = cheerio.load(originalHtml);
|
|
178
223
|
|
|
224
|
+
// Inject hidden control tag as last child of <body>
|
|
225
|
+
const controlTag = `<span id="${CONTROL}" style="display:none;">${CONTROL}</span>`;
|
|
226
|
+
$('body').append(controlTag);
|
|
227
|
+
|
|
228
|
+
// Reset originalHtml
|
|
229
|
+
originalHtml = $.html();
|
|
230
|
+
|
|
179
231
|
// Collect text nodes with tags
|
|
180
232
|
const textNodes = collectTextNodes($, { tag: true });
|
|
181
233
|
|
|
@@ -217,11 +269,30 @@ async function processTranslation() {
|
|
|
217
269
|
const age = entry?.timestamp
|
|
218
270
|
? (Date.now() - new Date(entry.timestamp).getTime()) / (1000 * 60 * 60 * 24)
|
|
219
271
|
: Infinity;
|
|
220
|
-
const useCached = entry
|
|
272
|
+
const useCached = entry
|
|
273
|
+
&& entry.hash === hash
|
|
274
|
+
&& (RECHECK_DAYS === 0 || age < RECHECK_DAYS);
|
|
221
275
|
const startTime = Date.now();
|
|
222
276
|
|
|
277
|
+
function setResult(success) {
|
|
278
|
+
if (success) {
|
|
279
|
+
meta[relativePath] = {
|
|
280
|
+
timestamp: new Date().toISOString(),
|
|
281
|
+
hash,
|
|
282
|
+
};
|
|
283
|
+
} else {
|
|
284
|
+
meta[relativePath] = {
|
|
285
|
+
timestamp: 0,
|
|
286
|
+
hash: '__fail__',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
223
291
|
// Check if we can use cached translation
|
|
224
|
-
if (
|
|
292
|
+
if (
|
|
293
|
+
(useCached || process.env.UJ_TRANSLATION_CACHE === 'true')
|
|
294
|
+
&& jetpack.exists(cachePath)
|
|
295
|
+
) {
|
|
225
296
|
translated = jetpack.read(cachePath);
|
|
226
297
|
logger.log(`📦 Using cached translation for ${relativePath} [${lang}]`);
|
|
227
298
|
} else {
|
|
@@ -241,10 +312,9 @@ async function processTranslation() {
|
|
|
241
312
|
|
|
242
313
|
// Save to cache
|
|
243
314
|
jetpack.write(cachePath, translated);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
315
|
+
|
|
316
|
+
// Set result
|
|
317
|
+
setResult(true);
|
|
248
318
|
} catch (e) {
|
|
249
319
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
250
320
|
logger.error(`⚠️ Translation failed: ${relativePath} [${lang}] — ${e.message} (Elapsed time: ${elapsedTime}s)`);
|
|
@@ -253,10 +323,7 @@ async function processTranslation() {
|
|
|
253
323
|
translated = bodyText;
|
|
254
324
|
|
|
255
325
|
// Save failure to cache
|
|
256
|
-
|
|
257
|
-
timestamp: 0,
|
|
258
|
-
hash: '__fail__',
|
|
259
|
-
};
|
|
326
|
+
setResult(false);
|
|
260
327
|
}
|
|
261
328
|
}
|
|
262
329
|
|
|
@@ -278,19 +345,41 @@ async function processTranslation() {
|
|
|
278
345
|
return logger.warn(`⚠️ Could not find translated tag for index ${i}`);
|
|
279
346
|
}
|
|
280
347
|
|
|
348
|
+
// Extract original leading and trailing whitespace
|
|
349
|
+
const originalText = n.text;
|
|
350
|
+
const leadingWhitespace = originalText.match(/^\s*/)?.[0] || '';
|
|
351
|
+
const trailingWhitespace = originalText.match(/\s*$/)?.[0] || '';
|
|
352
|
+
|
|
353
|
+
// Reapply the original whitespace to the translation
|
|
354
|
+
const adjustedTranslation = `${leadingWhitespace}${translation.trim()}${trailingWhitespace}`;
|
|
355
|
+
|
|
281
356
|
if (n.type === 'data') {
|
|
282
|
-
n.reference.data =
|
|
357
|
+
n.reference.data = adjustedTranslation;
|
|
283
358
|
} else if (n.type === 'text') {
|
|
284
|
-
n.node.text(
|
|
359
|
+
n.node.text(adjustedTranslation);
|
|
285
360
|
} else if (n.type === 'attr') {
|
|
286
|
-
n.node.attr(n.attr,
|
|
361
|
+
n.node.attr(n.attr, adjustedTranslation);
|
|
287
362
|
}
|
|
288
|
-
if (LOUD) logger.log(`${i}: ${n.text} → ${
|
|
363
|
+
if (LOUD) logger.log(`${i}: "${n.text.trim()}" → "${adjustedTranslation.trim()}"`);
|
|
289
364
|
});
|
|
290
365
|
|
|
291
366
|
// Rewrite links
|
|
292
367
|
rewriteLinks($, lang);
|
|
293
368
|
|
|
369
|
+
// Check that the control tag matches the expected value
|
|
370
|
+
const controlTag = $(`#${CONTROL}`);
|
|
371
|
+
if (
|
|
372
|
+
controlTag.length === 0
|
|
373
|
+
|| controlTag.text() !== CONTROL
|
|
374
|
+
) {
|
|
375
|
+
logger.error(`⚠️ Control tag mismatch in ${relativePath} [${lang}]`);
|
|
376
|
+
|
|
377
|
+
return setResult(false);
|
|
378
|
+
} else {
|
|
379
|
+
// Delete the control tag
|
|
380
|
+
controlTag.remove();
|
|
381
|
+
}
|
|
382
|
+
|
|
294
383
|
// Set the lang attribute on the <html> tag
|
|
295
384
|
$('html').attr('lang', lang);
|
|
296
385
|
|
|
@@ -377,19 +466,16 @@ async function processTranslation() {
|
|
|
377
466
|
|
|
378
467
|
// Push updated translation cache back to uj-translations
|
|
379
468
|
if (Manager.isBuildMode()) {
|
|
380
|
-
await pushTranslationBranch(
|
|
469
|
+
await pushTranslationBranch( );
|
|
381
470
|
}
|
|
382
471
|
}
|
|
383
472
|
|
|
384
473
|
async function translateWithAPI(openAIKey, content, lang) {
|
|
385
|
-
|
|
386
|
-
const systemPrompt =
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
The content is TAGGED with [0]...[/0], etc. to mark the text. You MUST KEEP THESE TAGS IN PLACE IN YOUR RESPONSE and OPEN ([0]) and CLOSE ([/0]) them PROPERLY.
|
|
391
|
-
Translate to ${lang}.
|
|
392
|
-
`;
|
|
474
|
+
const brand = config?.brand?.name || 'Unknown Brand';
|
|
475
|
+
const systemPrompt = template(SYSTEM_PROMPT, {
|
|
476
|
+
lang,
|
|
477
|
+
brand
|
|
478
|
+
});
|
|
393
479
|
|
|
394
480
|
// Request
|
|
395
481
|
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
@@ -550,9 +636,6 @@ async function insertLanguageTags($, languages, relativePath, filePath) {
|
|
|
550
636
|
const format = isHtml ? 'html' : 'xml';
|
|
551
637
|
const formatted = await formatDocument($.html(), format);
|
|
552
638
|
|
|
553
|
-
console.log('---SAVING filePath', filePath);
|
|
554
|
-
console.log('---SAVING formatted.error', formatted.error);
|
|
555
|
-
|
|
556
639
|
// Write the formatted content back to the file
|
|
557
640
|
jetpack.write(filePath, formatted.content);
|
|
558
641
|
}
|
|
@@ -628,6 +711,17 @@ function getIgnoredPages() {
|
|
|
628
711
|
};
|
|
629
712
|
}
|
|
630
713
|
|
|
714
|
+
function getGlobOptions() {
|
|
715
|
+
const ignoredPages = getIgnoredPages();
|
|
716
|
+
return {
|
|
717
|
+
nodir: true,
|
|
718
|
+
ignore: [
|
|
719
|
+
...ignoredPages.files.map(key => `_site/${key}.html`),
|
|
720
|
+
...ignoredPages.folders.map(folder => `_site/${folder}/**/*`)
|
|
721
|
+
]
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
631
725
|
// Git Sync: Pull
|
|
632
726
|
async function fetchTranslationsBranch() {
|
|
633
727
|
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
|
|
@@ -724,7 +818,7 @@ async function fetchTranslationsBranch() {
|
|
|
724
818
|
}
|
|
725
819
|
|
|
726
820
|
// Git Sync: Push
|
|
727
|
-
async function pushTranslationBranch(
|
|
821
|
+
async function pushTranslationBranch( ) {
|
|
728
822
|
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
|
|
729
823
|
const localRoot = path.join('.temp', 'translations');
|
|
730
824
|
|
|
@@ -21,14 +21,14 @@ const collectTextNodes = ($, options) => {
|
|
|
21
21
|
const i = textNodes.length;
|
|
22
22
|
const text = node.text().trim();
|
|
23
23
|
if (text) {
|
|
24
|
+
|
|
25
|
+
// Push
|
|
24
26
|
textNodes.push({
|
|
25
27
|
node,
|
|
26
28
|
type: 'text',
|
|
27
29
|
attr: null,
|
|
28
30
|
text,
|
|
29
31
|
tagged: `[${i}]${text}[/${i}]`,
|
|
30
|
-
line: el.startIndex || 0, // Add line information
|
|
31
|
-
column: 0 // Column is not directly available, default to 0
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
return;
|
|
@@ -51,29 +51,51 @@ const collectTextNodes = ($, options) => {
|
|
|
51
51
|
const text = node.attr('content')?.trim();
|
|
52
52
|
if (text) {
|
|
53
53
|
const i = textNodes.length;
|
|
54
|
+
|
|
55
|
+
// Push
|
|
54
56
|
textNodes.push({
|
|
55
57
|
node,
|
|
56
58
|
type: 'attr',
|
|
57
59
|
attr: 'content',
|
|
58
60
|
text,
|
|
59
61
|
tagged: `[${i}]${text}[/${i}]`,
|
|
60
|
-
line: el.startIndex || 0, // Add line information
|
|
61
|
-
column: 0 // Column is not directly available, default to 0
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Handle attributes like title and placeholder
|
|
69
|
+
const translatableAttributes = ['title', 'placeholder', 'alt', 'aria-label', 'aria-placeholder', 'aria-describedby', 'aria-labelledby', 'value', 'label'];
|
|
70
|
+
translatableAttributes.forEach(attr => {
|
|
71
|
+
const text = node.attr(attr)?.trim();
|
|
72
|
+
if (text) {
|
|
73
|
+
const i = textNodes.length;
|
|
74
|
+
|
|
75
|
+
// Push
|
|
76
|
+
textNodes.push({
|
|
77
|
+
node,
|
|
78
|
+
type: 'attr',
|
|
79
|
+
attr,
|
|
80
|
+
text,
|
|
81
|
+
tagged: `[${i}]${text}[/${i}]`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
68
86
|
// Handle regular DOM text nodes
|
|
69
87
|
node.contents().each((_, child) => {
|
|
70
88
|
if (child.type === 'text' && child.data?.trim()) {
|
|
71
89
|
const i = textNodes.length;
|
|
72
90
|
const text = child.data
|
|
73
|
-
|
|
74
|
-
.replace(
|
|
91
|
+
// Preserve a single leading whitespace if it exists
|
|
92
|
+
.replace(/^\s*(\s)\s*/, '$1')
|
|
93
|
+
// Preserve a single trailing whitespace if it exists
|
|
94
|
+
.replace(/\s*(\s)\s*$/, '$1')
|
|
95
|
+
// Normalize internal whitespace
|
|
75
96
|
.replace(/\s+/g, ' ');
|
|
76
97
|
|
|
98
|
+
// Push
|
|
77
99
|
textNodes.push({
|
|
78
100
|
node,
|
|
79
101
|
type: 'data',
|
|
@@ -81,8 +103,6 @@ const collectTextNodes = ($, options) => {
|
|
|
81
103
|
reference: child,
|
|
82
104
|
text,
|
|
83
105
|
tagged: `[${i}]${text}[/${i}]`,
|
|
84
|
-
line: el.startIndex || 0, // Add line information
|
|
85
|
-
column: 0 // Column is not directly available, default to 0
|
|
86
106
|
});
|
|
87
107
|
}
|
|
88
108
|
});
|
|
@@ -92,3 +112,12 @@ const collectTextNodes = ($, options) => {
|
|
|
92
112
|
};
|
|
93
113
|
|
|
94
114
|
module.exports = collectTextNodes;
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
function trimPreserveOneCleanInner(str) {
|
|
118
|
+
const match = str.match(/^(\s*)(.*?)(\s*)$/);
|
|
119
|
+
const leading = match[1] ? match[1][0] || '' : '';
|
|
120
|
+
const content = match[2].replace(/\s+/g, ' ');
|
|
121
|
+
const trailing = match[3] ? match[3][0] || '' : '';
|
|
122
|
+
return leading + content + trailing;
|
|
123
|
+
}
|
|
@@ -186,7 +186,7 @@ function webpackWatcher(complete) {
|
|
|
186
186
|
watch(input, { delay: delay, dot: true }, webpack)
|
|
187
187
|
.on('change', (path) => {
|
|
188
188
|
// Log
|
|
189
|
-
logger.log(`[watcher] File ${path}
|
|
189
|
+
logger.log(`[watcher] File changed (${path})`);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// Complete
|