@vocab/core
Advanced tools
Comparing version 1.1.2 to 1.2.0
@@ -1,2 +0,2 @@ | ||
import { TranslationsByKey, UserConfig, LoadedTranslation, LanguageTarget } from '@vocab/types'; | ||
import type { TranslationsByKey, UserConfig, LoadedTranslation, LanguageTarget } from '@vocab/types'; | ||
import { Fallback } from './utils'; | ||
@@ -23,9 +23,11 @@ export declare function getUniqueKey(key: string, namespace: string): string; | ||
}, { devLanguage, languages }: UserConfig): TranslationsByKey; | ||
export declare function loadTranslation({ filePath, fallbacks, }: { | ||
export declare function loadTranslation({ filePath, fallbacks, withTags, }: { | ||
filePath: string; | ||
fallbacks: Fallback; | ||
withTags?: boolean; | ||
}, userConfig: UserConfig): LoadedTranslation; | ||
export declare function loadAllTranslations({ fallbacks, includeNodeModules, }: { | ||
export declare function loadAllTranslations({ fallbacks, includeNodeModules, withTags, }: { | ||
fallbacks: Fallback; | ||
includeNodeModules: boolean; | ||
withTags?: boolean; | ||
}, config: UserConfig): Promise<Array<LoadedTranslation>>; |
@@ -282,8 +282,9 @@ 'use strict'; | ||
function getTranslationsFromFile(translations, { | ||
function getTranslationsFromFile(translationFileContents, { | ||
isAltLanguage, | ||
filePath | ||
filePath, | ||
withTags | ||
}) { | ||
if (!translations || typeof translations !== 'object') { | ||
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object`); | ||
if (!translationFileContents || typeof translationFileContents !== 'object') { | ||
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object.`); | ||
} | ||
@@ -293,4 +294,5 @@ | ||
$namespace, | ||
_meta, | ||
...keys | ||
} = translations; | ||
} = translationFileContents; | ||
@@ -305,5 +307,14 @@ if (isAltLanguage && $namespace) { | ||
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 | ||
const includeTags = !isAltLanguage && withTags; | ||
const validKeys = {}; | ||
for (const [translationKey, translation] of Object.entries(keys)) { | ||
for (const [translationKey, { | ||
tags, | ||
...translation | ||
}] of Object.entries(keys)) { | ||
if (typeof translation === 'string') { | ||
@@ -324,8 +335,14 @@ printValidationError(`Found string for a 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 = { | ||
tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined | ||
}; | ||
return { | ||
$namespace, | ||
keys: validKeys | ||
keys: validKeys, | ||
metadata | ||
}; | ||
@@ -381,5 +398,14 @@ } | ||
} | ||
function stripTagsFromTranslations(translations) { | ||
return Object.fromEntries(Object.entries(translations).map(([key, { | ||
tags, | ||
...rest | ||
}]) => [key, rest])); | ||
} | ||
function loadTranslation({ | ||
filePath, | ||
fallbacks | ||
fallbacks, | ||
withTags | ||
}, userConfig) { | ||
@@ -395,6 +421,8 @@ trace(`Loading translation file in "${fallbacks}" fallback mode: "${filePath}"`); | ||
$namespace, | ||
keys: devTranslation | ||
keys: devTranslation, | ||
metadata | ||
} = getTranslationsFromFile(translationContent, { | ||
filePath, | ||
isAltLanguage: false | ||
isAltLanguage: false, | ||
withTags | ||
}); | ||
@@ -404,2 +432,3 @@ const namespace = typeof $namespace === 'string' ? $namespace : getNamespaceByFilePath(relativePath, userConfig); | ||
languageSet[userConfig.devLanguage] = devTranslation; | ||
const devTranslationNoTags = withTags ? stripTagsFromTranslations(devTranslation) : devTranslation; | ||
const altLanguages = getAltLanguages(userConfig); | ||
@@ -411,3 +440,3 @@ | ||
languageName, | ||
devTranslation, | ||
devTranslation: devTranslationNoTags, | ||
fallbacks | ||
@@ -435,3 +464,4 @@ }, userConfig); | ||
relativePath, | ||
languages: languageSet | ||
languages: languageSet, | ||
metadata | ||
}; | ||
@@ -441,3 +471,4 @@ } | ||
fallbacks, | ||
includeNodeModules | ||
includeNodeModules, | ||
withTags | ||
}, config) { | ||
@@ -456,3 +487,4 @@ const { | ||
filePath, | ||
fallbacks | ||
fallbacks, | ||
withTags | ||
}, config))); | ||
@@ -583,3 +615,3 @@ const keys = new Set(); | ||
} = loadedTranslation; | ||
trace('Generating types for', loadedTranslation.filePath); | ||
trace('Generating types for', filePath); | ||
const translationTypes = new Map(); | ||
@@ -644,3 +676,3 @@ let imports = new Set(); | ||
try { | ||
const loadedTranslation = await loadTranslation({ | ||
const loadedTranslation = loadTranslation({ | ||
filePath: targetFile, | ||
@@ -647,0 +679,0 @@ fallbacks: 'all' |
@@ -282,8 +282,9 @@ 'use strict'; | ||
function getTranslationsFromFile(translations, { | ||
function getTranslationsFromFile(translationFileContents, { | ||
isAltLanguage, | ||
filePath | ||
filePath, | ||
withTags | ||
}) { | ||
if (!translations || typeof translations !== 'object') { | ||
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object`); | ||
if (!translationFileContents || typeof translationFileContents !== 'object') { | ||
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object.`); | ||
} | ||
@@ -293,4 +294,5 @@ | ||
$namespace, | ||
_meta, | ||
...keys | ||
} = translations; | ||
} = translationFileContents; | ||
@@ -305,5 +307,14 @@ if (isAltLanguage && $namespace) { | ||
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 | ||
const includeTags = !isAltLanguage && withTags; | ||
const validKeys = {}; | ||
for (const [translationKey, translation] of Object.entries(keys)) { | ||
for (const [translationKey, { | ||
tags, | ||
...translation | ||
}] of Object.entries(keys)) { | ||
if (typeof translation === 'string') { | ||
@@ -324,8 +335,14 @@ printValidationError(`Found string for a 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 = { | ||
tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined | ||
}; | ||
return { | ||
$namespace, | ||
keys: validKeys | ||
keys: validKeys, | ||
metadata | ||
}; | ||
@@ -381,5 +398,14 @@ } | ||
} | ||
function stripTagsFromTranslations(translations) { | ||
return Object.fromEntries(Object.entries(translations).map(([key, { | ||
tags, | ||
...rest | ||
}]) => [key, rest])); | ||
} | ||
function loadTranslation({ | ||
filePath, | ||
fallbacks | ||
fallbacks, | ||
withTags | ||
}, userConfig) { | ||
@@ -395,6 +421,8 @@ trace(`Loading translation file in "${fallbacks}" fallback mode: "${filePath}"`); | ||
$namespace, | ||
keys: devTranslation | ||
keys: devTranslation, | ||
metadata | ||
} = getTranslationsFromFile(translationContent, { | ||
filePath, | ||
isAltLanguage: false | ||
isAltLanguage: false, | ||
withTags | ||
}); | ||
@@ -404,2 +432,3 @@ const namespace = typeof $namespace === 'string' ? $namespace : getNamespaceByFilePath(relativePath, userConfig); | ||
languageSet[userConfig.devLanguage] = devTranslation; | ||
const devTranslationNoTags = withTags ? stripTagsFromTranslations(devTranslation) : devTranslation; | ||
const altLanguages = getAltLanguages(userConfig); | ||
@@ -411,3 +440,3 @@ | ||
languageName, | ||
devTranslation, | ||
devTranslation: devTranslationNoTags, | ||
fallbacks | ||
@@ -435,3 +464,4 @@ }, userConfig); | ||
relativePath, | ||
languages: languageSet | ||
languages: languageSet, | ||
metadata | ||
}; | ||
@@ -441,3 +471,4 @@ } | ||
fallbacks, | ||
includeNodeModules | ||
includeNodeModules, | ||
withTags | ||
}, config) { | ||
@@ -456,3 +487,4 @@ const { | ||
filePath, | ||
fallbacks | ||
fallbacks, | ||
withTags | ||
}, config))); | ||
@@ -583,3 +615,3 @@ const keys = new Set(); | ||
} = loadedTranslation; | ||
trace('Generating types for', loadedTranslation.filePath); | ||
trace('Generating types for', filePath); | ||
const translationTypes = new Map(); | ||
@@ -644,3 +676,3 @@ let imports = new Set(); | ||
try { | ||
const loadedTranslation = await loadTranslation({ | ||
const loadedTranslation = loadTranslation({ | ||
filePath: targetFile, | ||
@@ -647,0 +679,0 @@ fallbacks: 'all' |
@@ -266,8 +266,9 @@ import { existsSync, promises } from 'fs'; | ||
function getTranslationsFromFile(translations, { | ||
function getTranslationsFromFile(translationFileContents, { | ||
isAltLanguage, | ||
filePath | ||
filePath, | ||
withTags | ||
}) { | ||
if (!translations || typeof translations !== 'object') { | ||
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object`); | ||
if (!translationFileContents || typeof translationFileContents !== 'object') { | ||
throw new Error(`Unable to read translation file ${filePath}. Translations must be an object.`); | ||
} | ||
@@ -277,4 +278,5 @@ | ||
$namespace, | ||
_meta, | ||
...keys | ||
} = translations; | ||
} = translationFileContents; | ||
@@ -289,5 +291,14 @@ if (isAltLanguage && $namespace) { | ||
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 | ||
const includeTags = !isAltLanguage && withTags; | ||
const validKeys = {}; | ||
for (const [translationKey, translation] of Object.entries(keys)) { | ||
for (const [translationKey, { | ||
tags, | ||
...translation | ||
}] of Object.entries(keys)) { | ||
if (typeof translation === 'string') { | ||
@@ -308,8 +319,14 @@ printValidationError(`Found string for a 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 = { | ||
tags: includeTags ? _meta === null || _meta === void 0 ? void 0 : _meta.tags : undefined | ||
}; | ||
return { | ||
$namespace, | ||
keys: validKeys | ||
keys: validKeys, | ||
metadata | ||
}; | ||
@@ -365,5 +382,14 @@ } | ||
} | ||
function stripTagsFromTranslations(translations) { | ||
return Object.fromEntries(Object.entries(translations).map(([key, { | ||
tags, | ||
...rest | ||
}]) => [key, rest])); | ||
} | ||
function loadTranslation({ | ||
filePath, | ||
fallbacks | ||
fallbacks, | ||
withTags | ||
}, userConfig) { | ||
@@ -379,6 +405,8 @@ trace(`Loading translation file in "${fallbacks}" fallback mode: "${filePath}"`); | ||
$namespace, | ||
keys: devTranslation | ||
keys: devTranslation, | ||
metadata | ||
} = getTranslationsFromFile(translationContent, { | ||
filePath, | ||
isAltLanguage: false | ||
isAltLanguage: false, | ||
withTags | ||
}); | ||
@@ -388,2 +416,3 @@ const namespace = typeof $namespace === 'string' ? $namespace : getNamespaceByFilePath(relativePath, userConfig); | ||
languageSet[userConfig.devLanguage] = devTranslation; | ||
const devTranslationNoTags = withTags ? stripTagsFromTranslations(devTranslation) : devTranslation; | ||
const altLanguages = getAltLanguages(userConfig); | ||
@@ -395,3 +424,3 @@ | ||
languageName, | ||
devTranslation, | ||
devTranslation: devTranslationNoTags, | ||
fallbacks | ||
@@ -419,3 +448,4 @@ }, userConfig); | ||
relativePath, | ||
languages: languageSet | ||
languages: languageSet, | ||
metadata | ||
}; | ||
@@ -425,3 +455,4 @@ } | ||
fallbacks, | ||
includeNodeModules | ||
includeNodeModules, | ||
withTags | ||
}, config) { | ||
@@ -440,3 +471,4 @@ const { | ||
filePath, | ||
fallbacks | ||
fallbacks, | ||
withTags | ||
}, config))); | ||
@@ -567,3 +599,3 @@ const keys = new Set(); | ||
} = loadedTranslation; | ||
trace('Generating types for', loadedTranslation.filePath); | ||
trace('Generating types for', filePath); | ||
const translationTypes = new Map(); | ||
@@ -628,3 +660,3 @@ let imports = new Set(); | ||
try { | ||
const loadedTranslation = await loadTranslation({ | ||
const loadedTranslation = loadTranslation({ | ||
filePath: targetFile, | ||
@@ -631,0 +663,0 @@ fallbacks: 'all' |
{ | ||
"name": "@vocab/core", | ||
"version": "1.1.2", | ||
"version": "1.2.0", | ||
"main": "dist/vocab-core.cjs.js", | ||
@@ -43,3 +43,3 @@ "module": "dist/vocab-core.esm.js", | ||
"@formatjs/icu-messageformat-parser": "^2.0.10", | ||
"@vocab/types": "^1.1.1", | ||
"@vocab/types": "^1.1.2", | ||
"chalk": "^4.1.0", | ||
@@ -46,0 +46,0 @@ "chokidar": "^3.4.3", |
@@ -425,16 +425,77 @@ # Vocab | ||
## External translation tooling | ||
## External Translation Tooling | ||
Vocab can be used to synchronize your translations with translations from a remote translation platform. | ||
| Platform | Environment Variables | | ||
| -------------------------------------------- | ----------------------------------- | | ||
| [Phrase](https://developers.phrase.com/api/) | PHRASE_PROJECT_ID, PHRASE_API_TOKEN | | ||
| Platform | Environment Variables | | ||
| -------- | ----------------------------------- | | ||
| [Phrase] | PHRASE_PROJECT_ID, PHRASE_API_TOKEN | | ||
```bash | ||
$ vocab push --branch my-branch | ||
$ vocab push --branch my-branch --delete-unused-keys | ||
$ vocab pull --branch my-branch | ||
``` | ||
### [Phrase] Platform Features | ||
#### Delete Unused keys | ||
When uploading translations, Phrase identifies keys that exist in the Phrase project, but were not | ||
referenced in the upload. These keys can be deleted from Phrase by providing the | ||
`---delete-unused-keys` flag to `vocab push`. E.g. | ||
```sh | ||
$ vocab push --branch my-branch --delete-unused-keys | ||
``` | ||
[phrase]: https://developers.phrase.com/api/ | ||
#### [Tags] | ||
`vocab push` supports uploading [tags] to Phrase. | ||
Tags can be added to an individual key via the `tags` property: | ||
```jsonc | ||
// translations.json | ||
{ | ||
"Hello": { | ||
"message": "Hello", | ||
"tags": ["greeting", "home_page"] | ||
}, | ||
"Goodbye": { | ||
"message": "Goodbye", | ||
"tags": ["home_page"] | ||
} | ||
} | ||
``` | ||
Tags can also be added under a top-level `_meta` field. This will result in the tags applying to all | ||
keys specified in the file: | ||
```jsonc | ||
// translations.json | ||
{ | ||
"_meta": { | ||
"tags": ["home_page"] | ||
}, | ||
"Hello": { | ||
"message": "Hello", | ||
"tags": ["greeting"] | ||
}, | ||
"Goodbye": { | ||
"message": "Goodbye" | ||
} | ||
} | ||
``` | ||
In the above example, both the `Hello` and `Goodbye` keys would have the `home_page` tag attached to | ||
them, but only the `Hello` key would have the `usage_greeting` tag attached to it. | ||
**NOTE**: Only tags specified on keys in your [`devLanguage`][configuration] will be uploaded. | ||
Tags on keys in other languages will be ignored. | ||
[tags]: https://support.phrase.com/hc/en-us/articles/5822598372252-Tags-Strings- | ||
[configuration]: #Configuration | ||
## Troubleshooting | ||
@@ -441,0 +502,0 @@ |
2778
510
126494
37
Updated@vocab/types@^1.1.2