Explain the DeepL glossary limit instead of forwarding the raw error

Saving terms for a domain/direction that has no glossary yet skips the
delete-before-create step, so the create runs against an account whose
glossary allowance may already be spent. DeepL answers 456 with a bare
{"message":"Too many glossaries"}, which was passed straight to the browser
and reads like a bug in this app.

On the API Free plan the allowance is one glossary for the entire account,
so this triggers as soon as a second domain or direction is used — the UI
offers eight slots (4 domains x 2 directions) that the plan cannot back.

The 456 case now reports which glossaries hold the slots and names the two
ways out: free one up, or upgrade the plan.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-31 11:54:26 +02:00
parent dd6095d402
commit e70b432fe6

View File

@@ -224,7 +224,18 @@ async function handleSaveGlossary(req, res, domainId, direction) {
domains[domainId].glossaries[direction] = null; domains[domainId].glossaries[direction] = null;
saveDomains(domains); saveDomains(domains);
} }
return sendJson(res, createResp.status, { error: await forwardDeepLError(createResp) }); const detail = await forwardDeepLError(createResp);
// DeepL answers 456 "Too many glossaries" once the account's glossary
// allowance is used up. On the API Free plan that allowance is a single
// glossary for the whole account, so this fires as soon as a second
// domain/direction is used - which reads like an app bug unless we say
// which glossary is holding the only slot.
if (createResp.status === 456 && /too many glossaries/i.test(detail)) {
return sendJson(res, 456, {
error: await describeGlossaryLimit(base, domainId, direction),
});
}
return sendJson(res, createResp.status, { error: detail });
} }
const created = await createResp.json(); const created = await createResp.json();
@@ -296,6 +307,33 @@ async function parseJsonBody(req) {
} }
} }
// Turns DeepL's bare "Too many glossaries" into something the person in the
// browser can act on: which glossaries already occupy the account's slots,
// and what the two ways out are. Falls back to a generic message if the
// account can't be listed for some reason.
async function describeGlossaryLimit(base, domainId, direction) {
const wanted = `${DOMAIN_LABELS[domainId]} (${GLOSSARY_DIRECTIONS[direction].label})`;
let inUse = [];
try {
const resp = await fetch(`${base}/v2/glossaries`, {
headers: { Authorization: `DeepL-Auth-Key ${DEEPL_API_KEY}` },
});
if (resp.ok) inUse = (await resp.json()).glossaries || [];
} catch (_) {
/* fall through to the generic wording below */
}
const listed = inUse.length
? ` Currently in use: ${inUse.map((g) => `"${g.name}"`).join(', ')}.`
: '';
return (
`Your DeepL plan's glossary limit is already reached, so the glossary for ` +
`${wanted} could not be created.${listed} ` +
`Delete a glossary you no longer need to free up a slot, or upgrade the ` +
`DeepL API plan (the Free plan allows only one glossary per account, ` +
`which is not enough for one glossary per domain and direction).`
);
}
async function forwardDeepLError(resp) { async function forwardDeepLError(resp) {
let detail = ''; let detail = '';
try { try {