Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fes-locale-gen

Package Overview
Dependencies
Maintainers
0
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fes-locale-gen - npm Package Compare versions

Comparing version 1.0.8 to 1.0.9-beta.1

example/source/pages/test2/a.tsx

8

package.json
{
"name": "fes-locale-gen",
"version": "1.0.8",
"version": "1.0.9-beta.1",
"description": "",

@@ -13,3 +13,6 @@ "main": "index.js",

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node ./src/locale -d example/source/pages",
"translate": "node ./src/locale translate",
"list": "node ./src/locale config list"
},

@@ -20,2 +23,3 @@ "dependencies": {

"@babel/traverse": "^7.25.7",
"@babel/types": "^7.25.9",
"@vue/compiler-sfc": "^3.5.12",

@@ -22,0 +26,0 @@ "dotenv": "^16.4.5",

@@ -87,3 +87,14 @@ # FES 国际化自动脚本工具

- template中的表达式,如`<p>{{ row.compare === 1 ? '是' : '否' }}</p>`
- 待补充...
- 指令中的插值,例如
```
:label="`${variable}`"
:rules="[
{
validator: (rule, value) => {
return true
},
trigger: ['blur', 'change'],
message: `${test}工作流名称需以字母开头,允许字母、数字、下划线,不超过 128 字符`
}
]"
```

@@ -5,3 +5,3 @@ #!/usr/bin/env node

Object.keys(require.cache).forEach(function (key) { delete require.cache[key] });
// const { parse: parseVue } = require("@vue/compiler-sfc")
const { parse: parseVue } = require("@vue/compiler-sfc")
const fs = require('fs');

@@ -17,2 +17,3 @@ const { parse } = require('vue-eslint-parser');

const { OpenAI } = require('openai');
const t = require('@babel/types');

@@ -71,3 +72,3 @@ // 解析命令行参数

sourceType: isModule ? 'module' : 'script',
plugins: ['jsx'],
plugins: ['jsx', 'typescript'],
});

@@ -93,3 +94,3 @@

StringLiteral(path) {
if (/[\u4e00-\u9fa5]/.test(path.node.value) &&
if (path.parent.type !== 'JSXAttribute' && /[\u4e00-\u9fa5]/.test(path.node.value) &&
!path.findParent(p => p.isCallExpression() && (p.node.callee.name === '$t' || (p.node.callee.type === 'MemberExpression' && p.node.callee.object.name === 'locale' && p.node.callee.property.name === 't'))) &&

@@ -106,6 +107,32 @@ !path.node.value.startsWith('_.$t(') &&

end: path.node.end,
text: isJSFile ? `locale.t('_.${escapedText}')` : `$t('_.${escapedText}')`
text: `$t('_.${escapedText}')`
});
}
},
// 处理标签属性中的中文字符
JSXAttribute(path) {
if (t.isJSXIdentifier(path.node.name) && t.isStringLiteral(path.node.value) && /[\u4e00-\u9fa5]/.test(path.node.value.value)) {
const trimmedText = path.node.value.value.trim();
if (trimmedText && !trimmedText.startsWith('$t(\'_')) {
const escapedText = escapeForTranslation(trimmedText);
replacedTexts.add(escapedText);
const wrappedText = `${path.node.name.name}={$t('_.${escapedText}')}`;
replacements.push({ start: path.node.start, end: path.node.end, text: wrappedText });
}
}
},
// 处理标签内的中文字符
JSXText(path) {
if (/[\u4e00-\u9fa5]/.test(path.node.value) && !path.node.value.startsWith('$t(\'_') ) {
const text = path.node.value.trim();
const escapedText = escapeForTranslation(text);
replacedTexts.add(escapedText);
const wrappedText = '{`${' + `$t('_.${escapedText}')` + '}`}';
replacements.push({
start: path.node.start,
end: path.node.end,
text: wrappedText
});
}
},
TemplateLiteral(path) {

@@ -125,3 +152,3 @@ if (!path.findParent((p) => p.isCallExpression() && p.node.callee.type === 'MemberExpression' && p.node.callee.object.name === 'console')) {

end: quasi.end,
text: isJSFile ? `\${locale.t('_.${escapedText}')}` : `\${$t('_.${escapedText}')}`
text: `\${$t('_.${escapedText}')}`
});

@@ -144,3 +171,3 @@ }

sourceType: isModule ? 'module' : 'script',
plugins: ['jsx'],
plugins: ['jsx', 'typescript'],
});

@@ -178,40 +205,54 @@

// 只有在非 JS 文件的情况下才添加 $t 声明
if (!isJSFile) {
ast = parseJS(code, {
sourceType: isModule ? 'module' : 'script',
plugins: ['jsx'],
});
ast = parseJS(code, {
sourceType: isModule ? 'module' : 'script',
plugins: ['jsx', 'typescript'],
});
let hasDollarTDeclaration = false;
let hasDollarTDeclaration = false;
// 第三次遍历:检查是否有使用插件
traverse(ast, {
ImportDeclaration(path) {
lastImportIndex = Math.max(lastImportIndex, path.node.loc.end.line);
},
VariableDeclaration(path) {
path.node.declarations.forEach(declaration => {
if (declaration.init &&
declaration.init.type === 'CallExpression' &&
declaration.init.callee.name === 'useI18n') {
if (declaration.id.type === 'ObjectPattern') {
const tProperty = declaration.id.properties.find(prop =>
prop.key.name === 't' && prop.value.name === '$t'
);
if (tProperty) {
hasDollarTDeclaration = true;
}
// 第三次遍历:检查是否有使用插件
traverse(ast, {
ImportDeclaration(path) {
lastImportIndex = Math.max(lastImportIndex, path.node.loc.end.line);
},
VariableDeclaration(path) {
path.node.declarations.forEach(declaration => {
if (declaration.init &&
declaration.init.type === 'CallExpression' &&
declaration.init.callee.name === 'useI18n') {
if (declaration.id.type === 'ObjectPattern') {
const tProperty = declaration.id.properties.find(prop =>
prop.key.name === 't' && prop.value.name === '$t'
);
if (tProperty) {
hasDollarTDeclaration = true;
}
}
});
}
});
}
// 添加 $t 声明(如果需要)
if (!hasDollarTDeclaration) {
const lines = code.split('\n');
const insertIndex = lastImportIndex !== -1 ? lastImportIndex + 1 : 0;
lines.splice(insertIndex, 0, `\nconst { t: $t } = useI18n();\n`);
code = lines.join('\n');
}
// 检查是否有 const $t = locale.t 的代码
if (
declaration.id.type === 'Identifier' &&
declaration.id.name === '$t' &&
declaration.init &&
declaration.init.type === 'MemberExpression' &&
declaration.init.object.name === 'locale' &&
declaration.init.property.name === 't'
) {
hasDollarTDeclaration = true;
}
});
},
});
// 添加 $t 声明(如果需要)
if (!hasDollarTDeclaration) {
const lines = code.split('\n');
const insertIndex = lastImportIndex !== -1 ? lastImportIndex + 1 : 0;
const text = isJSFile? `\nconst $t = locale.t;\n`: `\nconst { t: $t } = useI18n();\n`
lines.splice(insertIndex, 0, text);
code = lines.join('\n');
}

@@ -234,4 +275,9 @@ }

handleAttributeNode(attr, replacements);
}else if (attr.value && attr.value.type === 'VExpressionContainer' && t.isConditionalExpression(attr.value.expression)) {
handleConditionExpression(attr.value.expression, replacements);
}
}
} else if (node.type === 'VExpressionContainer' && t.isConditionalExpression(node.expression)) {
handleConditionExpression(node.expression, replacements);
}

@@ -266,2 +312,30 @@

function handleConditionExpression(node, replacements) {
// 检查字符串是否包含中文
const containsChinese = (str)=> {
return /[\u4e00-\u9fa5]+/.test(str);
}
// 处理三元运算符
if (containsChinese(node.consequent.value)) {
const trimmedText = node.consequent.value.trim();
if (trimmedText && !trimmedText.startsWith('$t(\'_')) {
const escapedText = escapeForTranslation(trimmedText);
replacedTexts.add(escapedText);
const wrappedText = '`${' + `$t('_.${escapedText}')` + '}`';
replacements.push({ start: node.consequent.range[0], end: node.consequent.range[1], text: `${wrappedText}` });
}
}
if (containsChinese(node.alternate.value)) {
const trimmedText = node.alternate.value.trim();
if (trimmedText && !trimmedText.startsWith('$t(\'_')) {
const escapedText = escapeForTranslation(trimmedText);
replacedTexts.add(escapedText);
const wrappedText = '`${' + `$t('_.${escapedText}')` + '}`';
replacements.push({ start: node.alternate.range[0], end: node.alternate.range[1], text: `${wrappedText}` });
}
}
}
function singleFileProcessor(filePath) {

@@ -271,3 +345,3 @@ const fileContent = fs.readFileSync(filePath, 'utf-8');

if (fileExt === '.js' || fileExt === '.jsx') {
if (fileExt === '.js' || fileExt === '.jsx' || fileExt === '.ts' || fileExt === '.tsx') {
const processedContent = processJavaScript(fileContent, true, false, true);

@@ -287,2 +361,11 @@ return {

traverseTemplate(ast.templateBody, replacements);
// 处理template中变量
const vueParseResult = parseVue(processedContent, {
sourceType: 'module',
});
if (vueParseResult.descriptor.template && vueParseResult.descriptor.template.ast) {
const templateAst = vueParseResult.descriptor.template.ast;
processTemplateAst(templateAst, replacements);
}
replacements.sort((a, b) => b.start - a.start);

@@ -296,7 +379,2 @@ for (const { start, end, text } of replacements) {

}
// TODO: 处理template中变量
// const vueParseResult = parseVue(processedContent, {
// sourceType: 'module',
// });
// console.log('VUE:', vueParseResult.descriptor.template.ast.children[0].props);
// 处理 script 和 script setup

@@ -322,2 +400,45 @@ const scriptMatch = processedContent.match(/<script(\s+setup)?[^>]*>([\s\S]*?)<\/script>/i);

function processTemplateAst(ast, replacements) {
if (ast.props) {
ast.props.forEach(prop => {
if (prop.type === 7) { // 指令
processDirective(prop, replacements);
}
});
}
if (ast.children) {
ast.children.forEach(child => {
content = processTemplateAst(child, replacements);
});
}
}
function processDirective(prop, replacements) {
if (prop.exp && prop.exp.content && !prop.exp.content.includes('$t(')) {
const chineseRegex = /['"]([^'"]*[\u4e00-\u9fa5]+[^'"]*)['"]/g;
let match;
let processedContent = prop.exp.content;
while ((match = chineseRegex.exec(prop.exp.content)) !== null) {
const originalText = match[1];
const escapedText = escapeForTranslation(originalText);
if (!replacedTexts.has(escapedText)) {
replacedTexts.add(escapedText);
}
processedContent = processedContent.replace(
`'${originalText}'`,
`$t('_.${escapedText}')`
);
}
if (processedContent !== prop.exp.content) {
// 修改content(原文件)中对应内容
replacements.push({start: prop.exp.loc.start.offset, end: prop.exp.loc.end.offset, text: processedContent})
}
}
}
async function generateLocaleFile() {

@@ -405,3 +526,3 @@ // 获取用户执行命令时的当前工作目录

// console.log('dirPath', dirPath, excludedDirList);
const files = glob.sync(path.join(dirPath, '**/*.{vue,js,jsx}').replace(/\\/g, '/'), {
const files = glob.sync(path.join(dirPath, '**/*.{vue,js,jsx,tsx,ts}').replace(/\\/g, '/'), {
ignore: excludedDirList,

@@ -408,0 +529,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc