Mintplex-Labs/anything-llm · warning

Warning: Missing placeholders in translation: ${validation.m

Error message

Warning: Missing placeholders in translation: ${validation.missing.join(', ')}

What it means

extras/translator rewrites ICU interpolation placeholders like {{count}} into opaque __PLACEHOLDER_n__ tokens before translation and restores them afterwards. validatePlaceholders() then compares the set of {{...}} occurrences in source vs translated text; when any placeholder from the source is missing, this summary warning lists it (e.g. '{{count}}'). The translation is still returned, but i18next interpolation for that key will render the literal translation or break at runtime.

Source

Thrown at extras/translator/index.mjs:211

            const tagValidation = validateTransTags(text, translatedText);
            if (!tagValidation.valid) {
                console.warn(`Warning: Missing Trans tags in translation: ${tagValidation.missing.join(', ')}`);
                for (let i = 0; i < tags.length; i++) {
                    if (!translatedText.includes(tags[i])) {
                        console.warn(`  Tag ${tags[i]} was lost in translation`);
                    }
                }
            }
        }
        
        // Restore original placeholders
        if (hasPlaceholders) {
            translatedText = restorePlaceholders(translatedText, placeholders);
            
            // Validate all placeholders were preserved
            const validation = validatePlaceholders(text, translatedText);
            if (!validation.valid) {
                console.warn(`Warning: Missing placeholders in translation: ${validation.missing.join(', ')}`);
                // Attempt to fix by checking if tokens remain untranslated
                for (let i = 0; i < placeholders.length; i++) {
                    if (!translatedText.includes(placeholders[i])) {
                        console.warn(`  Placeholder ${placeholders[i]} was lost in translation`);
                    }
                }
            }
        }
        
        return translatedText;
    }

    writeTranslations(langCode, translations) {
        let langFilename = langCode.toLowerCase();
        // Special cases
        if(langCode === 'pt') langFilename = 'pt_BR';
        if(langCode === 'zh-tw') langFilename = 'zh_TW';
        if(langCode === 'vi') langFilename = 'vn';

View on GitHub (pinned to 3aec848f28)

Solutions

  1. Re-run translation for the affected keys — placeholder loss is stochastic and often recovers on retry.
  2. Use a stronger/larger translation model and avoid creative sampling settings so tokens survive verbatim.
  3. Manually re-insert the listed {{placeholders}} into the generated locale file before merging (the warning names each missing one).
  4. Keep strings that contain placeholders short and avoid surrounding them with text the model is tempted to inflect.
  5. If a token like __PLACEHOLDER_0__ survives into the output verbatim, replace it with the original placeholder from the source string — that means the restore regex missed it.

Example fix

// before: translate once, ship even if {{count}} was dropped
translations[key] = await translator.translate(source[key], lang);

// after: validate placeholder set and retry, then flag for manual review
let out = await translator.translate(source[key], lang);
const ph = (s) => s.match(/\{\{[^}]+\}\}/g) || [];
for (let i = 0; i < 2 && ph(source[key]).some(p => !ph(out).includes(p)); i++) {
  out = await translator.translate(source[key], lang);
}
translations[key] = out;
Defensive patterns

Strategy: retry

Validate before calling

const placeholdersOf = (s) => s.match(/\{\{[^}]+\}\}/g) || [];
const missingPlaceholders = (source, translated) =>
  placeholdersOf(source).filter(p => !placeholdersOf(translated).includes(p));

const ok = missingPlaceholders(sourceText, translatedText).length === 0;

Try / catch

let result;
for (let attempt = 0; attempt < 3; attempt++) {
  result = await translator.translate(text, lang);
  if (missingPlaceholders(text, result).length === 0) break;
  if (result.includes("__PLACEHOLDER_")) continue; // leaked token — restore may fix on retry
  if (attempt === 2) throw new Error(`Placeholders lost in translation for: ${text}`);
}
return result;

Prevention

When it happens

Trigger: The LLM deletes a __PLACEHOLDER_n__ token, translates or rewrites it ("__PLATZHALTER_0__"), changes case ({{Count}}), or converts it to native syntax the restore regex doesn't match — the source/translated {{...}} sets then differ and the warning fires.

Common situations: Batch-translating i18next locale files containing pluralization ({{count}}), usernames, or dates; small local translation models that 'localize' identifier-like tokens; target languages where the model reorders or rewrites around the placeholder. The shipped locale then shows raw tokens or drops the variable.

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@3aec848f28 (2026-08-18). Data as JSON: /api/errors/3cef2002550a0f4e. Report an issue: GitHub.