ultimate-jekyll-manager 0.0.40 → 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.
|
@@ -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>
|
|
@@ -40,6 +40,7 @@ const AI_MODEL = 'gpt-4.1-mini';
|
|
|
40
40
|
const TRANSLATION_BRANCH = 'uj-translations';
|
|
41
41
|
// const LOUD = false;
|
|
42
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
|
|
@@ -51,9 +52,12 @@ const SYSTEM_PROMPT = `
|
|
|
51
52
|
Translate the provided content, preserving all original formatting, HTML structure, metadata, and links.
|
|
52
53
|
Do not explain anything — just return the translated content.
|
|
53
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.
|
|
54
|
-
|
|
55
|
-
DO NOT translate the
|
|
56
|
-
|
|
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
|
+
|
|
57
61
|
Translate to {lang}
|
|
58
62
|
`;
|
|
59
63
|
|
|
@@ -214,9 +218,16 @@ async function processTranslation() {
|
|
|
214
218
|
for (const filePath of allFiles) {
|
|
215
219
|
// Get relative path and original HTML
|
|
216
220
|
const relativePath = filePath.replace(/^_site[\\/]/, '');
|
|
217
|
-
|
|
221
|
+
let originalHtml = jetpack.read(filePath);
|
|
218
222
|
const $ = cheerio.load(originalHtml);
|
|
219
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
|
+
|
|
220
231
|
// Collect text nodes with tags
|
|
221
232
|
const textNodes = collectTextNodes($, { tag: true });
|
|
222
233
|
|
|
@@ -263,6 +274,20 @@ async function processTranslation() {
|
|
|
263
274
|
&& (RECHECK_DAYS === 0 || age < RECHECK_DAYS);
|
|
264
275
|
const startTime = Date.now();
|
|
265
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
|
+
|
|
266
291
|
// Check if we can use cached translation
|
|
267
292
|
if (
|
|
268
293
|
(useCached || process.env.UJ_TRANSLATION_CACHE === 'true')
|
|
@@ -287,10 +312,9 @@ async function processTranslation() {
|
|
|
287
312
|
|
|
288
313
|
// Save to cache
|
|
289
314
|
jetpack.write(cachePath, translated);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
};
|
|
315
|
+
|
|
316
|
+
// Set result
|
|
317
|
+
setResult(true);
|
|
294
318
|
} catch (e) {
|
|
295
319
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
296
320
|
logger.error(`⚠️ Translation failed: ${relativePath} [${lang}] — ${e.message} (Elapsed time: ${elapsedTime}s)`);
|
|
@@ -299,10 +323,7 @@ async function processTranslation() {
|
|
|
299
323
|
translated = bodyText;
|
|
300
324
|
|
|
301
325
|
// Save failure to cache
|
|
302
|
-
|
|
303
|
-
timestamp: 0,
|
|
304
|
-
hash: '__fail__',
|
|
305
|
-
};
|
|
326
|
+
setResult(false);
|
|
306
327
|
}
|
|
307
328
|
}
|
|
308
329
|
|
|
@@ -345,6 +366,20 @@ async function processTranslation() {
|
|
|
345
366
|
// Rewrite links
|
|
346
367
|
rewriteLinks($, lang);
|
|
347
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
|
+
|
|
348
383
|
// Set the lang attribute on the <html> tag
|
|
349
384
|
$('html').attr('lang', lang);
|
|
350
385
|
|
|
@@ -431,7 +466,7 @@ async function processTranslation() {
|
|
|
431
466
|
|
|
432
467
|
// Push updated translation cache back to uj-translations
|
|
433
468
|
if (Manager.isBuildMode()) {
|
|
434
|
-
await pushTranslationBranch(
|
|
469
|
+
await pushTranslationBranch( );
|
|
435
470
|
}
|
|
436
471
|
}
|
|
437
472
|
|
|
@@ -783,7 +818,7 @@ async function fetchTranslationsBranch() {
|
|
|
783
818
|
}
|
|
784
819
|
|
|
785
820
|
// Git Sync: Push
|
|
786
|
-
async function pushTranslationBranch(
|
|
821
|
+
async function pushTranslationBranch( ) {
|
|
787
822
|
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
|
|
788
823
|
const localRoot = path.join('.temp', 'translations');
|
|
789
824
|
|
|
@@ -65,6 +65,24 @@ const collectTextNodes = ($, options) => {
|
|
|
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()) {
|
|
@@ -75,8 +93,7 @@ const collectTextNodes = ($, options) => {
|
|
|
75
93
|
// Preserve a single trailing whitespace if it exists
|
|
76
94
|
.replace(/\s*(\s)\s*$/, '$1')
|
|
77
95
|
// Normalize internal whitespace
|
|
78
|
-
.replace(/\s+/g, ' ')
|
|
79
|
-
// const text = trimPreserveOneCleanInner(child.data);
|
|
96
|
+
.replace(/\s+/g, ' ');
|
|
80
97
|
|
|
81
98
|
// Push
|
|
82
99
|
textNodes.push({
|