import type * as tt from '@babel/types' ; import type { NodePath } from '@babel/traverse' ; import Logger from '../util/logger' ; import { needWrap } from '../util/helper' ; import { iTransInfo , iI18nConf , iWordInfo } from '../types' ; type TLQuasisExpressions = ( tt . TemplateElement | tt . Expression | tt . TSType ) [ ] ; const i18nPlugin = ( transInfo , i18nConf ) : any => { const JSX_WRAPPER = 'trans' ; const { wrapCharacter , wrapperFuncName : T_WRAPPER } = i18nConf ; param ME param names const getName = ( ME , names ) => { let { property } = ME ; const { object } = ME ; property = property as tt . Identifier ; names . push ( property . name ) ; if ( object . type === 'MemberExpression' ) { getName ( object , names ) ; } else if ( object . type === 'Identifier' ) { names . push ( ( object as tt . Identifier ) . name ) ; } } ; const plugin = ( { types : t } ) => { const combine = ( value ) => Object . assign ( t . StringLiteral ( value ) , { extra : { raw : `\'${ value . replace ( "'" , "\\'" ) }\'` , rawValue : value , } , } ) ; const replaceLineBreak = ( value ) => { return value . replace ( /\n/g , ' ' ) . trim ( ) ; } ; const collectWordingInfo = ( value , path , thisArg , list ) => { const filename = thisArg . filename || thisArg . file . opts . filename || 'unknown' ; const line = path . node . loc ?. start ?. line ?? 0 ; const wordInfo = { key : value , filename , line , } ; list . push ( wordInfo ) ; } ; return { visitor : { StringLiteral ( path ) { let { value } = path . node ; value = replaceLineBreak ( value ) ; if ( needWrap ( wrapCharacter , value ) ) { let newNode = t . CallExpression ( t . Identifier ( T_WRAPPER ) , [ combine ( value ) , ] ) ; if ( path . parentPath ?. node . type === 'JSXAttribute' ) { newNode = t . JSXExpressionContainer ( newNode ) ; } path . replaceWith ( newNode ) ; transInfo . needT = true ; transInfo . wrapCount += 1 ; collectWordingInfo ( value , path as NodePath , this , transInfo . wordInfoArray ) ; } } , TemplateLiteral ( path ) { if ( path . node . quasis . every ( ( word ) => ! needWrap ( wrapCharacter , word . value . raw ) ) ) { return ; } const quasisExpressionsList = ( [ ] . concat ( path . node . quasis as [ ] , path . node . expressions as [ ] ) as TLQuasisExpressions ) . sort ( ( a , b ) => { const aStart = a . start ?? 0 ; const bStart = b . start ?? 0 ; return aStart - bStart ; } ) ; let value = '' ; let CEIndex = 0 ; let BEIndex = 0 ; let LEIndex = 0 ; const variableList = [ ] ; quasisExpressionsList . forEach ( ( templateLiteralItem ) => { const variable = { } ; switch ( templateLiteralItem . type ) { case 'TemplateElement' : value += `${ replaceLineBreak ( templateLiteralItem . value . cooked ?? '' ) }` ; break ; case 'Identifier' : { const identifierName = templateLiteralItem . name ; variable . type = 'Identifier' ; variable . key = identifierName ; variable . value = templateLiteralItem ; variableList . push ( variable ) ; value += `{{${ identifierName }}}` ; break ; } case 'CallExpression' : { const { type } = templateLiteralItem . callee ; let callExpressionName = '' ; if ( type === 'Identifier' ) { callExpressionName = ( templateLiteralItem . callee as tt . Identifier ) . name ; } else if ( type === 'MemberExpression' ) { callExpressionName = ( ( templateLiteralItem . callee as tt . MemberExpression ) . property as tt . Identifier ) . name ; } variable . type = 'CallExpression' ; variable . key = callExpressionName ; variable . value = templateLiteralItem ; variableList . push ( variable ) ; value += `{{${ callExpressionName }}}` ; break ; } case 'MemberExpression' : { let memberExpressionName ; switch ( templateLiteralItem . property . type ) { case 'Identifier' : memberExpressionName = ( templateLiteralItem . property as tt . Identifier ) . name ; break ; case 'StringLiteral' : memberExpressionName = ( templateLiteralItem . property as tt . StringLiteral ) . value ; break ; case 'NumericLiteral' : memberExpressionName = ( templateLiteralItem . property as tt . NumericLiteral ) . value . toString ( ) ; break ; case 'MemberExpression' : memberExpressionName = ( templateLiteralItem . property . property as tt . Identifier ) . name ; break ; case 'BinaryExpression' : memberExpressionName = 'BinaryExpression' ; break ; default : break ; } variable . type = 'MemberExpression' ; variable . key = memberExpressionName ; variable . value = templateLiteralItem ; variableList . push ( variable ) ; value += `{{${ memberExpressionName }}}` ; break ; } case 'ConditionalExpression' : { CEIndex += 1 ; variable . type = 'ConditionalExpression' ; variable . key = `ConditionalExpression${ CEIndex }` ; variable . value = templateLiteralItem ; variableList . push ( variable ) ; value += `{{${ variable . key }}}` ; break ; } case 'BinaryExpression' : { BEIndex += 1 ; variable . type = 'BinaryExpression' ; variable . key = `BinaryExpression${ BEIndex }` ; variable . value = templateLiteralItem ; variableList . push ( variable ) ; value += `{{${ variable . key }}}` ; break ; } case 'LogicalExpression' : { LEIndex += 1 ; variable . type = 'LogicalExpression' ; variable . key = `LogicalExpression${ LEIndex }` ; variable . value = templateLiteralItem ; variableList . push ( variable ) ; value += `{{${ variable . key }}}` ; break ; } default : { const thisArgs = this as any ; const filename = thisArgs . filename || thisArgs . file . opts . filename || 'unknown' ; const line = path . node . loc ?. start ?. line ?? 0 ; Logger . appendFile ( `[${ filename }][${ line }]: ${ templateLiteralItem . type } 未处理` ) ; break ; } } } ) ; const objArray = [ ] ; variableList . map ( ( item ) => { let obj ; switch ( item . type ) { case 'Identifier' : { obj = t . objectProperty ( t . Identifier ( item . key ) , item . value as tt . Identifier ) ; obj . shorthand = true ; break ; } case 'CallExpression' : obj = t . objectProperty ( t . Identifier ( item . key ) , item . value as tt . CallExpression ) ; break ; case 'MemberExpression' : obj = t . objectProperty ( t . Identifier ( item . key ) , item . value as tt . MemberExpression ) ; break ; case 'ConditionalExpression' : obj = t . objectProperty ( t . Identifier ( item . key ) , item . value as tt . ConditionalExpression ) ; break ; case 'BinaryExpression' : obj = t . objectProperty ( t . Identifier ( item . key ) , item . value as tt . BinaryExpression ) ; break ; case 'LogicalExpression' : obj = t . objectProperty ( t . Identifier ( item . key ) , item . value as tt . LogicalExpression ) ; break ; default : break ; } objArray . push ( obj ) ; } ) ; let newNode ; if ( objArray . length > 0 ) { newNode = t . CallExpression ( t . Identifier ( T_WRAPPER ) , [ combine ( value ) , t . ObjectExpression ( objArray ) , ] ) ; } else { newNode = t . CallExpression ( t . Identifier ( T_WRAPPER ) , [ combine ( value ) , ] ) ; } path . replaceWith ( newNode ) ; transInfo . needT = true ; transInfo . wrapCount += 1 ; collectWordingInfo ( value , path as NodePath , this , transInfo . wordInfoArray ) ; } , JSXText ( path ) { const { node } = path ; const { value } = node ; if ( needWrap ( wrapCharacter , value ) ) { let newNode ; if ( i18nConf . jsx2Trans ) { newNode = t . jsxElement ( t . jsxOpeningElement ( t . jsxIdentifier ( JSX_WRAPPER ) , [ ] ) , t . jsxClosingElement ( t . jsxIdentifier ( JSX_WRAPPER ) ) , [ t . jsxText ( value ) ] , true ) ; } else { newNode = t . CallExpression ( t . Identifier ( T_WRAPPER ) , [ combine ( replaceLineBreak ( value ) ) , ] ) ; newNode = t . JSXExpressionContainer ( newNode ) ; } path . replaceWith ( newNode ) ; transInfo . needTrans = true ; transInfo . wrapCount += 1 ; collectWordingInfo ( replaceLineBreak ( node . value . trim ( ) ) , path as NodePath , this , transInfo . wordInfoArray ) ; } } , CallExpression ( path ) { switch ( path . node . callee . type ) { case 'Identifier' : { const { name } = path . node . callee as tt . Identifier ; if ( name === T_WRAPPER ) { path . node . arguments . filter ( ( arg ) => arg . type === 'StringLiteral' ) . map ( ( sl ) => { const node = sl as tt . StringLiteral ; collectWordingInfo ( node . value . trim ( ) , path as NodePath , this , transInfo . wordInfoArray ) ; } ) ; path . skip ( ) ; } break ; } case 'MemberExpression' : { const excludeFuncName = i18nConf . parsedExcludeWrapperFuncName ; if ( excludeFuncName . length > 0 ) { const names = [ ] ; const me = path . node . callee as tt . MemberExpression ; getName ( me , names ) ; const MEName = names . reverse ( ) . join ( '.' ) ; if ( excludeFuncName . includes ( MEName ) ) { path . skip ( ) ; } } break ; } default : break ; } } , ImportDeclaration ( path ) { if ( path . node . source . extra ?. raw === i18nConf . parsedImportKey ) { transInfo . needImport = false ; } } , } , } ; } ; return plugin ; } ; export default i18nPlugin ;