@synthetic-ai/photoshop-mcp 1.0.8-dev.0 → 1.1.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/README.md CHANGED
@@ -92,6 +92,20 @@ constante `PANEL_VERSION` de `main.js`, ofusca `main.js` (no `host.jsx` — corr
92
92
  ExtendScript de Photoshop, no en Chromium, y el código ofuscado puede fallar ahí) y firma el
93
93
  `.zxp` resultante. Ver `scripts/package-cep.mjs` para el detalle completo del pipeline.
94
94
 
95
+ ## Idioma
96
+
97
+ Tools, mensajes del server y panel están en inglés por defecto y se traducen con los diccionarios
98
+ de `synthetic-idiomas` (vendorizados en `i18n/`, `bridge-cep/i18n-*` y `bridge-uxp/i18n-*` con
99
+ `npm run sync-i18n`). Idiomas: `en`, `es`, `pt-BR`, `fr`, `de`, `ja`; lo que falte traducir cae al inglés.
100
+
101
+ - **Panel CEP:** ⚙ (Settings) → **Language**. La interfaz del panel cambia al instante; la elección
102
+ se guarda en `settings.json`, en la misma carpeta que `license.json`
103
+ (`%APPDATA%\com.synthetic.photoshop-mcp.bridge\` en Windows,
104
+ `~/Library/Application Support/com.synthetic.photoshop-mcp.bridge/` en macOS).
105
+ - **Server MCP:** elige el idioma **una sola vez al arrancar**: `PS_MCP_LANG` → `SYNTHETIC_LANG` →
106
+ ese `settings.json` → inglés (lo loguea en stderr y en `--doctor`). Las descripciones de las tools
107
+ cambian recién al reiniciar el server MCP (o el cliente que lo lanza).
108
+
95
109
  ## Notas
96
110
 
97
111
  - Puerto configurable con `PS_MCP_PORT` (default 41219). Si lo cambiás, ajustá `PORT` en el
@@ -0,0 +1,224 @@
1
+ /* synthetic-idiomas · core/i18n-core.cjs
2
+ *
3
+ * Runtime de idiomas compartido por todos los productos Synthetic (servers MCP en Node y
4
+ * paneles CEP en el navegador). Sin dependencias. UMD: `require()`/`import` desde Node,
5
+ * o <script> (queda en window.SyntheticI18n).
6
+ *
7
+ * Reglas:
8
+ * - Inglés es el idioma fuente. Cualquier clave que falte en otro idioma cae al inglés; si
9
+ * tampoco está, al texto por defecto que pase el código; si tampoco, a la clave. Nunca vacío.
10
+ * - Las traducciones de descripciones de tools llevan la huella (hash) del inglés del que se
11
+ * tradujeron. Si el inglés cambió desde entonces, se usa el inglés nuevo: nunca se muestra
12
+ * una traducción vieja que ya no corresponde.
13
+ */
14
+ (function (root, factory) {
15
+ var api = factory();
16
+ if (typeof module === 'object' && module.exports) module.exports = api;
17
+ // En CEP con Node habilitado existen `module` y `window` a la vez: el panel lo busca en window.
18
+ if (root && (typeof module !== 'object' || typeof root.document !== 'undefined')) root.SyntheticI18n = api;
19
+ })(typeof self !== 'undefined' ? self : this, function () {
20
+ 'use strict';
21
+
22
+ // Idiomas soportados, en el orden en que se muestran en los selectores.
23
+ var LANGUAGES = [
24
+ { code: 'en', name: 'English' },
25
+ { code: 'es', name: 'Español' },
26
+ { code: 'pt-BR', name: 'Português (Brasil)' },
27
+ { code: 'fr', name: 'Français' },
28
+ { code: 'de', name: 'Deutsch' },
29
+ { code: 'ja', name: '日本語' }
30
+ ];
31
+ var CODES = LANGUAGES.map(function (l) { return l.code; });
32
+ var SOURCE = 'en';
33
+
34
+ // "es-AR", "es_419" → "es"; "pt", "pt-PT" → "pt-BR"; "EN-us" → "en"; desconocido → null.
35
+ function normalizeLanguage(input) {
36
+ if (!input || typeof input !== 'string') return null;
37
+ var s = input.trim().replace('_', '-').toLowerCase();
38
+ if (!s) return null;
39
+ for (var i = 0; i < CODES.length; i++) if (CODES[i].toLowerCase() === s) return CODES[i];
40
+ var base = s.split('-')[0];
41
+ if (base === 'pt') return 'pt-BR';
42
+ for (var j = 0; j < CODES.length; j++) if (CODES[j].toLowerCase() === base) return CODES[j];
43
+ return null;
44
+ }
45
+
46
+ // FNV-1a de 32 bits sobre UTF-16: rápido, determinístico e idéntico en Node y navegador.
47
+ function hash(text) {
48
+ var h = 0x811c9dc5;
49
+ var s = String(text);
50
+ for (var i = 0; i < s.length; i++) {
51
+ h ^= s.charCodeAt(i);
52
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
53
+ }
54
+ return ('0000000' + h.toString(16)).slice(-8);
55
+ }
56
+
57
+ // "Hola {name}" + {name:"Ana"} → "Hola Ana". Placeholders sin valor quedan tal cual.
58
+ function interpolate(text, vars) {
59
+ if (!vars) return text;
60
+ return String(text).replace(/\{([a-zA-Z0-9_]+)\}/g, function (m, k) {
61
+ return Object.prototype.hasOwnProperty.call(vars, k) && vars[k] != null ? String(vars[k]) : m;
62
+ });
63
+ }
64
+
65
+ /**
66
+ * @param {object} opts
67
+ * locales: { en: {key: text}, es: {...}, ... } diccionarios planos
68
+ * hashes: { es: {key: hashDelInglés}, ... } (opcional) huellas de las traducciones de tools
69
+ * language: código pedido (se normaliza; si no está soportado, inglés)
70
+ */
71
+ function createI18n(opts) {
72
+ opts = opts || {};
73
+ var locales = opts.locales || {};
74
+ var hashes = opts.hashes || {};
75
+ var language = normalizeLanguage(opts.language) || SOURCE;
76
+ if (!locales[language] && language !== SOURCE) language = SOURCE;
77
+ var en = locales[SOURCE] || {};
78
+ var lockMap = hashes[language] || {};
79
+ // Una traducción cuyo inglés cambió desde que se tradujo no se usa (cae al inglés actual).
80
+ var dict = {};
81
+ var raw = locales[language] || {};
82
+ Object.keys(raw).forEach(function (k) {
83
+ var lock = lockMap[k];
84
+ if (lock && typeof en[k] === 'string' && hash(en[k]) !== lock) return;
85
+ dict[k] = raw[k];
86
+ });
87
+
88
+ function has(key) { return Object.prototype.hasOwnProperty.call(dict, key) && typeof dict[key] === 'string' && dict[key] !== ''; }
89
+
90
+ // Texto de una clave de UI/mensajes con caída al inglés.
91
+ function t(key, vars, fallbackText) {
92
+ var text = has(key) ? dict[key]
93
+ : (typeof en[key] === 'string' && en[key] !== '') ? en[key]
94
+ : (fallbackText != null && fallbackText !== '') ? fallbackText
95
+ : key;
96
+ return interpolate(text, vars);
97
+ }
98
+
99
+ // Traducción de un texto en inglés "vivo" (tomado del código en ejecución): solo si la
100
+ // traducción se hizo a partir de ese mismo inglés. Si no, devuelve el inglés.
101
+ function translateLive(key, englishNow) {
102
+ if (language === SOURCE || typeof englishNow !== 'string' || englishNow === '') return englishNow;
103
+ if (!has(key)) return englishNow;
104
+ var lock = lockMap[key];
105
+ if (lock && lock !== hash(englishNow)) return englishNow;
106
+ return dict[key];
107
+ }
108
+
109
+ // Recorre un JSON Schema y traduce cada `description` (y `title`) con claves estables:
110
+ // tool.<nombre>.param.<ruta> ej.: tool.apply_effect.param.clipId
111
+ // rutas anidadas con ".", ítems de arrays con "[]", ramas anyOf/oneOf con "|n".
112
+ function localizeSchema(schema, keyBase) {
113
+ if (!schema || typeof schema !== 'object') return schema;
114
+ var out = Array.isArray(schema) ? schema.slice() : Object.assign({}, schema);
115
+ if (typeof out.description === 'string') out.description = translateLive(keyBase, out.description);
116
+ if (typeof out.title === 'string') out.title = translateLive(keyBase + '#title', out.title);
117
+ if (out.properties && typeof out.properties === 'object') {
118
+ var props = {};
119
+ Object.keys(out.properties).forEach(function (name) {
120
+ props[name] = localizeSchema(out.properties[name], joinPath(keyBase, name));
121
+ });
122
+ out.properties = props;
123
+ }
124
+ if (out.items && typeof out.items === 'object' && !Array.isArray(out.items)) out.items = localizeSchema(out.items, keyBase + '[]');
125
+ ['anyOf', 'oneOf', 'allOf'].forEach(function (k) {
126
+ if (Array.isArray(out[k])) out[k] = out[k].map(function (s, i) { return localizeSchema(s, keyBase + '|' + i); });
127
+ });
128
+ if (out.additionalProperties && typeof out.additionalProperties === 'object') {
129
+ out.additionalProperties = localizeSchema(out.additionalProperties, keyBase + '{}');
130
+ }
131
+ return out;
132
+ }
133
+
134
+ // Traduce la lista de tools de una respuesta tools/list. Devuelve copias; no toca el original.
135
+ function localizeTools(tools) {
136
+ if (language === SOURCE || !Array.isArray(tools)) return tools;
137
+ return tools.map(function (tool) {
138
+ if (!tool || typeof tool.name !== 'string') return tool;
139
+ var base = 'tool.' + tool.name;
140
+ var copy = Object.assign({}, tool);
141
+ if (typeof copy.description === 'string') copy.description = translateLive(base, copy.description);
142
+ if (typeof copy.title === 'string') copy.title = translateLive(base + '#title', copy.title);
143
+ if (copy.inputSchema) copy.inputSchema = localizeSchemaRoot(copy.inputSchema, base);
144
+ return copy;
145
+ });
146
+ }
147
+ // En la raíz del inputSchema la descripción del objeto no se usa como parámetro.
148
+ function localizeSchemaRoot(schema, base) {
149
+ var out = Object.assign({}, schema);
150
+ if (out.properties) {
151
+ var props = {};
152
+ Object.keys(out.properties).forEach(function (name) {
153
+ props[name] = localizeSchema(out.properties[name], base + '.param.' + name);
154
+ });
155
+ out.properties = props;
156
+ }
157
+ return out;
158
+ }
159
+
160
+ // Plurales: busca "<clave>.<categoría>" según las reglas del idioma (Intl.PluralRules:
161
+ // one/other, en japonés solo other), después "<clave>.other" y por último "<clave>".
162
+ // {count} queda disponible como placeholder.
163
+ var pluralRules = null;
164
+ try { pluralRules = new Intl.PluralRules(language === 'pt-BR' ? 'pt-BR' : language); } catch (e) { pluralRules = null; }
165
+ function tn(key, count, vars) {
166
+ var v = Object.assign({ count: count }, vars || {});
167
+ var cat = pluralRules ? pluralRules.select(count) : (count === 1 ? 'one' : 'other');
168
+ var candidates = [key + '.' + cat, key + '.other', key];
169
+ for (var i = 0; i < candidates.length; i++) {
170
+ var k = candidates[i];
171
+ if (has(k) || (typeof en[k] === 'string' && en[k] !== '')) return t(k, v);
172
+ }
173
+ return t(key, v);
174
+ }
175
+
176
+ return {
177
+ language: language,
178
+ t: t,
179
+ tn: tn,
180
+ translateLive: translateLive,
181
+ localizeTools: localizeTools,
182
+ has: has
183
+ };
184
+ }
185
+
186
+ function joinPath(base, name) { return base + '.' + name; }
187
+
188
+ // Aplana la lista de tools de tools/list a { clave: inglés } con las mismas claves que usa
189
+ // localizeTools. Lo usa el extractor para generar el diccionario fuente.
190
+ function flattenTools(tools) {
191
+ var out = {};
192
+ (tools || []).forEach(function (tool) {
193
+ if (!tool || typeof tool.name !== 'string') return;
194
+ var base = 'tool.' + tool.name;
195
+ if (typeof tool.description === 'string' && tool.description) out[base] = tool.description;
196
+ if (typeof tool.title === 'string' && tool.title) out[base + '#title'] = tool.title;
197
+ var props = tool.inputSchema && tool.inputSchema.properties;
198
+ if (props) Object.keys(props).forEach(function (name) { walk(props[name], base + '.param.' + name); });
199
+ });
200
+ function walk(schema, key) {
201
+ if (!schema || typeof schema !== 'object') return;
202
+ if (typeof schema.description === 'string' && schema.description) out[key] = schema.description;
203
+ if (typeof schema.title === 'string' && schema.title) out[key + '#title'] = schema.title;
204
+ if (schema.properties) Object.keys(schema.properties).forEach(function (n) { walk(schema.properties[n], key + '.' + n); });
205
+ if (schema.items && typeof schema.items === 'object' && !Array.isArray(schema.items)) walk(schema.items, key + '[]');
206
+ ['anyOf', 'oneOf', 'allOf'].forEach(function (k) {
207
+ if (Array.isArray(schema[k])) schema[k].forEach(function (s, i) { walk(s, key + '|' + i); });
208
+ });
209
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') walk(schema.additionalProperties, key + '{}');
210
+ }
211
+ return out;
212
+ }
213
+
214
+ return {
215
+ LANGUAGES: LANGUAGES,
216
+ CODES: CODES,
217
+ SOURCE: SOURCE,
218
+ normalizeLanguage: normalizeLanguage,
219
+ hash: hash,
220
+ interpolate: interpolate,
221
+ createI18n: createI18n,
222
+ flattenTools: flattenTools
223
+ };
224
+ });