@intlayer/core
Version:
Includes core Intlayer functions like translation, dictionary, and utility functions shared across multiple packages.
147 lines (145 loc) • 5.27 kB
JavaScript
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