{"version":3,"file":"getMissingLocalesContent.cjs","names":["NodeTypes","internationalization"],"sources":["../../../src/deepTransformPlugins/getMissingLocalesContent.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { ContentNode, Dictionary } from '@intlayer/types/dictionary';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport type { DeepTransformContent, NodeProps, Plugins } from '../interpreter';\nimport { deepTransformNode } from '../interpreter/getContent/deepTransform';\nimport type { TranslationContent } from '../transpiler';\n\n/**\n * Returns all key paths present in obj, INCLUDING those whose leaf value is null.\n * Used for structural presence checks (a locale must have every key another locale has,\n * even if the value is a null placeholder).\n */\nconst getAllKeyPaths = (obj: any, prefix: string[] = []): string[][] => {\n  if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {\n    return [];\n  }\n\n  return Object.keys(obj).flatMap((key) => {\n    const newPath = [...prefix, key];\n    const value = obj[key];\n    // Stop recursing into null — include the path but don't descend further\n    if (value === null) {\n      return [newPath];\n    }\n    return [newPath, ...getAllKeyPaths(value, newPath)];\n  });\n};\n\n/**\n * Returns key paths whose leaf value is non-null.\n * Used for translation value checks (a locale must not have null where another locale\n * already has a real translated value).\n */\nconst getNonNullKeyPaths = (obj: any, prefix: string[] = []): string[][] => {\n  if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {\n    return [];\n  }\n\n  return Object.keys(obj).flatMap((key) => {\n    const newPath = [...prefix, key];\n    const value = obj[key];\n    // Skip null-valued keys entirely\n    if (value === null) {\n      return [];\n    }\n    return [newPath, ...getNonNullKeyPaths(value, newPath)];\n  });\n};\n\n/**\n * Returns true if the key path EXISTS in obj (even if the terminal value is null).\n * Used for the structural presence check.\n */\nconst hasKey = (obj: any, keyPath: string[]): boolean => {\n  let current = obj;\n  for (const key of keyPath) {\n    if (\n      current === undefined ||\n      current === null ||\n      typeof current !== 'object'\n    ) {\n      return false;\n    }\n    if (!(key in current)) {\n      return false;\n    }\n    current = current[key];\n  }\n  return true; // key exists; value may be null\n};\n\n/**\n * Returns true if the key path exists in obj AND the terminal value is non-null.\n * Used for the translation value check.\n */\nconst hasNonNullValue = (obj: any, keyPath: string[]): boolean => {\n  let current = obj;\n  for (const key of keyPath) {\n    if (\n      current === undefined ||\n      current === null ||\n      typeof current !== 'object'\n    ) {\n      return false;\n    }\n    if (!(key in current)) {\n      return false;\n    }\n    current = current[key];\n  }\n  // null is treated as a missing translation (e.g., i18next-scanner sets null for untranslated keys)\n  return current !== null;\n};\n\n/** Translation plugin. Replaces node with a locale string if nodeType = Translation. */\nexport const checkMissingLocalesPlugin = (\n  locales: Locale[],\n  onMissingLocale: (locale: Locale) => void\n): Plugins => ({\n  id: 'check-missing-locales-plugin',\n  canHandle: (node) =>\n    typeof node === 'object' && node?.nodeType === NodeTypes.TRANSLATION,\n  transform: (node: TranslationContent, props, deepTransformNode) => {\n    const translations = node[NodeTypes.TRANSLATION] as Record<string, any>;\n\n    /**\n     * Two path sets built from all locales' content:\n     *\n     * presentPaths — every key path that exists in ANY locale, even those whose value\n     *   is null. A locale that is missing a path from this set is structurally incomplete\n     *   (the key doesn't exist at all).\n     *\n     * nonNullPaths — every key path that has a non-null value in at least one locale.\n     *   A locale that has the key but with null, when another locale already has a real\n     *   value, needs translation.\n     */\n    const presentPaths = new Set<string>();\n    const nonNullPaths = new Set<string>();\n\n    for (const locale of locales) {\n      const value = translations[locale];\n      if (value && typeof value === 'object' && !Array.isArray(value)) {\n        getAllKeyPaths(value).forEach((path) => {\n          presentPaths.add(JSON.stringify(path));\n        });\n\n        getNonNullKeyPaths(value).forEach((path) => {\n          nonNullPaths.add(JSON.stringify(path));\n        });\n      }\n    }\n\n    // If no locale has any content at all (all are null/undefined), the key is\n    // universally pending — don't flag anyone.\n    const hasAnyDefinedValue = locales.some(\n      (locale) =>\n        translations[locale] !== undefined && translations[locale] !== null\n    );\n\n    for (const locale of locales) {\n      const value = translations[locale];\n\n      if (value === null) {\n        // Entire locale content is a null placeholder.\n        // Flag only when some other locale already has real content.\n        if (hasAnyDefinedValue) {\n          onMissingLocale(locale);\n        }\n        continue;\n      }\n\n      if (!value) {\n        // undefined / entirely absent\n        onMissingLocale(locale);\n        continue;\n      }\n\n      let flagged = false;\n\n      // Structural check: every key that exists in any locale must also exist here\n      // (even if the local value is null — at least the key must be present).\n      for (const pathStr of presentPaths) {\n        if (!hasKey(value, JSON.parse(pathStr))) {\n          onMissingLocale(locale);\n          flagged = true;\n          break;\n        }\n      }\n\n      if (!flagged) {\n        // Value check: every key that has a non-null value in some locale must also\n        // be non-null here (null = untranslated, needs filling).\n        for (const pathStr of nonNullPaths) {\n          if (!hasNonNullValue(value, JSON.parse(pathStr))) {\n            onMissingLocale(locale);\n            break;\n          }\n        }\n      }\n    }\n\n    // Continue traversal inside the translation values\n    for (const key in translations) {\n      const child = translations[key];\n      deepTransformNode(child, {\n        ...props,\n        children: child,\n      });\n    }\n\n    // Return the original node; the return value is ignored by the caller\n    return node;\n  },\n});\n\n/**\n * Return the content of a node with only the translation plugin.\n *\n * @param node The node to transform.\n * @param locales The locales to check for missing translations.\n */\nexport const getMissingLocalesContent = <T extends ContentNode>(\n  node: T,\n  locales: LocalesValues[] = internationalization?.locales,\n\n  nodeProps: NodeProps\n): Locale[] => {\n  const missingLocales = new Set<Locale>();\n\n  const plugins: Plugins[] = [\n    checkMissingLocalesPlugin(locales as Locale[], (locale) =>\n      missingLocales.add(locale)\n    ),\n    ...(nodeProps.plugins ?? []),\n  ];\n\n  // eager: traversal of plain objects must run immediately. Without it the\n  // returned tree is a proxy of lazy getters; we discard the tree, so nested\n  // translation nodes inside plain objects (the typical dictionary shape) are\n  // never visited and their missing locales go undetected.\n  deepTransformNode(node, {\n    ...nodeProps,\n    plugins,\n    eager: true,\n  }) as DeepTransformContent<T>;\n\n  return Array.from(missingLocales);\n};\n\nexport const getMissingLocalesContentFromDictionary = (\n  dictionary: Dictionary,\n  locales: LocalesValues[] = internationalization?.locales\n) =>\n  getMissingLocalesContent(dictionary.content, locales, {\n    dictionaryKey: dictionary.key,\n    keyPath: [],\n  });\n"],"mappings":"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"}