Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@vocab/core

Package Overview
Dependencies
Maintainers
5
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vocab/core - npm Package Compare versions

Comparing version 1.0.4 to 1.1.0

dist/declarations/src/generate-language.d.ts

14

CHANGELOG.md
# @vocab/core
## 1.1.0
### Minor Changes
- [`87333d7`](https://github.com/seek-oss/vocab/commit/87333d79c4a883b07d7d8f2c272b16e2243c49bd) [#80](https://github.com/seek-oss/vocab/pull/80) Thanks [@askoufis](https://github.com/askoufis)! - Enable the creation of generated languages via the `generatedLanguages` config.
See [the docs] for more information and examples.
[the docs]: https://github.com/seek-oss/vocab#generated-languages
### Patch Changes
- Updated dependencies [[`87333d7`](https://github.com/seek-oss/vocab/commit/87333d79c4a883b07d7d8f2c272b16e2243c49bd)]:
- @vocab/types@1.1.0
## 1.0.4

@@ -4,0 +18,0 @@

143

dist/vocab-core.cjs.dev.js

@@ -13,2 +13,4 @@ 'use strict';

var glob = require('fast-glob');
var IntlMessageFormat = require('intl-messageformat');
var printer = require('@formatjs/icu-messageformat-parser/printer');
var findUp = require('find-up');

@@ -25,2 +27,3 @@ var Validator = require('fastest-validator');

var glob__default = /*#__PURE__*/_interopDefault(glob);
var IntlMessageFormat__default = /*#__PURE__*/_interopDefault(IntlMessageFormat);
var findUp__default = /*#__PURE__*/_interopDefault(findUp);

@@ -113,2 +116,67 @@ var Validator__default = /*#__PURE__*/_interopDefault(Validator);

function generateLanguageFromTranslations({
baseTranslations,
generator
}) {
if (!generator.transformElement && !generator.transformMessage) {
return baseTranslations;
}
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 transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement));
transformedMessage = printer.printAST(transformedAst);
}
if (generator.transformMessage) {
transformedMessage = generator.transformMessage(transformedMessage);
}
generatedTranslations[translationKey] = {
message: transformedMessage
};
}
return generatedTranslations;
}
function transformMessageFormatElement(transformElement) {
return messageFormatElement => {
const transformedMessageFormatElement = { ...messageFormatElement
};
switch (transformedMessageFormatElement.type) {
case icuMessageformatParser.TYPE.literal:
const transformedValue = transformElement(transformedMessageFormatElement.value);
transformedMessageFormatElement.value = transformedValue;
break;
case icuMessageformatParser.TYPE.select:
case icuMessageformatParser.TYPE.plural:
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:
const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement));
transformedMessageFormatElement.children = transformedChildren;
break;
}
return transformedMessageFormatElement;
};
}
function getUniqueKey(key, namespace) {

@@ -242,3 +310,3 @@ return `${key}.${namespace}`;

if (typeof translation === 'string') {
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -248,3 +316,3 @@ }

if (!translation) {
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -254,3 +322,3 @@ }

if (!translation.message || typeof translation.message !== 'string') {
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -347,2 +415,15 @@ }

for (const generatedLanguage of userConfig.generatedLanguages || []) {
const {
name: generatedLanguageName,
generator
} = generatedLanguage;
const baseLanguage = generatedLanguage.extends || userConfig.devLanguage;
const baseTranslations = languageSet[baseLanguage];
languageSet[generatedLanguageName] = generateLanguageFromTranslations({
baseTranslations,
generator
});
}
return {

@@ -719,2 +800,31 @@ filePath,

},
generatedLanguages: {
type: 'array',
items: {
type: 'object',
props: {
name: {
type: 'string'
},
extends: {
type: 'string',
optional: true
},
generator: {
type: 'object',
props: {
transformElement: {
type: 'function',
optional: true
},
transformMessage: {
type: 'function',
optional: true
}
}
}
}
},
optional: true
},
translationsDirectorySuffix: {

@@ -757,9 +867,8 @@ type: 'string',

}).join(' \n'));
} // Dev Language should exist in languages
}
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages
const languageStrings = c.languages.map(v => v.name);
if (!languageStrings.includes(c.devLanguage)) {
throw new ValidationError('InvalidDevLanguage', `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(', ')}.`);
}

@@ -782,2 +891,22 @@

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.`);
}
foundGeneratedLanguages.push(generatedLang.name); // 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
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(', ')}.`);
}
}
trace('Configuration file is valid');

@@ -784,0 +913,0 @@ return true;

@@ -13,2 +13,4 @@ 'use strict';

var glob = require('fast-glob');
var IntlMessageFormat = require('intl-messageformat');
var printer = require('@formatjs/icu-messageformat-parser/printer');
var findUp = require('find-up');

@@ -25,2 +27,3 @@ var Validator = require('fastest-validator');

var glob__default = /*#__PURE__*/_interopDefault(glob);
var IntlMessageFormat__default = /*#__PURE__*/_interopDefault(IntlMessageFormat);
var findUp__default = /*#__PURE__*/_interopDefault(findUp);

@@ -113,2 +116,67 @@ var Validator__default = /*#__PURE__*/_interopDefault(Validator);

function generateLanguageFromTranslations({
baseTranslations,
generator
}) {
if (!generator.transformElement && !generator.transformMessage) {
return baseTranslations;
}
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 transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement));
transformedMessage = printer.printAST(transformedAst);
}
if (generator.transformMessage) {
transformedMessage = generator.transformMessage(transformedMessage);
}
generatedTranslations[translationKey] = {
message: transformedMessage
};
}
return generatedTranslations;
}
function transformMessageFormatElement(transformElement) {
return messageFormatElement => {
const transformedMessageFormatElement = { ...messageFormatElement
};
switch (transformedMessageFormatElement.type) {
case icuMessageformatParser.TYPE.literal:
const transformedValue = transformElement(transformedMessageFormatElement.value);
transformedMessageFormatElement.value = transformedValue;
break;
case icuMessageformatParser.TYPE.select:
case icuMessageformatParser.TYPE.plural:
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:
const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement));
transformedMessageFormatElement.children = transformedChildren;
break;
}
return transformedMessageFormatElement;
};
}
function getUniqueKey(key, namespace) {

@@ -242,3 +310,3 @@ return `${key}.${namespace}`;

if (typeof translation === 'string') {
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -248,3 +316,3 @@ }

if (!translation) {
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -254,3 +322,3 @@ }

if (!translation.message || typeof translation.message !== 'string') {
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -347,2 +415,15 @@ }

for (const generatedLanguage of userConfig.generatedLanguages || []) {
const {
name: generatedLanguageName,
generator
} = generatedLanguage;
const baseLanguage = generatedLanguage.extends || userConfig.devLanguage;
const baseTranslations = languageSet[baseLanguage];
languageSet[generatedLanguageName] = generateLanguageFromTranslations({
baseTranslations,
generator
});
}
return {

@@ -719,2 +800,31 @@ filePath,

},
generatedLanguages: {
type: 'array',
items: {
type: 'object',
props: {
name: {
type: 'string'
},
extends: {
type: 'string',
optional: true
},
generator: {
type: 'object',
props: {
transformElement: {
type: 'function',
optional: true
},
transformMessage: {
type: 'function',
optional: true
}
}
}
}
},
optional: true
},
translationsDirectorySuffix: {

@@ -757,9 +867,8 @@ type: 'string',

}).join(' \n'));
} // Dev Language should exist in languages
}
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages
const languageStrings = c.languages.map(v => v.name);
if (!languageStrings.includes(c.devLanguage)) {
throw new ValidationError('InvalidDevLanguage', `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(', ')}.`);
}

@@ -782,2 +891,22 @@

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.`);
}
foundGeneratedLanguages.push(generatedLang.name); // 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
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(', ')}.`);
}
}
trace('Configuration file is valid');

@@ -784,0 +913,0 @@ return true;

import { existsSync, promises } from 'fs';
import path from 'path';
import { parse, isSelectElement, isTagElement, isArgumentElement, isNumberElement, isPluralElement, isDateElement, isTimeElement } from '@formatjs/icu-messageformat-parser';
import { TYPE, parse, isSelectElement, isTagElement, isArgumentElement, isNumberElement, isPluralElement, isDateElement, isTimeElement } from '@formatjs/icu-messageformat-parser';
import prettier from 'prettier';

@@ -9,2 +9,4 @@ import chokidar from 'chokidar';

import glob from 'fast-glob';
import IntlMessageFormat from 'intl-messageformat';
import { printAST } from '@formatjs/icu-messageformat-parser/printer';
import findUp from 'find-up';

@@ -97,2 +99,67 @@ import Validator from 'fastest-validator';

function generateLanguageFromTranslations({
baseTranslations,
generator
}) {
if (!generator.transformElement && !generator.transformMessage) {
return baseTranslations;
}
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(translation.message).getAst();
const transformedAst = messageAst.map(transformMessageFormatElement(generator.transformElement));
transformedMessage = printAST(transformedAst);
}
if (generator.transformMessage) {
transformedMessage = generator.transformMessage(transformedMessage);
}
generatedTranslations[translationKey] = {
message: transformedMessage
};
}
return generatedTranslations;
}
function transformMessageFormatElement(transformElement) {
return messageFormatElement => {
const transformedMessageFormatElement = { ...messageFormatElement
};
switch (transformedMessageFormatElement.type) {
case TYPE.literal:
const transformedValue = transformElement(transformedMessageFormatElement.value);
transformedMessageFormatElement.value = transformedValue;
break;
case TYPE.select:
case TYPE.plural:
const transformedOptions = { ...transformedMessageFormatElement.options
};
for (const key of Object.keys(transformedOptions)) {
transformedOptions[key].value = transformedOptions[key].value.map(transformMessageFormatElement(transformElement));
}
break;
case TYPE.tag:
const transformedChildren = transformedMessageFormatElement.children.map(transformMessageFormatElement(transformElement));
transformedMessageFormatElement.children = transformedChildren;
break;
}
return transformedMessageFormatElement;
};
}
function getUniqueKey(key, namespace) {

@@ -226,3 +293,3 @@ return `${key}.${namespace}`;

if (typeof translation === 'string') {
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`Found string for a translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -232,3 +299,3 @@ }

if (!translation) {
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`Found empty translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -238,3 +305,3 @@ }

if (!translation.message || typeof translation.message !== 'string') {
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {mesage: string}.`);
printValidationError(`No message found for translation "${translationKey}" in ${filePath}. Translation must be an object of the format {message: string}.`);
continue;

@@ -331,2 +398,15 @@ }

for (const generatedLanguage of userConfig.generatedLanguages || []) {
const {
name: generatedLanguageName,
generator
} = generatedLanguage;
const baseLanguage = generatedLanguage.extends || userConfig.devLanguage;
const baseTranslations = languageSet[baseLanguage];
languageSet[generatedLanguageName] = generateLanguageFromTranslations({
baseTranslations,
generator
});
}
return {

@@ -703,2 +783,31 @@ filePath,

},
generatedLanguages: {
type: 'array',
items: {
type: 'object',
props: {
name: {
type: 'string'
},
extends: {
type: 'string',
optional: true
},
generator: {
type: 'object',
props: {
transformElement: {
type: 'function',
optional: true
},
transformMessage: {
type: 'function',
optional: true
}
}
}
}
},
optional: true
},
translationsDirectorySuffix: {

@@ -741,9 +850,8 @@ type: 'string',

}).join(' \n'));
} // Dev Language should exist in languages
}
const languageStrings = c.languages.map(v => v.name); // Dev Language should exist in languages
const languageStrings = c.languages.map(v => v.name);
if (!languageStrings.includes(c.devLanguage)) {
throw new ValidationError('InvalidDevLanguage', `InvalidDevLanguage: The dev language "${chalk.bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
throw new ValidationError('InvalidDevLanguage', `The dev language "${chalk.bold.cyan(c.devLanguage)}" was not found in languages ${languageStrings.join(', ')}.`);
}

@@ -766,2 +874,22 @@

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.bold.cyan(generatedLang.name)}" was defined multiple times.`);
}
foundGeneratedLanguages.push(generatedLang.name); // 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
if (generatedLang.extends && !languageStrings.includes(generatedLang.extends)) {
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');

@@ -768,0 +896,0 @@ return true;

10

package.json
{
"name": "@vocab/core",
"version": "1.0.4",
"version": "1.1.0",
"main": "dist/vocab-core.cjs.js",

@@ -16,5 +16,11 @@ "module": "dist/vocab-core.esm.js",

},
"files": [
"dist",
"runtime",
"icu-handler",
"translation-file"
],
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.0.10",
"@vocab/types": "^1.0.1",
"@vocab/types": "^1.1.0",
"chalk": "^4.1.0",

@@ -21,0 +27,0 @@ "chokidar": "^3.4.3",

@@ -187,2 +187,10 @@ # Vocab

```js
function capitalize(element) {
return element.toUpperCase();
}
function pad(message) {
return '[' + message + ']';
}
module.exports = {

@@ -197,6 +205,20 @@ devLanguage: 'en',

/**
* An array of languages to generate based off translations for existing languages
* Default: []
*/
generatedLanguages: [
{
name: 'generatedLangauge',
extends: 'en',
generator: {
transformElement: capitalize,
transformMessage: pad
}
}
],
/**
* The root directory to compile and validate translations
* Default: Current working directory
*/
projectRoot: './example/';
projectRoot: './example/',
/**

@@ -214,2 +236,127 @@ * A custom suffix to name vocab translation directories

## Generated languages
Vocab supports the creation of generated languages via the `generatedLanguages` config.
Generated languages are created by running a message `generator` over every translation message in an existing translation.
A `generator` may contain a `transformElement` function, a `transformMessage` function, or both.
Both of these functions accept a single string parameter and return a string.
`transformElement` is applied to string literal values contained within `MessageFormatElement`s.
A `MessageFormatElement` is an object representing a node in the AST of a compiled translation message.
Simply put, any text that would end up being translated by a translator, i.e. anything that is not part of the [ICU Message syntax], will be passed to `transformElement`.
An example of a use case for this function would be adding [diacritics] to every letter in order to stress your UI from a vertical line-height perspective.
`transformMessage` receives the entire translation message _after_ `transformElement` has been applied to its individual elements.
An example of a use case for this function would be adding padding text to the start/end of your messages in order to easily identify which text in your app has not been extracted into a `translations.json` file.
By default, a generated language's messages will be based off the `devLanguage`'s messages, but this can be overridden by providing an `extends` value that references another language.
**vocab.config.js**
```js
function capitalize(message) {
return message.toUpperCase();
}
function pad(message) {
return '[' + message + ']';
}
module.exports = {
devLanguage: 'en',
languages: [{ name: 'en' }, { name: 'fr' }],
generatedLanguages: [
{
name: 'generatedLanguage',
extends: 'en',
generator: {
transformElement: capitalize,
transformMessage: pad
}
}
]
};
```
Generated languages are consumed the same way as regular languages.
Any Vocab API that accepts a `language` parameter will work with a generated language as well as a regular language.
**App.tsx**
```tsx
const App = () => (
<VocabProvider language="generatedLanguage">
...
</VocabProvider>
);
```
[icu message syntax]: https://formatjs.io/docs/intl-messageformat/#message-syntax
[diacritics]: https://en.wikipedia.org/wiki/Diacritic
## Pseudo-localization
The `@vocab/pseudo-localize` package exports low-level functions that can be used for pseudo-localization of translation messages.
```ts
import {
extendVowels,
padString,
pseudoLocalize,
substituteCharacters
} from '@vocab/pseudo-localize';
const message = 'Hello';
// [Hello]
const paddedMessage = padString(message);
// Ḩẽƚƚö
const substitutedMessage = substituteCharacters(message);
// Heelloo
const extendedMessage = extendVowels(message);
// Extend the message and then substitute characters
// Ḩẽẽƚƚöö
const pseudoLocalizedMessage = pseudoLocalize(message);
```
Pseudo-localization is a transformation that can be applied to a translation message.
Vocab's implementation of this transformation contains the following elements:
- _Start and end markers (`padString`):_ All strings are encapsulated in `[` and `]`. If a developer doesn’t see these characters they know the string has been clipped by an inflexible UI element.
- _Transformation of ASCII characters to extended character equivalents (`substituteCharacters`):_ Stresses the UI from a vertical line-height perspective, tests font and encoding support, and weeds out strings that haven’t been externalized correctly (they will not have the pseudo-localization applied to them).
- _Padding text (`extendVowels`):_ Simulates translation-induced expansion. Vocab's implementation of this involves repeating vowels (and `y`) to simulate a 40% expansion in the message's length.
This Netflix technology [blog post][blog post] inspired Vocab's implementation of this
functionality.
### Generating a pseudo-localized language using Vocab
Vocab can generate a pseudo-localized language via the [`generatedLanguages` config][generated languages config], either via the webpack plugin or your `vocab.config.js` file.
`@vocab/pseudo-localize` exports a `generator` that can be used directly in your config.
**vocab.config.js**
```js
const { generator } = require('@vocab/pseudo-localize');
module.exports = {
devLanguage: 'en',
languages: [{ name: 'en' }, { name: 'fr' }],
generatedLanguages: [
{
name: 'pseudo',
extends: 'en',
generator
}
]
};
```
[blog post]: https://netflixtechblog.com/pseudo-localization-netflix-12fff76fbcbe
[generated languages config]: #generated-languages
## Use without React

@@ -216,0 +363,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc