pabal-resource-mcp 1.5.5 → 1.5.6

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.
Files changed (2) hide show
  1. package/dist/bin/mcp-server.js +160 -149
  2. package/package.json +1 -1
@@ -3448,127 +3448,26 @@ var DEVICE_ASPECT_RATIOS = {
3448
3448
  phone: "9:16",
3449
3449
  tablet: "3:4"
3450
3450
  };
3451
- var GEMINI_SUPPORTED_LOCALES = {
3452
- // English variants
3453
- "en": "EN",
3454
- "en-US": "EN",
3455
- "en-GB": "EN",
3456
- "en-AU": "EN",
3457
- "en-CA": "EN",
3458
- // Arabic
3459
- "ar": "ar-EG",
3460
- "ar-EG": "ar-EG",
3461
- "ar-SA": "ar-EG",
3462
- // German
3463
- "de": "de-DE",
3464
- "de-DE": "de-DE",
3465
- // Spanish
3466
- "es": "es-MX",
3467
- "es-MX": "es-MX",
3468
- "es-ES": "es-MX",
3469
- "es-419": "es-MX",
3470
- // French
3471
- "fr": "fr-FR",
3472
- "fr-FR": "fr-FR",
3473
- "fr-CA": "fr-FR",
3474
- // Hindi
3475
- "hi": "hi-IN",
3476
- "hi-IN": "hi-IN",
3477
- // Indonesian
3478
- "id": "id-ID",
3479
- "id-ID": "id-ID",
3480
- // Italian
3481
- "it": "it-IT",
3482
- "it-IT": "it-IT",
3483
- // Japanese
3484
- "ja": "ja-JP",
3485
- "ja-JP": "ja-JP",
3486
- // Korean
3487
- "ko": "ko-KR",
3488
- "ko-KR": "ko-KR",
3489
- // Portuguese
3490
- "pt": "pt-BR",
3491
- "pt-BR": "pt-BR",
3492
- "pt-PT": "pt-BR",
3493
- // Russian
3494
- "ru": "ru-RU",
3495
- "ru-RU": "ru-RU",
3496
- // Ukrainian
3497
- "uk": "ua-UA",
3498
- "uk-UA": "ua-UA",
3499
- "ua-UA": "ua-UA",
3500
- // Vietnamese
3501
- "vi": "vi-VN",
3502
- "vi-VN": "vi-VN",
3503
- // Chinese
3504
- "zh": "zh-CN",
3505
- "zh-CN": "zh-CN",
3506
- "zh-Hans": "zh-CN",
3507
- "zh-TW": "zh-CN",
3508
- "zh-Hant": "zh-CN"
3509
- };
3510
- function isGeminiSupportedLocale(locale) {
3511
- return locale in GEMINI_SUPPORTED_LOCALES;
3512
- }
3513
- function getUnsupportedLocales(locales) {
3514
- return locales.filter((locale) => !isGeminiSupportedLocale(locale));
3515
- }
3516
- var LANGUAGE_NAMES = {
3517
- "en-US": "English (US)",
3518
- "en-GB": "English (UK)",
3519
- "en-AU": "English (Australia)",
3520
- "en-CA": "English (Canada)",
3521
- "ko-KR": "Korean",
3522
- "ja-JP": "Japanese",
3523
- "zh-Hans": "Simplified Chinese",
3524
- "zh-Hant": "Traditional Chinese",
3525
- "zh-CN": "Simplified Chinese",
3526
- "zh-TW": "Traditional Chinese",
3527
- "fr-FR": "French",
3528
- "fr-CA": "French (Canada)",
3451
+ var GEMINI_LANGUAGE_NAMES = {
3452
+ "en-US": "English",
3453
+ "ar-EG": "Arabic",
3529
3454
  "de-DE": "German",
3530
- "es-ES": "Spanish (Spain)",
3531
- "es-419": "Spanish (Latin America)",
3532
- "es-MX": "Spanish (Mexico)",
3533
- "pt-BR": "Portuguese (Brazil)",
3534
- "pt-PT": "Portuguese (Portugal)",
3455
+ "es-MX": "Spanish",
3456
+ "fr-FR": "French",
3457
+ "hi-IN": "Hindi",
3458
+ "id-ID": "Indonesian",
3535
3459
  "it-IT": "Italian",
3536
- "nl-NL": "Dutch",
3460
+ "ja-JP": "Japanese",
3461
+ "ko-KR": "Korean",
3462
+ "pt-BR": "Portuguese",
3537
3463
  "ru-RU": "Russian",
3538
- "ar": "Arabic",
3539
- "ar-SA": "Arabic",
3540
- "hi-IN": "Hindi",
3541
- "th-TH": "Thai",
3464
+ "ua-UA": "Ukrainian",
3542
3465
  "vi-VN": "Vietnamese",
3543
- "id-ID": "Indonesian",
3544
- "ms-MY": "Malay",
3545
- "tr-TR": "Turkish",
3546
- "pl-PL": "Polish",
3547
- "uk-UA": "Ukrainian",
3548
- "cs-CZ": "Czech",
3549
- "el-GR": "Greek",
3550
- "ro-RO": "Romanian",
3551
- "hu-HU": "Hungarian",
3552
- "sv-SE": "Swedish",
3553
- "da-DK": "Danish",
3554
- "fi-FI": "Finnish",
3555
- "no-NO": "Norwegian",
3556
- "he-IL": "Hebrew",
3557
- "sk-SK": "Slovak",
3558
- "bg-BG": "Bulgarian",
3559
- "hr-HR": "Croatian",
3560
- "ca-ES": "Catalan"
3466
+ "zh-CN": "Chinese"
3561
3467
  };
3562
3468
  function getLanguageName(locale) {
3563
- if (LANGUAGE_NAMES[locale]) {
3564
- return LANGUAGE_NAMES[locale];
3565
- }
3566
- const baseCode = locale.split("-")[0];
3567
- const matchingKey = Object.keys(LANGUAGE_NAMES).find(
3568
- (key) => key.startsWith(baseCode + "-") || key === baseCode
3569
- );
3570
- if (matchingKey) {
3571
- return LANGUAGE_NAMES[matchingKey];
3469
+ if (locale in GEMINI_LANGUAGE_NAMES) {
3470
+ return GEMINI_LANGUAGE_NAMES[locale];
3572
3471
  }
3573
3472
  return locale;
3574
3473
  }
@@ -3591,7 +3490,7 @@ function readImageAsBase64(imagePath) {
3591
3490
  function getAspectRatioForDevice(deviceType) {
3592
3491
  return DEVICE_ASPECT_RATIOS[deviceType];
3593
3492
  }
3594
- async function translateImage(sourcePath, sourceLocale, targetLocale, outputPath, deviceType, preserveWords) {
3493
+ async function translateImage(sourcePath, sourceLocale, targetLocale, outputPaths, deviceType, preserveWords) {
3595
3494
  try {
3596
3495
  const client = getGeminiClient();
3597
3496
  const sourceLanguage = getLanguageName(sourceLocale);
@@ -3650,14 +3549,17 @@ IMPORTANT INSTRUCTIONS:
3650
3549
  for (const part of parts) {
3651
3550
  if (part.inlineData?.data) {
3652
3551
  const imageBuffer = Buffer.from(part.inlineData.data, "base64");
3653
- const outputDir = path10.dirname(outputPath);
3654
- if (!fs10.existsSync(outputDir)) {
3655
- fs10.mkdirSync(outputDir, { recursive: true });
3552
+ for (const outputPath of outputPaths) {
3553
+ const outputDir = path10.dirname(outputPath);
3554
+ if (!fs10.existsSync(outputDir)) {
3555
+ fs10.mkdirSync(outputDir, { recursive: true });
3556
+ }
3557
+ await sharp(imageBuffer).png().toFile(outputPath);
3656
3558
  }
3657
- await sharp(imageBuffer).png().toFile(outputPath);
3658
3559
  return {
3659
3560
  success: true,
3660
- outputPath
3561
+ outputPath: outputPaths[0]
3562
+ // Return primary path
3661
3563
  };
3662
3564
  }
3663
3565
  }
@@ -3695,7 +3597,7 @@ async function translateImagesWithProgress(translations, onProgress, preserveWor
3695
3597
  translation.sourcePath,
3696
3598
  translation.sourceLocale,
3697
3599
  translation.targetLocale,
3698
- translation.outputPath,
3600
+ translation.outputPaths,
3699
3601
  translation.deviceType,
3700
3602
  preserveWords
3701
3603
  );
@@ -3717,6 +3619,81 @@ async function translateImagesWithProgress(translations, onProgress, preserveWor
3717
3619
  return { successful, failed, errors };
3718
3620
  }
3719
3621
 
3622
+ // src/tools/aso/utils/localize-screenshots/locale-mapping.constants.ts
3623
+ var GEMINI_LOCALE_GROUPS = {
3624
+ // English - covers all English variants
3625
+ "en-US": ["en-US", "en-AU", "en-CA", "en-GB", "en-IN", "en-SG", "en-ZA"],
3626
+ // Arabic - Gemini ar-EG → Unified ar
3627
+ "ar-EG": ["ar"],
3628
+ // German
3629
+ "de-DE": ["de-DE"],
3630
+ // Spanish - covers Latin America, Spain, and US Spanish
3631
+ "es-MX": ["es-419", "es-ES", "es-US"],
3632
+ // French - covers France and Canada
3633
+ "fr-FR": ["fr-FR", "fr-CA"],
3634
+ // Hindi
3635
+ "hi-IN": ["hi-IN"],
3636
+ // Indonesian
3637
+ "id-ID": ["id-ID"],
3638
+ // Italian
3639
+ "it-IT": ["it-IT"],
3640
+ // Japanese
3641
+ "ja-JP": ["ja-JP"],
3642
+ // Korean
3643
+ "ko-KR": ["ko-KR"],
3644
+ // Portuguese - covers Brazil and Portugal
3645
+ "pt-BR": ["pt-BR", "pt-PT"],
3646
+ // Russian
3647
+ "ru-RU": ["ru-RU"],
3648
+ // Ukrainian - Gemini ua-UA → Unified uk-UA
3649
+ "ua-UA": ["uk-UA"],
3650
+ // Vietnamese
3651
+ "vi-VN": ["vi-VN"],
3652
+ // Chinese - covers Simplified, Traditional, and Hong Kong
3653
+ "zh-CN": ["zh-Hans", "zh-Hant", "zh-HK"]
3654
+ };
3655
+ function getGeminiLocale(unifiedLocale) {
3656
+ for (const [geminiLocale, unifiedLocales] of Object.entries(
3657
+ GEMINI_LOCALE_GROUPS
3658
+ )) {
3659
+ if (unifiedLocales.includes(unifiedLocale)) {
3660
+ return geminiLocale;
3661
+ }
3662
+ }
3663
+ return null;
3664
+ }
3665
+ function prepareLocalesForTranslation(locales, primaryLocale) {
3666
+ const targetLocales = locales.filter((l) => l !== primaryLocale);
3667
+ const localeMapping = /* @__PURE__ */ new Map();
3668
+ const geminiLocalesNeeded = /* @__PURE__ */ new Set();
3669
+ const skippedLocales = [];
3670
+ for (const locale of targetLocales) {
3671
+ const geminiLocale = getGeminiLocale(locale);
3672
+ if (geminiLocale === null) {
3673
+ skippedLocales.push(locale);
3674
+ continue;
3675
+ }
3676
+ geminiLocalesNeeded.add(geminiLocale);
3677
+ const existing = localeMapping.get(geminiLocale) || [];
3678
+ if (!existing.includes(locale)) {
3679
+ existing.push(locale);
3680
+ }
3681
+ localeMapping.set(geminiLocale, existing);
3682
+ }
3683
+ const groupedLocales = [];
3684
+ for (const [, unifiedLocales] of localeMapping) {
3685
+ if (unifiedLocales.length > 1) {
3686
+ groupedLocales.push(...unifiedLocales.slice(1));
3687
+ }
3688
+ }
3689
+ return {
3690
+ translatableLocales: Array.from(geminiLocalesNeeded),
3691
+ localeMapping,
3692
+ skippedLocales,
3693
+ groupedLocales
3694
+ };
3695
+ }
3696
+
3720
3697
  // src/tools/aso/utils/localize-screenshots/image-resizer.util.ts
3721
3698
  import sharp2 from "sharp";
3722
3699
  import fs11 from "fs";
@@ -3927,7 +3904,7 @@ function getSupportedLocales(slug) {
3927
3904
  };
3928
3905
  }
3929
3906
  function getTargetLocales(allLocales, primaryLocale, requestedTargets) {
3930
- let targets = allLocales.filter((locale) => locale !== primaryLocale);
3907
+ let localesToProcess = allLocales;
3931
3908
  if (requestedTargets && requestedTargets.length > 0) {
3932
3909
  const validTargets = requestedTargets.filter((t) => allLocales.includes(t));
3933
3910
  const invalidTargets = requestedTargets.filter(
@@ -3938,31 +3915,47 @@ function getTargetLocales(allLocales, primaryLocale, requestedTargets) {
3938
3915
  `Warning: Some requested locales are not in product: ${invalidTargets.join(", ")}`
3939
3916
  );
3940
3917
  }
3941
- targets = validTargets.filter((t) => t !== primaryLocale);
3918
+ localesToProcess = validTargets;
3942
3919
  }
3943
- const skippedLocales = getUnsupportedLocales(targets);
3944
- const supportedTargets = targets.filter((t) => isGeminiSupportedLocale(t));
3945
- return { targets: supportedTargets, skippedLocales };
3920
+ const {
3921
+ translatableLocales,
3922
+ localeMapping,
3923
+ skippedLocales,
3924
+ groupedLocales
3925
+ } = prepareLocalesForTranslation(localesToProcess, primaryLocale);
3926
+ return {
3927
+ targets: translatableLocales,
3928
+ skippedLocales,
3929
+ groupedLocales,
3930
+ localeMapping
3931
+ };
3946
3932
  }
3947
- function buildTranslationTasks(slug, screenshots, primaryLocale, targetLocales, skipExisting) {
3933
+ function buildTranslationTasks(slug, screenshots, primaryLocale, targetLocales, localeMapping, skipExisting) {
3948
3934
  const tasks = [];
3949
3935
  const screenshotsDir = getScreenshotsDir(slug);
3950
3936
  for (const targetLocale of targetLocales) {
3937
+ const outputLocales = localeMapping.get(targetLocale) || [];
3951
3938
  for (const screenshot of screenshots) {
3952
- const outputPath = path11.join(
3953
- screenshotsDir,
3954
- targetLocale,
3955
- screenshot.type,
3956
- screenshot.filename
3957
- );
3958
- if (skipExisting && fs12.existsSync(outputPath)) {
3939
+ const outputPaths = [];
3940
+ for (const locale of outputLocales) {
3941
+ const outputPath = path11.join(
3942
+ screenshotsDir,
3943
+ locale,
3944
+ screenshot.type,
3945
+ screenshot.filename
3946
+ );
3947
+ if (!skipExisting || !fs12.existsSync(outputPath)) {
3948
+ outputPaths.push(outputPath);
3949
+ }
3950
+ }
3951
+ if (outputPaths.length === 0) {
3959
3952
  continue;
3960
3953
  }
3961
3954
  tasks.push({
3962
3955
  sourcePath: screenshot.fullPath,
3963
3956
  sourceLocale: primaryLocale,
3964
3957
  targetLocale,
3965
- outputPath,
3958
+ outputPaths,
3966
3959
  deviceType: screenshot.type,
3967
3960
  filename: screenshot.filename
3968
3961
  });
@@ -4013,11 +4006,12 @@ async function handleLocalizeScreenshots(input) {
4013
4006
  ]
4014
4007
  };
4015
4008
  }
4016
- const { targets: targetLocales, skippedLocales } = getTargetLocales(
4017
- allLocales,
4018
- primaryLocale,
4019
- requestedTargetLocales
4020
- );
4009
+ const {
4010
+ targets: targetLocales,
4011
+ skippedLocales,
4012
+ groupedLocales,
4013
+ localeMapping
4014
+ } = getTargetLocales(allLocales, primaryLocale, requestedTargetLocales);
4021
4015
  if (targetLocales.length === 0) {
4022
4016
  const skippedMsg = skippedLocales.length > 0 ? ` (Skipped due to Gemini limitation: ${skippedLocales.join(", ")})` : "";
4023
4017
  return {
@@ -4029,9 +4023,16 @@ async function handleLocalizeScreenshots(input) {
4029
4023
  ]
4030
4024
  };
4031
4025
  }
4032
- results.push(`\u{1F3AF} Target locales: ${targetLocales.join(", ")}`);
4026
+ results.push(`\u{1F3AF} Target locales to translate: ${targetLocales.join(", ")}`);
4027
+ if (groupedLocales.length > 0) {
4028
+ results.push(
4029
+ `\u{1F4CB} Grouped locales (saved together): ${groupedLocales.join(", ")}`
4030
+ );
4031
+ }
4033
4032
  if (skippedLocales.length > 0) {
4034
- results.push(`\u26A0\uFE0F Skipped locales (not supported by Gemini): ${skippedLocales.join(", ")}`);
4033
+ results.push(
4034
+ `\u26A0\uFE0F Skipped locales (not supported by Gemini): ${skippedLocales.join(", ")}`
4035
+ );
4035
4036
  }
4036
4037
  const sourceScreenshots = scanLocaleScreenshots(appInfo.slug, primaryLocale);
4037
4038
  let filteredScreenshots = sourceScreenshots.filter(
@@ -4089,6 +4090,7 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4089
4090
  filteredScreenshots,
4090
4091
  primaryLocale,
4091
4092
  targetLocales,
4093
+ localeMapping,
4092
4094
  skipExisting
4093
4095
  );
4094
4096
  if (tasks.length === 0) {
@@ -4117,8 +4119,11 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4117
4119
  tasksByLocale[task.targetLocale].push(task);
4118
4120
  }
4119
4121
  for (const [locale, localeTasks] of Object.entries(tasksByLocale)) {
4122
+ const grouped = localeMapping.get(locale) || [];
4123
+ const groupedOthers = grouped.filter((l) => l !== locale);
4124
+ const groupInfo = groupedOthers.length > 0 ? ` \u2192 also: ${groupedOthers.join(", ")}` : "";
4120
4125
  results.push(`
4121
- \u{1F4C1} ${locale}:`);
4126
+ \u{1F4C1} ${locale}${groupInfo}:`);
4122
4127
  for (const task of localeTasks) {
4123
4128
  results.push(` - ${task.deviceType}/${task.filename}`);
4124
4129
  }
@@ -4174,11 +4179,17 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4174
4179
  if (translationResult.successful > 0) {
4175
4180
  results.push(`
4176
4181
  \u{1F50D} Validating image dimensions...`);
4177
- const successfulTasks = tasks.filter((t) => fs12.existsSync(t.outputPath));
4178
- const resizePairs = successfulTasks.map((t) => ({
4179
- sourcePath: t.sourcePath,
4180
- translatedPath: t.outputPath
4181
- }));
4182
+ const resizePairs = [];
4183
+ for (const task of tasks) {
4184
+ for (const outputPath of task.outputPaths) {
4185
+ if (fs12.existsSync(outputPath)) {
4186
+ resizePairs.push({
4187
+ sourcePath: task.sourcePath,
4188
+ translatedPath: outputPath
4189
+ });
4190
+ }
4191
+ }
4192
+ }
4182
4193
  const resizeResult = await batchValidateAndResize(resizePairs);
4183
4194
  if (resizeResult.resized > 0) {
4184
4195
  results.push(` \u{1F527} Resized ${resizeResult.resized} images to match source dimensions`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-resource-mcp",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "type": "module",
5
5
  "description": "MCP server for ASO data management with shared types and utilities",
6
6
  "author": "skyu",