@vocab/core
Advanced tools
Comparing version 1.2.0 to 1.2.1
@@ -0,0 +0,0 @@ import { LoadedTranslation, UserConfig } from '@vocab/types'; |
@@ -0,0 +0,0 @@ import { UserConfig } from '@vocab/types'; |
@@ -0,0 +0,0 @@ import { MessageGenerator, TranslationsByKey } from '@vocab/types'; |
import { ParsedICUMessages, TranslationMessagesByKey } from '@vocab/types'; | ||
export declare const getParsedICUMessages: (m: TranslationMessagesByKey, locale: string) => ParsedICUMessages<any>; |
@@ -0,0 +0,0 @@ export { compile, watch } from './compile'; |
@@ -0,0 +0,0 @@ import type { TranslationsByKey, UserConfig, LoadedTranslation, LanguageTarget } from '@vocab/types'; |
import debug from 'debug'; | ||
export declare const trace: debug.Debugger; | ||
export declare const log: (...params: unknown[]) => void; |
import { TranslationModule, TranslationMessagesByKey } from '@vocab/types'; | ||
export { createTranslationFile } from './translation-file'; | ||
export declare const createLanguage: (module: TranslationMessagesByKey) => TranslationModule<any>; |
import { TranslationModuleByLanguage, LanguageName, ParsedFormatFnByKey, TranslationFile } from '@vocab/types'; | ||
export declare function createTranslationFile<Language extends LanguageName, FormatFnByKey extends ParsedFormatFnByKey>(translationsByLanguage: TranslationModuleByLanguage<Language, FormatFnByKey>): TranslationFile<Language, FormatFnByKey>; |
import type { LanguageName, LanguageTarget, TranslationsByKey, TranslationMessagesByKey, UserConfig } from '@vocab/types'; | ||
export declare const defaultTranslationDirSuffix = ".vocab"; | ||
export declare const devTranslationFileName = "translations.json"; | ||
export declare type Fallback = 'none' | 'valid' | 'all'; | ||
export type Fallback = 'none' | 'valid' | 'all'; | ||
export declare function isDevLanguageFile(filePath: string): boolean; | ||
@@ -6,0 +6,0 @@ export declare function isAltLanguageFile(filePath: string): boolean; |
import { UserConfig, LoadedTranslation, LanguageName } from '@vocab/types'; | ||
export declare function findMissingKeys(loadedTranslation: LoadedTranslation, devLanguageName: LanguageName, altLanguages: Array<LanguageName>): readonly [boolean, Record<string, string[]>]; | ||
export declare function validate(config: UserConfig): Promise<boolean>; |
@@ -0,0 +0,0 @@ export declare class ValidationError extends Error { |
@@ -30,3 +30,3 @@ 'use strict'; | ||
const trace = debug__default['default'](`vocab:core`); | ||
const trace = debug__default["default"](`vocab:core`); | ||
@@ -78,4 +78,4 @@ const defaultTranslationDirSuffix = '.vocab'; | ||
function getDevLanguageFileFromTsFile(tsFilePath) { | ||
const directory = path__default['default'].dirname(tsFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, devTranslationFileName)); | ||
const directory = path__default["default"].dirname(tsFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, devTranslationFileName)); | ||
trace(`Returning dev language path ${result} for path ${tsFilePath}`); | ||
@@ -85,4 +85,4 @@ return result; | ||
function getDevLanguageFileFromAltLanguageFile(altLanguageFilePath) { | ||
const directory = path__default['default'].dirname(altLanguageFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, devTranslationFileName)); | ||
const directory = path__default["default"].dirname(altLanguageFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, devTranslationFileName)); | ||
trace(`Returning dev language path ${result} for path ${altLanguageFilePath}`); | ||
@@ -92,4 +92,4 @@ return result; | ||
function getTSFileFromDevLanguageFile(devLanguageFilePath) { | ||
const directory = path__default['default'].dirname(devLanguageFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, 'index.ts')); | ||
const directory = path__default["default"].dirname(devLanguageFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, 'index.ts')); | ||
trace(`Returning TS path ${result} for path ${devLanguageFilePath}`); | ||
@@ -99,6 +99,6 @@ return result; | ||
function getAltLanguageFilePath(devLanguageFilePath, language) { | ||
const directory = path__default['default'].dirname(devLanguageFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, `${language}.translations.json`)); | ||
const directory = path__default["default"].dirname(devLanguageFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, `${language}.translations.json`)); | ||
trace(`Returning alt language path ${result} for path ${devLanguageFilePath}`); | ||
return path__default['default'].normalize(result); | ||
return path__default["default"].normalize(result); | ||
} | ||
@@ -108,7 +108,5 @@ function mapValues(obj, func) { | ||
const keys = Object.keys(obj); | ||
for (const key of keys) { | ||
newObj[key] = func(obj[key]); | ||
} | ||
return newObj; | ||
@@ -127,20 +125,15 @@ } | ||
} | ||
const translationKeys = Object.keys(baseTranslations); | ||
const generatedTranslations = {}; | ||
for (const translationKey of translationKeys) { | ||
const translation = baseTranslations[translationKey]; | ||
let transformedMessage = translation.message; | ||
if (generator.transformElement) { | ||
const messageAst = new IntlMessageFormat__default['default'](translation.message).getAst(); | ||
const messageAst = new IntlMessageFormat__default["default"](translation.message).getAst(); | ||
const transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement)); | ||
transformedMessage = printer.printAST(transformedAst); | ||
} | ||
if (generator.transformMessage) { | ||
transformedMessage = generator.transformMessage(transformedMessage); | ||
} | ||
generatedTranslations[translationKey] = { | ||
@@ -150,11 +143,9 @@ message: transformedMessage | ||
} | ||
return generatedTranslations; | ||
} | ||
function transformMessageFormatElement(transformElement) { | ||
return messageFormatElement => { | ||
const transformedMessageFormatElement = { ...messageFormatElement | ||
const transformedMessageFormatElement = { | ||
...messageFormatElement | ||
}; | ||
switch (transformedMessageFormatElement.type) { | ||
@@ -165,14 +156,11 @@ case icuMessageformatParser.TYPE.literal: | ||
break; | ||
case icuMessageformatParser.TYPE.select: | ||
case icuMessageformatParser.TYPE.plural: | ||
const transformedOptions = { ...transformedMessageFormatElement.options | ||
const transformedOptions = { | ||
...transformedMessageFormatElement.options | ||
}; | ||
for (const key of Object.keys(transformedOptions)) { | ||
transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement)); | ||
} | ||
break; | ||
case icuMessageformatParser.TYPE.tag: | ||
@@ -183,3 +171,2 @@ const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement)); | ||
} | ||
return transformedMessageFormatElement; | ||
@@ -199,3 +186,2 @@ }; | ||
const newLanguage = {}; | ||
for (const key of keys) { | ||
@@ -209,6 +195,4 @@ if (translation[key]) { | ||
} | ||
return newLanguage; | ||
} | ||
function getLanguageFallbacks({ | ||
@@ -218,3 +202,2 @@ languages | ||
const languageFallbackMap = new Map(); | ||
for (const lang of languages) { | ||
@@ -225,6 +208,4 @@ if (lang.extends) { | ||
} | ||
return languageFallbackMap; | ||
} | ||
function getLanguageHierarchy({ | ||
@@ -237,7 +218,5 @@ languages | ||
}); | ||
for (const lang of languages) { | ||
const langHierarchy = []; | ||
let currLang = lang.extends; | ||
while (currLang) { | ||
@@ -247,6 +226,4 @@ langHierarchy.push(currLang); | ||
} | ||
hierarchyMap.set(lang.name, langHierarchy); | ||
} | ||
return hierarchyMap; | ||
@@ -263,12 +240,8 @@ } | ||
}).get(languageName); | ||
if (!languageHierarchy) { | ||
throw new Error(`Missing language hierarchy for ${languageName}`); | ||
} | ||
const fallbackLanguageOrder = [languageName]; | ||
if (fallbacks !== 'none') { | ||
fallbackLanguageOrder.unshift(...languageHierarchy.reverse()); | ||
if (fallbacks === 'all' && fallbackLanguageOrder[0] !== devLanguage) { | ||
@@ -278,23 +251,17 @@ fallbackLanguageOrder.unshift(devLanguage); | ||
} | ||
return fallbackLanguageOrder; | ||
} | ||
function getNamespaceByFilePath(relativePath, { | ||
translationsDirectorySuffix = defaultTranslationDirSuffix | ||
}) { | ||
let namespace = path__default['default'].dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_'); | ||
let namespace = path__default["default"].dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_'); | ||
if (namespace.endsWith(translationsDirectorySuffix)) { | ||
namespace = namespace.slice(0, -translationsDirectorySuffix.length); | ||
} | ||
return namespace; | ||
} | ||
function printValidationError(...params) { | ||
// eslint-disable-next-line no-console | ||
console.error(chalk__default['default'].red('Error loading translation:'), ...params); | ||
console.error(chalk__default["default"].red('Error loading translation:'), ...params); | ||
} | ||
function getTranslationsFromFile(translationFileContents, { | ||
@@ -308,3 +275,2 @@ isAltLanguage, | ||
} | ||
const { | ||
@@ -315,19 +281,15 @@ $namespace, | ||
} = translationFileContents; | ||
if (isAltLanguage && $namespace) { | ||
printValidationError(`Found $namespace in alt language file in ${filePath}. $namespace is only used in the dev language and will be ignored.`); | ||
} | ||
if (!isAltLanguage && $namespace && typeof $namespace !== 'string') { | ||
printValidationError(`Found non-string $namespace in language file in ${filePath}. $namespace must be a string.`); | ||
} | ||
if (isAltLanguage && _meta !== null && _meta !== void 0 && _meta.tags) { | ||
printValidationError(`Found _meta.tags in alt language file in ${filePath}. _meta.tags is only used in the dev language and will be ignored.`); | ||
} // Never return tags if we're fetching translations for an alt language | ||
} | ||
// Never return tags if we're fetching translations for an alt language | ||
const includeTags = !isAltLanguage && withTags; | ||
const validKeys = {}; | ||
for (const [translationKey, { | ||
@@ -341,3 +303,2 @@ tags, | ||
} | ||
if (!translation) { | ||
@@ -347,3 +308,2 @@ printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`); | ||
} | ||
if (!translation.message || typeof translation.message !== 'string') { | ||
@@ -353,8 +313,7 @@ printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`); | ||
} | ||
validKeys[translationKey] = { ...translation, | ||
validKeys[translationKey] = { | ||
...translation, | ||
tags: includeTags ? tags : undefined | ||
}; | ||
} | ||
const metadata = { | ||
@@ -369,3 +328,2 @@ tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined | ||
} | ||
function loadAltLanguageFile({ | ||
@@ -388,3 +346,2 @@ filePath, | ||
trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`); | ||
for (const fallbackLanguage of fallbackLanguageOrder) { | ||
@@ -395,5 +352,3 @@ if (fallbackLanguage !== devLanguage) { | ||
delete require.cache[altFilePath]; | ||
const translationFile = require(altFilePath); | ||
const { | ||
@@ -417,6 +372,4 @@ keys: fallbackLanguageTranslation | ||
} | ||
return altLanguageTranslation; | ||
} | ||
function stripTagsFromTranslations(translations) { | ||
@@ -428,3 +381,2 @@ return Object.fromEntries(Object.entries(translations).map(([key, { | ||
} | ||
function loadTranslation({ | ||
@@ -438,6 +390,4 @@ filePath, | ||
delete require.cache[filePath]; | ||
const translationContent = require(filePath); | ||
const relativePath = path__default['default'].relative(userConfig.projectRoot || process.cwd(), filePath); | ||
const relativePath = path__default["default"].relative(userConfig.projectRoot || process.cwd(), filePath); | ||
const { | ||
@@ -457,3 +407,2 @@ $namespace, | ||
const altLanguages = getAltLanguages(userConfig); | ||
for (const languageName of altLanguages) { | ||
@@ -467,3 +416,2 @@ languageSet[languageName] = loadAltLanguageFile({ | ||
} | ||
for (const generatedLanguage of userConfig.generatedLanguages || []) { | ||
@@ -481,3 +429,2 @@ const { | ||
} | ||
return { | ||
@@ -501,3 +448,3 @@ filePath, | ||
} = config; | ||
const translationFiles = await glob__default['default'](getDevTranslationFileGlob(config), { | ||
const translationFiles = await glob__default["default"](getDevTranslationFileGlob(config), { | ||
ignore: includeNodeModules ? ignore : [...ignore, '**/node_modules/**'], | ||
@@ -514,7 +461,5 @@ absolute: true, | ||
const keys = new Set(); | ||
for (const loadedTranslation of result) { | ||
for (const key of loadedTranslation.keys) { | ||
const uniqueKey = getUniqueKey(key, loadedTranslation.namespace); | ||
if (keys.has(uniqueKey)) { | ||
@@ -524,7 +469,5 @@ trace(`Duplicate keys found`); | ||
} | ||
keys.add(uniqueKey); | ||
} | ||
} | ||
return result; | ||
@@ -534,5 +477,3 @@ } | ||
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'"); | ||
const encodeBackslash = v => v.replace(/\\/g, '\\\\'); | ||
function extractHasTags(ast) { | ||
@@ -544,11 +485,8 @@ return ast.some(element => { | ||
} | ||
return icuMessageformatParser.isTagElement(element); | ||
}); | ||
} | ||
function extractParamTypes(ast) { | ||
let params = {}; | ||
let imports = new Set(); | ||
for (const element of ast) { | ||
@@ -561,2 +499,11 @@ if (icuMessageformatParser.isArgumentElement(element)) { | ||
params[element.value] = 'number'; | ||
const children = Object.values(element.options).map(o => o.value); | ||
for (const child of children) { | ||
const [subParams, subImports] = extractParamTypes(child); | ||
imports = new Set([...imports, ...subImports]); | ||
params = { | ||
...params, | ||
...subParams | ||
}; | ||
} | ||
} else if (icuMessageformatParser.isDateElement(element) || icuMessageformatParser.isTimeElement(element)) { | ||
@@ -569,3 +516,4 @@ params[element.value] = 'Date | number'; | ||
imports = new Set([...imports, ...subImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...subParams | ||
@@ -576,7 +524,7 @@ }; | ||
const children = Object.values(element.options).map(o => o.value); | ||
for (const child of children) { | ||
const [subParams, subImports] = extractParamTypes(child); | ||
imports = new Set([...imports, ...subImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...subParams | ||
@@ -587,9 +535,6 @@ }; | ||
} | ||
return [params, imports]; | ||
} | ||
function serialiseObjectToType(v) { | ||
let result = ''; | ||
for (const [key, value] of Object.entries(v)) { | ||
@@ -602,12 +547,8 @@ if (value && typeof value === 'object') { | ||
} | ||
return `{ ${result} }`; | ||
} | ||
const banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`; | ||
function serialiseTranslationRuntime(value, imports, loadedTranslation) { | ||
trace('Serialising translations:', loadedTranslation); | ||
const translationsType = {}; | ||
for (const [key, { | ||
@@ -619,3 +560,2 @@ params, | ||
let translationFunctionString = `() => ${message}`; | ||
if (Object.keys(params).length > 0) { | ||
@@ -626,6 +566,4 @@ const formatGeneric = hasTags ? '<T = string>' : ''; | ||
} | ||
translationsType[encodeBackslash(key)] = translationFunctionString; | ||
} | ||
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(','); | ||
@@ -642,3 +580,2 @@ const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | '); | ||
} | ||
async function generateRuntime(loadedTranslation) { | ||
@@ -652,3 +589,2 @@ const { | ||
let imports = new Set(); | ||
for (const key of loadedTranslation.keys) { | ||
@@ -658,3 +594,2 @@ let params = {}; | ||
let hasTags = false; | ||
for (const translatedLanguage of Object.values(loadedLanguages)) { | ||
@@ -666,3 +601,4 @@ if (translatedLanguage[key]) { | ||
imports = new Set([...imports, ...parsedImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...parsedParams | ||
@@ -673,3 +609,2 @@ }; | ||
} | ||
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string'; | ||
@@ -683,6 +618,6 @@ translationTypes.set(key, { | ||
} | ||
const prettierConfig = await prettier__default['default'].resolveConfig(filePath); | ||
const prettierConfig = await prettier__default["default"].resolveConfig(filePath); | ||
const serializedTranslationType = serialiseTranslationRuntime(translationTypes, imports, loadedTranslation); | ||
const declaration = prettier__default['default'].format(serializedTranslationType, { ...prettierConfig, | ||
const declaration = prettier__default["default"].format(serializedTranslationType, { | ||
...prettierConfig, | ||
parser: 'typescript' | ||
@@ -696,3 +631,3 @@ }); | ||
const cwd = config.projectRoot || process.cwd(); | ||
const watcher = chokidar__default['default'].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], { | ||
const watcher = chokidar__default["default"].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], { | ||
cwd, | ||
@@ -702,13 +637,10 @@ ignored: config.ignore ? [...config.ignore, '**/node_modules/**'] : ['**/node_modules/**'], | ||
}); | ||
const onTranslationChange = async relativePath => { | ||
trace(`Detected change for file ${relativePath}`); | ||
let targetFile; | ||
if (isDevLanguageFile(relativePath)) { | ||
targetFile = path__default['default'].resolve(cwd, relativePath); | ||
targetFile = path__default["default"].resolve(cwd, relativePath); | ||
} else if (isAltLanguageFile(relativePath)) { | ||
targetFile = getDevLanguageFileFromAltLanguageFile(path__default['default'].resolve(cwd, relativePath)); | ||
targetFile = getDevLanguageFileFromAltLanguageFile(path__default["default"].resolve(cwd, relativePath)); | ||
} | ||
if (targetFile) { | ||
@@ -723,4 +655,4 @@ try { | ||
// eslint-disable-next-line no-console | ||
console.log('Failed to generate types for', relativePath); // eslint-disable-next-line no-console | ||
console.log('Failed to generate types for', relativePath); | ||
// eslint-disable-next-line no-console | ||
console.error(e); | ||
@@ -730,6 +662,4 @@ } | ||
}; | ||
const onNewDirectory = async relativePath => { | ||
trace('Detected new directory', relativePath); | ||
if (!isTranslationDirectory(relativePath, config)) { | ||
@@ -739,5 +669,3 @@ trace('Ignoring non-translation directory:', relativePath); | ||
} | ||
const newFilePath = path__default['default'].join(relativePath, devTranslationFileName); | ||
const newFilePath = path__default["default"].join(relativePath, devTranslationFileName); | ||
if (!fs.existsSync(newFilePath)) { | ||
@@ -750,3 +678,2 @@ await fs.promises.writeFile(newFilePath, JSON.stringify({}, null, 2)); | ||
}; | ||
watcher.on('addDir', onNewDirectory); | ||
@@ -763,7 +690,5 @@ watcher.on('add', onTranslationChange).on('change', onTranslationChange); | ||
}, config); | ||
for (const loadedTranslation of translations) { | ||
await generateRuntime(loadedTranslation); | ||
} | ||
if (shouldWatch) { | ||
@@ -774,6 +699,4 @@ trace('Listening for changes to files...'); | ||
} | ||
async function writeIfChanged(filepath, contents) { | ||
let hasChanged = true; | ||
try { | ||
@@ -784,5 +707,5 @@ const existingContents = await fs.promises.readFile(filepath, { | ||
hasChanged = existingContents !== contents; | ||
} catch (e) {// ignore error, likely a file doesn't exist error so we want to write anyway | ||
} catch (e) { | ||
// ignore error, likely a file doesn't exist error so we want to write anyway | ||
} | ||
if (hasChanged) { | ||
@@ -798,20 +721,14 @@ await fs.promises.writeFile(filepath, contents, { | ||
const devLanguage = loadedTranslation.languages[devLanguageName]; | ||
if (!devLanguage) { | ||
throw new Error(`Failed to load dev language: ${loadedTranslation.filePath}`); | ||
} | ||
const result = {}; | ||
let valid = true; | ||
const requiredKeys = Object.keys(devLanguage); | ||
if (requiredKeys.length > 0) { | ||
for (const altLanguageName of altLanguages) { | ||
var _loadedTranslation$la; | ||
const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {}; | ||
for (const key of requiredKeys) { | ||
var _altLanguage$key; | ||
if (typeof ((_altLanguage$key = altLanguage[key]) === null || _altLanguage$key === void 0 ? void 0 : _altLanguage$key.message) !== 'string') { | ||
@@ -821,3 +738,2 @@ if (!result[altLanguageName]) { | ||
} | ||
result[altLanguageName].push(key); | ||
@@ -829,3 +745,2 @@ valid = false; | ||
} | ||
return [valid, result]; | ||
@@ -839,17 +754,13 @@ } | ||
let valid = true; | ||
for (const loadedTranslation of allTranslations) { | ||
const [translationValid, result] = findMissingKeys(loadedTranslation, config.devLanguage, getAltLanguages(config)); | ||
if (!translationValid) { | ||
valid = false; | ||
console.log(chalk__default['default'].red`Incomplete translations: "${chalk__default['default'].bold(loadedTranslation.relativePath)}"`); | ||
console.log(chalk__default["default"].red`Incomplete translations: "${chalk__default["default"].bold(loadedTranslation.relativePath)}"`); | ||
for (const lang of Object.keys(result)) { | ||
const missingKeys = result[lang]; | ||
console.log(chalk__default['default'].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', ')); | ||
console.log(chalk__default["default"].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', ')); | ||
} | ||
} | ||
} | ||
return valid; | ||
@@ -864,6 +775,5 @@ } | ||
} | ||
} | ||
const validator = new Validator__default['default'](); | ||
const validator = new Validator__default["default"](); | ||
const schema = { | ||
@@ -933,73 +843,61 @@ $$strict: true, | ||
const checkConfigFile = validator.compile(schema); | ||
const splitMap = (message, callback) => message.split(' ,').map(v => callback(v)).join(' ,'); | ||
function validateConfig(c) { | ||
trace('Validating configuration file'); // Note: checkConfigFile mutates the config file by applying defaults | ||
trace('Validating configuration file'); | ||
// Note: checkConfigFile mutates the config file by applying defaults | ||
const isValid = checkConfigFile(c); | ||
if (isValid !== true) { | ||
throw new ValidationError('InvalidStructure', (Array.isArray(isValid) ? isValid : []).map(v => { | ||
if (v.type === 'objectStrict') { | ||
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default['default'].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default['default'].green)}`; | ||
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default["default"].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default["default"].green)}`; | ||
} | ||
if (v.field) { | ||
var _v$message; | ||
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk__default['default'].cyan(v.field)); | ||
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk__default["default"].cyan(v.field)); | ||
} | ||
return v.message; | ||
}).join(' \n')); | ||
} | ||
const languageStrings = c.languages.map(v => v.name); | ||
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages | ||
// Dev Language should exist in languages | ||
if (!languageStrings.includes(c.devLanguage)) { | ||
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default['default'].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`); | ||
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default["default"].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
const foundLanguages = []; | ||
for (const lang of c.languages) { | ||
// Languages must only exist once | ||
if (foundLanguages.includes(lang.name)) { | ||
throw new ValidationError('DuplicateLanguage', `The language "${chalk__default['default'].bold.cyan(lang.name)}" was defined multiple times.`); | ||
throw new ValidationError('DuplicateLanguage', `The language "${chalk__default["default"].bold.cyan(lang.name)}" was defined multiple times.`); | ||
} | ||
foundLanguages.push(lang.name); | ||
foundLanguages.push(lang.name); // Any extends must be in languages | ||
// Any extends must be in languages | ||
if (lang.extends && !languageStrings.includes(lang.extends)) { | ||
throw new ValidationError('InvalidExtends', `The language "${chalk__default['default'].bold.cyan(lang.name)}"'s extends of ${chalk__default['default'].bold.cyan(lang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
throw new ValidationError('InvalidExtends', `The language "${chalk__default["default"].bold.cyan(lang.name)}"'s extends of ${chalk__default["default"].bold.cyan(lang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
} | ||
const foundGeneratedLanguages = []; | ||
for (const generatedLang of c.generatedLanguages || []) { | ||
// Generated languages must only exist once | ||
if (foundGeneratedLanguages.includes(generatedLang.name)) { | ||
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" was defined multiple times.`); | ||
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" was defined multiple times.`); | ||
} | ||
foundGeneratedLanguages.push(generatedLang.name); | ||
foundGeneratedLanguages.push(generatedLang.name); // Generated language names must not conflict with language names | ||
// Generated language names must not conflict with language names | ||
if (languageStrings.includes(generatedLang.name)) { | ||
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" is already defined as a language.`); | ||
} // Any extends must be in languages | ||
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" is already defined as a language.`); | ||
} | ||
// Any extends must be in languages | ||
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) { | ||
throw new ValidationError('InvalidExtends', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}"'s extends of ${chalk__default['default'].bold.cyan(generatedLang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
throw new ValidationError('InvalidExtends', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}"'s extends of ${chalk__default["default"].bold.cyan(generatedLang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
} | ||
trace('Configuration file is valid'); | ||
return true; | ||
} | ||
function createConfig(configFilePath) { | ||
const cwd = path__default['default'].dirname(configFilePath); | ||
const cwd = path__default["default"].dirname(configFilePath); | ||
return { | ||
@@ -1010,6 +908,4 @@ projectRoot: cwd, | ||
} | ||
async function resolveConfig(customConfigFilePath) { | ||
const configFilePath = customConfigFilePath ? path__default['default'].resolve(customConfigFilePath) : await findUp__default['default']('vocab.config.js'); | ||
const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : await findUp__default["default"]('vocab.config.js'); | ||
if (configFilePath) { | ||
@@ -1019,3 +915,2 @@ trace(`Resolved configuration file to ${configFilePath}`); | ||
} | ||
trace('No configuration file found'); | ||
@@ -1025,4 +920,3 @@ return null; | ||
function resolveConfigSync(customConfigFilePath) { | ||
const configFilePath = customConfigFilePath ? path__default['default'].resolve(customConfigFilePath) : findUp__default['default'].sync('vocab.config.js'); | ||
const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : findUp__default["default"].sync('vocab.config.js'); | ||
if (configFilePath) { | ||
@@ -1032,3 +926,2 @@ trace(`Resolved configuration file to ${configFilePath}`); | ||
} | ||
trace('No configuration file found'); | ||
@@ -1035,0 +928,0 @@ return null; |
@@ -30,3 +30,3 @@ 'use strict'; | ||
const trace = debug__default['default'](`vocab:core`); | ||
const trace = debug__default["default"](`vocab:core`); | ||
@@ -78,4 +78,4 @@ const defaultTranslationDirSuffix = '.vocab'; | ||
function getDevLanguageFileFromTsFile(tsFilePath) { | ||
const directory = path__default['default'].dirname(tsFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, devTranslationFileName)); | ||
const directory = path__default["default"].dirname(tsFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, devTranslationFileName)); | ||
trace(`Returning dev language path ${result} for path ${tsFilePath}`); | ||
@@ -85,4 +85,4 @@ return result; | ||
function getDevLanguageFileFromAltLanguageFile(altLanguageFilePath) { | ||
const directory = path__default['default'].dirname(altLanguageFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, devTranslationFileName)); | ||
const directory = path__default["default"].dirname(altLanguageFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, devTranslationFileName)); | ||
trace(`Returning dev language path ${result} for path ${altLanguageFilePath}`); | ||
@@ -92,4 +92,4 @@ return result; | ||
function getTSFileFromDevLanguageFile(devLanguageFilePath) { | ||
const directory = path__default['default'].dirname(devLanguageFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, 'index.ts')); | ||
const directory = path__default["default"].dirname(devLanguageFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, 'index.ts')); | ||
trace(`Returning TS path ${result} for path ${devLanguageFilePath}`); | ||
@@ -99,6 +99,6 @@ return result; | ||
function getAltLanguageFilePath(devLanguageFilePath, language) { | ||
const directory = path__default['default'].dirname(devLanguageFilePath); | ||
const result = path__default['default'].normalize(path__default['default'].join(directory, `${language}.translations.json`)); | ||
const directory = path__default["default"].dirname(devLanguageFilePath); | ||
const result = path__default["default"].normalize(path__default["default"].join(directory, `${language}.translations.json`)); | ||
trace(`Returning alt language path ${result} for path ${devLanguageFilePath}`); | ||
return path__default['default'].normalize(result); | ||
return path__default["default"].normalize(result); | ||
} | ||
@@ -108,7 +108,5 @@ function mapValues(obj, func) { | ||
const keys = Object.keys(obj); | ||
for (const key of keys) { | ||
newObj[key] = func(obj[key]); | ||
} | ||
return newObj; | ||
@@ -127,20 +125,15 @@ } | ||
} | ||
const translationKeys = Object.keys(baseTranslations); | ||
const generatedTranslations = {}; | ||
for (const translationKey of translationKeys) { | ||
const translation = baseTranslations[translationKey]; | ||
let transformedMessage = translation.message; | ||
if (generator.transformElement) { | ||
const messageAst = new IntlMessageFormat__default['default'](translation.message).getAst(); | ||
const messageAst = new IntlMessageFormat__default["default"](translation.message).getAst(); | ||
const transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement)); | ||
transformedMessage = printer.printAST(transformedAst); | ||
} | ||
if (generator.transformMessage) { | ||
transformedMessage = generator.transformMessage(transformedMessage); | ||
} | ||
generatedTranslations[translationKey] = { | ||
@@ -150,11 +143,9 @@ message: transformedMessage | ||
} | ||
return generatedTranslations; | ||
} | ||
function transformMessageFormatElement(transformElement) { | ||
return messageFormatElement => { | ||
const transformedMessageFormatElement = { ...messageFormatElement | ||
const transformedMessageFormatElement = { | ||
...messageFormatElement | ||
}; | ||
switch (transformedMessageFormatElement.type) { | ||
@@ -165,14 +156,11 @@ case icuMessageformatParser.TYPE.literal: | ||
break; | ||
case icuMessageformatParser.TYPE.select: | ||
case icuMessageformatParser.TYPE.plural: | ||
const transformedOptions = { ...transformedMessageFormatElement.options | ||
const transformedOptions = { | ||
...transformedMessageFormatElement.options | ||
}; | ||
for (const key of Object.keys(transformedOptions)) { | ||
transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement)); | ||
} | ||
break; | ||
case icuMessageformatParser.TYPE.tag: | ||
@@ -183,3 +171,2 @@ const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement)); | ||
} | ||
return transformedMessageFormatElement; | ||
@@ -199,3 +186,2 @@ }; | ||
const newLanguage = {}; | ||
for (const key of keys) { | ||
@@ -209,6 +195,4 @@ if (translation[key]) { | ||
} | ||
return newLanguage; | ||
} | ||
function getLanguageFallbacks({ | ||
@@ -218,3 +202,2 @@ languages | ||
const languageFallbackMap = new Map(); | ||
for (const lang of languages) { | ||
@@ -225,6 +208,4 @@ if (lang.extends) { | ||
} | ||
return languageFallbackMap; | ||
} | ||
function getLanguageHierarchy({ | ||
@@ -237,7 +218,5 @@ languages | ||
}); | ||
for (const lang of languages) { | ||
const langHierarchy = []; | ||
let currLang = lang.extends; | ||
while (currLang) { | ||
@@ -247,6 +226,4 @@ langHierarchy.push(currLang); | ||
} | ||
hierarchyMap.set(lang.name, langHierarchy); | ||
} | ||
return hierarchyMap; | ||
@@ -263,12 +240,8 @@ } | ||
}).get(languageName); | ||
if (!languageHierarchy) { | ||
throw new Error(`Missing language hierarchy for ${languageName}`); | ||
} | ||
const fallbackLanguageOrder = [languageName]; | ||
if (fallbacks !== 'none') { | ||
fallbackLanguageOrder.unshift(...languageHierarchy.reverse()); | ||
if (fallbacks === 'all' && fallbackLanguageOrder[0] !== devLanguage) { | ||
@@ -278,23 +251,17 @@ fallbackLanguageOrder.unshift(devLanguage); | ||
} | ||
return fallbackLanguageOrder; | ||
} | ||
function getNamespaceByFilePath(relativePath, { | ||
translationsDirectorySuffix = defaultTranslationDirSuffix | ||
}) { | ||
let namespace = path__default['default'].dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_'); | ||
let namespace = path__default["default"].dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_'); | ||
if (namespace.endsWith(translationsDirectorySuffix)) { | ||
namespace = namespace.slice(0, -translationsDirectorySuffix.length); | ||
} | ||
return namespace; | ||
} | ||
function printValidationError(...params) { | ||
// eslint-disable-next-line no-console | ||
console.error(chalk__default['default'].red('Error loading translation:'), ...params); | ||
console.error(chalk__default["default"].red('Error loading translation:'), ...params); | ||
} | ||
function getTranslationsFromFile(translationFileContents, { | ||
@@ -308,3 +275,2 @@ isAltLanguage, | ||
} | ||
const { | ||
@@ -315,19 +281,15 @@ $namespace, | ||
} = translationFileContents; | ||
if (isAltLanguage && $namespace) { | ||
printValidationError(`Found $namespace in alt language file in ${filePath}. $namespace is only used in the dev language and will be ignored.`); | ||
} | ||
if (!isAltLanguage && $namespace && typeof $namespace !== 'string') { | ||
printValidationError(`Found non-string $namespace in language file in ${filePath}. $namespace must be a string.`); | ||
} | ||
if (isAltLanguage && _meta !== null && _meta !== void 0 && _meta.tags) { | ||
printValidationError(`Found _meta.tags in alt language file in ${filePath}. _meta.tags is only used in the dev language and will be ignored.`); | ||
} // Never return tags if we're fetching translations for an alt language | ||
} | ||
// Never return tags if we're fetching translations for an alt language | ||
const includeTags = !isAltLanguage && withTags; | ||
const validKeys = {}; | ||
for (const [translationKey, { | ||
@@ -341,3 +303,2 @@ tags, | ||
} | ||
if (!translation) { | ||
@@ -347,3 +308,2 @@ printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`); | ||
} | ||
if (!translation.message || typeof translation.message !== 'string') { | ||
@@ -353,8 +313,7 @@ printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`); | ||
} | ||
validKeys[translationKey] = { ...translation, | ||
validKeys[translationKey] = { | ||
...translation, | ||
tags: includeTags ? tags : undefined | ||
}; | ||
} | ||
const metadata = { | ||
@@ -369,3 +328,2 @@ tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined | ||
} | ||
function loadAltLanguageFile({ | ||
@@ -388,3 +346,2 @@ filePath, | ||
trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`); | ||
for (const fallbackLanguage of fallbackLanguageOrder) { | ||
@@ -395,5 +352,3 @@ if (fallbackLanguage !== devLanguage) { | ||
delete require.cache[altFilePath]; | ||
const translationFile = require(altFilePath); | ||
const { | ||
@@ -417,6 +372,4 @@ keys: fallbackLanguageTranslation | ||
} | ||
return altLanguageTranslation; | ||
} | ||
function stripTagsFromTranslations(translations) { | ||
@@ -428,3 +381,2 @@ return Object.fromEntries(Object.entries(translations).map(([key, { | ||
} | ||
function loadTranslation({ | ||
@@ -438,6 +390,4 @@ filePath, | ||
delete require.cache[filePath]; | ||
const translationContent = require(filePath); | ||
const relativePath = path__default['default'].relative(userConfig.projectRoot || process.cwd(), filePath); | ||
const relativePath = path__default["default"].relative(userConfig.projectRoot || process.cwd(), filePath); | ||
const { | ||
@@ -457,3 +407,2 @@ $namespace, | ||
const altLanguages = getAltLanguages(userConfig); | ||
for (const languageName of altLanguages) { | ||
@@ -467,3 +416,2 @@ languageSet[languageName] = loadAltLanguageFile({ | ||
} | ||
for (const generatedLanguage of userConfig.generatedLanguages || []) { | ||
@@ -481,3 +429,2 @@ const { | ||
} | ||
return { | ||
@@ -501,3 +448,3 @@ filePath, | ||
} = config; | ||
const translationFiles = await glob__default['default'](getDevTranslationFileGlob(config), { | ||
const translationFiles = await glob__default["default"](getDevTranslationFileGlob(config), { | ||
ignore: includeNodeModules ? ignore : [...ignore, '**/node_modules/**'], | ||
@@ -514,7 +461,5 @@ absolute: true, | ||
const keys = new Set(); | ||
for (const loadedTranslation of result) { | ||
for (const key of loadedTranslation.keys) { | ||
const uniqueKey = getUniqueKey(key, loadedTranslation.namespace); | ||
if (keys.has(uniqueKey)) { | ||
@@ -524,7 +469,5 @@ trace(`Duplicate keys found`); | ||
} | ||
keys.add(uniqueKey); | ||
} | ||
} | ||
return result; | ||
@@ -534,5 +477,3 @@ } | ||
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'"); | ||
const encodeBackslash = v => v.replace(/\\/g, '\\\\'); | ||
function extractHasTags(ast) { | ||
@@ -544,11 +485,8 @@ return ast.some(element => { | ||
} | ||
return icuMessageformatParser.isTagElement(element); | ||
}); | ||
} | ||
function extractParamTypes(ast) { | ||
let params = {}; | ||
let imports = new Set(); | ||
for (const element of ast) { | ||
@@ -561,2 +499,11 @@ if (icuMessageformatParser.isArgumentElement(element)) { | ||
params[element.value] = 'number'; | ||
const children = Object.values(element.options).map(o => o.value); | ||
for (const child of children) { | ||
const [subParams, subImports] = extractParamTypes(child); | ||
imports = new Set([...imports, ...subImports]); | ||
params = { | ||
...params, | ||
...subParams | ||
}; | ||
} | ||
} else if (icuMessageformatParser.isDateElement(element) || icuMessageformatParser.isTimeElement(element)) { | ||
@@ -569,3 +516,4 @@ params[element.value] = 'Date | number'; | ||
imports = new Set([...imports, ...subImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...subParams | ||
@@ -576,7 +524,7 @@ }; | ||
const children = Object.values(element.options).map(o => o.value); | ||
for (const child of children) { | ||
const [subParams, subImports] = extractParamTypes(child); | ||
imports = new Set([...imports, ...subImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...subParams | ||
@@ -587,9 +535,6 @@ }; | ||
} | ||
return [params, imports]; | ||
} | ||
function serialiseObjectToType(v) { | ||
let result = ''; | ||
for (const [key, value] of Object.entries(v)) { | ||
@@ -602,12 +547,8 @@ if (value && typeof value === 'object') { | ||
} | ||
return `{ ${result} }`; | ||
} | ||
const banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`; | ||
function serialiseTranslationRuntime(value, imports, loadedTranslation) { | ||
trace('Serialising translations:', loadedTranslation); | ||
const translationsType = {}; | ||
for (const [key, { | ||
@@ -619,3 +560,2 @@ params, | ||
let translationFunctionString = `() => ${message}`; | ||
if (Object.keys(params).length > 0) { | ||
@@ -626,6 +566,4 @@ const formatGeneric = hasTags ? '<T = string>' : ''; | ||
} | ||
translationsType[encodeBackslash(key)] = translationFunctionString; | ||
} | ||
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(','); | ||
@@ -642,3 +580,2 @@ const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | '); | ||
} | ||
async function generateRuntime(loadedTranslation) { | ||
@@ -652,3 +589,2 @@ const { | ||
let imports = new Set(); | ||
for (const key of loadedTranslation.keys) { | ||
@@ -658,3 +594,2 @@ let params = {}; | ||
let hasTags = false; | ||
for (const translatedLanguage of Object.values(loadedLanguages)) { | ||
@@ -666,3 +601,4 @@ if (translatedLanguage[key]) { | ||
imports = new Set([...imports, ...parsedImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...parsedParams | ||
@@ -673,3 +609,2 @@ }; | ||
} | ||
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string'; | ||
@@ -683,6 +618,6 @@ translationTypes.set(key, { | ||
} | ||
const prettierConfig = await prettier__default['default'].resolveConfig(filePath); | ||
const prettierConfig = await prettier__default["default"].resolveConfig(filePath); | ||
const serializedTranslationType = serialiseTranslationRuntime(translationTypes, imports, loadedTranslation); | ||
const declaration = prettier__default['default'].format(serializedTranslationType, { ...prettierConfig, | ||
const declaration = prettier__default["default"].format(serializedTranslationType, { | ||
...prettierConfig, | ||
parser: 'typescript' | ||
@@ -696,3 +631,3 @@ }); | ||
const cwd = config.projectRoot || process.cwd(); | ||
const watcher = chokidar__default['default'].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], { | ||
const watcher = chokidar__default["default"].watch([getDevTranslationFileGlob(config), getAltTranslationFileGlob(config), getTranslationFolderGlob(config)], { | ||
cwd, | ||
@@ -702,13 +637,10 @@ ignored: config.ignore ? [...config.ignore, '**/node_modules/**'] : ['**/node_modules/**'], | ||
}); | ||
const onTranslationChange = async relativePath => { | ||
trace(`Detected change for file ${relativePath}`); | ||
let targetFile; | ||
if (isDevLanguageFile(relativePath)) { | ||
targetFile = path__default['default'].resolve(cwd, relativePath); | ||
targetFile = path__default["default"].resolve(cwd, relativePath); | ||
} else if (isAltLanguageFile(relativePath)) { | ||
targetFile = getDevLanguageFileFromAltLanguageFile(path__default['default'].resolve(cwd, relativePath)); | ||
targetFile = getDevLanguageFileFromAltLanguageFile(path__default["default"].resolve(cwd, relativePath)); | ||
} | ||
if (targetFile) { | ||
@@ -723,4 +655,4 @@ try { | ||
// eslint-disable-next-line no-console | ||
console.log('Failed to generate types for', relativePath); // eslint-disable-next-line no-console | ||
console.log('Failed to generate types for', relativePath); | ||
// eslint-disable-next-line no-console | ||
console.error(e); | ||
@@ -730,6 +662,4 @@ } | ||
}; | ||
const onNewDirectory = async relativePath => { | ||
trace('Detected new directory', relativePath); | ||
if (!isTranslationDirectory(relativePath, config)) { | ||
@@ -739,5 +669,3 @@ trace('Ignoring non-translation directory:', relativePath); | ||
} | ||
const newFilePath = path__default['default'].join(relativePath, devTranslationFileName); | ||
const newFilePath = path__default["default"].join(relativePath, devTranslationFileName); | ||
if (!fs.existsSync(newFilePath)) { | ||
@@ -750,3 +678,2 @@ await fs.promises.writeFile(newFilePath, JSON.stringify({}, null, 2)); | ||
}; | ||
watcher.on('addDir', onNewDirectory); | ||
@@ -763,7 +690,5 @@ watcher.on('add', onTranslationChange).on('change', onTranslationChange); | ||
}, config); | ||
for (const loadedTranslation of translations) { | ||
await generateRuntime(loadedTranslation); | ||
} | ||
if (shouldWatch) { | ||
@@ -774,6 +699,4 @@ trace('Listening for changes to files...'); | ||
} | ||
async function writeIfChanged(filepath, contents) { | ||
let hasChanged = true; | ||
try { | ||
@@ -784,5 +707,5 @@ const existingContents = await fs.promises.readFile(filepath, { | ||
hasChanged = existingContents !== contents; | ||
} catch (e) {// ignore error, likely a file doesn't exist error so we want to write anyway | ||
} catch (e) { | ||
// ignore error, likely a file doesn't exist error so we want to write anyway | ||
} | ||
if (hasChanged) { | ||
@@ -798,20 +721,14 @@ await fs.promises.writeFile(filepath, contents, { | ||
const devLanguage = loadedTranslation.languages[devLanguageName]; | ||
if (!devLanguage) { | ||
throw new Error(`Failed to load dev language: ${loadedTranslation.filePath}`); | ||
} | ||
const result = {}; | ||
let valid = true; | ||
const requiredKeys = Object.keys(devLanguage); | ||
if (requiredKeys.length > 0) { | ||
for (const altLanguageName of altLanguages) { | ||
var _loadedTranslation$la; | ||
const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {}; | ||
for (const key of requiredKeys) { | ||
var _altLanguage$key; | ||
if (typeof ((_altLanguage$key = altLanguage[key]) === null || _altLanguage$key === void 0 ? void 0 : _altLanguage$key.message) !== 'string') { | ||
@@ -821,3 +738,2 @@ if (!result[altLanguageName]) { | ||
} | ||
result[altLanguageName].push(key); | ||
@@ -829,3 +745,2 @@ valid = false; | ||
} | ||
return [valid, result]; | ||
@@ -839,17 +754,13 @@ } | ||
let valid = true; | ||
for (const loadedTranslation of allTranslations) { | ||
const [translationValid, result] = findMissingKeys(loadedTranslation, config.devLanguage, getAltLanguages(config)); | ||
if (!translationValid) { | ||
valid = false; | ||
console.log(chalk__default['default'].red`Incomplete translations: "${chalk__default['default'].bold(loadedTranslation.relativePath)}"`); | ||
console.log(chalk__default["default"].red`Incomplete translations: "${chalk__default["default"].bold(loadedTranslation.relativePath)}"`); | ||
for (const lang of Object.keys(result)) { | ||
const missingKeys = result[lang]; | ||
console.log(chalk__default['default'].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', ')); | ||
console.log(chalk__default["default"].yellow(lang), '->', missingKeys.map(v => `"${v}"`).join(', ')); | ||
} | ||
} | ||
} | ||
return valid; | ||
@@ -864,6 +775,5 @@ } | ||
} | ||
} | ||
const validator = new Validator__default['default'](); | ||
const validator = new Validator__default["default"](); | ||
const schema = { | ||
@@ -933,73 +843,61 @@ $$strict: true, | ||
const checkConfigFile = validator.compile(schema); | ||
const splitMap = (message, callback) => message.split(' ,').map(v => callback(v)).join(' ,'); | ||
function validateConfig(c) { | ||
trace('Validating configuration file'); // Note: checkConfigFile mutates the config file by applying defaults | ||
trace('Validating configuration file'); | ||
// Note: checkConfigFile mutates the config file by applying defaults | ||
const isValid = checkConfigFile(c); | ||
if (isValid !== true) { | ||
throw new ValidationError('InvalidStructure', (Array.isArray(isValid) ? isValid : []).map(v => { | ||
if (v.type === 'objectStrict') { | ||
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default['default'].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default['default'].green)}`; | ||
return `Invalid key(s) ${splitMap(v.actual, m => `"${chalk__default["default"].cyan(m)}"`)}. Expected one of ${splitMap(v.expected, chalk__default["default"].green)}`; | ||
} | ||
if (v.field) { | ||
var _v$message; | ||
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk__default['default'].cyan(v.field)); | ||
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk__default["default"].cyan(v.field)); | ||
} | ||
return v.message; | ||
}).join(' \n')); | ||
} | ||
const languageStrings = c.languages.map(v => v.name); | ||
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages | ||
// Dev Language should exist in languages | ||
if (!languageStrings.includes(c.devLanguage)) { | ||
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default['default'].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`); | ||
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk__default["default"].bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
const foundLanguages = []; | ||
for (const lang of c.languages) { | ||
// Languages must only exist once | ||
if (foundLanguages.includes(lang.name)) { | ||
throw new ValidationError('DuplicateLanguage', `The language "${chalk__default['default'].bold.cyan(lang.name)}" was defined multiple times.`); | ||
throw new ValidationError('DuplicateLanguage', `The language "${chalk__default["default"].bold.cyan(lang.name)}" was defined multiple times.`); | ||
} | ||
foundLanguages.push(lang.name); | ||
foundLanguages.push(lang.name); // Any extends must be in languages | ||
// Any extends must be in languages | ||
if (lang.extends && !languageStrings.includes(lang.extends)) { | ||
throw new ValidationError('InvalidExtends', `The language "${chalk__default['default'].bold.cyan(lang.name)}"'s extends of ${chalk__default['default'].bold.cyan(lang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
throw new ValidationError('InvalidExtends', `The language "${chalk__default["default"].bold.cyan(lang.name)}"'s extends of ${chalk__default["default"].bold.cyan(lang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
} | ||
const foundGeneratedLanguages = []; | ||
for (const generatedLang of c.generatedLanguages || []) { | ||
// Generated languages must only exist once | ||
if (foundGeneratedLanguages.includes(generatedLang.name)) { | ||
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" was defined multiple times.`); | ||
throw new ValidationError('DuplicateGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" was defined multiple times.`); | ||
} | ||
foundGeneratedLanguages.push(generatedLang.name); | ||
foundGeneratedLanguages.push(generatedLang.name); // Generated language names must not conflict with language names | ||
// Generated language names must not conflict with language names | ||
if (languageStrings.includes(generatedLang.name)) { | ||
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}" is already defined as a language.`); | ||
} // Any extends must be in languages | ||
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}" is already defined as a language.`); | ||
} | ||
// Any extends must be in languages | ||
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) { | ||
throw new ValidationError('InvalidExtends', `The generated language "${chalk__default['default'].bold.cyan(generatedLang.name)}"'s extends of ${chalk__default['default'].bold.cyan(generatedLang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
throw new ValidationError('InvalidExtends', `The generated language "${chalk__default["default"].bold.cyan(generatedLang.name)}"'s extends of ${chalk__default["default"].bold.cyan(generatedLang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
} | ||
trace('Configuration file is valid'); | ||
return true; | ||
} | ||
function createConfig(configFilePath) { | ||
const cwd = path__default['default'].dirname(configFilePath); | ||
const cwd = path__default["default"].dirname(configFilePath); | ||
return { | ||
@@ -1010,6 +908,4 @@ projectRoot: cwd, | ||
} | ||
async function resolveConfig(customConfigFilePath) { | ||
const configFilePath = customConfigFilePath ? path__default['default'].resolve(customConfigFilePath) : await findUp__default['default']('vocab.config.js'); | ||
const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : await findUp__default["default"]('vocab.config.js'); | ||
if (configFilePath) { | ||
@@ -1019,3 +915,2 @@ trace(`Resolved configuration file to ${configFilePath}`); | ||
} | ||
trace('No configuration file found'); | ||
@@ -1025,4 +920,3 @@ return null; | ||
function resolveConfigSync(customConfigFilePath) { | ||
const configFilePath = customConfigFilePath ? path__default['default'].resolve(customConfigFilePath) : findUp__default['default'].sync('vocab.config.js'); | ||
const configFilePath = customConfigFilePath ? path__default["default"].resolve(customConfigFilePath) : findUp__default["default"].sync('vocab.config.js'); | ||
if (configFilePath) { | ||
@@ -1032,3 +926,2 @@ trace(`Resolved configuration file to ${configFilePath}`); | ||
} | ||
trace('No configuration file found'); | ||
@@ -1035,0 +928,0 @@ return null; |
@@ -87,7 +87,5 @@ import { existsSync, promises } from 'fs'; | ||
const keys = Object.keys(obj); | ||
for (const key of keys) { | ||
newObj[key] = func(obj[key]); | ||
} | ||
return newObj; | ||
@@ -106,10 +104,7 @@ } | ||
} | ||
const translationKeys = Object.keys(baseTranslations); | ||
const generatedTranslations = {}; | ||
for (const translationKey of translationKeys) { | ||
const translation = baseTranslations[translationKey]; | ||
let transformedMessage = translation.message; | ||
if (generator.transformElement) { | ||
@@ -120,7 +115,5 @@ const messageAst = new IntlMessageFormat(translation.message).getAst(); | ||
} | ||
if (generator.transformMessage) { | ||
transformedMessage = generator.transformMessage(transformedMessage); | ||
} | ||
generatedTranslations[translationKey] = { | ||
@@ -130,11 +123,9 @@ message: transformedMessage | ||
} | ||
return generatedTranslations; | ||
} | ||
function transformMessageFormatElement(transformElement) { | ||
return messageFormatElement => { | ||
const transformedMessageFormatElement = { ...messageFormatElement | ||
const transformedMessageFormatElement = { | ||
...messageFormatElement | ||
}; | ||
switch (transformedMessageFormatElement.type) { | ||
@@ -145,14 +136,11 @@ case TYPE.literal: | ||
break; | ||
case TYPE.select: | ||
case TYPE.plural: | ||
const transformedOptions = { ...transformedMessageFormatElement.options | ||
const transformedOptions = { | ||
...transformedMessageFormatElement.options | ||
}; | ||
for (const key of Object.keys(transformedOptions)) { | ||
transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement)); | ||
} | ||
break; | ||
case TYPE.tag: | ||
@@ -163,3 +151,2 @@ const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement)); | ||
} | ||
return transformedMessageFormatElement; | ||
@@ -179,3 +166,2 @@ }; | ||
const newLanguage = {}; | ||
for (const key of keys) { | ||
@@ -189,6 +175,4 @@ if (translation[key]) { | ||
} | ||
return newLanguage; | ||
} | ||
function getLanguageFallbacks({ | ||
@@ -198,3 +182,2 @@ languages | ||
const languageFallbackMap = new Map(); | ||
for (const lang of languages) { | ||
@@ -205,6 +188,4 @@ if (lang.extends) { | ||
} | ||
return languageFallbackMap; | ||
} | ||
function getLanguageHierarchy({ | ||
@@ -217,7 +198,5 @@ languages | ||
}); | ||
for (const lang of languages) { | ||
const langHierarchy = []; | ||
let currLang = lang.extends; | ||
while (currLang) { | ||
@@ -227,6 +206,4 @@ langHierarchy.push(currLang); | ||
} | ||
hierarchyMap.set(lang.name, langHierarchy); | ||
} | ||
return hierarchyMap; | ||
@@ -243,12 +220,8 @@ } | ||
}).get(languageName); | ||
if (!languageHierarchy) { | ||
throw new Error(`Missing language hierarchy for ${languageName}`); | ||
} | ||
const fallbackLanguageOrder = [languageName]; | ||
if (fallbacks !== 'none') { | ||
fallbackLanguageOrder.unshift(...languageHierarchy.reverse()); | ||
if (fallbacks === 'all' && fallbackLanguageOrder[0] !== devLanguage) { | ||
@@ -258,6 +231,4 @@ fallbackLanguageOrder.unshift(devLanguage); | ||
} | ||
return fallbackLanguageOrder; | ||
} | ||
function getNamespaceByFilePath(relativePath, { | ||
@@ -267,10 +238,7 @@ translationsDirectorySuffix = defaultTranslationDirSuffix | ||
let namespace = path.dirname(relativePath).replace(/^src\//, '').replace(/\//g, '_'); | ||
if (namespace.endsWith(translationsDirectorySuffix)) { | ||
namespace = namespace.slice(0, -translationsDirectorySuffix.length); | ||
} | ||
return namespace; | ||
} | ||
function printValidationError(...params) { | ||
@@ -280,3 +248,2 @@ // eslint-disable-next-line no-console | ||
} | ||
function getTranslationsFromFile(translationFileContents, { | ||
@@ -290,3 +257,2 @@ isAltLanguage, | ||
} | ||
const { | ||
@@ -297,19 +263,15 @@ $namespace, | ||
} = translationFileContents; | ||
if (isAltLanguage && $namespace) { | ||
printValidationError(`Found $namespace in alt language file in ${filePath}. $namespace is only used in the dev language and will be ignored.`); | ||
} | ||
if (!isAltLanguage && $namespace && typeof $namespace !== 'string') { | ||
printValidationError(`Found non-string $namespace in language file in ${filePath}. $namespace must be a string.`); | ||
} | ||
if (isAltLanguage && _meta !== null && _meta !== void 0 && _meta.tags) { | ||
printValidationError(`Found _meta.tags in alt language file in ${filePath}. _meta.tags is only used in the dev language and will be ignored.`); | ||
} // Never return tags if we're fetching translations for an alt language | ||
} | ||
// Never return tags if we're fetching translations for an alt language | ||
const includeTags = !isAltLanguage && withTags; | ||
const validKeys = {}; | ||
for (const [translationKey, { | ||
@@ -323,3 +285,2 @@ tags, | ||
} | ||
if (!translation) { | ||
@@ -329,3 +290,2 @@ printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`); | ||
} | ||
if (!translation.message || typeof translation.message !== 'string') { | ||
@@ -335,8 +295,7 @@ printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`); | ||
} | ||
validKeys[translationKey] = { ...translation, | ||
validKeys[translationKey] = { | ||
...translation, | ||
tags: includeTags ? tags : undefined | ||
}; | ||
} | ||
const metadata = { | ||
@@ -351,3 +310,2 @@ tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined | ||
} | ||
function loadAltLanguageFile({ | ||
@@ -370,3 +328,2 @@ filePath, | ||
trace(`Loading alt language file with precedence: ${fallbackLanguageOrder.slice().reverse().join(' -> ')}`); | ||
for (const fallbackLanguage of fallbackLanguageOrder) { | ||
@@ -377,5 +334,3 @@ if (fallbackLanguage !== devLanguage) { | ||
delete require.cache[altFilePath]; | ||
const translationFile = require(altFilePath); | ||
const { | ||
@@ -399,6 +354,4 @@ keys: fallbackLanguageTranslation | ||
} | ||
return altLanguageTranslation; | ||
} | ||
function stripTagsFromTranslations(translations) { | ||
@@ -410,3 +363,2 @@ return Object.fromEntries(Object.entries(translations).map(([key, { | ||
} | ||
function loadTranslation({ | ||
@@ -420,5 +372,3 @@ filePath, | ||
delete require.cache[filePath]; | ||
const translationContent = require(filePath); | ||
const relativePath = path.relative(userConfig.projectRoot || process.cwd(), filePath); | ||
@@ -439,3 +389,2 @@ const { | ||
const altLanguages = getAltLanguages(userConfig); | ||
for (const languageName of altLanguages) { | ||
@@ -449,3 +398,2 @@ languageSet[languageName] = loadAltLanguageFile({ | ||
} | ||
for (const generatedLanguage of userConfig.generatedLanguages || []) { | ||
@@ -463,3 +411,2 @@ const { | ||
} | ||
return { | ||
@@ -495,7 +442,5 @@ filePath, | ||
const keys = new Set(); | ||
for (const loadedTranslation of result) { | ||
for (const key of loadedTranslation.keys) { | ||
const uniqueKey = getUniqueKey(key, loadedTranslation.namespace); | ||
if (keys.has(uniqueKey)) { | ||
@@ -505,7 +450,5 @@ trace(`Duplicate keys found`); | ||
} | ||
keys.add(uniqueKey); | ||
} | ||
} | ||
return result; | ||
@@ -515,5 +458,3 @@ } | ||
const encodeWithinSingleQuotes = v => v.replace(/'/g, "\\'"); | ||
const encodeBackslash = v => v.replace(/\\/g, '\\\\'); | ||
function extractHasTags(ast) { | ||
@@ -525,11 +466,8 @@ return ast.some(element => { | ||
} | ||
return isTagElement(element); | ||
}); | ||
} | ||
function extractParamTypes(ast) { | ||
let params = {}; | ||
let imports = new Set(); | ||
for (const element of ast) { | ||
@@ -542,2 +480,11 @@ if (isArgumentElement(element)) { | ||
params[element.value] = 'number'; | ||
const children = Object.values(element.options).map(o => o.value); | ||
for (const child of children) { | ||
const [subParams, subImports] = extractParamTypes(child); | ||
imports = new Set([...imports, ...subImports]); | ||
params = { | ||
...params, | ||
...subParams | ||
}; | ||
} | ||
} else if (isDateElement(element) || isTimeElement(element)) { | ||
@@ -550,3 +497,4 @@ params[element.value] = 'Date | number'; | ||
imports = new Set([...imports, ...subImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...subParams | ||
@@ -557,7 +505,7 @@ }; | ||
const children = Object.values(element.options).map(o => o.value); | ||
for (const child of children) { | ||
const [subParams, subImports] = extractParamTypes(child); | ||
imports = new Set([...imports, ...subImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...subParams | ||
@@ -568,9 +516,6 @@ }; | ||
} | ||
return [params, imports]; | ||
} | ||
function serialiseObjectToType(v) { | ||
let result = ''; | ||
for (const [key, value] of Object.entries(v)) { | ||
@@ -583,12 +528,8 @@ if (value && typeof value === 'object') { | ||
} | ||
return `{ ${result} }`; | ||
} | ||
const banner = `// This file is automatically generated by Vocab.\n// To make changes update translation.json files directly.`; | ||
function serialiseTranslationRuntime(value, imports, loadedTranslation) { | ||
trace('Serialising translations:', loadedTranslation); | ||
const translationsType = {}; | ||
for (const [key, { | ||
@@ -600,3 +541,2 @@ params, | ||
let translationFunctionString = `() => ${message}`; | ||
if (Object.keys(params).length > 0) { | ||
@@ -607,6 +547,4 @@ const formatGeneric = hasTags ? '<T = string>' : ''; | ||
} | ||
translationsType[encodeBackslash(key)] = translationFunctionString; | ||
} | ||
const content = Object.entries(loadedTranslation.languages).map(([languageName, translations]) => `'${encodeWithinSingleQuotes(languageName)}': createLanguage(${JSON.stringify(getTranslationMessages(translations))})`).join(','); | ||
@@ -623,3 +561,2 @@ const languagesUnionAsString = Object.keys(loadedTranslation.languages).map(l => `'${l}'`).join(' | '); | ||
} | ||
async function generateRuntime(loadedTranslation) { | ||
@@ -633,3 +570,2 @@ const { | ||
let imports = new Set(); | ||
for (const key of loadedTranslation.keys) { | ||
@@ -639,3 +575,2 @@ let params = {}; | ||
let hasTags = false; | ||
for (const translatedLanguage of Object.values(loadedLanguages)) { | ||
@@ -647,3 +582,4 @@ if (translatedLanguage[key]) { | ||
imports = new Set([...imports, ...parsedImports]); | ||
params = { ...params, | ||
params = { | ||
...params, | ||
...parsedParams | ||
@@ -654,3 +590,2 @@ }; | ||
} | ||
const returnType = hasTags ? 'NonNullable<ReactNode>' : 'string'; | ||
@@ -664,6 +599,6 @@ translationTypes.set(key, { | ||
} | ||
const prettierConfig = await prettier.resolveConfig(filePath); | ||
const serializedTranslationType = serialiseTranslationRuntime(translationTypes, imports, loadedTranslation); | ||
const declaration = prettier.format(serializedTranslationType, { ...prettierConfig, | ||
const declaration = prettier.format(serializedTranslationType, { | ||
...prettierConfig, | ||
parser: 'typescript' | ||
@@ -682,7 +617,5 @@ }); | ||
}); | ||
const onTranslationChange = async relativePath => { | ||
trace(`Detected change for file ${relativePath}`); | ||
let targetFile; | ||
if (isDevLanguageFile(relativePath)) { | ||
@@ -693,3 +626,2 @@ targetFile = path.resolve(cwd, relativePath); | ||
} | ||
if (targetFile) { | ||
@@ -704,4 +636,4 @@ try { | ||
// eslint-disable-next-line no-console | ||
console.log('Failed to generate types for', relativePath); // eslint-disable-next-line no-console | ||
console.log('Failed to generate types for', relativePath); | ||
// eslint-disable-next-line no-console | ||
console.error(e); | ||
@@ -711,6 +643,4 @@ } | ||
}; | ||
const onNewDirectory = async relativePath => { | ||
trace('Detected new directory', relativePath); | ||
if (!isTranslationDirectory(relativePath, config)) { | ||
@@ -720,5 +650,3 @@ trace('Ignoring non-translation directory:', relativePath); | ||
} | ||
const newFilePath = path.join(relativePath, devTranslationFileName); | ||
if (!existsSync(newFilePath)) { | ||
@@ -731,3 +659,2 @@ await promises.writeFile(newFilePath, JSON.stringify({}, null, 2)); | ||
}; | ||
watcher.on('addDir', onNewDirectory); | ||
@@ -744,7 +671,5 @@ watcher.on('add', onTranslationChange).on('change', onTranslationChange); | ||
}, config); | ||
for (const loadedTranslation of translations) { | ||
await generateRuntime(loadedTranslation); | ||
} | ||
if (shouldWatch) { | ||
@@ -755,6 +680,4 @@ trace('Listening for changes to files...'); | ||
} | ||
async function writeIfChanged(filepath, contents) { | ||
let hasChanged = true; | ||
try { | ||
@@ -765,5 +688,5 @@ const existingContents = await promises.readFile(filepath, { | ||
hasChanged = existingContents !== contents; | ||
} catch (e) {// ignore error, likely a file doesn't exist error so we want to write anyway | ||
} catch (e) { | ||
// ignore error, likely a file doesn't exist error so we want to write anyway | ||
} | ||
if (hasChanged) { | ||
@@ -779,20 +702,14 @@ await promises.writeFile(filepath, contents, { | ||
const devLanguage = loadedTranslation.languages[devLanguageName]; | ||
if (!devLanguage) { | ||
throw new Error(`Failed to load dev language: ${loadedTranslation.filePath}`); | ||
} | ||
const result = {}; | ||
let valid = true; | ||
const requiredKeys = Object.keys(devLanguage); | ||
if (requiredKeys.length > 0) { | ||
for (const altLanguageName of altLanguages) { | ||
var _loadedTranslation$la; | ||
const altLanguage = (_loadedTranslation$la = loadedTranslation.languages[altLanguageName]) !== null && _loadedTranslation$la !== void 0 ? _loadedTranslation$la : {}; | ||
for (const key of requiredKeys) { | ||
var _altLanguage$key; | ||
if (typeof ((_altLanguage$key = altLanguage[key]) === null || _altLanguage$key === void 0 ? void 0 : _altLanguage$key.message) !== 'string') { | ||
@@ -802,3 +719,2 @@ if (!result[altLanguageName]) { | ||
} | ||
result[altLanguageName].push(key); | ||
@@ -810,3 +726,2 @@ valid = false; | ||
} | ||
return [valid, result]; | ||
@@ -820,10 +735,7 @@ } | ||
let valid = true; | ||
for (const loadedTranslation of allTranslations) { | ||
const [translationValid, result] = findMissingKeys(loadedTranslation, config.devLanguage, getAltLanguages(config)); | ||
if (!translationValid) { | ||
valid = false; | ||
console.log(chalk.red`Incomplete translations: "${chalk.bold(loadedTranslation.relativePath)}"`); | ||
for (const lang of Object.keys(result)) { | ||
@@ -835,3 +747,2 @@ const missingKeys = result[lang]; | ||
} | ||
return valid; | ||
@@ -846,3 +757,2 @@ } | ||
} | ||
} | ||
@@ -915,10 +825,7 @@ | ||
const checkConfigFile = validator.compile(schema); | ||
const splitMap = (message, callback) => message.split(' ,').map(v => callback(v)).join(' ,'); | ||
function validateConfig(c) { | ||
trace('Validating configuration file'); // Note: checkConfigFile mutates the config file by applying defaults | ||
trace('Validating configuration file'); | ||
// Note: checkConfigFile mutates the config file by applying defaults | ||
const isValid = checkConfigFile(c); | ||
if (isValid !== true) { | ||
@@ -929,21 +836,16 @@ throw new ValidationError('InvalidStructure', (Array.isArray(isValid) ? isValid : []).map(v => { | ||
} | ||
if (v.field) { | ||
var _v$message; | ||
return (_v$message = v.message) === null || _v$message === void 0 ? void 0 : _v$message.replace(v.field, chalk.cyan(v.field)); | ||
} | ||
return v.message; | ||
}).join(' \n')); | ||
} | ||
const languageStrings = c.languages.map(v => v.name); | ||
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages | ||
// Dev Language should exist in languages | ||
if (!languageStrings.includes(c.devLanguage)) { | ||
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk.bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
const foundLanguages = []; | ||
for (const lang of c.languages) { | ||
@@ -954,5 +856,5 @@ // Languages must only exist once | ||
} | ||
foundLanguages.push(lang.name); | ||
foundLanguages.push(lang.name); // Any extends must be in languages | ||
// Any extends must be in languages | ||
if (lang.extends && !languageStrings.includes(lang.extends)) { | ||
@@ -962,5 +864,3 @@ throw new ValidationError('InvalidExtends', `The language "${chalk.bold.cyan(lang.name)}"'s extends of ${chalk.bold.cyan(lang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
const foundGeneratedLanguages = []; | ||
for (const generatedLang of c.generatedLanguages || []) { | ||
@@ -971,10 +871,10 @@ // Generated languages must only exist once | ||
} | ||
foundGeneratedLanguages.push(generatedLang.name); | ||
foundGeneratedLanguages.push(generatedLang.name); // Generated language names must not conflict with language names | ||
// Generated language names must not conflict with language names | ||
if (languageStrings.includes(generatedLang.name)) { | ||
throw new ValidationError('InvalidGeneratedLanguage', `The generated language "${chalk.bold.cyan(generatedLang.name)}" is already defined as a language.`); | ||
} // Any extends must be in languages | ||
} | ||
// Any extends must be in languages | ||
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) { | ||
@@ -984,7 +884,5 @@ throw new ValidationError('InvalidExtends', `The generated language "${chalk.bold.cyan(generatedLang.name)}"'s extends of ${chalk.bold.cyan(generatedLang.extends)} was not found in languages ${languageStrings.join(', ')}.`); | ||
} | ||
trace('Configuration file is valid'); | ||
return true; | ||
} | ||
function createConfig(configFilePath) { | ||
@@ -997,6 +895,4 @@ const cwd = path.dirname(configFilePath); | ||
} | ||
async function resolveConfig(customConfigFilePath) { | ||
const configFilePath = customConfigFilePath ? path.resolve(customConfigFilePath) : await findUp('vocab.config.js'); | ||
if (configFilePath) { | ||
@@ -1006,3 +902,2 @@ trace(`Resolved configuration file to ${configFilePath}`); | ||
} | ||
trace('No configuration file found'); | ||
@@ -1013,3 +908,2 @@ return null; | ||
const configFilePath = customConfigFilePath ? path.resolve(customConfigFilePath) : findUp.sync('vocab.config.js'); | ||
if (configFilePath) { | ||
@@ -1019,3 +913,2 @@ trace(`Resolved configuration file to ${configFilePath}`); | ||
} | ||
trace('No configuration file found'); | ||
@@ -1022,0 +915,0 @@ return null; |
@@ -14,11 +14,8 @@ 'use strict'; | ||
const moduleCachedResult = moduleCache.get(m); | ||
if (moduleCachedResult && moduleCachedResult[locale]) { | ||
return moduleCachedResult[locale]; | ||
} | ||
const parsedICUMessages = {}; | ||
for (const translation of Object.keys(m)) { | ||
const intlMessageFormat = new IntlMessageFormat__default['default'](m[translation], locale); | ||
const intlMessageFormat = new IntlMessageFormat__default["default"](m[translation], locale); | ||
parsedICUMessages[translation] = { | ||
@@ -28,4 +25,4 @@ format: params => intlMessageFormat.format(params) | ||
} | ||
moduleCache.set(m, { ...moduleCachedResult, | ||
moduleCache.set(m, { | ||
...moduleCachedResult, | ||
[locale]: parsedICUMessages | ||
@@ -32,0 +29,0 @@ }); |
@@ -14,11 +14,8 @@ 'use strict'; | ||
const moduleCachedResult = moduleCache.get(m); | ||
if (moduleCachedResult && moduleCachedResult[locale]) { | ||
return moduleCachedResult[locale]; | ||
} | ||
const parsedICUMessages = {}; | ||
for (const translation of Object.keys(m)) { | ||
const intlMessageFormat = new IntlMessageFormat__default['default'](m[translation], locale); | ||
const intlMessageFormat = new IntlMessageFormat__default["default"](m[translation], locale); | ||
parsedICUMessages[translation] = { | ||
@@ -28,4 +25,4 @@ format: params => intlMessageFormat.format(params) | ||
} | ||
moduleCache.set(m, { ...moduleCachedResult, | ||
moduleCache.set(m, { | ||
...moduleCachedResult, | ||
[locale]: parsedICUMessages | ||
@@ -32,0 +29,0 @@ }); |
@@ -6,9 +6,6 @@ import IntlMessageFormat from 'intl-messageformat'; | ||
const moduleCachedResult = moduleCache.get(m); | ||
if (moduleCachedResult && moduleCachedResult[locale]) { | ||
return moduleCachedResult[locale]; | ||
} | ||
const parsedICUMessages = {}; | ||
for (const translation of Object.keys(m)) { | ||
@@ -20,4 +17,4 @@ const intlMessageFormat = new IntlMessageFormat(m[translation], locale); | ||
} | ||
moduleCache.set(m, { ...moduleCachedResult, | ||
moduleCache.set(m, { | ||
...moduleCachedResult, | ||
[locale]: parsedICUMessages | ||
@@ -24,0 +21,0 @@ }); |
{ | ||
"name": "@vocab/core", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"main": "dist/vocab-core.cjs.js", | ||
@@ -57,2 +57,2 @@ "module": "dist/vocab-core.esm.js", | ||
} | ||
} | ||
} |
@@ -8,10 +8,7 @@ 'use strict'; | ||
const translationModule = translationsByLanguage[language]; | ||
if (!translationModule) { | ||
throw new Error(`Attempted to retrieve translations for unknown language "${language}"`); | ||
} | ||
return translationModule; | ||
} | ||
return { | ||
@@ -22,3 +19,2 @@ getLoadedMessages(language, locale) { | ||
}, | ||
getMessages(language, locale) { | ||
@@ -28,11 +24,8 @@ const translationModule = getByLanguage(language); | ||
const result = translationModule.getValue(locale || language); | ||
if (!result) { | ||
throw new Error(`Unable to find translations for ${language} after attempting to load. Module may have failed to load or an internal error may have occurred.`); | ||
} | ||
return result; | ||
}); | ||
}, | ||
load(language) { | ||
@@ -42,3 +35,2 @@ const translationModule = getByLanguage(language); | ||
} | ||
}; | ||
@@ -45,0 +37,0 @@ } |
@@ -8,10 +8,7 @@ 'use strict'; | ||
const translationModule = translationsByLanguage[language]; | ||
if (!translationModule) { | ||
throw new Error(`Attempted to retrieve translations for unknown language "${language}"`); | ||
} | ||
return translationModule; | ||
} | ||
return { | ||
@@ -22,3 +19,2 @@ getLoadedMessages(language, locale) { | ||
}, | ||
getMessages(language, locale) { | ||
@@ -28,11 +24,8 @@ const translationModule = getByLanguage(language); | ||
const result = translationModule.getValue(locale || language); | ||
if (!result) { | ||
throw new Error(`Unable to find translations for ${language} after attempting to load. Module may have failed to load or an internal error may have occurred.`); | ||
} | ||
return result; | ||
}); | ||
}, | ||
load(language) { | ||
@@ -42,3 +35,2 @@ const translationModule = getByLanguage(language); | ||
} | ||
}; | ||
@@ -45,0 +37,0 @@ } |
function createTranslationFile(translationsByLanguage) { | ||
function getByLanguage(language) { | ||
const translationModule = translationsByLanguage[language]; | ||
if (!translationModule) { | ||
throw new Error(`Attempted to retrieve translations for unknown language "${language}"`); | ||
} | ||
return translationModule; | ||
} | ||
return { | ||
@@ -17,3 +14,2 @@ getLoadedMessages(language, locale) { | ||
}, | ||
getMessages(language, locale) { | ||
@@ -23,11 +19,8 @@ const translationModule = getByLanguage(language); | ||
const result = translationModule.getValue(locale || language); | ||
if (!result) { | ||
throw new Error(`Unable to find translations for ${language} after attempting to load. Module may have failed to load or an internal error may have occurred.`); | ||
} | ||
return result; | ||
}); | ||
}, | ||
load(language) { | ||
@@ -37,3 +30,2 @@ const translationModule = getByLanguage(language); | ||
} | ||
}; | ||
@@ -40,0 +32,0 @@ } |
128236
38
2853