-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Use hook for i18n
- Loading branch information
Showing
14 changed files
with
574 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
const mkRemoveIfUnused = j => root => { | ||
const removeIfUnused = (importSpecifier, importDeclaration) => { | ||
if (!importSpecifier.value.local) { | ||
return | ||
} | ||
const varName = importSpecifier.value.local.name | ||
if (varName === 'React') { | ||
return false | ||
} | ||
|
||
const isUsedInScopes = () => { | ||
return ( | ||
j(importDeclaration) | ||
.closestScope() | ||
.find(j.Identifier, { name: varName }) | ||
.filter(p => { | ||
if (p.value.start === importSpecifier.value.local.start) | ||
return false | ||
if (p.parentPath.value.type === 'Property' && p.name === 'key') | ||
return false | ||
if (p.name === 'property') return false | ||
return true | ||
}) | ||
.size() > 0 | ||
) | ||
} | ||
|
||
// Caveat, this doesn't work with annonymously exported class declarations. | ||
const isUsedInDecorators = () => { | ||
// one could probably cache these, but I'm lazy. | ||
let used = false | ||
root.find(j.ClassDeclaration).forEach(klass => { | ||
used = | ||
used || | ||
(klass.node.decorators && | ||
j(klass.node.decorators) | ||
.find(j.Identifier, { name: varName }) | ||
.filter(p => { | ||
if (p.parentPath.value.type === 'Property' && p.name === 'key') | ||
return false | ||
if (p.name === 'property') return false | ||
return true | ||
}) | ||
.size() > 0) | ||
}) | ||
return used | ||
} | ||
|
||
if (!(isUsedInScopes() || isUsedInDecorators())) { | ||
j(importSpecifier).remove() | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
const removeUnusedDefaultImport = importDeclaration => { | ||
return ( | ||
j(importDeclaration) | ||
.find(j.ImportDefaultSpecifier) | ||
.filter(s => removeIfUnused(s, importDeclaration)) | ||
.size() > 0 | ||
) | ||
} | ||
|
||
const removeUnusedNonDefaultImports = importDeclaration => { | ||
return ( | ||
j(importDeclaration) | ||
.find(j.ImportSpecifier) | ||
.filter(s => removeIfUnused(s, importDeclaration)) | ||
.size() > 0 | ||
) | ||
} | ||
|
||
// Return True if somethin was transformed. | ||
const processImportDeclaration = importDeclaration => { | ||
// e.g. import 'styles.css'; // please Don't Touch these imports! | ||
if (importDeclaration.value.specifiers.length === 0) return false | ||
|
||
const hadUnusedDefaultImport = removeUnusedDefaultImport(importDeclaration) | ||
const hadUnusedNonDefaultImports = removeUnusedNonDefaultImports( | ||
importDeclaration | ||
) | ||
|
||
if (importDeclaration.value.specifiers.length === 0) { | ||
j(importDeclaration).remove() | ||
return true | ||
} | ||
return hadUnusedDefaultImport || hadUnusedNonDefaultImports | ||
} | ||
|
||
root.find(j.ImportDeclaration).forEach(processImportDeclaration) | ||
} | ||
|
||
module.exports = mkRemoveIfUnused |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import React from 'react' | ||
import { Text, useI18n } from 'cozy-ui/transpiled/react' | ||
|
||
const ComponentUsingT = () => { | ||
const { t } = useI18n() | ||
|
||
return ( | ||
<Padded> | ||
<Title>{t('Title1')}</Title> | ||
<Text>{t('Text1')}</Text> | ||
</Padded> | ||
) | ||
} | ||
|
||
const DumbComponentUsingTAndF = () => { | ||
const { t,f } = useI18n() | ||
|
||
return ( | ||
<Padded> | ||
<Title>{t('Title2')}</Title> | ||
<Text>{f(new Date())}</Text> | ||
</Padded> | ||
) | ||
} | ||
|
||
const EnhancedComponentUsingTAndF = DumbComponentUsingTAndF | ||
|
||
const DumbDefaultSimpleComponent = () => { | ||
const { t } = useI18n() | ||
return <div>t('Hello')</div> | ||
} | ||
|
||
export default compose(hoc1, hoc2)(DumbDefaultSimpleComponent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React from 'react' | ||
import { translate, Text, Modal } from 'cozy-ui/transpiled/react' | ||
|
||
import OtherImports from 'other/imports' | ||
|
||
const ComponentUsingT = translate()(({ t }) => ( | ||
<Padded> | ||
<Title>{t('Title1')}</Title> | ||
<Text>{t('Text1')}</Text> | ||
</Padded> | ||
)) | ||
|
||
const DumbComponentUsingTAndF = ({ t, f }) => ( | ||
<Padded> | ||
<Title>{t('Title2')}</Title> | ||
<Text>{f(new Date())}</Text> | ||
</Padded> | ||
) | ||
|
||
const EnhancedComponentUsingTAndF = translate()(DumbComponentUsingTAndF) | ||
|
||
const DumbDefaultSimpleComponent = ({ t }) => <div>t('Hello')</div> | ||
|
||
export default compose( | ||
hoc1, | ||
hoc2, | ||
translate() | ||
)(DumbDefaultSimpleComponent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import makeUtils from '../utils' | ||
|
||
const prepend = (arr, item) => { | ||
arr.splice(0, 0, item) | ||
} | ||
|
||
const isI18nProp = prop => { | ||
return prop.key && (prop.key.name === 't' || prop.key.name === 'f') | ||
} | ||
|
||
const findI18nProps = objPattern => { | ||
if (!objPattern) { | ||
return | ||
} | ||
if (objPattern.type !== 'ObjectPattern') { | ||
return | ||
} | ||
return objPattern.properties | ||
? objPattern.properties.filter(isI18nProp).map(prop => prop.key.name) | ||
: [] | ||
} | ||
|
||
const findNearest = (path, condition) => { | ||
while (path && !condition(path) && path.parentPath) { | ||
path = path.parentPath | ||
} | ||
return path | ||
} | ||
|
||
const findPropObjectPattern = (j, functionBodyPath) => { | ||
const functionBody = functionBodyPath.node | ||
const propsArg = functionBody.params[0] | ||
const propObjPattern = | ||
propsArg && propsArg.type === 'ObjectPattern' ? propsArg : null | ||
if (propObjPattern) { | ||
return { objPattern: propObjPattern, from: 'params' } | ||
} | ||
|
||
const bodyPropsDeclarators = j(functionBodyPath).find(j.VariableDeclarator, { | ||
init: { | ||
name: 'props' | ||
} | ||
}) | ||
const bodyPropsDeclarator = | ||
bodyPropsDeclarators.length > 0 ? bodyPropsDeclarators.get(0) : 0 | ||
|
||
if ( | ||
bodyPropsDeclarator && | ||
bodyPropsDeclarator.node.id.type === 'ObjectPattern' | ||
) { | ||
return { | ||
objPattern: bodyPropsDeclarator.node.id, | ||
from: 'body', | ||
declarator: bodyPropsDeclarator | ||
} | ||
} | ||
} | ||
|
||
export default function transformer(file, api) { | ||
const j = api.jscodeshift | ||
const utils = makeUtils(j) | ||
const root = j(file.source) | ||
|
||
const replaceI18nPropsByHook = arrowFunctionBodyPath => { | ||
const arrowFunctionBody = arrowFunctionBodyPath.node | ||
const objPattern = findPropObjectPattern(j, arrowFunctionBodyPath) | ||
if (!objPattern) { | ||
return | ||
} | ||
|
||
const { | ||
objPattern: propObjPattern, | ||
from: objPatternOrigin, | ||
declarator: objPatternDeclarator | ||
} = objPattern | ||
|
||
const i18nProps = findI18nProps(propObjPattern) | ||
|
||
if (!i18nProps || !i18nProps.length) { | ||
return | ||
} | ||
|
||
if (!arrowFunctionBody.body.body || !arrowFunctionBody.body.body.splice) { | ||
arrowFunctionBody.body = j.blockStatement([ | ||
j.returnStatement(arrowFunctionBody.body) | ||
]) | ||
} | ||
|
||
const updatedProperties = propObjPattern.properties.filter( | ||
prop => !isI18nProp(prop) | ||
) | ||
|
||
if ( | ||
updatedProperties.length === 0 && | ||
arrowFunctionBody.params.length === 1 && | ||
objPatternOrigin === 'params' | ||
) { | ||
arrowFunctionBody.params = [] | ||
} else if (objPatternOrigin === 'params') { | ||
arrowFunctionBody.params[0].properties = updatedProperties | ||
} else if (updatedProperties.length > 0 && objPatternOrigin === 'body') { | ||
objPatternDeclarator.node.id.properties = updatedProperties | ||
} else if (updatedProperties === 0 && objPatternOrigin === 'body') { | ||
objPatternDeclarator.prune() | ||
} | ||
|
||
prepend(arrowFunctionBody.body.body, `const { ${i18nProps} } = useI18n()`) | ||
utils.hoc.removeHOC(arrowFunctionBodyPath, 'translate') | ||
|
||
const declarator = findNearest( | ||
arrowFunctionBodyPath, | ||
x => x.node.type === 'VariableDeclarator' | ||
) | ||
const ComponentName = declarator.node.id.name | ||
j(declarator) | ||
.closestScope() | ||
.find(j.Identifier, { | ||
name: ComponentName | ||
}) | ||
.forEach(path => { | ||
utils.hoc.removeHOC(path, 'translate') | ||
}) | ||
|
||
utils.hoc.removeDefaultExportHOC(root, ComponentName, 'translate') | ||
return true | ||
} | ||
|
||
const components = root.find(j.ArrowFunctionExpression) | ||
|
||
let needToAddUseI18nImport = false | ||
components.forEach(path => { | ||
if (replaceI18nPropsByHook(path)) { | ||
needToAddUseI18nImport = true | ||
} | ||
}) | ||
|
||
if (needToAddUseI18nImport) { | ||
utils.imports.add( | ||
root, | ||
{ | ||
useI18n: true | ||
}, | ||
x => { | ||
return ( | ||
x.source.value == 'cozy-ui/transpiled/react' || | ||
x.source.value == 'cozy-ui/react' | ||
) | ||
}, | ||
'cozy-ui/transpiled/react' | ||
) | ||
utils.simplifyCompose(root) | ||
utils.imports.removeUnused(root) | ||
return root.toSource() | ||
} | ||
|
||
return null | ||
} |
Oops, something went wrong.