UNPKG

@intlayer/core

Version:

Includes core Intlayer functions like translation, dictionary, and utility functions shared across multiple packages.

147 lines (145 loc) 5.27 kB
import { deepTransformNode } from "../interpreter/getContent/deepTransform.mjs"; import * as NodeTypes from "@intlayer/types/nodeType"; import { internationalization } from "@intlayer/config/built"; //#region src/deepTransformPlugins/getMissingLocalesContent.ts /** * Returns all key paths present in obj, INCLUDING those whose leaf value is null. * Used for structural presence checks (a locale must have every key another locale has, * even if the value is a null placeholder). */ const getAllKeyPaths = (obj, prefix = []) => { if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return []; return Object.keys(obj).flatMap((key) => { const newPath = [...prefix, key]; const value = obj[key]; if (value === null) return [newPath]; return [newPath, ...getAllKeyPaths(value, newPath)]; }); }; /** * Returns key paths whose leaf value is non-null. * Used for translation value checks (a locale must not have null where another locale * already has a real translated value). */ const getNonNullKeyPaths = (obj, prefix = []) => { if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return []; return Object.keys(obj).flatMap((key) => { const newPath = [...prefix, key]; const value = obj[key]; if (value === null) return []; return [newPath, ...getNonNullKeyPaths(value, newPath)]; }); }; /** * Returns true if the key path EXISTS in obj (even if the terminal value is null). * Used for the structural presence check. */ const hasKey = (obj, keyPath) => { let current = obj; for (const key of keyPath) { if (current === void 0 || current === null || typeof current !== "object") return false; if (!(key in current)) return false; current = current[key]; } return true; }; /** * Returns true if the key path exists in obj AND the terminal value is non-null. * Used for the translation value check. */ const hasNonNullValue = (obj, keyPath) => { let current = obj; for (const key of keyPath) { if (current === void 0 || current === null || typeof current !== "object") return false; if (!(key in current)) return false; current = current[key]; } return current !== null; }; /** Translation plugin. Replaces node with a locale string if nodeType = Translation. */ const checkMissingLocalesPlugin = (locales, onMissingLocale) => ({ id: "check-missing-locales-plugin", canHandle: (node) => typeof node === "object" && node?.nodeType === NodeTypes.TRANSLATION, transform: (node, props, deepTransformNode) => { const translations = node[NodeTypes.TRANSLATION]; /** * Two path sets built from all locales' content: * * presentPaths — every key path that exists in ANY locale, even those whose value * is null. A locale that is missing a path from this set is structurally incomplete * (the key doesn't exist at all). * * nonNullPaths — every key path that has a non-null value in at least one locale. * A locale that has the key but with null, when another locale already has a real * value, needs translation. */ const presentPaths = /* @__PURE__ */ new Set(); const nonNullPaths = /* @__PURE__ */ new Set(); for (const locale of locales) { const value = translations[locale]; if (value && typeof value === "object" && !Array.isArray(value)) { getAllKeyPaths(value).forEach((path) => { presentPaths.add(JSON.stringify(path)); }); getNonNullKeyPaths(value).forEach((path) => { nonNullPaths.add(JSON.stringify(path)); }); } } const hasAnyDefinedValue = locales.some((locale) => translations[locale] !== void 0 && translations[locale] !== null); for (const locale of locales) { const value = translations[locale]; if (value === null) { if (hasAnyDefinedValue) onMissingLocale(locale); continue; } if (!value) { onMissingLocale(locale); continue; } let flagged = false; for (const pathStr of presentPaths) if (!hasKey(value, JSON.parse(pathStr))) { onMissingLocale(locale); flagged = true; break; } if (!flagged) { for (const pathStr of nonNullPaths) if (!hasNonNullValue(value, JSON.parse(pathStr))) { onMissingLocale(locale); break; } } } for (const key in translations) { const child = translations[key]; deepTransformNode(child, { ...props, children: child }); } return node; } }); /** * Return the content of a node with only the translation plugin. * * @param node The node to transform. * @param locales The locales to check for missing translations. */ const getMissingLocalesContent = (node, locales = internationalization?.locales, nodeProps) => { const missingLocales = /* @__PURE__ */ new Set(); const plugins = [checkMissingLocalesPlugin(locales, (locale) => missingLocales.add(locale)), ...nodeProps.plugins ?? []]; deepTransformNode(node, { ...nodeProps, plugins, eager: true }); return Array.from(missingLocales); }; const getMissingLocalesContentFromDictionary = (dictionary, locales = internationalization?.locales) => getMissingLocalesContent(dictionary.content, locales, { dictionaryKey: dictionary.key, keyPath: [] }); //#endregion export { checkMissingLocalesPlugin, getMissingLocalesContent, getMissingLocalesContentFromDictionary }; //# sourceMappingURL=getMissingLocalesContent.mjs.map