npm package diff
Package: @forge/manifest
Versions: 7.7.0-next.8-experimental-c7a7d36 - 7.7.0-next.11
File: package/out/validators/translations-validator.js
Index: package/out/validators/translations-validator.js
===================================================================
--- package/out/validators/translations-validator.js
+++ package/out/validators/translations-validator.js
@@ -3,16 +3,21 @@
exports.TranslationsValidator = void 0;
const tslib_1 = require("tslib");
const fs_1 = tslib_1.__importDefault(require("fs"));
const path_1 = require("path");
+const lodash_1 = require("lodash");
+const ajv_1 = tslib_1.__importDefault(require("ajv"));
+const manifest_schema_json_1 = tslib_1.__importDefault(require("../schema/manifest-schema.json"));
const text_1 = require("../text");
const text_2 = require("../text");
const utils_1 = require("../utils");
class TranslationsValidator {
+ validateCache = new Map();
+ ajv = new ajv_1.default({ allErrors: true, verbose: true, strict: false });
ensureValidResourcesDefinition(validationErrors, manifest) {
- const { resources, fallback } = manifest.yamlContent.translations;
+ const { resources } = manifest.yamlContent.translations;
const resourcesMap = new Map();
- resources.forEach(({ key, path }) => {
+ for (const { key, path } of resources) {
if (resourcesMap.has(key)) {
validationErrors.push({
message: text_1.errors.translations.duplicateResourceKey(key),
reference: text_2.References.SchemaError,
@@ -22,18 +27,16 @@
}
else {
resourcesMap.set(key, path);
}
- });
- let defaultLanguageLookup = {};
- resourcesMap.forEach((path, key) => {
+ }
+ const allLanguageLookup = {};
+ for (const [key, path] of resourcesMap) {
const resourcePath = (0, path_1.resolve)(path);
try {
if (fs_1.default.lstatSync(resourcePath).isFile()) {
const data = JSON.parse(fs_1.default.readFileSync((0, path_1.resolve)(path), 'utf8'));
- if (fallback?.default === key) {
- defaultLanguageLookup = data;
- }
+ allLanguageLookup[key] = data;
}
}
catch (e) {
validationErrors.push({
@@ -42,27 +45,27 @@
level: 'error',
...(0, utils_1.findPosition)(`path: ${path}`, manifest?.yamlContentByLine)
});
}
- });
- return defaultLanguageLookup;
+ }
+ return allLanguageLookup;
}
ensureValidFallbackDefinition(validationErrors, manifest) {
const { resources, fallback } = manifest.yamlContent.translations;
const defaultLanguage = fallback.default;
const resourcesSet = new Set(resources.map((resource) => resource.key));
const fallbackLanguages = Object.keys(fallback).filter((fallbackLanguage) => fallbackLanguage !== 'default');
const allFallbackLanguagesSet = new Set([defaultLanguage, ...fallbackLanguages]);
- allFallbackLanguagesSet.forEach((fallbackLanguage) => {
+ for (const fallbackLanguage of allFallbackLanguagesSet) {
if (!resourcesSet.has(fallbackLanguage)) {
validationErrors.push({
message: text_1.errors.translations.missingTranslationsJsonFile(fallbackLanguage),
reference: text_2.References.SchemaError,
level: 'error',
...(0, utils_1.findPosition)(fallbackLanguage === defaultLanguage ? `default: ${fallbackLanguage}` : `${fallbackLanguage}:`, manifest.yamlContentByLine)
});
}
- });
+ }
const allLanguagesList = [
defaultLanguage,
...fallbackLanguages,
...fallbackLanguages.flatMap((language) => fallback[language])
@@ -70,33 +73,140 @@
const [, duplicates] = allLanguagesList.reduce(([languageSet, duplicates], language) => {
languageSet.has(language) ? duplicates.add(language) : languageSet.add(language);
return [languageSet, duplicates];
}, [new Set(), new Set()]);
- duplicates.forEach((duplicate) => {
+ for (const duplicate of duplicates) {
validationErrors.push({
message: text_1.errors.translations.duplicateFallbackConfig(duplicate),
reference: text_2.References.SchemaError,
level: 'error',
...(0, utils_1.findPosition)(duplicate, manifest.yamlContentByLine)
});
- });
+ }
}
- ensureI18nKeysExistInDefaultJson(validationErrors, i18nKeys, defaultLanguageLookup, manifest) {
- const i18nKeysSet = new Set(i18nKeys);
- const defaultLocalCode = manifest.yamlContent.translations.fallback.default;
- const languageLookUp = { [defaultLocalCode]: defaultLanguageLookup };
- i18nKeysSet.forEach((key) => {
- const i18nValue = (0, utils_1.getTranslationValue)(languageLookUp, key, defaultLocalCode);
+ getAllLocalesLookup(validationErrors, i18nKeys, translationsLookup, manifest) {
+ const { resources, fallback } = manifest.yamlContent.translations;
+ const defaultLocaleCode = fallback.default;
+ return resources
+ .map((resource) => resource.key)
+ .reduce((allLocalesLookup, locale) => {
+ const i18nMap = this.getI18nMap(i18nKeys, translationsLookup, locale);
+ if (locale === defaultLocaleCode) {
+ this.ensureI18nKeysExistInDefaultJson(validationErrors, i18nKeys, translationsLookup, locale, manifest);
+ }
+ allLocalesLookup.set(locale, i18nMap);
+ return allLocalesLookup;
+ }, new Map());
+ }
+ ensureI18nKeysExistInDefaultJson(validationErrors, i18nKeys, translationsLookup, locale, manifest) {
+ for (const i18nKey of i18nKeys) {
+ const i18nValue = (0, utils_1.getTranslationValue)(translationsLookup, i18nKey, locale);
if (!i18nValue) {
validationErrors.push({
- message: text_1.errors.translations.i18nKeyNotFound(key),
+ message: text_1.errors.translations.i18nKeyNotFound(i18nKey),
reference: text_2.References.SchemaError,
level: 'error',
- ...(0, utils_1.findPosition)(`i18n: ${key}`, manifest.yamlContentByLine)
+ ...(0, utils_1.findPosition)(`i18n: ${i18nKey}`, manifest.yamlContentByLine)
});
}
+ }
+ }
+ getI18nMap(i18nKeys, translationsLookup, locale) {
+ return i18nKeys.reduce((i18nMap, key) => {
+ const i18nValue = (0, utils_1.getTranslationValue)(translationsLookup, key, locale);
+ if (i18nValue) {
+ i18nMap.set(key, i18nValue);
+ }
+ return i18nMap;
+ }, new Map());
+ }
+ getI18nPropertySchema(schemaSlice, i18nPath) {
+ if (typeof schemaSlice !== 'object' || schemaSlice === null) {
+ return [];
+ }
+ const [propertyName, ...restPath] = i18nPath;
+ if (Array.isArray(schemaSlice)) {
+ return schemaSlice.flatMap((object) => {
+ return this.getI18nPropertySchema(object, i18nPath);
+ });
+ }
+ return Object.entries(schemaSlice).flatMap(([key, value]) => {
+ if (key === propertyName) {
+ if (restPath.length === 0 && typeof value === 'object') {
+ return [value];
+ }
+ else if (restPath.length > 0) {
+ return this.getI18nPropertySchema(value, restPath);
+ }
+ else {
+ return [];
+ }
+ }
+ else {
+ return this.getI18nPropertySchema(value, i18nPath);
+ }
});
}
+ getValidateI18nFn(i18nPropertySchema) {
+ const schemaKey = JSON.stringify(i18nPropertySchema);
+ if (this.validateCache.has(schemaKey)) {
+ return this.validateCache.get(schemaKey);
+ }
+ const validate = this.ajv.compile(i18nPropertySchema);
+ this.validateCache.set(schemaKey, validate);
+ return validate;
+ }
+ getI18nPropertyValidator({ modulesSchema, i18nPropertyPath, i18nKey, locale, manifest }) {
+ const i18nPropertySchemas = this.getI18nPropertySchema(modulesSchema, i18nPropertyPath);
+ return (i18nValue) => {
+ const validationResults = i18nPropertySchemas.reduce((validationResults, i18nPropertySchema) => {
+ const validate = this.getValidateI18nFn(i18nPropertySchema);
+ if (!validationResults.isValid && !validate(i18nValue)) {
+ const validationErrors = validate.errors.reduce((validationErrors, validationError) => {
+ if (validationError.message) {
+ validationErrors.push({
+ message: text_1.errors.translations.i18nValueValidationError(i18nKey, locale, validationError.message),
+ reference: text_2.References.SchemaError,
+ level: 'error',
+ ...(0, utils_1.findPosition)(`i18n: ${i18nKey}`, manifest?.yamlContentByLine)
+ });
+ }
+ return validationErrors;
+ }, []);
+ validationResults.errors.push(validationErrors);
+ }
+ else {
+ validationResults.isValid = true;
+ }
+ return validationResults;
+ }, { errors: [], isValid: false });
+ return validationResults.isValid ? [] : validationResults.errors;
+ };
+ }
+ ensureValidI18nValue(validationErrors, i18nMap, moduleI18nProperties, locale, manifest) {
+ for (const i18n of moduleI18nProperties) {
+ const i18nValue = i18nMap.get(i18n.key);
+ if (i18nValue) {
+ const modulesSchema = (0, lodash_1.get)(manifest_schema_json_1.default.definitions.ModuleSchema.properties, i18n.moduleName);
+ const validator = this.getI18nPropertyValidator({
+ modulesSchema,
+ i18nPropertyPath: i18n.propertyPath,
+ i18nKey: i18n.key,
+ locale,
+ manifest
+ });
+ const validationResults = validator(i18nValue);
+ if (validationResults.length > 0) {
+ validationErrors.push(...validationResults[0]);
+ }
+ }
+ }
+ }
+ ensureAllValidI18nValue(validationErrors, allI18nMap, moduleI18nProperties, manifest) {
+ for (const [locale, i18nMap] of allI18nMap) {
+ this.ensureValidI18nValue(validationErrors, i18nMap, moduleI18nProperties, locale, manifest);
+ }
+ }
validateManifestWithoutI18nConfig(manifest, i18nKeys) {
if (i18nKeys.length === 0) {
return {
success: true,
@@ -116,13 +226,14 @@
manifestObject: manifest,
errors: missingTranslationsPropertyError
};
}
- validateManifestI18nConfig(manifest, i18nKeys) {
+ validateManifestI18nConfig(manifest, i18nKeys, moduleI18nProperties) {
const validationErrors = [];
- const defaultLanguageLookup = this.ensureValidResourcesDefinition(validationErrors, manifest);
+ const allLanguageLookup = this.ensureValidResourcesDefinition(validationErrors, manifest);
this.ensureValidFallbackDefinition(validationErrors, manifest);
- this.ensureI18nKeysExistInDefaultJson(validationErrors, i18nKeys, defaultLanguageLookup, manifest);
+ const i18nMap = this.getAllLocalesLookup(validationErrors, i18nKeys, allLanguageLookup, manifest);
+ this.ensureAllValidI18nValue(validationErrors, i18nMap, moduleI18nProperties, manifest);
return validationErrors;
}
validateInternalI18nPropertyKeysNotInModules(manifest) {
const modules = manifest?.typedContent?.modules ?? {};
@@ -140,15 +251,16 @@
success: false,
manifestObject: manifest
};
}
- const i18nKeys = (0, utils_1.extractI18nKeysFromModules)(manifest?.typedContent?.modules ?? {});
+ const moduleI18nProperties = (0, utils_1.extractI18nPropertiesFromModules)(manifest?.typedContent?.modules ?? {});
+ const i18nKeys = moduleI18nProperties.map((i18nConfig) => i18nConfig.key);
const i18nConfig = manifest?.yamlContent?.translations;
if (!i18nConfig) {
return this.validateManifestWithoutI18nConfig(manifest, i18nKeys);
}
const validationErrors = [
- ...this.validateManifestI18nConfig(manifest, i18nKeys),
+ ...this.validateManifestI18nConfig(manifest, i18nKeys, moduleI18nProperties),
...this.validateInternalI18nPropertyKeysNotInModules(manifest)
];
return {
success: validationErrors.length === 0,