PowerShell脚本提取vue页面文本

1、打开PowerShell

image

编写PowerShell脚本特别注意:

新建 TXT 文件本身不会导致报错,但文件编码、字符格式(全角 / 半角)、特殊隐形字符会干扰 PowerShell 解析脚本,所以新建txt文件要另存为选择文件编码;

修改txt文件的编码步骤:

1.打开extract-vue-i18n.ps1文件(记事本);

2.点击「文件」→「另存为」;

3.在 “编码” 下拉框选择"带有BOM的UTF-8",覆盖保存文件;

image

 

2.新建extract_config.json文件编写配置

注意实际运行的json文件中不能有后面//的注释,把注释删掉

{
  "search_dir": "D:\\文件目录\\项目\\src\\views\\files", // 要扫描的 Vue 文件目录
  "output_file": "./i18n/zh-CN.json", // 输出文件路径
  "skip_dir": "node_modules,dist,build", // 跳过的目录
  "file_pattern": "*.vue", // 文件匹配模式
  "encoding": "UTF8", // 文件编码
  "verbose": true // 是否显示详细信息
}

保存文件时需要重新另存为选择"带有BOM的UTF-8"编码;

3.新建脚本文件extract-vue-i18n.ps1

脚本:

脚本一执行效果:提炼出来文本效果

{
 "要翻译的内容":  {
    "(要翻译内容一共出现几次)":  1,
    "en-US":  "翻译后的英文",
    "ja-JP":  "翻译后的日文",
    "zh-CN":  "翻译的文本",
    "(要翻译的内容所在位置信息)":  [
          "该翻译内容所在vue页面名:(这里的需要目前不知道是什么)"
          ]
    },
}

image

# extract-vue-i18n.ps1
# Vue 文件国际化提取脚本
# 功能:从 Vue 文件中提取中文字符串,跳过注释,用于国际化处理

param(
    [string]$ConfigPath = "./extract_config.json"
)

# 设置控制台编码
$OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# ANSI 颜色定义
$ESC = [char]27
$COLOR_RESET = "$ESC[0m"
$COLOR_RED = "$ESC[91m"
$COLOR_GREEN = "$ESC[92m"
$COLOR_YELLOW = "$ESC[93m"
$COLOR_CYAN = "$ESC[96m"
$COLOR_MAGENTA = "$ESC[95m"

# 加载配置
function Load-Config {
    param([string]$ConfigPath)
    
    Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 加载配置文件: $ConfigPath" -ForegroundColor Cyan
    
    if (-not (Test-Path $ConfigPath)) {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 未找到配置文件: $ConfigPath" -ForegroundColor Red
        exit 1
    }
    
    try {
        $config = Get-Content $ConfigPath -Encoding UTF8 | ConvertFrom-Json
        Write-Host "$COLOR_GREEN[SUCCESS]$COLOR_RESET 配置文件加载成功" -ForegroundColor Green
        return $config
    } catch {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 配置文件格式错误: $_" -ForegroundColor Red
        exit 1
    }
}

# 验证配置
function Validate-Config {
    param($config)
    
    $requiredFields = @("search_dir", "output_file", "file_pattern")
    $missingFields = @()
    
    foreach ($field in $requiredFields) {
        if (-not $config.$field) {
            $missingFields += $field
        }
    }
    
    if ($missingFields.Count -gt 0) {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 配置文件中缺少必要字段: $($missingFields -join ', ')" -ForegroundColor Red
        exit 1
    }
    
    # 设置默认值
    if (-not $config.encoding) { $config.encoding = "UTF8" }
    if (-not $config.verbose) { $config.verbose = $true }
    
    Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 搜索目录: $($config.search_dir)" -ForegroundColor Green
    Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 输出文件: $($config.output_file)" -ForegroundColor Green
    Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 文件模式: $($config.file_pattern)" -ForegroundColor Green
    
    return $config
}

# 获取要扫描的文件列表
function Get-FileList {
    param($config)
    
    $searchPath = $config.search_dir
    $pattern = $config.file_pattern
    
    Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 开始扫描目录: $searchPath" -ForegroundColor Cyan
    
    if (-not (Test-Path $searchPath)) {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 搜索目录不存在: $searchPath" -ForegroundColor Red
        exit 1
    }
    
    # 获取跳过的目录
    $skipDirs = @()
    if ($config.skip_dir) {
        $skipDirs = $config.skip_dir.Split(',', [StringSplitOptions]::RemoveEmptyEntries) | 
            ForEach-Object { $_.Trim() }
    }
    
    # 递归获取所有 Vue 文件
    $allFiles = Get-ChildItem -Path $searchPath -Filter $pattern -Recurse -File
    
    # 过滤跳过的目录
    $files = @()
    $skippedFiles = 0
    
    foreach ($file in $allFiles) {
        $shouldSkip = $false
        
        foreach ($skipDir in $skipDirs) {
            if (-not [string]::IsNullOrWhiteSpace($skipDir)) {
                if ($file.FullName -like "*\$skipDir\*") {
                    $shouldSkip = $true
                    $skippedFiles++
                    if ($config.verbose) {
                        Write-Host "$COLOR_YELLOW[SKIP]$COLOR_RESET 跳过目录: $skipDir -> $($file.Name)" -ForegroundColor DarkYellow
                    }
                    break
                }
            }
        }
        
        if (-not $shouldSkip) {
            $files += $file
        }
    }
    
    Write-Host "$COLOR_GREEN[FOUND]$COLOR_RESET 找到 $($files.Count) 个 Vue 文件" -ForegroundColor Green
    if ($skippedFiles -gt 0) {
        Write-Host "$COLOR_YELLOW[SKIP]$COLOR_RESET 跳过了 $skippedFiles 个文件" -ForegroundColor Yellow
    }
    
    return $files
}

# 从 Vue 文件中提取中文字符串
function Extract-ChineseFromVue {
    param(
        [System.IO.FileInfo]$File,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    $fileName = $File.Name
    $fullPath = $File.FullName
    
    try {
        # 读取文件内容
        $content = Get-Content $fullPath -Encoding UTF8 -Raw
        
        # 状态机变量
        $inComment = $false
        $inScriptComment = $false
        $inStyle = $false
        $inTemplate = $false
        $inScript = $false
        $currentLine = 0
        
        # 按行处理
        $lines = $content -split "`n"
        
        foreach ($line in $lines) {
            $currentLine++
            $trimmed = $line.Trim()
            
            # 跳过空行
            if ([string]::IsNullOrWhiteSpace($trimmed)) { continue }
            
            # 1. 检测 <!-- 注释开始
            if ($trimmed -match '^\s*<!--' -and $trimmed -notmatch '-->') {
                $inComment = $true
                if ($Verbose) {
                    Write-Host "$COLOR_MAGENTA[COMMENT]$COLOR_RESET $fileName`:$currentLine 进入 HTML 注释" -ForegroundColor Magenta
                }
                continue
            }
            
            # 2. 检测 --> 注释结束
            if ($inComment -and $trimmed -match '-->') {
                $inComment = $false
                if ($Verbose) {
                    Write-Host "$COLOR_MAGENTA[COMMENT]$COLOR_RESET $fileName`:$currentLine 退出 HTML 注释" -ForegroundColor Magenta
                }
                continue
            }
            
            # 3. 如果在注释中,跳过
            if ($inComment) { continue }
            
            # 4. 检测 <template> 开始
            if ($trimmed -match '^\s*<template\b') {
                $inTemplate = $true
                continue
            }
            
            # 5. 检测 </template> 结束
            if ($trimmed -match '^\s*</template>') {
                $inTemplate = $false
                continue
            }
            
            # 6. 检测 <script> 开始
            if ($trimmed -match '^\s*<script\b') {
                $inScript = $true
                continue
            }
            
            # 7. 检测 </script> 结束
            if ($trimmed -match '^\s*</script>') {
                $inScript = $false
                $inScriptComment = $false
                continue
            }
            
            # 8. 检测 <style> 开始
            if ($trimmed -match '^\s*<style\b') {
                $inStyle = $true
                continue
            }
            
            # 9. 检测 </style> 结束
            if ($trimmed -match '^\s*</style>') {
                $inStyle = $false
                continue
            }
            
            # 10. 如果在 style 中,跳过
            if ($inStyle) { continue }
            
            # 11. 处理 template 中的内容
            if ($inTemplate) {
                # 跳过 template 中的 <!-- 单行注释 -->
                if ($trimmed -match '^\s*<!--.*-->') { continue }
                
                # 跳过 v-html 等指令中的内容
                if ($trimmed -match 'v-html\s*=') { continue }
                if ($trimmed -match 'v-text\s*=') { continue }
                
                # 提取模板中的中文
                Extract-ChineseFromLine $line $fileName $currentLine $FoundStrings $Verbose
            }
            
            # 12. 处理 script 中的内容
            if ($inScript) {
                # 跳过 JavaScript 注释
                if ($trimmed.StartsWith("//")) { continue }
                if ($trimmed.StartsWith("/*")) { 
                    $inScriptComment = $true
                    continue
                }
                if ($trimmed.EndsWith("*/")) { 
                    $inScriptComment = $false
                    continue
                }
                if ($inScriptComment) { continue }
                
                # 跳过 #region 注释
                if ($trimmed.StartsWith("#region")) { continue }
                if ($trimmed.StartsWith("#endregion")) { continue }
                
                # 提取 script 中的中文(字符串字面量)
                Extract-ChineseFromScript $line $fileName $currentLine $FoundStrings $Verbose
            }
        }
        
        # 额外处理:从属性中提取中文
        Extract-ChineseFromAttributes $content $fileName $FoundStrings $Verbose
        
    } catch {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 处理文件时出错: $fileName - $_" -ForegroundColor Red
    }
    
    return $FoundStrings
}

# 从一行中提取中文字符
function Extract-ChineseFromLine {
    param(
        [string]$Line,
        [string]$FileName,
        [int]$LineNumber,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 匹配模板中的中文文本
    $patterns = @(
        # 匹配 >中文< 或 > 中文 <
        '(?<=>)[^<>]*?[\u4e00-\u9fa5][^<>]*?(?=<)',
        # 匹配属性值中的中文: title="中文" placeholder="请输入中文"
        '(?<=\=\")[^\"]*?[\u4e00-\u9fa5][^\"]*?(?=\")',
        # 匹配 {{ }} 中的中文
        '(?<=\{\{)[^\}]*?[\u4e00-\u9fa5][^\}]*?(?=\}\})'
    )
    
    foreach ($pattern in $patterns) {
        $matches = [regex]::Matches($Line, $pattern)
        foreach ($match in $matches) {
            $text = $match.Value.Trim()
            if (-not [string]::IsNullOrWhiteSpace($text)) {
                Add-ToStringTable $text $FileName $LineNumber $FoundStrings $Verbose
            }
        }
    }
}

# 从 script 中提取中文字符
function Extract-ChineseFromScript {
    param(
        [string]$Line,
        [string]$FileName,
        [int]$LineNumber,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 匹配 JavaScript 字符串字面量中的中文
    $patterns = @(
        # 双引号字符串
        '"(?<content>[^"\\]*(?:\\.[^"\\]*)*?[\u4e00-\u9fa5][^"\\]*(?:\\.[^"\\]*)*?)"',
        # 单引号字符串
        "'(?<content>[^'\\]*(?:\\.[^'\\]*)*?[\u4e00-\u9fa5][^'\\]*(?:\\.[^'\\]*)*?)'",
        # 模板字符串
        '`(?<content>[^`\\]*(?:\\.[^`\\]*)*?[\u4e00-\u9fa5][^`\\]*(?:\\.[^`\\]*)*?)`'
    )
    
    foreach ($pattern in $patterns) {
        $matches = [regex]::Matches($Line, $pattern)
        foreach ($match in $matches) {
            if ($match.Groups["content"].Success) {
                $text = $match.Groups["content"].Value
                # 处理转义字符
                $text = $text -replace '\\"', '"' -replace "\\'", "'" -replace '\\\\', '\'
                if (-not [string]::IsNullOrWhiteSpace($text)) {
                    Add-ToStringTable $text $FileName $LineNumber $FoundStrings $Verbose
                }
            }
        }
    }
}

# 从 HTML 属性中提取中文
function Extract-ChineseFromAttributes {
    param(
        [string]$Content,
        [string]$FileName,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 匹配特定属性中的中文
    $attrPatterns = @(
        'placeholder\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        'title\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        'label\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        'aria-label\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        ':label\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        ':placeholder\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']'
    )
    
    $lineNumber = 0
    $lines = $Content -split "`n"
    
    foreach ($line in $lines) {
        $lineNumber++
        foreach ($pattern in $attrPatterns) {
            $matches = [regex]::Matches($line, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
            foreach ($match in $matches) {
                if ($match.Groups.Count -gt 1) {
                    $text = $match.Groups[1].Value
                    if (-not [string]::IsNullOrWhiteSpace($text)) {
                        Add-ToStringTable $text $FileName $lineNumber $FoundStrings $Verbose
                    }
                }
            }
        }
    }
}

# 添加到字符串表
function Add-ToStringTable {
    param(
        [string]$Text,
        [string]$FileName,
        [int]$LineNumber,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    $cleanText = $Text.Trim()
    
    # 跳过太短的文本(可能不是有意义的翻译)
    if ($cleanText.Length -lt 2) { return }
    
    # 跳过纯数字或符号
    if ($cleanText -match '^[\d\s\p{P}]+$') { return }
    
    # 跳过 URL、文件路径等
    if ($cleanText -match '^(https?|ftp|file)://|^[a-zA-Z]:\\|^\.?\.?/') { return }
    
    # 跳过 CSS 类名、ID
    if ($cleanText -match '^[.#]') { return }
    
    # 去重
    if (-not $FoundStrings.ContainsKey($cleanText)) {
        $FoundStrings[$cleanText] = @{
            files = @("${FileName}:${LineNumber}")
            count = 1
        }
        
        if ($Verbose) {
            Write-Host "$COLOR_GREEN[FOUND]$COLOR_RESET $FileName`:$LineNumber" -NoNewline
            Write-Host " -> " -NoNewline
            Write-Host "`"$cleanText`"" -ForegroundColor Cyan
        }
    } else {
        # 如果已存在,添加新的位置
        $existing = $FoundStrings[$cleanText]
        $existing.count++
        $location = "${FileName}:${LineNumber}"
        if ($location -notin $existing.files) {
            $existing.files += $location
        }
    }
}

# 生成输出文件
function Generate-OutputFile {
    param(
        [hashtable]$FoundStrings,
        [string]$OutputPath,
        [bool]$Verbose
    )
    
    # 确保输出目录存在
    $outputDir = Split-Path $OutputPath -Parent
    if (-not (Test-Path $outputDir)) {
        New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
        Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 创建输出目录: $outputDir" -ForegroundColor Cyan
    }
    
    # 按字母顺序排序
    $sortedKeys = $FoundStrings.Keys | Sort-Object
    
    # 生成 JSON 结构
    $i18nData = @{}
    $index = 1
    
    foreach ($key in $sortedKeys) {
        # 生成翻译键(自动生成或使用原文本)
        $translationKey = Generate-TranslationKey $key $index
        
        $i18nData[$translationKey] = @{
            "zh-CN" = $key
            "en-US" = ""  # 留空,用于后续翻译
            "ja-JP" = ""
            "locations" = $FoundStrings[$key].files
            "count" = $FoundStrings[$key].count
        }
        $index++
    }
    
    # 转换为 JSON
    $json = $i18nData | ConvertTo-Json -Depth 10
    
    # 写入文件
    try {
        Set-Content -Path $OutputPath -Value $json -Encoding UTF8
        Write-Host "$COLOR_GREEN[SUCCESS]$COLOR_RESET 已保存到: $OutputPath" -ForegroundColor Green
    } catch {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 写入文件失败: $_" -ForegroundColor Red
        exit 1
    }
    
    return $sortedKeys.Count
}

# 生成翻译键
function Generate-TranslationKey {
    param(
        [string]$Text,
        [int]$Index
    )
    
    # 移除特殊字符,用下划线连接
    $key = $Text -replace '[^\w\u4e00-\u9fa5]+', '_'  # 保留中文字符
    $key = $key -replace '^_+|_+$', ''  # 移除首尾下划线
    $key = $key -replace '_+', '_'  # 合并多个下划线
    
    # 如果处理后为空,使用索引
    if ([string]::IsNullOrWhiteSpace($key)) {
        $key = "key_$Index"
    }
    
    # 确保键不以数字开头
    if ($key -match '^\d') {
        $key = "key_$key"
    }
    
    return $key.ToLower()
}

# 显示统计信息
function Show-Statistics {
    param(
        [int]$TotalFiles,
        [int]$TotalStrings,
        [string]$OutputFile
    )
    
    Write-Host "`n$COLOR_CYAN========================================$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_GREEN✓ 提取完成!$COLOR_RESET" -ForegroundColor Green
    Write-Host "$COLOR_YELLOW✓ 处理文件数: $COLOR_RESET$TotalFiles" -ForegroundColor Yellow
    Write-Host "$COLOR_GREEN✓ 找到中文字符串: $COLOR_RESET$TotalStrings 个" -ForegroundColor Green
    Write-Host "$COLOR_CYAN✓ 已保存到: $COLOR_RESET$OutputFile" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN========================================$COLOR_RESET" -ForegroundColor Cyan
    Write-Host ""
}

# 主函数
function Main {
    Write-Host "$COLOR_CYAN╔══════════════════════════════════════╗$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN║     Vue 国际化字符串提取工具         ║$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN║     Version 1.0.0                    ║$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN╚══════════════════════════════════════╝$COLOR_RESET" -ForegroundColor Cyan
    Write-Host ""
    
    # 1. 加载配置
    $config = Load-Config $ConfigPath
    $config = Validate-Config $config
    
    # 2. 获取文件列表
    $files = Get-FileList $config
    
    if ($files.Count -eq 0) {
        Write-Host "$COLOR_YELLOW[WARN]$COLOR_RESET 没有找到要处理的 Vue 文件" -ForegroundColor Yellow
        exit 0
    }
    
    # 3. 提取中文
    $foundStrings = @{}
    $processedFiles = 0
    $startTime = Get-Date
    
    foreach ($file in $files) {
        $processedFiles++
        $fileName = $file.Name
        
        if ($config.verbose) {
            Write-Host "$COLOR_CYAN[PROCESSING]$COLOR_RESET ($processedFiles/$($files.Count)) $fileName" -ForegroundColor Cyan
        } else {
            Write-Progress -Activity "正在处理 Vue 文件" -Status "$fileName ($processedFiles/$($files.Count))" -PercentComplete (($processedFiles / $files.Count) * 100)
        }
        
        $foundStrings = Extract-ChineseFromVue -File $file -FoundStrings $foundStrings -Verbose $config.verbose
    }
    
    if (-not $config.verbose) {
        Write-Progress -Activity "正在处理 Vue 文件" -Completed
    }
    
    $endTime = Get-Date
    $duration = $endTime - $startTime
    
    # 4. 生成输出文件
    $totalStrings = Generate-OutputFile -FoundStrings $foundStrings -OutputPath $config.output_file -Verbose $config.verbose
    
    # 5. 显示统计信息
    Show-Statistics -TotalFiles $files.Count -TotalStrings $totalStrings -OutputFile $config.output_file
    
    # 6. 处理时间
    Write-Host "$COLOR_MAGENTA[TIME]$COLOR_RESET 处理耗时: $($duration.TotalSeconds.ToString('0.00')) 秒" -ForegroundColor Magenta
    Write-Host ""
    
    # 7. 显示示例
    if ($totalStrings -gt 0) {
        Write-Host "${COLOR_YELLOW}示例输出:${COLOR_RESET}" -ForegroundColor Yellow
        
        $sampleKeys = $foundStrings.Keys | Select-Object -First 3
        foreach ($key in $sampleKeys) {
            Write-Host "  - $key" -ForegroundColor Gray
        }
        
        if ($totalStrings -gt 3) {
            Write-Host "  ... 还有 $($totalStrings - 3) 个字符串" -ForegroundColor DarkGray
        }
    }
}

# 运行主函数
try {
    Main
} catch {
    Write-Host "$COLOR_RED[FATAL ERROR]$COLOR_RESET $_" -ForegroundColor Red
    Write-Host "$COLOR_REDStack Trace:$COLOR_RESET" -ForegroundColor Red
    Write-Host $_.ScriptStackTrace -ForegroundColor Red
    exit 1
}
脚本一

 

脚本二执行效果:提炼出来文本效果

会根据当前vue页面所在文件夹生成对象,

对象名为所在文件夹名,对象内容为键值对,其中

{
"vue页面所在文件夹名": {
   "要翻译内容键值key(不用语言包键值key一致)": "要翻译内容value",
 },
}

image

# extract-vue-i18n.ps1
# Vue 文件国际化提取脚本
# 功能:1.汇总同一文件夹下所有Vue文件的中文字符串 2.过滤特殊符号/序号 3.生成重复空键的指定JSON格式

param(
    [string]$ConfigPath = "./extract_config.json"
)

# 设置控制台编码
$OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# ANSI 颜色定义
$ESC = [char]27
$COLOR_RESET = "$ESC[0m"
$COLOR_RED = "$ESC[91m"
$COLOR_GREEN = "$ESC[92m"
$COLOR_YELLOW = "$ESC[93m"
$COLOR_CYAN = "$ESC[96m"
$COLOR_MAGENTA = "$ESC[95m"

# 获取脚本所在目录
$ScriptDir = $PSScriptRoot
Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 脚本所在目录: $ScriptDir" -ForegroundColor Cyan

# 加载配置
function Load-Config {
    param([string]$ConfigPath)
    
    # 处理配置文件路径(如果是相对路径,基于脚本目录)
    if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
        $ConfigPath = Join-Path -Path $ScriptDir -ChildPath $ConfigPath
    }
    
    Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 加载配置文件: $ConfigPath" -ForegroundColor Cyan
    
    if (-not (Test-Path $ConfigPath)) {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 未找到配置文件: $ConfigPath" -ForegroundColor Red
        exit 1
    }
    
    try {
        $config = Get-Content $ConfigPath -Encoding UTF8 | ConvertFrom-Json
        Write-Host "$COLOR_GREEN[SUCCESS]$COLOR_RESET 配置文件加载成功" -ForegroundColor Green
        return $config
    } catch {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 配置文件格式错误: $_" -ForegroundColor Red
        exit 1
    }
}

# 验证配置(移除search_dir的强制校验)
function Validate-Config {
    param($config)
    
    # 移除search_dir,只校验必要的字段
    $requiredFields = @("output_file", "file_pattern")
    $missingFields = @()
    
    foreach ($field in $requiredFields) {
        if (-not $config.$field) {
            $missingFields += $field
        }
    }
    
    if ($missingFields.Count -gt 0) {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 配置文件中缺少必要字段: $($missingFields -join ', ')" -ForegroundColor Red
        exit 1
    }
    
    # 设置默认值
    if (-not $config.encoding) { $config.encoding = "UTF8" }
    if (-not $config.verbose) { $config.verbose = $true }
    # 给skip_dir设置默认空值,避免后续报错
    if (-not $config.skip_dir) { $config.skip_dir = "" }

    # 核心:强制输出文件路径基于脚本目录
    if (-not [System.IO.Path]::IsPathRooted($config.output_file)) {
        $config.output_file = Join-Path -Path $ScriptDir -ChildPath $config.output_file
    }

    # 兼容处理:如果配置里仍有search_dir,就用它;否则默认用脚本所在目录
    if ($config.search_dir) {
        # 处理search_dir的相对路径(基于脚本目录)
        if (-not [System.IO.Path]::IsPathRooted($config.search_dir)) {
            $config.search_dir = Join-Path -Path $ScriptDir -ChildPath $config.search_dir
        }
        Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 搜索目录: $($config.search_dir)" -ForegroundColor Green
    } else {
        $config.search_dir = $ScriptDir
        Write-Host "$COLOR_YELLOW[CONFIG]$COLOR_RESET 未配置search_dir,默认使用脚本目录: $ScriptDir" -ForegroundColor Yellow
    }
    
    Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 输出文件: $($config.output_file)" -ForegroundColor Green
    Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 文件模式: $($config.file_pattern)" -ForegroundColor Green
    Write-Host "$COLOR_GREEN[CONFIG]$COLOR_RESET 跳过目录: $($config.skip_dir)" -ForegroundColor Green
    
    return $config
}

# 获取要扫描的文件列表
function Get-FileList {
    param($config)
    
    $searchPath = $config.search_dir
    $pattern = $config.file_pattern
    
    Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 开始扫描目录: $searchPath" -ForegroundColor Cyan
    
    if (-not (Test-Path $searchPath)) {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 搜索目录不存在: $searchPath" -ForegroundColor Red
        exit 1
    }
    
    # 获取跳过的目录
    $skipDirs = @()
    if ($config.skip_dir) {
        $skipDirs = $config.skip_dir.Split(',', [StringSplitOptions]::RemoveEmptyEntries) | 
            ForEach-Object { $_.Trim() }
    }
    
    # 递归获取所有 Vue 文件
    $allFiles = Get-ChildItem -Path $searchPath -Filter $pattern -Recurse -File
    
    # 过滤跳过的目录
    $files = @()
    $skippedFiles = 0
    
    foreach ($file in $allFiles) {
        $shouldSkip = $false
        
        foreach ($skipDir in $skipDirs) {
            if (-not [string]::IsNullOrWhiteSpace($skipDir)) {
                if ($file.FullName -like "*\$skipDir\*") {
                    $shouldSkip = $true
                    $skippedFiles++
                    if ($config.verbose) {
                        Write-Host "$COLOR_YELLOW[SKIP]$COLOR_RESET 跳过目录: $skipDir -> $($file.Name)" -ForegroundColor DarkYellow
                    }
                    break
                }
            }
        }
        
        if (-not $shouldSkip) {
            $files += $file
        }
    }
    
    Write-Host "$COLOR_GREEN[FOUND]$COLOR_RESET 找到 $($files.Count) 个 Vue 文件" -ForegroundColor Green
    if ($skippedFiles -gt 0) {
        Write-Host "$COLOR_YELLOW[SKIP]$COLOR_RESET 跳过了 $skippedFiles 个文件" -ForegroundColor Yellow
    }
    
    return $files
}

# 新增:清理文本(移除数字、序号、特殊符号,只保留核心中文)--(20260115,先取消了不清理文本)
function Clean-Text {
    param([string]$Text)
    $cleaned = $Text
    # 1. 移除开头的数字+符号(如 2. / 3、 / ① / 1) 等)
    #$cleaned = $cleaned  -replace '^[\d\u2460-\u24FF\u3007\u3220-\u3229\u002E\u3002\u002C\u3001\u003B\u301B\u003A\u3010\u0021\uFF01\u003F\uFF1F\u0028\uFF08\u0029\uFF09\u005B\u3014\u005D\u3015\u007B\uFF5B\u007D\uFF5D\u0020\u3000]+', ''
    
    # 2. 移除所有非中文字符(只保留中文、中文标点)
    #$cleaned = $cleaned -replace '[^\u4e00-\u9fa5\u3000-\u303F\uFF00-\uFFEF]', ''
    
    # 3. 移除首尾空白/标点
    #$cleaned = $cleaned.Trim().Trim('。,、;:!?()【】{}""''''《》〈〉·…—~¥')
    
    return $cleaned
}

# 从 Vue 文件中提取中文字符串
function Extract-ChineseFromVue {
    param(
        [System.IO.FileInfo]$File,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    $fileName = $File.Name
    $fullPath = $File.FullName
    # 获取当前文件所在的文件夹名
    $folderName = $File.Directory.Name

    try {
        # 读取文件内容
        $content = Get-Content $fullPath -Encoding UTF8 -Raw
        
        # 状态机变量
        $inComment = $false
        $inScriptComment = $false
        $inStyle = $false
        $inTemplate = $false
        $inScript = $false
        $currentLine = 0
        
        # 按行处理
        $lines = $content -split "`n"
        
        foreach ($line in $lines) {
            $currentLine++
            $trimmed = $line.Trim()
            
            # 跳过空行
            if ([string]::IsNullOrWhiteSpace($trimmed)) { continue }
            
            # 1. 检测 <!-- 注释开始
            if ($trimmed -match '^\s*<!--' -and $trimmed -notmatch '-->') {
                $inComment = $true
                if ($Verbose) {
                    Write-Host "$COLOR_MAGENTA[COMMENT]$COLOR_RESET $fileName`:$currentLine 进入 HTML 注释" -ForegroundColor Magenta
                }
                continue
            }
            
            # 2. 检测 --> 注释结束
            if ($inComment -and $trimmed -match '-->') {
                $inComment = $false
                if ($Verbose) {
                    Write-Host "$COLOR_MAGENTA[COMMENT]$COLOR_RESET $fileName`:$currentLine 退出 HTML 注释" -ForegroundColor Magenta
                }
                continue
            }
            
            # 3. 如果在注释中,跳过
            if ($inComment) { continue }
            
            # 4. 检测 <template> 开始
            if ($trimmed -match '^\s*<template\b') {
                $inTemplate = $true
                continue
            }
            
            # 5. 检测 </template> 结束
            if ($trimmed -match '^\s*</template>') {
                $inTemplate = $false
                continue
            }
            
            # 6. 检测 <script> 开始
            if ($trimmed -match '^\s*<script\b') {
                $inScript = $true
                continue
            }
            
            # 7. 检测 </script> 结束
            if ($trimmed -match '^\s*</script>') {
                $inScript = $false
                $inScriptComment = $false
                continue
            }
            
            # 8. 检测 <style> 开始
            if ($trimmed -match '^\s*<style\b') {
                $inStyle = $true
                continue
            }
            
            # 9. 检测 </style> 结束
            if ($trimmed -match '^\s*</style>') {
                $inStyle = $false
                continue
            }
            
            # 10. 如果在 style 中,跳过
            if ($inStyle) { continue }
            
            # 11. 处理 template 中的内容
            if ($inTemplate) {
                # 跳过 template 中的 <!-- 单行注释 -->
                if ($trimmed -match '^\s*<!--.*-->') { continue }
                
                # 跳过 v-html 等指令中的内容
                if ($trimmed -match 'v-html\s*=') { continue }
                if ($trimmed -match 'v-text\s*=') { continue }
                
                # 提取模板中的中文,传入文件夹名
                Extract-ChineseFromLine $line $fileName $folderName $currentLine $FoundStrings $Verbose
            }
            
            # 12. 处理 script 中的内容
            if ($inScript) {
                # 跳过 JavaScript 注释
                if ($trimmed.StartsWith("//")) { continue }
                if ($trimmed.StartsWith("/*")) { 
                    $inScriptComment = $true
                    continue
                }
                if ($trimmed.EndsWith("*/")) { 
                    $inScriptComment = $false
                    continue
                }
                if ($inScriptComment) { continue }
                
                # 跳过 #region 注释
                if ($trimmed.StartsWith("#region")) { continue }
                if ($trimmed.StartsWith("#endregion")) { continue }
                
                # 提取 script 中的中文(字符串字面量),传入文件夹名
                Extract-ChineseFromScript $line $fileName $folderName $currentLine $FoundStrings $Verbose
            }
        }
        
        # 额外处理:从属性中提取中文,传入文件夹名
        Extract-ChineseFromAttributes $content $fileName $folderName $FoundStrings $Verbose
        
    } catch {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 处理文件时出错: $fileName - $_" -ForegroundColor Red
    }
    
    return $FoundStrings
}

# 从一行中提取中文字符
function Extract-ChineseFromLine {
    param(
        [string]$Line,
        [string]$FileName,
        [string]$FolderName,
        [int]$LineNumber,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 匹配模板中的中文文本
    $patterns = @(
        # 匹配 >中文< 或 > 中文 <
        '(?<=>)[^<>]*?[\u4e00-\u9fa5][^<>]*?(?=<)',
        # 匹配属性值中的中文: title="中文" placeholder="请输入中文"
        '(?<=\=\")[^\"]*?[\u4e00-\u9fa5][^\"]*?(?=\")',
        # 匹配 {{ }} 中的中文
        '(?<=\{\{)[^\}]*?[\u4e00-\u9fa5][^\}]*?(?=\}\})'
    )
    
    foreach ($pattern in $patterns) {
        $matches = [regex]::Matches($Line, $pattern)
        foreach ($match in $matches) {
            $text = $match.Value.Trim()
            if (-not [string]::IsNullOrWhiteSpace($text)) {
                Add-ToStringTable $text $FileName $FolderName $LineNumber $FoundStrings $Verbose
            }
        }
    }
}

# 从 script 中提取中文字符
function Extract-ChineseFromScript {
    param(
        [string]$Line,
        [string]$FileName,
        [string]$FolderName,
        [int]$LineNumber,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 匹配 JavaScript 字符串字面量中的中文
    $patterns = @(
        # 双引号字符串
        '"(?<content>[^"\\]*(?:\\.[^"\\]*)*?[\u4e00-\u9fa5][^"\\]*(?:\\.[^"\\]*)*?)"',
        # 单引号字符串
        "'(?<content>[^'\\]*(?:\\.[^'\\]*)*?[\u4e00-\u9fa5][^'\\]*(?:\\.[^'\\]*)*?)'",
        # 模板字符串
        '`(?<content>[^`\\]*(?:\\.[^`\\]*)*?[\u4e00-\u9fa5][^`\\]*(?:\\.[^`\\]*)*?)`'
    )
    
    foreach ($pattern in $patterns) {
        $matches = [regex]::Matches($Line, $pattern)
        foreach ($match in $matches) {
            if ($match.Groups["content"].Success) {
                $text = $match.Groups["content"].Value
                # 处理转义字符
                $text = $text -replace '\\"', '"' -replace "\\'", "'" -replace '\\\\', '\'
                if (-not [string]::IsNullOrWhiteSpace($text)) {
                    Add-ToStringTable $text $FileName $FolderName $LineNumber $FoundStrings $Verbose
                }
            }
        }
    }
}

# 从 HTML 属性中提取中文
function Extract-ChineseFromAttributes {
    param(
        [string]$Content,
        [string]$FileName,
        [string]$FolderName,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 匹配特定属性中的中文
    $attrPatterns = @(
        'placeholder\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        'title\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        'label\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        'aria-label\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        ':label\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']',
        ':placeholder\s*=\s*["'']([^"'']*?[\u4e00-\u9fa5][^"'']*?)["'']'
    )
    
    $lineNumber = 0
    $lines = $Content -split "`n"
    
    foreach ($line in $lines) {
        $lineNumber++
        foreach ($pattern in $attrPatterns) {
            $matches = [regex]::Matches($line, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
            foreach ($match in $matches) {
                if ($match.Groups.Count -gt 1) {
                    $text = $match.Groups[1].Value
                    if (-not [string]::IsNullOrWhiteSpace($text)) {
                        Add-ToStringTable $text $FileName $FolderName $lineNumber $FoundStrings $Verbose
                    }
                }
            }
        }
    }
}

# 修改:1.汇总同一文件夹所有文本 2.调用清理函数过滤特殊内容
function Add-ToStringTable {
    param(
        [string]$Text,
        [string]$FileName,
        [string]$FolderName,
        [int]$LineNumber,
        [hashtable]$FoundStrings,
        [bool]$Verbose
    )
    
    # 第一步:清理文本(移除数字、序号、特殊符号)
    $cleanText = Clean-Text $Text
    
    # 跳过清理后无意义的文本
    if ([string]::IsNullOrWhiteSpace($cleanText) -or $cleanText.Length -lt 2) { return }
    
    # 跳过纯数字或符号(清理后理论上不会有,但做兜底)
    if ($cleanText -match '^[\d\s\p{P}]+$') { return }
    
    # 第二步:初始化文件夹对应的数组(汇总同一文件夹所有文本)
    if (-not $FoundStrings.ContainsKey($FolderName)) {
        $FoundStrings[$FolderName] = @()
    }

    # 第三步:去重后添加到数组(确保同一文件夹下文本不重复、且汇总所有文件的内容)
    if ($cleanText -notin $FoundStrings[$FolderName]) {
        $FoundStrings[$FolderName] += $cleanText
        
        if ($Verbose) {
            Write-Host "$COLOR_GREEN[FOUND]$COLOR_RESET $FolderName\$FileName`:$LineNumber" -NoNewline
            Write-Host " -> 原始: `"$Text`" -> 清理后: `"$cleanText`"" -ForegroundColor Cyan
        }
    }
}

# 核心修改:生成你指定的「重复空键」格式,且汇总所有文本
function Generate-OutputFile {
    param(
        [hashtable]$FoundStrings,
        [string]$OutputPath,
        [bool]$Verbose
    )
    
    # 确保输出目录存在(此时OutputPath已基于脚本目录)
    $outputDir = Split-Path $OutputPath -Parent
    if (-not (Test-Path $outputDir)) {
        New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
        Write-Host "$COLOR_CYAN[INFO]$COLOR_RESET 创建输出目录: $outputDir" -ForegroundColor Cyan
    }
    
    # 按文件夹名排序
    $sortedFolders = $FoundStrings.Keys | Sort-Object
    
    # 手动拼接JSON字符串(绕过PowerShell自动处理重复键的逻辑)
    $jsonLines = @()
    $jsonLines += "{"
    
    foreach ($folder in $sortedFolders) {
        $textList = $FoundStrings[$folder]
        # 添加文件夹名开头
        $jsonLines += "  `"$folder`": {"
        # 为每个文本添加空键行
        foreach ($text in $textList) {
            $jsonLines += "    `"`": `"$text`","
        }
        # 移除最后一行的逗号,添加文件夹结尾
        if ($jsonLines[-1].EndsWith(",")) {
            $jsonLines[-1] = $jsonLines[-1].TrimEnd(",")
        }
        $jsonLines += "  },"
    }
    
    # 移除最后一个文件夹的逗号,添加整体结尾
    if ($jsonLines[-1].EndsWith(",")) {
        $jsonLines[-1] = $jsonLines[-1].TrimEnd(",")
    }
    $jsonLines += "}"
    
    # 合并所有行,形成最终JSON
    $jsonContent = $jsonLines -join "`n"
    
    # 写入文件(确保UTF8编码,中文正常)
    try {
        [System.IO.File]::WriteAllText($OutputPath, $jsonContent, [System.Text.Encoding]::UTF8)
        Write-Host "$COLOR_GREEN[SUCCESS]$COLOR_RESET 已保存到: $OutputPath" -ForegroundColor Green
    } catch {
        Write-Host "$COLOR_RED[ERROR]$COLOR_RESET 写入文件失败: $_" -ForegroundColor Red
        exit 1
    }
    
    # 统计总字符串数
    $totalStrings = 0
    foreach ($folder in $FoundStrings.Keys) {
        $totalStrings += $FoundStrings[$folder].Count
    }
    return $totalStrings
}

# 生成翻译键:保留原函数(备用)
function Generate-TranslationKey {
    param(
        [string]$Text,
        [int]$Index
    )
    
    # 移除特殊字符,用下划线连接
    $key = $Text -replace '[^\w\u4e00-\u9fa5]+', '_'  # 保留中文字符
    $key = $key -replace '^_+|_+$', ''  # 移除首尾下划线
    $key = $key -replace '_+', '_'  # 合并多个下划线
    
    # 如果处理后为空,使用索引
    if ([string]::IsNullOrWhiteSpace($key)) {
        $key = "key_$Index"
    }
    
    # 确保键不以数字开头
    if ($key -match '^\d') {
        $key = "key_$key"
    }
    
    return $key.ToLower()
}

# 显示统计信息
function Show-Statistics {
    param(
        [int]$TotalFiles,
        [int]$TotalStrings,
        [string]$OutputFile
    )
    
    Write-Host "`n$COLOR_CYAN========================================$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_GREEN✓ 提取完成!$COLOR_RESET" -ForegroundColor Green
    Write-Host "$COLOR_YELLOW✓ 处理文件数: $COLOR_RESET$TotalFiles" -ForegroundColor Yellow
    Write-Host "$COLOR_GREEN✓ 找到中文字符串: $COLOR_RESET$TotalStrings 个" -ForegroundColor Green
    Write-Host "$COLOR_CYAN✓ 已保存到: $COLOR_RESET$OutputFile" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN========================================$COLOR_RESET" -ForegroundColor Cyan
    Write-Host ""
}

# 主函数
function Main {
    Write-Host "$COLOR_CYAN╔══════════════════════════════════════╗$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN║     Vue 国际化字符串提取工具         ║$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN║     Version 1.0.0                    ║$COLOR_RESET" -ForegroundColor Cyan
    Write-Host "$COLOR_CYAN╚══════════════════════════════════════╝$COLOR_RESET" -ForegroundColor Cyan
    Write-Host ""
    
    # 1. 加载配置
    $config = Load-Config $ConfigPath
    $config = Validate-Config $config
    
    # 2. 获取文件列表
    $files = Get-FileList $config
    
    if ($files.Count -eq 0) {
        Write-Host "$COLOR_YELLOW[WARN]$COLOR_RESET 没有找到要处理的 Vue 文件" -ForegroundColor Yellow
        exit 0
    }
    
    # 3. 提取中文
    $foundStrings = @{}
    $processedFiles = 0
    $startTime = Get-Date
    
    foreach ($file in $files) {
        $processedFiles++
        $fileName = $file.Name
        
        if ($config.verbose) {
            Write-Host "$COLOR_CYAN[PROCESSING]$COLOR_RESET ($processedFiles/$($files.Count)) $fileName" -ForegroundColor Cyan
        } else {
            Write-Progress -Activity "正在处理 Vue 文件" -Status "$fileName ($processedFiles/$($files.Count))" -PercentComplete (($processedFiles / $files.Count) * 100)
        }
        
        $foundStrings = Extract-ChineseFromVue -File $file -FoundStrings $foundStrings -Verbose $config.verbose
    }
    
    if (-not $config.verbose) {
        Write-Progress -Activity "正在处理 Vue 文件" -Completed
    }
    
    $endTime = Get-Date
    $duration = $endTime - $startTime
    
    # 4. 生成输出文件
    $totalStrings = Generate-OutputFile -FoundStrings $foundStrings -OutputPath $config.output_file -Verbose $config.verbose
    
    # 5. 显示统计信息
    Show-Statistics -TotalFiles $files.Count -TotalStrings $totalStrings -OutputFile $config.output_file
    
    # 6. 处理时间
    Write-Host "$COLOR_MAGENTA[TIME]$COLOR_RESET 处理耗时: $($duration.TotalSeconds.ToString('0.00')) 秒" -ForegroundColor Magenta
    Write-Host ""
    
    # 7. 显示示例
    if ($totalStrings -gt 0) {
        Write-Host "${COLOR_YELLOW}示例输出:${COLOR_RESET}" -ForegroundColor Yellow
        
        $firstFolder = $foundStrings.Keys | Select-Object -First 1
        $sampleTexts = $foundStrings[$firstFolder] | Select-Object -First 3
        Write-Host "  `"$firstFolder`": { " -ForegroundColor Gray
        foreach ($text in $sampleTexts) {
            Write-Host "    `"\`": `"$text`", " -ForegroundColor Gray
        }
        if ($foundStrings[$firstFolder].Count -gt 3) {
            Write-Host "    ..." -ForegroundColor DarkGray
        }
        Write-Host "  }" -ForegroundColor Gray
    }
}

# 运行主函数
try {
    Main
} catch {
    Write-Host "$COLOR_RED[FATAL ERROR]$COLOR_RESET $_" -ForegroundColor Red
    Write-Host "$COLOR_REDStack Trace:$COLOR_RESET" -ForegroundColor Red
    Write-Host $_.ScriptStackTrace -ForegroundColor Red
    exit 1
}
脚本二

保存文件时需要重新另存为选择"带有BOM的UTF-8"编码;

4.执行脚本文件

1.打开PowerShell

Win+R,输入Powershell

2.进入脚本所在目录

cd  D:\文件\VUE项目\Vue国际化

image

 3.执行脚本

 ./extract-vue-i18nV1.ps1

image

 执行成功!

image

 

posted @ 2026-01-15 10:56  じ逐梦  阅读(0)  评论(0)    收藏  举报