You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
would be extracted as I&apros;m Cielquan and not I'm Cielquan, because i18next-parser currently does not respect the shouldUnescape prop of the <Trans /> component from react-i18next. I made a custom lexer which does.
Motivation
Because I currently do not have the time to make a PR I wanted to at least throw the code out into the wild for others to use or someone else to make a PR.
Example
I added the this.unescapeFn class property and the unescapeChars function. I also updated the jsxExtractor function for the part where <Trans /> components are handled.
This adds react-i18next as a dependency.
classJsxLexerextendsJavascriptLexer{constructor(options={}){super(options);this.transSupportBasicHtmlNodes=options.transSupportBasicHtmlNodes||false;this.transKeepBasicHtmlNodesFor=options.transKeepBasicHtmlNodesFor||["br","strong","i","p",];this.omitAttributes=[this.attr,"ns","defaults"];this.unescapeFn=undefined;}extract(content,filename="__default.jsx"){constkeys=[];constparseCommentNode=this.createCommentNodeParser();constparseTree=(node)=>{letentry;parseCommentNode(keys,node,content);switch(node.kind){casets.SyntaxKind.CallExpression:
entry=this.expressionExtractor.call(this,node);break;casets.SyntaxKind.TaggedTemplateExpression:
entry=this.taggedTemplateExpressionExtractor.call(this,node);break;casets.SyntaxKind.JsxElement:
entry=this.jsxExtractor.call(this,node,content);break;casets.SyntaxKind.JsxSelfClosingElement:
entry=this.jsxExtractor.call(this,node,content);break;}if(entry){keys.push(entry);}node.forEachChild(parseTree);};constsourceFile=ts.createSourceFile(filename,content,ts.ScriptTarget.Latest);parseTree(sourceFile);constkeysWithNamespace=this.setNamespaces(keys);constkeysWithPrefixes=this.setKeyPrefixes(keysWithNamespace);returnkeysWithPrefixes;}unescapeChars(escapedString){if(this.unescapeFn===undefined)this.unescapeFn=require("react-i18next").getDefaults().unescape;returnthis.unescapeFn(escapedString);}jsxExtractor(node,sourceText){consttagNode=node.openingElement||node;constgetPropValue=(node,attributeName)=>{constattribute=node.attributes.properties.find((attr)=>attr.name!==undefined&&attr.name.text===attributeName,);if(!attribute){returnundefined;}if(attribute.initializer.expression?.kind===ts.SyntaxKind.Identifier){this.emit("warning",`Namespace is not a string literal: ${attribute.initializer.expression.text}`,);returnundefined;}returnattribute.initializer.expression
? attribute.initializer.expression.text
: attribute.initializer.text;};constgetKey=(node)=>getPropValue(node,this.attr);if(tagNode.tagName.text==="Trans"){constentry={};entry.key=getKey(tagNode);constnamespace=getPropValue(tagNode,"ns");if(namespace){entry.namespace=namespace;}tagNode.attributes.properties.forEach((property)=>{if(property.kind===ts.SyntaxKind.JsxSpreadAttribute){this.emit("warning",`Component attribute is a JSX spread attribute : ${property.expression.text}`,);return;}if(this.omitAttributes.includes(property.name.text)){return;}if(property.initializer){if(property.initializer.expression){if(property.initializer.expression.kind===ts.SyntaxKind.TrueKeyword){entry[property.name.text]=true;}elseif(property.initializer.expression.kind===ts.SyntaxKind.FalseKeyword){entry[property.name.text]=false;}else{entry[property.name.text]=`{${property.initializer.expression.text}}`;}}else{entry[property.name.text]=property.initializer.text;}}elseentry[property.name.text]=true;});constdefaultsProp=getPropValue(tagNode,"defaults");letdefaultValue=defaultsProp||this.nodeToString.call(this,node,sourceText);if(entry.shouldUnescape===true){defaultValue=this.unescapeChars(defaultValue);}if(defaultValue!==""){entry.defaultValue=defaultValue;if(!entry.key){entry.key=entry.defaultValue;}}returnentry.key ? entry : null;}elseif(tagNode.tagName.text==="Interpolate"){constentry={};entry.key=getKey(tagNode);returnentry.key ? entry : null;}}nodeToString(node,sourceText){constchildren=this.parseChildren.call(this,node.children,sourceText);constelemsToString=(children)=>children.map((child,index)=>{switch(child.type){case"js":
case"text":
returnchild.content;case"tag":
constuseTagName=child.isBasic&&this.transSupportBasicHtmlNodes&&this.transKeepBasicHtmlNodesFor.includes(child.name);constelementName=useTagName ? child.name : index;constchildrenString=elemsToString(child.children);returnchildrenString||!(useTagName&&child.selfClosing)
? `<${elementName}>${childrenString}</${elementName}>`
: `<${elementName} />`;default:
thrownewError("Unknown parsed content: "+child.type);}}).join("");returnelemsToString(children);}parseChildren(children=[],sourceText){returnchildren.map((child)=>{if(child.kind===ts.SyntaxKind.JsxText){return{type: "text",content: child.text.replace(/(^(\n|\r)\s*)|((\n|\r)\s*$)/g,"").replace(/(\n|\r)\s*/g," "),};}elseif(child.kind===ts.SyntaxKind.JsxElement||child.kind===ts.SyntaxKind.JsxSelfClosingElement){constelement=child.openingElement||child;constname=element.tagName.escapedText;constisBasic=!element.attributes.properties.length;return{type: "tag",children: this.parseChildren(child.children,sourceText),
name,
isBasic,selfClosing: child.kind===ts.SyntaxKind.JsxSelfClosingElement,};}elseif(child.kind===ts.SyntaxKind.JsxExpression){// strip empty expressionsif(!child.expression){return{type: "text",content: "",};}// simplify trivial expressions, like TypeScript typecastsif(child.expression.kind===ts.SyntaxKind.AsExpression){child=child.expression;}if(child.expression.kind===ts.SyntaxKind.StringLiteral){return{type: "text",content: child.expression.text,};}// strip properties from ObjectExpressions// annoying (and who knows how many other exceptions we'll need to write) but necessaryelseif(child.expression.kind===ts.SyntaxKind.ObjectLiteralExpression){// i18next-react only accepts two props, any random single prop, and a format prop// for our purposes, format prop is always ignoredletnonFormatProperties=child.expression.properties.filter((prop)=>prop.name.text!=="format",);// more than one property throw a warning in i18next-react, but still works as a keyif(nonFormatProperties.length>1){this.emit("warning",`The passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.`,);return{type: "text",content: "",};}return{type: "js",content: `{{${nonFormatProperties[0].name.text}}}`,};}// slice on the expression so that we ignore comments around itreturn{type: "js",content: `{${sourceText.slice(child.expression.pos,child.expression.end)}}`,};}else{thrownewError("Unknown ast element when parsing jsx: "+child.kind);}}).filter((child)=>child.type!=="text"||child.content);}}
The text was updated successfully, but these errors were encountered:
🚀 Feature Proposal
Currently
would be extracted as
I&apros;m Cielquan
and notI'm Cielquan
, because i18next-parser currently does not respect theshouldUnescape
prop of the<Trans />
component from react-i18next. I made a custom lexer which does.Motivation
Because I currently do not have the time to make a PR I wanted to at least throw the code out into the wild for others to use or someone else to make a PR.
Example
I added the
this.unescapeFn
class property and theunescapeChars
function. I also updated thejsxExtractor
function for the part where<Trans />
components are handled.This adds
react-i18next
as a dependency.The text was updated successfully, but these errors were encountered: