datocms-plugin-ai-translations 2.2.1 → 2.4.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
@@ -14,21 +14,24 @@ See the [CHANGELOG.md](./CHANGELOG.md) file for details about all the latest fea
14
14
 
15
15
  On the plugin's Settings screen:
16
16
 
17
- 1. **AI Vendor**: Choose your provider — OpenAI (ChatGPT), Google (Gemini), or Anthropic (Claude).
17
+ 1. **AI Vendor**: Choose your provider — OpenAI (ChatGPT), Google (Gemini), Anthropic (Claude), or DeepL.
18
18
  2. If you chose OpenAI:
19
19
  - **OpenAI API Key**: Paste a valid OpenAI key.
20
- - **GPT Model**: After entering your key, the plugin lists relevant chat models and highlights a recommended default.
21
- - Default: gpt‑4.1‑mini (fastest and broadly available)
22
- - High‑stakes short copy: gpt‑4.1
23
- - Large or budget batches: gpt‑4o‑mini
20
+ - **GPT Model**: After entering your key, the plugin lists available chat models. Select a model from the dropdown.
24
21
  3. If you chose Google (Gemini):
25
22
  - **Google API Key**: Paste a valid key from a GCP project with the Generative Language API enabled.
26
- - **Gemini Model**: Recommended `gemini-2.5-flash` (fast/cost‑effective default). For the highest fidelity, use `gemini-2.5-pro`. For very large or budget batches, consider `gemini-2.5-flash-lite`.
27
- 4. **Translatable Field Types**: Pick which field editor types (single_line, markdown, structured_text, etc.) can be translated.
28
- 5. **Translate Whole Record**: Enable the sidebar that translates every localized field in a record.
29
- 6. **Translate Bulk Records**: Enable bulk translations from table view or via the dedicated page.
30
- 7. **AI Bulk Translations Page**: Translate whole models at once.
31
- 8. **Prompt Template**: Customize how translations are requested. Use `{fieldValue}`, `{fromLocale}`, `{toLocale}`, and `{recordContext}`.
23
+ - **Gemini Model**: Select a model from the dropdown.
24
+ 4. If you chose Anthropic (Claude):
25
+ - **Anthropic API Key**: Paste a valid Anthropic key.
26
+ - **Claude Model**: Select a model from the dropdown.
27
+ 5. If you chose DeepL:
28
+ - **DeepL API Key**: Paste your DeepL API key.
29
+ - **Use DeepL Free endpoint**: Enable this if your key ends with `:fx` (Free plan).
30
+ 6. **Translatable Field Types**: Pick which field editor types (single_line, markdown, structured_text, etc.) can be translated.
31
+ 7. **Translate Whole Record**: Enable the sidebar that translates every localized field in a record.
32
+ 8. **Translate Bulk Records**: Enable bulk translations from table view or via the dedicated page.
33
+ 9. **AI Bulk Translations Page**: Translate whole models at once.
34
+ 10. **Prompt Template** (AI vendors only): Customize how translations are requested. Use `{fieldValue}`, `{fromLocale}`, `{toLocale}`, and `{recordContext}`.
32
35
 
33
36
  ### Key Restrictions and Security
34
37
  - Keys are stored in plugin settings and used client‑side. Do not share your workspace publicly.
@@ -38,8 +41,8 @@ On the plugin's Settings screen:
38
41
  - The plugin redacts API keys from debug logs automatically.
39
42
 
40
43
  _**Models**_
41
- - OpenAI: the list is dynamic for your account; the plugin filters out embeddings, audio/whisper/tts, moderation, image, and realtime models, prioritizing general-purpose chat models used for translation.
42
- - Google: provides a fixed list in settings (`gemini-1.5-flash` and `gemini-1.5-pro`).
44
+ - OpenAI: the model list is fetched dynamically for your account; the plugin filters out embeddings, audio/whisper/tts, moderation, image, and realtime models.
45
+ - Google: the model list is fetched dynamically from the Generative Language API.
43
46
 
44
47
  Save your changes. The plugin is now ready.
45
48
 
@@ -99,6 +102,22 @@ The plugin now supports context-aware translations through the `{recordContext}`
99
102
  - More accurate translations that respect the overall content meaning
100
103
  - Appropriate tone and style based on context
101
104
 
105
+ ## ICU Message Format Support
106
+
107
+ The plugin supports **[ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/)** strings, ensuring that complex pluralization and selection logic is preserved during translation.
108
+
109
+ - **Smart Masking**: Simple variables like `{name}` are masked to protect them, while ICU structures like `{count, plural, ...}` are passed to the AI.
110
+ - **AI Instructions**: The AI is explicitly instructed to preserve the ICU structure and keywords, translating only the human-readable content inside.
111
+
112
+ **Example:**
113
+ ```
114
+ You have {count, plural, one {# message} other {# messages}}
115
+ ```
116
+ Becomes:
117
+ ```
118
+ Você tem {count, plural, one {# mensagem} other {# mensagens}}
119
+ ```
120
+
102
121
  ## Customizing Prompts
103
122
 
104
123
  You can customize the translation prompt template in the plugin settings:
@@ -119,167 +138,6 @@ You can customize the translation prompt template in the plugin settings:
119
138
  - **Model Not Found**: Verify the exact model id exists for your account/region and is spelled correctly.
120
139
  - **Localization**: Make sure your project has at least two locales, otherwise translation actions won't appear.
121
140
 
122
- ## DeepL Requires a Proxy (Why and How)
123
-
124
- DeepL’s API does not support browser‑origin requests (no CORS). If you call DeepL directly from the plugin (which runs in the browser), the preflight request fails and you’ll see network/CORS errors. To use DeepL in this plugin, you must route requests through a small server you control (a “proxy”).
125
-
126
- What the proxy must do
127
- - Accept a POST with JSON body that matches DeepL’s `/v2/translate` input. The plugin sends bodies like:
128
- - `{ "text": ["Hello"], "target_lang": "DE", ... }`
129
- - Add CORS headers to the response (at minimum `Access-Control-Allow-Origin: *` for testing; you can restrict later).
130
- - Forward the request to DeepL’s API with the Authorization header added server‑side:
131
- - `Authorization: DeepL-Auth-Key <YOUR_KEY>`
132
- - Choose the proper upstream host:
133
- - Free keys (end with `:fx`) → `https://api-free.deepl.com`
134
- - Pro keys → `https://api.deepl.com`
135
-
136
- Plugin settings to use with a proxy
137
- - In Settings → DeepL, set “Proxy URL” to the base of your function (examples below) — the plugin will call `POST <proxy>/v2/translate`.
138
- - If your key ends with `:fx`, also enable “Use DeepL Free endpoint (api-free.deepl.com)” so validation and error messages match your plan.
139
- - Use the “Test proxy” button: we send a tiny "Hello world" test, and show inline success/error feedback.
140
-
141
- Security notes
142
- - Never expose the DeepL key in the browser. Keep it in your serverless function/env.
143
- - For production, restrict CORS to `https://admin.datocms.com` and your own preview domains instead of `*`.
144
- - Do not log request bodies or headers; avoid leaving keys in logs.
145
-
146
- ### Option A: Cloudflare Workers
147
-
148
- Environment vars
149
- - `DEEPL_AUTH_KEY` — your DeepL key
150
- - `DEEPL_BASE_URL` — `https://api-free.deepl.com` (Free) or `https://api.deepl.com` (Pro)
151
-
152
- Worker (wrangler.toml configured; minimal example)
153
- ```
154
- export default {
155
- async fetch(req, env) {
156
- const cors = {
157
- 'Access-Control-Allow-Origin': '*',
158
- 'Access-Control-Allow-Headers': '*',
159
- };
160
- if (req.method === 'OPTIONS') return new Response(null, { headers: cors });
161
-
162
- const url = new URL(req.url);
163
- const isFree = url.searchParams.get('endpoint') === 'free';
164
- const baseUrl = isFree ? 'https://api-free.deepl.com' : 'https://api.deepl.com';
165
- const upstream = new URL('/v2/translate', baseUrl);
166
-
167
- const body = await req.text(); // passthrough JSON body
168
-
169
- const resp = await fetch(upstream, {
170
- method: 'POST',
171
- headers: {
172
- 'Content-Type': 'application/json',
173
- 'Authorization': `DeepL-Auth-Key ${env.DEEPL_AUTH_KEY}`,
174
- },
175
- body,
176
- });
177
-
178
- const text = await resp.text();
179
- return new Response(text, { status: resp.status, headers: { ...cors, 'Content-Type': 'application/json' } });
180
- }
181
- }
182
- ```
183
-
184
- Deploy
185
- - `wrangler deploy`
186
- - Set `DEEPL_AUTH_KEY` and `DEEPL_BASE_URL` in your Worker’s environment.
187
- - Copy the Worker URL (e.g., `https://your-worker.yourname.workers.dev`) into the plugin’s “Proxy URL”.
188
-
189
- ### Option B: Vercel Serverless Function (Next.js API Route)
190
-
191
- Environment vars (Project Settings → Environment Variables):
192
- - `DEEPL_AUTH_KEY` — your key
193
- - `DEEPL_BASE_URL` — `https://api-free.deepl.com` or `https://api.deepl.com`
194
-
195
- Create `pages/api/deepl.ts` (or `app/api/deepl/route.ts` for App Router):
196
- ```
197
- export default async function handler(req, res) {
198
- res.setHeader('Access-Control-Allow-Origin', '*');
199
- res.setHeader('Access-Control-Allow-Headers', '*');
200
- if (req.method === 'OPTIONS') return res.status(204).end();
201
-
202
- const isFree = req.query.endpoint === 'free';
203
- const baseUrl = isFree ? 'https://api-free.deepl.com' : 'https://api.deepl.com';
204
- const upstream = `${baseUrl}/v2/translate`;
205
-
206
- const r = await fetch(upstream, {
207
- method: 'POST',
208
- headers: {
209
- 'Content-Type': 'application/json',
210
- 'Authorization': `DeepL-Auth-Key ${process.env.DEEPL_AUTH_KEY}`,
211
- },
212
- body: JSON.stringify(req.body ?? {}),
213
- });
214
- const text = await r.text();
215
- res.status(r.status).setHeader('Content-Type', 'application/json').send(text);
216
- }
217
- ```
218
-
219
- Deploy
220
- - `vercel deploy` (or push to GitHub with Vercel connected).
221
- - Set env vars, redeploy, then use `https://your-app.vercel.app/api/deepl` as the “Proxy URL”.
222
-
223
- Note on Vercel Deployment Protection
224
- - If your Vercel organization enforces Deployment Protection, unauthenticated public requests return 401.
225
- - Go to Vercel → Project → Settings → Deployment Protection and either disable protection for this project or add a public bypass so DatoCMS can call the endpoint.
226
- - After changing this setting, the proxy should be reachable from the plugin’s “Test proxy” and during translations.
227
-
228
- ### Option C: Netlify Functions
229
-
230
- Environment vars (Netlify dashboard → Site settings → Environment):
231
- - `DEEPL_AUTH_KEY`, `DEEPL_BASE_URL`
232
-
233
- Create `netlify/functions/deepl-proxy.ts`:
234
- ```
235
- export const handler = async (event) => {
236
- if (event.httpMethod === 'OPTIONS') {
237
- return { statusCode: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*' } };
238
- }
239
-
240
- const isFree = event.queryStringParameters?.endpoint === 'free';
241
- const baseUrl = isFree ? 'https://api-free.deepl.com' : 'https://api.deepl.com';
242
- const upstream = `${baseUrl}/v2/translate`;
243
-
244
- const r = await fetch(upstream, {
245
- method: 'POST',
246
- headers: {
247
- 'Content-Type': 'application/json',
248
- 'Authorization': `DeepL-Auth-Key ${process.env.DEEPL_AUTH_KEY}`,
249
- },
250
- body: event.body || '{}',
251
- });
252
- const text = await r.text();
253
- return {
254
- statusCode: r.status,
255
- headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
256
- body: text,
257
- };
258
- };
259
- ```
260
-
261
- Deploy
262
- - `netlify deploy` (or via the Netlify app/CLI). Use `/.netlify/functions/deepl-proxy` as your “Proxy URL”.
263
-
264
- ### Testing and common errors
265
-
266
- - Use the “Test proxy” button in plugin settings to verify connectivity.
267
- - Wrong endpoint for key (403 / “Wrong endpoint”):
268
- - Free key (`…:fx`) must target `api-free.deepl.com`. Pro keys must target `api.deepl.com`.
269
- - Fix by setting `DEEPL_BASE_URL` accordingly (and/or toggle “Use DeepL Free endpoint” in the plugin).
270
- - CORS errors: ensure your proxy responds to OPTIONS and includes `Access-Control-Allow-Origin`.
271
- - 414/URI too long: means you’re not POSTing a body through your proxy. The examples above use POST and won’t hit this.
272
- - 429/Rate limit: lower concurrency or try smaller batches; upgrade plan if needed.
273
-
274
- Endpoint selection (Free vs Pro)
275
- - The example proxies choose the upstream via an environment variable `DEEPL_BASE_URL`.
276
- - Set `DEEPL_BASE_URL` to `https://api-free.deepl.com` if your key ends with `:fx` (DeepL Free), otherwise to `https://api.deepl.com` (Pro).
277
- - In the plugin settings, the toggle “Use DeepL Free endpoint (api-free.deepl.com)” should match the proxy’s `DEEPL_BASE_URL` so errors and validations are consistent. A mismatch will surface as a “Wrong endpoint for your API key” error.
278
-
279
- That’s it — once your proxy passes the test, DeepL translations (including large Structured Text fields) will work end‑to‑end.
280
-
281
-
282
-
283
141
  ## DeepL Glossaries
284
142
 
285
143
  The plugin supports DeepL glossaries to enforce preferred terminology. You can set a default glossary ID and/or map specific language pairs to specific glossary IDs. This works for all field types, including Structured Text.
@@ -287,18 +145,42 @@ The plugin supports DeepL glossaries to enforce preferred terminology. You can s
287
145
  ### Requirements
288
146
 
289
147
  - A DeepL API key with access to Glossaries. Check your DeepL account/plan capabilities.
290
- - The same proxy described above; translations with a glossary still call `POST <proxy>/v2/translate` with an extra `glossary_id` in the JSON body.
291
148
 
292
149
  ### Configure in the Plugin
293
150
 
294
- 1. Open Settings → vendor DeepL”.
295
- 2. Set “Proxy URL” and verify it via Test proxy”.
296
- 3. Expand Advanced settings”.
297
- 4. Optional: set Default glossary ID (e.g., `gls-abc123`).
298
- 5. Optional: fill in Glossaries by language pair with one mapping per line.
151
+ 1. Open Settings → vendor "DeepL".
152
+ 2. Enter your DeepL API Key and verify it via "Test API Key".
153
+ 3. Expand "Advanced settings".
154
+ 4. Optional: set "Default glossary ID" (e.g., `gls-abc123`).
155
+ 5. Optional: fill in "Glossaries by language pair" with one mapping per line.
299
156
 
300
157
  You can use either DatoCMS locales (e.g., `en-US`, `pt-BR`) or DeepL codes (e.g., `EN`, `PT-BR`). The plugin normalizes both to DeepL codes internally.
301
158
 
159
+ ### Configuration Examples
160
+
161
+ **Scenario A: Single Language Pair**
162
+ If you only translate from English to German, you only need one glossary.
163
+ - **Default glossary ID**: `gls-12345` (Your EN->DE glossary)
164
+ - **Glossaries by language pair**: *(Leave empty)*
165
+
166
+ **Scenario B: Multiple Language Pairs**
167
+ If you translate to multiple languages, map each one specifically.
168
+ - **Default glossary ID**: *(Leave empty)*
169
+ - **Glossaries by language pair**:
170
+ ```text
171
+ EN->DE=gls-german123
172
+ EN->FR=gls-french456
173
+ ```
174
+
175
+ **Scenario C: Fallback Strategy**
176
+ Use specific glossaries for main languages, and a default for everything else.
177
+ - **Default glossary ID**: `gls-fallback999`
178
+ - **Glossaries by language pair**:
179
+ ```text
180
+ EN->DE=gls-german123
181
+ ```
182
+ *(Note: If the default glossary doesn't match the language pair of a translation, the plugin will automatically retry without it.)*
183
+
302
184
  ### Mapping Syntax
303
185
 
304
186
  One entry per line. Supported forms:
@@ -353,14 +235,14 @@ curl -X POST https://api.deepl.com/v2/glossaries \
353
235
 
354
236
  Note: If your account uses the Free endpoint, replace the host with `https://api-free.deepl.com`.
355
237
 
356
- You do not need to expose `/v2/glossaries` through your proxy for the plugin to work — it only calls `/v2/translate`. Manage glossaries from your server/CLI, then paste the resulting IDs into the plugin settings.
238
+ Manage glossaries from your server/CLI or the DeepL dashboard, then paste the resulting IDs into the plugin settings.
357
239
 
358
240
  ### Tips and Limitations
359
241
 
360
242
  - Glossaries apply only to the DeepL vendor. OpenAI/Gemini/Anthropic do not use glossaries.
361
243
  - The plugin preserves placeholders and HTML tags automatically (`notranslate`, `ph`, etc.). Glossaries will not alter those tokens.
362
- - If you use DeepL formality”, it is sent only for targets that support it; otherwise omitted.
363
- - A wrong Pro/Free endpoint for your key will still raise the “Wrong endpoint” hint shown in settings and translation errors.
244
+ - If you use DeepL "formality", it is sent only for targets that support it; otherwise omitted.
245
+ - Ensure your API key matches the endpoint setting: Free keys (ending with `:fx`) should have "Use DeepL Free endpoint" enabled.
364
246
 
365
247
  ### Quick Sanity Test
366
248
 
@@ -375,6 +257,13 @@ You do not need to expose `/v2/glossaries` through your proxy for the plugin to
375
257
  1. In Google Cloud, enable the Generative Language API for your project.
376
258
  2. Create an API key and restrict it by HTTP referrer if possible.
377
259
  3. In the plugin settings, switch vendor to Google (Gemini), paste the key, and select a Gemini model.
260
+ - To use Anthropic (Claude):
261
+ 1. Get an API key from the Anthropic Console.
262
+ 2. In the plugin settings, switch vendor to Anthropic (Claude), paste the key, and select a Claude model.
263
+ - To use DeepL:
264
+ 1. Get an API key from your DeepL account (Pro or Free).
265
+ 2. In the plugin settings, switch vendor to DeepL and paste the key.
266
+ 3. If using a Free key (ends with `:fx`), enable "Use DeepL Free endpoint".
378
267
 
379
268
  ## License
380
269