Current Path : /var/www/ooareogundevinitiative/wp-content/plugins/foogallery/gutenberg/scripts/ |
Current File : /var/www/ooareogundevinitiative/wp-content/plugins/foogallery/gutenberg/scripts/translate.js |
/** * Credits: * * babel-gettext-extractor * https://github.com/getsentry/babel-gettext-extractor * * The MIT License (MIT) * * Copyright (c) 2015 jruchaud * Copyright (c) 2015 Sentry * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * External dependencies */ const { po } = require( 'gettext-parser' ); const { pick, reduce, uniq, forEach, sortBy, isEqual, merge, isEmpty, } = require( 'lodash' ); const { relative, sep } = require( 'path' ); const { writeFileSync } = require( 'fs' ); /** * Default output headers if none specified in plugin options. * * @type {Object} */ const DEFAULT_HEADERS = { 'content-type': 'text/plain; charset=UTF-8', 'x-generator': 'babel-plugin-wp-i18n', }; /** * Default functions to parse if none specified in plugin options. Each key is * a CallExpression name (or member name) and the value an array corresponding * to translation key argument position. * * @type {Object} */ const DEFAULT_FUNCTIONS = { __: [ 'msgid' ], _n: [ 'msgid', 'msgid_plural' ], _x: [ 'msgid', 'msgctxt' ], _nx: [ 'msgid', 'msgctxt', 'msgid_plural' ], }; /** * Default file output if none specified. * * @type {string} */ const DEFAULT_OUTPUT = 'gettext.pot'; /** * Set of keys which are valid to be assigned into a translation object. * * @type {string[]} */ const VALID_TRANSLATION_KEYS = [ 'msgid', 'msgid_plural', 'msgctxt' ]; /** * Regular expression matching translator comment value. * * @type {RegExp} */ const REGEXP_TRANSLATOR_COMMENT = /^\s*translators:\s*([\s\S]+)/im; /** * Given an argument node (or recursed node), attempts to return a string * represenation of that node's value. * * @param {Object} node AST node. * * @returns {string} String value. */ function getNodeAsString( node ) { switch ( node.type ) { case 'BinaryExpression': return getNodeAsString( node.left ) + getNodeAsString( node.right ); case 'StringLiteral': return node.value; default: return ''; } } /** * Returns translator 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} Translator comment. */ function getTranslatorComment( 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 getTranslatorComment( parentPath, _originalNodeLine ); } } /** * 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 -1 !== VALID_TRANSLATION_KEYS.indexOf( key ); } /** * Given two translation objects, returns true if valid translation keys match, * or false otherwise. * * @param {Object} a First translation object. * @param {Object} b Second translation object. * * @returns {boolean} Whether valid translation keys match. */ function isSameTranslation( a, b ) { return isEqual( pick( a, VALID_TRANSLATION_KEYS ), pick( b, VALID_TRANSLATION_KEYS ) ); } module.exports = function() { const strings = {}; let nplurals = 2, baseData; return { visitor: { CallExpression( path, state ) { const { callee } = path.node; // Determine function name by direct invocation or property name let name; if ( 'MemberExpression' === callee.type ) { name = callee.property.name; } else { name = callee.name; } // Skip unhandled functions const functionKeys = ( state.opts.functions || DEFAULT_FUNCTIONS )[ name ]; if ( ! functionKeys ) { return; } // Assign translation keys by argument position const translation = path.node.arguments.reduce( ( memo, arg, i ) => { const key = functionKeys[ i ]; if ( isValidTranslationKey( key ) ) { memo[ key ] = getNodeAsString( arg ); } return memo; }, {} ); // Can only assign translation with usable msgid if ( ! translation.msgid ) { return; } // At this point we assume we'll save data, so initialize if // we haven't already if ( ! baseData ) { baseData = { charset: 'utf-8', headers: state.opts.headers || DEFAULT_HEADERS, translations: { '': { '': { msgid: '', msgstr: [], }, }, }, }; for ( const key in baseData.headers ) { baseData.translations[ '' ][ '' ].msgstr.push( `${ key }: ${ baseData.headers[ key ] };\n` ); } // Attempt to exract nplurals from header const pluralsMatch = ( baseData.headers[ 'plural-forms' ] || '' ).match( /nplurals\s*=\s*(\d+);/ ); if ( pluralsMatch ) { nplurals = pluralsMatch[ 1 ]; } } // Create empty msgstr or array of empty msgstr by nplurals if ( translation.msgid_plural ) { translation.msgstr = Array.from( Array( nplurals ) ).map( () => '' ); } else { translation.msgstr = ''; } // Assign file reference comment, ensuring consistent pathname // reference between Win32 and POSIX const { filename } = this.file.opts; const pathname = relative( '.', filename ) .split( sep ) .join( '/' ); translation.comments = { reference: pathname + ':' + path.node.loc.start.line, }; // If exists, also assign translator comment const translator = getTranslatorComment( path ); if ( translator ) { translation.comments.translator = translator; } // Create context grouping for translation if not yet exists const { msgctxt = '', msgid } = translation; if ( ! strings[ filename ].hasOwnProperty( msgctxt ) ) { strings[ filename ][ msgctxt ] = {}; } strings[ filename ][ msgctxt ][ msgid ] = translation; }, Program: { enter() { strings[ this.file.opts.filename ] = {}; }, exit( path, state ) { const { filename } = this.file.opts; if ( isEmpty( strings[ filename ] ) ) { delete strings[ filename ]; return; } // Sort translations by filename for deterministic output const files = Object.keys( strings ).sort(); // Combine translations from each file grouped by context const translations = reduce( files, ( memo, file ) => { for ( const context in strings[ file ] ) { // Within the same file, sort translations by line const sortedTranslations = sortBy( strings[ file ][ context ], 'comments.reference' ); forEach( sortedTranslations, translation => { const { msgctxt = '', msgid } = translation; if ( ! memo.hasOwnProperty( msgctxt ) ) { memo[ msgctxt ] = {}; } // Merge references if translation already exists if ( isSameTranslation( translation, memo[ msgctxt ][ msgid ] ) ) { translation.comments.reference = uniq( [ memo[ msgctxt ][ msgid ].comments.reference, translation.comments.reference, ] .join( '\n' ) .split( '\n' ) ).join( '\n' ); } memo[ msgctxt ][ msgid ] = translation; } ); } return memo; }, {} ); // Merge translations from individual files into headers const data = merge( {}, baseData, { translations } ); // Ideally we could wait until Babel has finished parsing // all files or at least asynchronously write, but the // Babel loader doesn't expose these entry points and async // write may hit file lock (need queue). const compiled = po.compile( data ); writeFileSync( state.opts.output || DEFAULT_OUTPUT, compiled ); this.hasPendingWrite = false; }, }, }, }; }; module.exports.getNodeAsString = getNodeAsString; module.exports.getTranslatorComment = getTranslatorComment; module.exports.isValidTranslationKey = isValidTranslationKey; module.exports.isSameTranslation = isSameTranslation;