Files
web/apps/scandic-web/codemods/lokalise/lokalise.ts
2025-04-14 11:30:05 +00:00

229 lines
6.9 KiB
TypeScript

import {
type Expression,
type Node,
type ObjectLiteralExpression,
Project,
type SourceFile,
ts,
} from "ts-morph"
export function codemod(paths: string) {
const project = new Project()
project.addSourceFilesAtPaths(paths)
project.getSourceFiles().forEach((file) => {
processSourceFile(file)
file.saveSync()
})
}
export function processSourceFile(file: SourceFile): void {
file.getDescendantsOfKind(ts.SyntaxKind.CallExpression).forEach((call) => {
const expression = call.getExpression().getText()
if (expression === "intl.formatMessage") {
const formatMessageArgs = call.getArguments()
if (formatMessageArgs.length > 0) {
const firstFormatMessageArg = formatMessageArgs[0]
// Handle object literal in the first argument
if (
firstFormatMessageArg.getKind() ===
ts.SyntaxKind.ObjectLiteralExpression
) {
const expressionVariables = getVariableArguments(
firstFormatMessageArg.asKindOrThrow(
ts.SyntaxKind.ObjectLiteralExpression
)
)
if (expressionVariables === "{}") {
// No variables
transformObjectLiteral(
firstFormatMessageArg.asKindOrThrow(
ts.SyntaxKind.ObjectLiteralExpression
)
)
} else {
// Handle variables
const expressionReplacement = `intl.formatMessage(${transformObjectLiteralAndReturn(
firstFormatMessageArg.asKindOrThrow(
ts.SyntaxKind.ObjectLiteralExpression
)
)}, ${expressionVariables})`
call.replaceWithText(expressionReplacement)
}
}
// Handle ternary expressions in the first argument
else if (
firstFormatMessageArg.getKind() ===
ts.SyntaxKind.ConditionalExpression
) {
const conditional = firstFormatMessageArg.asKindOrThrow(
ts.SyntaxKind.ConditionalExpression
)
const whenTrue = conditional.getWhenTrue()
const whenFalse = conditional.getWhenFalse()
// Check for variables in message
const varArgsWhenTrue = getVariableArguments(whenTrue)
const varArgsWhenFalse = getVariableArguments(whenFalse)
// Replacements
const whenTrueReplacement =
varArgsWhenTrue !== "{}"
? `intl.formatMessage(${transformObjectLiteralAndReturn(whenTrue)}, ${varArgsWhenTrue})`
: `intl.formatMessage(${transformObjectLiteralAndReturn(whenTrue)})`
const whenFalseReplacement =
varArgsWhenFalse !== "{}"
? `intl.formatMessage(${transformObjectLiteralAndReturn(whenFalse)}, ${varArgsWhenFalse})`
: `intl.formatMessage(${transformObjectLiteralAndReturn(whenFalse)})`
// Replace the ternary expression
call.replaceWithText(
`${conditional.getCondition().getText()} ? ${whenTrueReplacement} : ${whenFalseReplacement}`
)
}
}
}
})
// Format the file using its existing formatting rules
file.formatText({
indentSize: 2,
})
}
// Helper function to transform object literals
function transformObjectLiteral(objectLiteral: ObjectLiteralExpression): void {
const idProperty = objectLiteral
.getProperties()
.find((prop) => {
const p = prop.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
return p.getName() === "id"
})
?.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
if (idProperty) {
const idValue = idProperty.getInitializer()?.getText()
if (idValue) {
// Add defaultMessage
if (
!objectLiteral
.getProperties()
.some(
(prop) => "getName" in prop && prop.getName() === "defaultMessage"
)
) {
objectLiteral.addPropertyAssignment({
name: "defaultMessage",
initializer: idValue,
})
}
// Remove the id property
idProperty.remove()
}
}
// Add description if not present
// if (
// !objectLiteral
// .getProperties()
// .some((prop) => "getName" in prop && prop.getName() === "description")
// ) {
// objectLiteral.addPropertyAssignment({
// name: "description",
// initializer: `{}`,
// })
// }
// Extract variables from the defaultMessage if present
const defaultMessageProp = objectLiteral
.getProperties()
.find((prop) => "getName" in prop && prop.getName() === "defaultMessage")
?.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
if (defaultMessageProp) {
const defaultMessageValue = defaultMessageProp.getInitializer()?.getText()
if (defaultMessageValue) {
const extractedVariables =
extractVariablesFromTemplateString(defaultMessageValue)
if (extractedVariables.length > 0) {
// Replace the variables in the defaultMessage with FormatJS placeholders
const transformedMessage = replaceWithFormatJSPlaceholders(
defaultMessageValue,
extractedVariables
)
defaultMessageProp.setInitializer(`${transformedMessage}`)
}
}
}
}
// Helper function to extract variables from a template string
function extractVariablesFromTemplateString(templateString: string): string[] {
const regex = /\${(.*?)}/g
const variables: string[] = []
let match
// Find all variable references in the template literal
while ((match = regex.exec(templateString)) !== null) {
variables.push(match[1].trim())
}
return variables
}
// Helper function to replace variables with FormatJS placeholders
function replaceWithFormatJSPlaceholders(
templateString: string,
variables: string[]
): string {
let transformedMessage = templateString
variables.forEach((variable) => {
transformedMessage = transformedMessage.replace(
`\${${variable}}`,
`{${variable}}`
)
})
return transformedMessage
}
// Helper function to get the variables from the ternary branch
function getVariableArguments(exp: Expression): string {
const idProp = exp
.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression)
.getProperties()
.find((prop) => "getName" in prop && prop.getName() === "id")
?.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
if (idProp) {
const extractedVariables = extractVariablesFromTemplateString(
idProp.getText()
)
if (extractedVariables.length > 0) {
return `{ ${extractedVariables.map((v) => `${v}: ${v}`).join(", ")} }`
}
}
return "{}" // Return empty if no variables found
}
// Helper function to transform object literals and return text
function transformObjectLiteralAndReturn(object: Node): string {
if (object.getKind() === ts.SyntaxKind.ObjectLiteralExpression) {
const obj = object.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression)
transformObjectLiteral(obj)
return obj.getText() // Return the transformed object literal as text
}
return object.getText()
}