{"record":{"id":"0aeeb74cddd95a34","repo":"facebook/docusaurus","slug":"translation-file-path-at-translationfilepath","errorCode":null,"errorMessage":"Translation file path at \"${translationFilePath}\" does not need to end with \".json\", we add the extension automatically.","messagePattern":"Translation file path at \"(.+?)\" does not need to end with \"\\.json\", we add the extension automatically\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/docusaurus/src/server/translations/translations.ts","lineNumber":171,"sourceCode":"}\nexport async function writeCodeTranslations(\n  context: TranslationContext,\n  content: TranslationFileContent,\n  options: WriteTranslationsOptions,\n): Promise<void> {\n  return writeTranslationFileContent({\n    filePath: getCodeTranslationsFilePath(context),\n    content,\n    options,\n  });\n}\n\n// We ask users to not provide any extension on purpose:\n// maybe some day we'll want to support multiple FS formats?\n// (json/yaml/toml/xml...)\nfunction addTranslationFileExtension(translationFilePath: string) {\n  if (translationFilePath.endsWith('.json')) {\n    throw new Error(\n      `Translation file path at \"${translationFilePath}\" does not need to end with \".json\", we add the extension automatically.`,\n    );\n  }\n  return `${translationFilePath}.json`;\n}\n\nfunction getPluginTranslationFilePath({\n  localizationDir,\n  plugin,\n  translationFilePath,\n}: TranslationContext & {\n  plugin: InitializedPlugin;\n  translationFilePath: string;\n}): string {\n  const dirPath = getPluginI18nPath({\n    localizationDir,\n    pluginName: plugin.name,\n    pluginId: plugin.options.id,","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/facebook/docusaurus/blob/3f483e80e326cc646b54b83d564b3f0c4881b9a6/packages/docusaurus/src/server/translations/translations.ts#L153-L189","documentation":"Thrown by `addTranslationFileExtension` when a plugin declares a translation file path that already ends in `.json`. Docusaurus appends the extension itself (the comment explains it reserves room for future formats like yaml/toml), so a user-supplied `.json` suffix would double it. This is a configuration contract enforced for plugin authors.","triggerScenarios":"A plugin's `getTranslationFiles` / `translate` hook returns a path like `'i18n/fr/messages.json'`. The check at translations.ts:171-176 fires before the internal `.json` append.","commonSituations":"Third-party plugin authors copying example code that included the extension; refactoring a plugin and adding `.json` for clarity; migrating content where source files used `.json`.","solutions":["Remove the `.json` suffix from the translation file path your plugin returns.","Use the path without extension consistently in `getTranslationFiles` and `translate`.","If you control the format and want `.json` literally, you cannot — rely on Docusaurus to append it."],"exampleFix":"// before\ngetTranslationFiles: () => [{path: 'fr/messages.json', content: {}}],\n// after\ngetTranslationFiles: () => [{path: 'fr/messages', content: {}}],","handlingStrategy":"validation","validationCode":"function assertNoJsonExt(path: string) {\n  if (path.endsWith('.json')) throw new Error(`Remove .json from translation path: ${path}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Return translation paths without any extension from plugin i18n hooks.","Document this contract for any third-party plugin you maintain.","Add a unit test asserting paths have no `.json` suffix."],"tags":["i18n","translations","plugins","config"],"backgroundTag":null,"analyzedSha":"3f483e80e326cc646b54b83d564b3f0c4881b9a6","analyzedAt":"2026-08-12T13:25:04.382Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}