{"record":{"id":"3cef2002550a0f4e","repo":"Mintplex-Labs/anything-llm","slug":"warning-missing-placeholders-in-translation-va","errorCode":null,"errorMessage":"Warning: Missing placeholders in translation: ${validation.missing.join(', ')}","messagePattern":"Warning: Missing placeholders in translation: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"extras/translator/index.mjs","lineNumber":211,"sourceCode":"            const tagValidation = validateTransTags(text, translatedText);\n            if (!tagValidation.valid) {\n                console.warn(`Warning: Missing Trans tags in translation: ${tagValidation.missing.join(', ')}`);\n                for (let i = 0; i < tags.length; i++) {\n                    if (!translatedText.includes(tags[i])) {\n                        console.warn(`  Tag ${tags[i]} was lost in translation`);\n                    }\n                }\n            }\n        }\n        \n        // Restore original placeholders\n        if (hasPlaceholders) {\n            translatedText = restorePlaceholders(translatedText, placeholders);\n            \n            // Validate all placeholders were preserved\n            const validation = validatePlaceholders(text, translatedText);\n            if (!validation.valid) {\n                console.warn(`Warning: Missing placeholders in translation: ${validation.missing.join(', ')}`);\n                // Attempt to fix by checking if tokens remain untranslated\n                for (let i = 0; i < placeholders.length; i++) {\n                    if (!translatedText.includes(placeholders[i])) {\n                        console.warn(`  Placeholder ${placeholders[i]} was lost in translation`);\n                    }\n                }\n            }\n        }\n        \n        return translatedText;\n    }\n\n    writeTranslations(langCode, translations) {\n        let langFilename = langCode.toLowerCase();\n        // Special cases\n        if(langCode === 'pt') langFilename = 'pt_BR';\n        if(langCode === 'zh-tw') langFilename = 'zh_TW';\n        if(langCode === 'vi') langFilename = 'vn';","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/extras/translator/index.mjs#L193-L229","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run translation for the affected keys — placeholder loss is stochastic and often recovers on retry.","Use a stronger/larger translation model and avoid creative sampling settings so tokens survive verbatim.","Manually re-insert the listed {{placeholders}} into the generated locale file before merging (the warning names each missing one).","Keep strings that contain placeholders short and avoid surrounding them with text the model is tempted to inflect.","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."],"exampleFix":"// before: translate once, ship even if {{count}} was dropped\ntranslations[key] = await translator.translate(source[key], lang);\n\n// after: validate placeholder set and retry, then flag for manual review\nlet out = await translator.translate(source[key], lang);\nconst ph = (s) => s.match(/\\{\\{[^}]+\\}\\}/g) || [];\nfor (let i = 0; i < 2 && ph(source[key]).some(p => !ph(out).includes(p)); i++) {\n  out = await translator.translate(source[key], lang);\n}\ntranslations[key] = out;","handlingStrategy":"retry","validationCode":"const placeholdersOf = (s) => s.match(/\\{\\{[^}]+\\}\\}/g) || [];\nconst missingPlaceholders = (source, translated) =>\n  placeholdersOf(source).filter(p => !placeholdersOf(translated).includes(p));\n\nconst ok = missingPlaceholders(sourceText, translatedText).length === 0;","typeGuard":null,"tryCatchPattern":"let result;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  result = await translator.translate(text, lang);\n  if (missingPlaceholders(text, result).length === 0) break;\n  if (result.includes(\"__PLACEHOLDER_\")) continue; // leaked token — restore may fix on retry\n  if (attempt === 2) throw new Error(`Placeholders lost in translation for: ${text}`);\n}\nreturn result;","preventionTips":["Post-validate every translated string: the {{...}} set must be identical (names and casing) to the source before writing the locale file.","If a raw __PLACEHOLDER_n__ token survives into output, substitute the original placeholder by index instead of re-translating blind.","For pluralization-heavy locales, prefer dedicated plural handling over free-text translation of '{{count}}' fragments."],"tags":["i18n","translation","placeholders","icu","llm"],"backgroundTag":"i18n-placeholder-mismatch","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}