New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

babel-plugin-i18next-extract

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

babel-plugin-i18next-extract - npm Package Compare versions

Comparing version 0.1.0-rc to 0.1.0-rc.1

186

lib/index.es.js

@@ -851,3 +851,4 @@ import i18next from 'i18next';

keyAsDefaultValueForDerivedKeys: coalesce(opts.keyAsDefaultValueForDerivedKeys, true),
exporterJsonSpace: coalesce(opts.exporterJsonSpace, 2),
discardOldKeys: coalesce(opts.discardOldKeys, false),
jsonSpace: coalesce(opts.jsonSpace, 2),
};

@@ -858,7 +859,76 @@ }

/**
* Thrown when an error happens during the translations export.
*/
class ExportError extends Error {
}
/**
* This creates a new empty cache for the exporter.
*
* The cache is required by the exporter and is used to merge the translations
* from the original translation file. It will be mutated by the exporter
* and the same instance must be given untouched across export calls.
*/
function createExporterCache() {
return {
originalTranslationFiles: {},
currentTranslationFiles: {},
};
}
/**
* Take a deep translation file and flatten it.
* This is to simplify merging keys later on.
*
* @param deep Deep translation file.
*/
function flattenTranslationFile(deep) {
const result = {};
for (const [k, v] of Object.entries(deep)) {
if (typeof v === 'object' && v !== null) {
// Nested case, we must recurse
const flat = flattenTranslationFile(v);
for (const [flatK, flatV] of Object.entries(flat)) {
result[JSON.stringify([k, ...JSON.parse(flatK)])] = flatV;
}
}
else {
// Leaf, just set the value.
result[JSON.stringify([k])] = v;
}
}
return result;
}
/**
* Take a flat translation file and make it deep.
*
* This is to make a flat translation file ready to be exported to a file.
*
* @param flat Flat translation file as returned by flattenTranslationFile
*/
function unflattenTranslationFile(flat) {
const result = {};
for (const [k, v] of Object.entries(flat)) {
const keyPath = JSON.parse(k);
const cleanKey = keyPath.pop();
const error = new ExportError(`${PLUGIN_NAME}: Couldn't export translations. Key "${keyPath}" ` +
`has a conflict.`);
let obj = result;
for (const p of keyPath) {
const currentValue = obj[p];
if (typeof currentValue === 'string' || currentValue === null) {
throw error;
}
obj = obj[p] = currentValue || {};
}
const currentValue = obj[cleanKey];
if (typeof currentValue === 'object' && currentValue !== null) {
throw error;
}
obj[cleanKey] = v;
}
return result;
}
/**
* Load translation given a file. If the file is not found, default to empty
* object
* object.
* @param filePath Path of the JSON translation file to load.

@@ -876,15 +946,41 @@ */

}
const obj = JSON.parse(content);
return obj;
return flattenTranslationFile(JSON.parse(content));
}
/**
* Exports all given translation keyr as JSON.
* Create a new translationFile with a key added to it.
*
* @param translationFile The translation file to add the key to.
* @param key The translation key to add.
* @param locale Current locale being exported
* @param config Configuration (that will help setting the proper default
* value)
*/
function addKeyToTranslationFile(translationFile, key, locale, config) {
// compute the default value
let defaultValue = config.defaultValue;
const keyAsDefaultValueEnabled = config.keyAsDefaultValue === true ||
(Array.isArray(config.keyAsDefaultValue) &&
config.keyAsDefaultValue.includes(locale));
const keyAsDefaultValueForDerivedKeys = config.keyAsDefaultValueForDerivedKeys;
if (keyAsDefaultValueEnabled &&
(keyAsDefaultValueForDerivedKeys || !key.isDerivedKey)) {
defaultValue = key.cleanKey;
}
return {
[JSON.stringify([...key.keyPath, key.cleanKey])]: defaultValue,
...translationFile,
};
}
/**
* Exports all given translation keys as JSON.
*
* @param keys: translation keys to export
* @param locale: the locale to export
* @param config: plugin configuration
* @param cache: cache instance to use (see createExporterCache)
*/
function exportTranslationKeys(keys, locale, config) {
function exportTranslationKeys(keys, locale, config, cache) {
const keysPerFilepath = {};
for (const key of keys) {
// Figure out in which path each key should go.
const filePath = config.outputPath

@@ -896,38 +992,36 @@ .replace('{{locale}}', locale)

for (const [filePath, keysForFilepath] of Object.entries(keysPerFilepath)) {
let obj = {};
obj = loadTranslationFile(filePath);
const originalObject = obj;
if (!(filePath in cache.originalTranslationFiles)) {
// Cache original translation file so that we don't loose it across babel
// passes.
cache.originalTranslationFiles[filePath] = loadTranslationFile(filePath);
}
let translationFile = cache.currentTranslationFiles[filePath] || {};
for (const k of keysForFilepath) {
// resolve key path
for (const p of k.keyPath) {
let value = obj[p];
if (value === undefined) {
value = obj[p] = {};
}
if (typeof value === 'string' || value === null) {
throw new ExportError(`${PLUGIN_NAME}: Couldn't export translations. Key "${k.key}" ` +
`has conflict.`);
}
obj = value;
}
// The key was already exported.
if (obj[k.cleanKey] !== undefined) {
continue;
}
// Set the default values for the path
let defaultValue = config.defaultValue;
const keyAsDefaultValueEnabled = config.keyAsDefaultValue === true ||
(Array.isArray(config.keyAsDefaultValue) &&
config.keyAsDefaultValue.includes(locale));
const keyAsDefaultValueForDerivedKeys = config.keyAsDefaultValueForDerivedKeys;
if (keyAsDefaultValueEnabled &&
(keyAsDefaultValueForDerivedKeys || !k.isDerivedKey)) {
defaultValue = k.cleanKey;
}
obj[k.cleanKey] = defaultValue;
translationFile = addKeyToTranslationFile(translationFile, k, locale, config);
}
cache.currentTranslationFiles[filePath] = translationFile;
let deepTranslationFile;
if (config.discardOldKeys) {
const originalTranslationFile = cache.originalTranslationFiles[filePath];
const alreadyTranslated = Object.keys(originalTranslationFile)
.filter(k => k in translationFile)
.reduce((accumulator, k) => ({
...accumulator,
[k]: originalTranslationFile[k],
}), {});
deepTranslationFile = unflattenTranslationFile({
...translationFile,
...alreadyTranslated,
});
}
else {
deepTranslationFile = unflattenTranslationFile({
...translationFile,
...cache.originalTranslationFiles[filePath],
});
}
// Finally do the export
const directoryPath = path.dirname(filePath);
fs.mkdirSync(directoryPath, { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(originalObject, null, config.exporterJsonSpace), {
fs.writeFileSync(filePath, JSON.stringify(deepTranslationFile, null, config.jsonSpace), {
encoding: 'utf8',

@@ -1152,5 +1246,2 @@ });

// We have to store which nodes were extracted because the plugin might be called multiple times
// by Babel and the state would be lost across calls.
const extractedNodes = new WeakSet();
/**

@@ -1169,9 +1260,10 @@ * Handle the extraction.

const lineNumber = (path.node.loc && path.node.loc.start.line) || '???';
const extractState = state.I18NextExtract;
const collect = (keys) => {
for (const key of keys) {
if (extractedNodes.has(key.nodePath.node)) {
if (extractState.extractedNodes.has(key.nodePath.node)) {
// The node was already extracted. Skip it.
continue;
}
extractedNodes.add(key.nodePath.node);
extractState.extractedNodes.add(key.nodePath.node);
state.I18NextExtract.extractedKeys.push(key);

@@ -1222,2 +1314,8 @@ }

api.assertVersion(7);
// We have to store which nodes were extracted because the visitor might be
// called multiple times by Babel and the state would be lost across calls.
const extractedNodes = new WeakSet();
// This is a cache for the exporter to keep track of the translation files.
// It must remain global and persist across transpiled files.
const exporterCache = createExporterCache();
return {

@@ -1229,2 +1327,4 @@ pre() {

commentHints: [],
extractedNodes,
exporterCache,
};

@@ -1241,3 +1341,3 @@ },

], Array());
exportTranslationKeys(derivedKeys, locale, extractState.config);
exportTranslationKeys(derivedKeys, locale, extractState.config, extractState.exporterCache);
}

@@ -1244,0 +1344,0 @@ },

@@ -855,3 +855,4 @@ 'use strict';

keyAsDefaultValueForDerivedKeys: coalesce(opts.keyAsDefaultValueForDerivedKeys, true),
exporterJsonSpace: coalesce(opts.exporterJsonSpace, 2),
discardOldKeys: coalesce(opts.discardOldKeys, false),
jsonSpace: coalesce(opts.jsonSpace, 2),
};

@@ -862,7 +863,76 @@ }

/**
* Thrown when an error happens during the translations export.
*/
class ExportError extends Error {
}
/**
* This creates a new empty cache for the exporter.
*
* The cache is required by the exporter and is used to merge the translations
* from the original translation file. It will be mutated by the exporter
* and the same instance must be given untouched across export calls.
*/
function createExporterCache() {
return {
originalTranslationFiles: {},
currentTranslationFiles: {},
};
}
/**
* Take a deep translation file and flatten it.
* This is to simplify merging keys later on.
*
* @param deep Deep translation file.
*/
function flattenTranslationFile(deep) {
const result = {};
for (const [k, v] of Object.entries(deep)) {
if (typeof v === 'object' && v !== null) {
// Nested case, we must recurse
const flat = flattenTranslationFile(v);
for (const [flatK, flatV] of Object.entries(flat)) {
result[JSON.stringify([k, ...JSON.parse(flatK)])] = flatV;
}
}
else {
// Leaf, just set the value.
result[JSON.stringify([k])] = v;
}
}
return result;
}
/**
* Take a flat translation file and make it deep.
*
* This is to make a flat translation file ready to be exported to a file.
*
* @param flat Flat translation file as returned by flattenTranslationFile
*/
function unflattenTranslationFile(flat) {
const result = {};
for (const [k, v] of Object.entries(flat)) {
const keyPath = JSON.parse(k);
const cleanKey = keyPath.pop();
const error = new ExportError(`${PLUGIN_NAME}: Couldn't export translations. Key "${keyPath}" ` +
`has a conflict.`);
let obj = result;
for (const p of keyPath) {
const currentValue = obj[p];
if (typeof currentValue === 'string' || currentValue === null) {
throw error;
}
obj = obj[p] = currentValue || {};
}
const currentValue = obj[cleanKey];
if (typeof currentValue === 'object' && currentValue !== null) {
throw error;
}
obj[cleanKey] = v;
}
return result;
}
/**
* Load translation given a file. If the file is not found, default to empty
* object
* object.
* @param filePath Path of the JSON translation file to load.

@@ -880,15 +950,41 @@ */

}
const obj = JSON.parse(content);
return obj;
return flattenTranslationFile(JSON.parse(content));
}
/**
* Exports all given translation keyr as JSON.
* Create a new translationFile with a key added to it.
*
* @param translationFile The translation file to add the key to.
* @param key The translation key to add.
* @param locale Current locale being exported
* @param config Configuration (that will help setting the proper default
* value)
*/
function addKeyToTranslationFile(translationFile, key, locale, config) {
// compute the default value
let defaultValue = config.defaultValue;
const keyAsDefaultValueEnabled = config.keyAsDefaultValue === true ||
(Array.isArray(config.keyAsDefaultValue) &&
config.keyAsDefaultValue.includes(locale));
const keyAsDefaultValueForDerivedKeys = config.keyAsDefaultValueForDerivedKeys;
if (keyAsDefaultValueEnabled &&
(keyAsDefaultValueForDerivedKeys || !key.isDerivedKey)) {
defaultValue = key.cleanKey;
}
return {
[JSON.stringify([...key.keyPath, key.cleanKey])]: defaultValue,
...translationFile,
};
}
/**
* Exports all given translation keys as JSON.
*
* @param keys: translation keys to export
* @param locale: the locale to export
* @param config: plugin configuration
* @param cache: cache instance to use (see createExporterCache)
*/
function exportTranslationKeys(keys, locale, config) {
function exportTranslationKeys(keys, locale, config, cache) {
const keysPerFilepath = {};
for (const key of keys) {
// Figure out in which path each key should go.
const filePath = config.outputPath

@@ -900,38 +996,36 @@ .replace('{{locale}}', locale)

for (const [filePath, keysForFilepath] of Object.entries(keysPerFilepath)) {
let obj = {};
obj = loadTranslationFile(filePath);
const originalObject = obj;
if (!(filePath in cache.originalTranslationFiles)) {
// Cache original translation file so that we don't loose it across babel
// passes.
cache.originalTranslationFiles[filePath] = loadTranslationFile(filePath);
}
let translationFile = cache.currentTranslationFiles[filePath] || {};
for (const k of keysForFilepath) {
// resolve key path
for (const p of k.keyPath) {
let value = obj[p];
if (value === undefined) {
value = obj[p] = {};
}
if (typeof value === 'string' || value === null) {
throw new ExportError(`${PLUGIN_NAME}: Couldn't export translations. Key "${k.key}" ` +
`has conflict.`);
}
obj = value;
}
// The key was already exported.
if (obj[k.cleanKey] !== undefined) {
continue;
}
// Set the default values for the path
let defaultValue = config.defaultValue;
const keyAsDefaultValueEnabled = config.keyAsDefaultValue === true ||
(Array.isArray(config.keyAsDefaultValue) &&
config.keyAsDefaultValue.includes(locale));
const keyAsDefaultValueForDerivedKeys = config.keyAsDefaultValueForDerivedKeys;
if (keyAsDefaultValueEnabled &&
(keyAsDefaultValueForDerivedKeys || !k.isDerivedKey)) {
defaultValue = k.cleanKey;
}
obj[k.cleanKey] = defaultValue;
translationFile = addKeyToTranslationFile(translationFile, k, locale, config);
}
cache.currentTranslationFiles[filePath] = translationFile;
let deepTranslationFile;
if (config.discardOldKeys) {
const originalTranslationFile = cache.originalTranslationFiles[filePath];
const alreadyTranslated = Object.keys(originalTranslationFile)
.filter(k => k in translationFile)
.reduce((accumulator, k) => ({
...accumulator,
[k]: originalTranslationFile[k],
}), {});
deepTranslationFile = unflattenTranslationFile({
...translationFile,
...alreadyTranslated,
});
}
else {
deepTranslationFile = unflattenTranslationFile({
...translationFile,
...cache.originalTranslationFiles[filePath],
});
}
// Finally do the export
const directoryPath = path.dirname(filePath);
fs.mkdirSync(directoryPath, { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(originalObject, null, config.exporterJsonSpace), {
fs.writeFileSync(filePath, JSON.stringify(deepTranslationFile, null, config.jsonSpace), {
encoding: 'utf8',

@@ -1156,5 +1250,2 @@ });

// We have to store which nodes were extracted because the plugin might be called multiple times
// by Babel and the state would be lost across calls.
const extractedNodes = new WeakSet();
/**

@@ -1173,9 +1264,10 @@ * Handle the extraction.

const lineNumber = (path.node.loc && path.node.loc.start.line) || '???';
const extractState = state.I18NextExtract;
const collect = (keys) => {
for (const key of keys) {
if (extractedNodes.has(key.nodePath.node)) {
if (extractState.extractedNodes.has(key.nodePath.node)) {
// The node was already extracted. Skip it.
continue;
}
extractedNodes.add(key.nodePath.node);
extractState.extractedNodes.add(key.nodePath.node);
state.I18NextExtract.extractedKeys.push(key);

@@ -1226,2 +1318,8 @@ }

api.assertVersion(7);
// We have to store which nodes were extracted because the visitor might be
// called multiple times by Babel and the state would be lost across calls.
const extractedNodes = new WeakSet();
// This is a cache for the exporter to keep track of the translation files.
// It must remain global and persist across transpiled files.
const exporterCache = createExporterCache();
return {

@@ -1233,2 +1331,4 @@ pre() {

commentHints: [],
extractedNodes,
exporterCache,
};

@@ -1245,3 +1345,3 @@ },

], Array());
exportTranslationKeys(derivedKeys, locale, extractState.config);
exportTranslationKeys(derivedKeys, locale, extractState.config, extractState.exporterCache);
}

@@ -1248,0 +1348,0 @@ },

@@ -14,3 +14,4 @@ export interface Config {

keyAsDefaultValueForDerivedKeys: boolean;
exporterJsonSpace: string | number;
discardOldKeys: boolean;
jsonSpace: string | number;
}

@@ -17,0 +18,0 @@ /**

import { TranslationKey } from './keys';
import { Config } from './config';
/**
* Thrown when an error happens during the translations export.
*/
export declare class ExportError extends Error {
}
/**
* Exports all given translation keyr as JSON.
* Flat version of TranslationFile. This is mainly used to simplify merging
* translations.
*/
interface FlatTranslationFile {
[keyPathAsJSON: string]: string | null;
}
/**
* An instance of exporter cache.
*
* See createExporterCache for details.
*/
export interface ExporterCache {
originalTranslationFiles: {
[path: string]: FlatTranslationFile;
};
currentTranslationFiles: {
[path: string]: FlatTranslationFile;
};
}
/**
* This creates a new empty cache for the exporter.
*
* The cache is required by the exporter and is used to merge the translations
* from the original translation file. It will be mutated by the exporter
* and the same instance must be given untouched across export calls.
*/
export declare function createExporterCache(): ExporterCache;
/**
* Exports all given translation keys as JSON.
*
* @param keys: translation keys to export
* @param locale: the locale to export
* @param config: plugin configuration
* @param cache: cache instance to use (see createExporterCache)
*/
export default function exportTranslationKeys(keys: TranslationKey[], locale: string, config: Config): void;
export default function exportTranslationKeys(keys: TranslationKey[], locale: string, config: Config, cache: ExporterCache): void;
export {};
import * as BabelCore from '@babel/core';
import * as BabelTypes from '@babel/types';
import { CommentHint } from './comments';
import { ExtractedKey } from './keys';
import { Config } from './config';
import { ExporterCache } from './exporter';
export interface VisitorState {

@@ -14,4 +16,6 @@ file: any;

config: Config;
extractedNodes: WeakSet<BabelTypes.Node>;
exporterCache: ExporterCache;
}
export default function (api: BabelCore.ConfigAPI): BabelCore.PluginObj<VisitorState>;
export {};
{
"name": "babel-plugin-i18next-extract",
"version": "0.1.0-rc",
"version": "0.1.0-rc.1",
"description": "Statically extract translation keys from i18next application.",

@@ -5,0 +5,0 @@ "repository": {

@@ -12,4 +12,18 @@ # babel-plugin-i18next-extract

babel-plugin-i18next-extract is a [Babel Plugin](https://babeljs.io/docs/en/plugins/) that will
traverse your JavaScript/Typescript code in order to find i18next translation keys.
traverse your Javascript/Typescript code in order to find i18next translation keys.
## Features
- ☑️ Keys extraction in [JSON v3 format](https://www.i18next.com/misc/json-format).
- ☑️ Detection of `i18next.t()` function calls.
- ☑️ Full [react-i18next](https://react.i18next.com/) support.
- ☑️ Plurals support.
- ☑️ Contexts support.
- ☑️ Namespace detection.
- ☑️ Disable extraction on a specific file sections or lines using [comment hints](
#comment-hints).
- ☑️ Overwrite namespaces, plurals and contexts on-the-fly using [comment hints](
#comment-hints).
- [… and more?](./CONTRIBUTING.md)
## Installation

@@ -28,3 +42,3 @@

If you already use [Babel](https://babeljs.io), chances are you already have an babel
configuration (e.g. a `.babelrc` file). Just add declare the plugin and you're good to go:
configuration (e.g. a `.babelrc` file). Just declare the plugin and you're good to go:

@@ -40,3 +54,3 @@ ```javascript

You can also specify additional [configuration options](#configuration) to the plugin:
You may want to specify additional [configuration options](#configuration):

@@ -52,3 +66,3 @@ ```javascript

Once you are set up, you can build your app normally or run Babel through [Babel CLI](
Once the plugin is setup, you can build your app normally or run Babel through [Babel CLI](
https://babeljs.io/docs/en/babel-cli):

@@ -64,85 +78,7 @@

Extracted translations should land in the `extractedTranslations/` directory. Magic huh?
Extracted translations should land in the `extractedTranslations/` directory by default.
If you don't have a babel configuration yet, you can follow the [Configure Babel](
https://babeljs.io/docs/en/configuration) documentation to try setting it up.
https://babeljs.io/docs/en/configuration) documentation to get started.
## Usage with create-react-app
[create-react-app](https://github.com/facebook/create-react-app) doesn't let you modify the babel
configuration. Fortunately, it's still possible to use this plugin without ejecting. First of all,
install Babel CLI:
```bash
yarn add --dev @babel/cli
# or
npm add --save-dev @babel/cli
```
Create a minimal `.babelrc` that uses the `react-app` babel preset (DO NOT install it, it's already
shipped with CRA):
```javascript
{
"presets": ["react-app"],
"plugins": ["i18next-extract"]
}
```
You should then be able to extract your translations using the CLI:
```bash
# NODE_ENV must be specified for react-app preset to work properly
NODE_ENV=development yarn run babel -f .babelrc 'src/**/*.{js,jsx,ts,tsx}'
```
To simplify the extraction, you can add a script to your `package.json`:
```javascript
"scripts": {
[…]
"i18n-extract": "NODE_ENV=development babel -f .babelrc 'src/**/*.{js,jsx,ts,tsx}'",
[…]
}
```
And then just run:
```bash
yarn run i18n-extract
# or
npm run i18n-extract
```
## Features
- [x] Translation extraction in [JSON v3 format](https://www.i18next.com/misc/json-format).
- [x] Detection of `i18next.t()` function calls.
- [x] Plural forms support:
- [x] Keys derivation depending on the locale.
- [x] Automatic detection from `i18next.t` function calls.
- [x] Automatic detection from `react-i18next` properties.
- [x] Manual detection from [comment hints](#comment-hints).
- [x] Contexts support:
- [x] Naïve implementation with default contexts.
- [x] Automatic detection from `i18next.t` function calls.
- [x] Automatic detection from `react-i18next` properties.
- [x] Manual detection from [comment hints](#comment-hints).
- [x] [react-i18next](https://react.i18next.com/) support:
- [x] `Trans` component support (with plural forms, contexts and namespaces).
- [x] `useTranslation` hook support (with plural forms, contexts and namespaces).
- [x] `Translation` render prop support (with plural forms, contexts and namespaces).
- [x] Namespace inference from `withTranslation` HOC.
- [x] Namespace inference:
- [x] Depending on the key value.
- [x] Depending on the `t()` function options.
- [x] Depending on the `ns` property in `Translation` render prop.
- [x] Depending on the `ns` attribute in the `Trans` component.
- [x] Explicitely disable extraction on a specific file sections or lines using [comment hints](#comment-hints).
- [ ] [… and more?](./CONTRIBUTING.md)
## Configuration

@@ -164,3 +100,4 @@

| keyAsDefaultValueForDerivedKeys | `boolean` | If false and `keyAsDefaultValue` is enabled, don't use derived keys (plural forms or contexts) as default value. `defaultValue` will be used instead. | `true` |
| exporterJsonSpace | `number` | Number of indentation space to use in extracted JSON files. | 2 |
| discardOldKeys | `boolean` | When set to `true`, keys that no longer exist are removed from the JSON files. By default, new keys will be added to the JSON files and never removed. | `false` |
| jsonSpace | `number` | Number of indentation space to use in extracted JSON files. | `2` |

@@ -241,7 +178,58 @@ ## Comment hints

## Usage with create-react-app
[create-react-app](https://github.com/facebook/create-react-app) doesn't let you modify the babel
configuration. Fortunately, it's still possible to use this plugin without ejecting. First of all,
install Babel CLI:
```bash
yarn add --dev @babel/cli
# or
npm add --save-dev @babel/cli
```
Create a minimal `.babelrc` that uses the `react-app` babel preset (DO NOT install it, it's already
shipped with CRA):
```javascript
{
"presets": ["react-app"],
"plugins": ["i18next-extract"]
}
```
You should then be able to extract your translations using the CLI:
```bash
# NODE_ENV must be specified for react-app preset to work properly
NODE_ENV=development yarn run babel -f .babelrc 'src/**/*.{js,jsx,ts,tsx}'
```
To simplify the extraction, you can add a script to your `package.json`:
```javascript
"scripts": {
[…]
"i18n-extract": "NODE_ENV=development babel -f .babelrc 'src/**/*.{js,jsx,ts,tsx}'",
[…]
}
```
And then just run:
```bash
yarn run i18n-extract
# or
npm run i18n-extract
```
## Gotchas
The plugin tries to be smart, but can't do magic. i18next has a runtime unlike this plugin which
must guess everything statically. For instance, you may want to disable extraction on dynamic
keys:
The plugin tries to be a little smart, but can't do magic. i18next has a runtime unlike this
plugin which must guess everything statically. For instance, you may want to disable extraction
on dynamic keys:

@@ -248,0 +236,0 @@ ```javascript

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