@automattic/babel-plugin-i18n-calypso
Advanced tools
Comparing version 1.0.3 to 1.1.0
{ | ||
"name": "@automattic/babel-plugin-i18n-calypso", | ||
"version": "1.0.3", | ||
"description": "A Babel plugin to generate a POT file for translate calls", | ||
"main": "src/index.js", | ||
"dependencies": { | ||
"@babel/runtime": "^7.0.0", | ||
"gettext-parser": "^1.3.1", | ||
"lodash": "^4.17.10" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.0.0", | ||
"@babel/traverse": "^7.0.0" | ||
}, | ||
"peerDependencies": { | ||
"@babel/core": "^7.0.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"keywords": [], | ||
"author": "Alex Kirk <alex.kirk@automattic.com> (https://automattic.com)", | ||
"license": "GPL-2.0+", | ||
"gitHead": "d63600c280795bbf8d4dc45eb0702b0a22961ca9" | ||
"name": "@automattic/babel-plugin-i18n-calypso", | ||
"version": "1.1.0", | ||
"description": "A Babel plugin to generate a POT file for translate calls", | ||
"main": "src/index.js", | ||
"dependencies": { | ||
"gettext-parser": "^4.0.0", | ||
"lodash": "^4.17.14" | ||
}, | ||
"peerDependencies": { | ||
"@babel/core": "^7.0.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Automattic/wp-calypso.git", | ||
"directory": "packages/babel-plugin-i18n-calypso" | ||
}, | ||
"keywords": [], | ||
"author": "Automattic Inc.", | ||
"license": "GPL-2.0-or-later", | ||
"gitHead": "e50bdc06da220ce237bf309703102dec8673436f" | ||
} |
205
src/index.js
/** | ||
* Extract i18n-calypso `translate` calls into a POT file. | ||
* Extract i18n-calypso `translate` and @wordpress/i18n `__`, `_n`, `_x`, `_nx` | ||
* calls into a POT file. | ||
* | ||
@@ -32,3 +33,2 @@ * Credits: | ||
* | ||
* @format | ||
*/ | ||
@@ -40,3 +40,3 @@ | ||
const { po } = require( 'gettext-parser' ); | ||
const { merge, isEmpty } = require( 'lodash' ); | ||
const { merge, isEmpty, forEach } = require( 'lodash' ); | ||
const { relative, sep } = require( 'path' ); | ||
@@ -48,3 +48,3 @@ const { existsSync, mkdirSync, writeFileSync } = require( 'fs' ); | ||
* | ||
* @type {Object} | ||
* @type {object} | ||
*/ | ||
@@ -64,8 +64,81 @@ const DEFAULT_HEADERS = { | ||
/** | ||
* The order of arguments in translate functions. | ||
* | ||
* @type {object} | ||
*/ | ||
const DEFAULT_FUNCTIONS_ARGUMENTS_ORDER = { | ||
__: [], | ||
_n: [ 'msgid_plural' ], | ||
_x: [ 'msgctxt' ], | ||
_nx: [ 'msgid_plural', null, 'msgctxt' ], | ||
translate: [ 'msgid_plural', 'options_object' ], | ||
}; | ||
/** | ||
* Regular expression matching translator comment value. | ||
* | ||
* @type {RegExp} | ||
*/ | ||
const REGEXP_TRANSLATOR_COMMENT = /^\s*translators:\s*([\s\S]+)/im; | ||
/** | ||
* Returns the extracted comment for a given AST traversal path if one exists. | ||
* | ||
* @param {object} path Traversal path. | ||
* @param {number} _originalNodeLine Private: In recursion, line number of | ||
* the original node passed. | ||
* | ||
* @returns {?string} Extracted comment. | ||
*/ | ||
function getExtractedComment( path, _originalNodeLine ) { | ||
const { node, parent, parentPath } = path; | ||
// Assign original node line so we can keep track in recursion whether a | ||
// matched comment or parent occurs on the same or previous line | ||
if ( ! _originalNodeLine ) { | ||
_originalNodeLine = node.loc.start.line; | ||
} | ||
let comment; | ||
forEach( node.leadingComments, commentNode => { | ||
const { line } = commentNode.loc.end; | ||
if ( line < _originalNodeLine - 1 || line > _originalNodeLine ) { | ||
return; | ||
} | ||
const match = commentNode.value.match( REGEXP_TRANSLATOR_COMMENT ); | ||
if ( match ) { | ||
// Extract text from matched translator prefix | ||
comment = match[ 1 ] | ||
.split( '\n' ) | ||
.map( text => text.trim() ) | ||
.join( ' ' ); | ||
// False return indicates to Lodash to break iteration | ||
return false; | ||
} | ||
} ); | ||
if ( comment ) { | ||
return comment; | ||
} | ||
if ( ! parent || ! parent.loc || ! parentPath ) { | ||
return; | ||
} | ||
// Only recurse as long as parent node is on the same or previous line | ||
const { line } = parent.loc.start; | ||
if ( line >= _originalNodeLine - 1 && line <= _originalNodeLine ) { | ||
return getExtractedComment( parentPath, _originalNodeLine ); | ||
} | ||
} | ||
/** | ||
* Given an argument node (or recursed node), attempts to return a string | ||
* represenation of that node's value. | ||
* | ||
* @param {Object} node AST node. | ||
* @param {object} node AST node. | ||
* | ||
* @return {string} String value. | ||
* @returns {string} String value. | ||
*/ | ||
@@ -84,2 +157,7 @@ function getNodeAsString( node ) { | ||
case 'TemplateLiteral': | ||
return ( node.quasis || [] ).reduce( ( string, element ) => { | ||
return ( string += element.value.cooked ); | ||
}, '' ); | ||
default: | ||
@@ -90,9 +168,47 @@ return ''; | ||
/** | ||
* Returns true if the specified funciton name is valid translate function name | ||
* | ||
* @param {string} name Function name to test. | ||
* | ||
* @returns {boolean} Whether function name is valid translate function name. | ||
*/ | ||
function isValidFunctionName( name ) { | ||
return Object.keys( DEFAULT_FUNCTIONS_ARGUMENTS_ORDER ).includes( name ); | ||
} | ||
/** | ||
* Returns true if the specified key of a function is valid for assignment in | ||
* the translation object. | ||
* | ||
* @param {string} key Key to test. | ||
* | ||
* @returns {boolean} Whether key is valid for assignment. | ||
*/ | ||
function isValidTranslationKey( key ) { | ||
return Object.values( DEFAULT_FUNCTIONS_ARGUMENTS_ORDER ).some( args => args.includes( key ) ); | ||
} | ||
module.exports = function() { | ||
let strings = {}, | ||
nplurals = 2, | ||
baseData; | ||
baseData, | ||
functions = { ...DEFAULT_FUNCTIONS_ARGUMENTS_ORDER }; | ||
return { | ||
visitor: { | ||
ImportDeclaration( path ) { | ||
// If `translate` from `i18n-calypso` is imported with an | ||
// alias, set the specified alias as a reference to translate. | ||
if ( 'i18n-calypso' !== path.node.source.value ) { | ||
return; | ||
} | ||
path.node.specifiers.forEach( specifier => { | ||
if ( specifier.imported && 'translate' === specifier.imported.name && specifier.local ) { | ||
functions[ specifier.local.name ] = functions.translate; | ||
} | ||
} ); | ||
}, | ||
CallExpression( path, state ) { | ||
@@ -104,7 +220,7 @@ const { callee } = path.node; | ||
if ( 'MemberExpression' === callee.type ) { | ||
name = callee.property.name; | ||
name = callee.property.loc ? callee.property.loc.identifierName : callee.property.name; | ||
} else { | ||
name = callee.name; | ||
name = callee.loc ? callee.loc.identifierName : callee.name; | ||
} | ||
if ( 'translate' !== name ) { | ||
if ( ! isValidFunctionName( name ) ) { | ||
return; | ||
@@ -155,14 +271,11 @@ } | ||
if ( path.node.arguments.length > i ) { | ||
const msgid_plural = getNodeAsString( path.node.arguments[ i ] ); | ||
if ( msgid_plural.length ) { | ||
translation.msgid_plural = msgid_plural; | ||
i++; | ||
// For plurals, create an empty mgstr array | ||
translation.msgstr = Array.from( Array( nplurals ) ).map( () => '' ); | ||
} | ||
// If exists, also assign translator comment | ||
const translator = getExtractedComment( path ); | ||
if ( translator ) { | ||
translation.comments.extracted = translator; | ||
} | ||
const { filename } = this.file.opts; | ||
const pathname = relative( '.', filename ) | ||
const base = state.opts.base || '.'; | ||
const pathname = relative( base, filename ) | ||
.split( sep ) | ||
@@ -172,21 +285,33 @@ .join( '/' ); | ||
if ( | ||
path.node.arguments.length > i && | ||
'ObjectExpression' === path.node.arguments[ i ].type | ||
) { | ||
for ( const j in path.node.arguments[ i ].properties ) { | ||
if ( 'ObjectProperty' === path.node.arguments[ i ].properties[ j ].type ) { | ||
switch ( path.node.arguments[ i ].properties[ j ].key.name ) { | ||
case 'context': | ||
translation.msgctxt = path.node.arguments[ i ].properties[ j ].value.value; | ||
break; | ||
case 'comment': | ||
translation.comments.extracted = | ||
path.node.arguments[ i ].properties[ j ].value.value; | ||
break; | ||
} | ||
const functionKeys = state.opts.functions || functions[ name ]; | ||
if ( functionKeys ) { | ||
path.node.arguments.slice( i ).forEach( ( arg, index ) => { | ||
const key = functionKeys[ index ]; | ||
if ( 'ObjectExpression' === arg.type ) { | ||
arg.properties.forEach( property => { | ||
if ( 'ObjectProperty' !== property.type ) { | ||
return; | ||
} | ||
if ( 'context' === property.key.name ) { | ||
translation.msgctxt = property.value.value; | ||
} | ||
if ( 'comment' === property.key.name ) { | ||
translation.comments.extracted = property.value.value; | ||
} | ||
} ); | ||
} else if ( isValidTranslationKey( key ) ) { | ||
translation[ key ] = getNodeAsString( arg ); | ||
} | ||
} | ||
} ); | ||
} | ||
// For plurals, create an empty mgstr array | ||
if ( ( translation.msgid_plural || '' ).length ) { | ||
translation.msgstr = Array.from( Array( nplurals ) ).map( () => '' ); | ||
} | ||
// Create context grouping for translation if not yet exists | ||
@@ -200,3 +325,7 @@ const { msgctxt = '', msgid } = translation; | ||
strings[ msgctxt ][ msgid ] = translation; | ||
} else { | ||
} else if ( | ||
! strings[ msgctxt ][ msgid ].comments.reference.includes( | ||
translation.comments.reference | ||
) | ||
) { | ||
strings[ msgctxt ][ msgid ].comments.reference += '\n' + translation.comments.reference; | ||
@@ -208,2 +337,3 @@ } | ||
strings = {}; | ||
functions = { ...DEFAULT_FUNCTIONS_ARGUMENTS_ORDER }; | ||
}, | ||
@@ -223,3 +353,4 @@ exit( path, state ) { | ||
const { filename } = this.file.opts; | ||
const pathname = relative( '.', filename ) | ||
const base = state.opts.base || '.'; | ||
const pathname = relative( base, filename ) | ||
.split( sep ) | ||
@@ -226,0 +357,0 @@ .join( '-' ); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found
Found 1 instance in 1 package
3
0
0
1
1
21
1
26510
320
2
+ Addedcontent-type@1.0.5(transitive)
+ Addedgettext-parser@4.2.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removed@babel/runtime@^7.0.0
- Removed@babel/runtime@7.24.5(transitive)
- Removedgettext-parser@1.4.0(transitive)
- Removedregenerator-runtime@0.14.1(transitive)
Updatedgettext-parser@^4.0.0
Updatedlodash@^4.17.14