pabal-resource-mcp 1.5.1 → 1.5.2
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/dist/bin/mcp-server.js +63 -28
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -3428,9 +3428,10 @@ function scanLocaleScreenshots(slug, locale) {
|
|
|
3428
3428
|
}
|
|
3429
3429
|
|
|
3430
3430
|
// src/tools/aso/utils/localize-screenshots/gemini-image-translator.util.ts
|
|
3431
|
-
import { GoogleGenAI
|
|
3431
|
+
import { GoogleGenAI } from "@google/genai";
|
|
3432
3432
|
import fs10 from "fs";
|
|
3433
3433
|
import path10 from "path";
|
|
3434
|
+
import sharp from "sharp";
|
|
3434
3435
|
var LANGUAGE_NAMES = {
|
|
3435
3436
|
"en-US": "English (US)",
|
|
3436
3437
|
"en-GB": "English (UK)",
|
|
@@ -3506,11 +3507,29 @@ function readImageAsBase64(imagePath) {
|
|
|
3506
3507
|
}
|
|
3507
3508
|
return { data: base64, mimeType };
|
|
3508
3509
|
}
|
|
3510
|
+
async function getImageDimensions(imagePath) {
|
|
3511
|
+
const metadata = await sharp(imagePath).metadata();
|
|
3512
|
+
return {
|
|
3513
|
+
width: metadata.width || 1080,
|
|
3514
|
+
height: metadata.height || 1920
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
function calculateAspectRatio(width, height) {
|
|
3518
|
+
const ratio = width / height;
|
|
3519
|
+
if (Math.abs(ratio - 1) < 0.1) return "1:1";
|
|
3520
|
+
if (Math.abs(ratio - 9 / 16) < 0.1) return "9:16";
|
|
3521
|
+
if (Math.abs(ratio - 16 / 9) < 0.1) return "16:9";
|
|
3522
|
+
if (Math.abs(ratio - 3 / 4) < 0.1) return "3:4";
|
|
3523
|
+
if (Math.abs(ratio - 4 / 3) < 0.1) return "4:3";
|
|
3524
|
+
return ratio < 1 ? "9:16" : "16:9";
|
|
3525
|
+
}
|
|
3509
3526
|
async function translateImage(sourcePath, sourceLocale, targetLocale, outputPath) {
|
|
3510
3527
|
try {
|
|
3511
3528
|
const client = getGeminiClient();
|
|
3512
3529
|
const sourceLanguage = getLanguageName(sourceLocale);
|
|
3513
3530
|
const targetLanguage = getLanguageName(targetLocale);
|
|
3531
|
+
const { width, height } = await getImageDimensions(sourcePath);
|
|
3532
|
+
const aspectRatio = calculateAspectRatio(width, height);
|
|
3514
3533
|
const { data: imageData, mimeType } = readImageAsBase64(sourcePath);
|
|
3515
3534
|
const prompt = `This is an app screenshot with text in ${sourceLanguage}.
|
|
3516
3535
|
Please translate ONLY the text/words in this image to ${targetLanguage}.
|
|
@@ -3522,24 +3541,27 @@ IMPORTANT INSTRUCTIONS:
|
|
|
3522
3541
|
- Do NOT add any new elements or remove existing design elements
|
|
3523
3542
|
- The output should look identical except the text language is ${targetLanguage}
|
|
3524
3543
|
- Preserve all icons, images, and graphical elements exactly as they are`;
|
|
3525
|
-
const
|
|
3526
|
-
model: "
|
|
3527
|
-
|
|
3544
|
+
const chat = client.chats.create({
|
|
3545
|
+
model: "gemini-3-pro-image-preview",
|
|
3546
|
+
config: {
|
|
3547
|
+
responseModalities: ["TEXT", "IMAGE"]
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
const response = await chat.sendMessage({
|
|
3551
|
+
message: [
|
|
3552
|
+
{ text: prompt },
|
|
3528
3553
|
{
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
inlineData: {
|
|
3534
|
-
mimeType,
|
|
3535
|
-
data: imageData
|
|
3536
|
-
}
|
|
3537
|
-
}
|
|
3538
|
-
]
|
|
3554
|
+
inlineData: {
|
|
3555
|
+
mimeType,
|
|
3556
|
+
data: imageData
|
|
3557
|
+
}
|
|
3539
3558
|
}
|
|
3540
3559
|
],
|
|
3541
3560
|
config: {
|
|
3542
|
-
responseModalities: [
|
|
3561
|
+
responseModalities: ["TEXT", "IMAGE"],
|
|
3562
|
+
imageConfig: {
|
|
3563
|
+
aspectRatio
|
|
3564
|
+
}
|
|
3543
3565
|
}
|
|
3544
3566
|
});
|
|
3545
3567
|
const candidates = response.candidates;
|
|
@@ -3563,7 +3585,7 @@ IMPORTANT INSTRUCTIONS:
|
|
|
3563
3585
|
if (!fs10.existsSync(outputDir)) {
|
|
3564
3586
|
fs10.mkdirSync(outputDir, { recursive: true });
|
|
3565
3587
|
}
|
|
3566
|
-
|
|
3588
|
+
await sharp(imageBuffer).png().toFile(outputPath);
|
|
3567
3589
|
return {
|
|
3568
3590
|
success: true,
|
|
3569
3591
|
outputPath
|
|
@@ -3586,13 +3608,18 @@ async function translateImagesWithProgress(translations, onProgress) {
|
|
|
3586
3608
|
let successful = 0;
|
|
3587
3609
|
let failed = 0;
|
|
3588
3610
|
const errors = [];
|
|
3589
|
-
|
|
3611
|
+
const total = translations.length;
|
|
3612
|
+
for (let i = 0; i < translations.length; i++) {
|
|
3613
|
+
const translation = translations[i];
|
|
3614
|
+
const current = i + 1;
|
|
3590
3615
|
const progress = {
|
|
3591
3616
|
sourceLocale: translation.sourceLocale,
|
|
3592
3617
|
targetLocale: translation.targetLocale,
|
|
3593
3618
|
deviceType: translation.deviceType,
|
|
3594
3619
|
filename: translation.filename,
|
|
3595
|
-
status: "translating"
|
|
3620
|
+
status: "translating",
|
|
3621
|
+
current,
|
|
3622
|
+
total
|
|
3596
3623
|
};
|
|
3597
3624
|
onProgress?.(progress);
|
|
3598
3625
|
const result = await translateImage(
|
|
@@ -3608,7 +3635,10 @@ async function translateImagesWithProgress(translations, onProgress) {
|
|
|
3608
3635
|
failed++;
|
|
3609
3636
|
progress.status = "failed";
|
|
3610
3637
|
progress.error = result.error;
|
|
3611
|
-
errors.push({
|
|
3638
|
+
errors.push({
|
|
3639
|
+
path: translation.sourcePath,
|
|
3640
|
+
error: result.error || "Unknown error"
|
|
3641
|
+
});
|
|
3612
3642
|
}
|
|
3613
3643
|
onProgress?.(progress);
|
|
3614
3644
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -3617,10 +3647,10 @@ async function translateImagesWithProgress(translations, onProgress) {
|
|
|
3617
3647
|
}
|
|
3618
3648
|
|
|
3619
3649
|
// src/tools/aso/utils/localize-screenshots/image-resizer.util.ts
|
|
3620
|
-
import
|
|
3650
|
+
import sharp2 from "sharp";
|
|
3621
3651
|
import fs11 from "fs";
|
|
3622
|
-
async function
|
|
3623
|
-
const metadata = await
|
|
3652
|
+
async function getImageDimensions2(imagePath) {
|
|
3653
|
+
const metadata = await sharp2(imagePath).metadata();
|
|
3624
3654
|
if (!metadata.width || !metadata.height) {
|
|
3625
3655
|
throw new Error(`Unable to read dimensions from ${imagePath}`);
|
|
3626
3656
|
}
|
|
@@ -3630,7 +3660,7 @@ async function getImageDimensions(imagePath) {
|
|
|
3630
3660
|
};
|
|
3631
3661
|
}
|
|
3632
3662
|
async function resizeImage(inputPath, outputPath, targetDimensions) {
|
|
3633
|
-
await
|
|
3663
|
+
await sharp2(inputPath).resize(targetDimensions.width, targetDimensions.height, {
|
|
3634
3664
|
fit: "fill",
|
|
3635
3665
|
// Exact resize to target dimensions
|
|
3636
3666
|
withoutEnlargement: false
|
|
@@ -3639,8 +3669,8 @@ async function resizeImage(inputPath, outputPath, targetDimensions) {
|
|
|
3639
3669
|
fs11.renameSync(outputPath + ".tmp", outputPath);
|
|
3640
3670
|
}
|
|
3641
3671
|
async function validateAndResizeImage(sourcePath, translatedPath) {
|
|
3642
|
-
const sourceDimensions = await
|
|
3643
|
-
const translatedDimensions = await
|
|
3672
|
+
const sourceDimensions = await getImageDimensions2(sourcePath);
|
|
3673
|
+
const translatedDimensions = await getImageDimensions2(translatedPath);
|
|
3644
3674
|
const needsResize = sourceDimensions.width !== translatedDimensions.width || sourceDimensions.height !== translatedDimensions.height;
|
|
3645
3675
|
if (needsResize) {
|
|
3646
3676
|
await resizeImage(translatedPath, translatedPath, sourceDimensions);
|
|
@@ -3928,13 +3958,18 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
|
|
|
3928
3958
|
const translationResult = await translateImagesWithProgress(
|
|
3929
3959
|
tasks,
|
|
3930
3960
|
(progress) => {
|
|
3931
|
-
|
|
3961
|
+
const progressPrefix = `[${progress.current}/${progress.total}]`;
|
|
3962
|
+
if (progress.status === "translating") {
|
|
3963
|
+
console.log(
|
|
3964
|
+
`\u{1F504} ${progressPrefix} Translating ${progress.targetLocale}/${progress.deviceType}/${progress.filename}...`
|
|
3965
|
+
);
|
|
3966
|
+
} else if (progress.status === "completed") {
|
|
3932
3967
|
console.log(
|
|
3933
|
-
`\u2705 ${progress.targetLocale}/${progress.deviceType}/${progress.filename}`
|
|
3968
|
+
`\u2705 ${progressPrefix} ${progress.targetLocale}/${progress.deviceType}/${progress.filename}`
|
|
3934
3969
|
);
|
|
3935
3970
|
} else if (progress.status === "failed") {
|
|
3936
3971
|
console.log(
|
|
3937
|
-
`\u274C ${progress.targetLocale}/${progress.deviceType}/${progress.filename}: ${progress.error}`
|
|
3972
|
+
`\u274C ${progressPrefix} ${progress.targetLocale}/${progress.deviceType}/${progress.filename}: ${progress.error}`
|
|
3938
3973
|
);
|
|
3939
3974
|
}
|
|
3940
3975
|
}
|