param( [string]$LocalesRoot = "./public/locales", [string]$MasterLocale = "en", [string]$GitHubToken = "" ) Write-Host "Current working directory: $(Get-Location)" if (-not $GitHubToken) { Write-Host "GitHubToken not provided. Please pass -GitHubToken 'YOUR_PAT' when running the script." -ForegroundColor Yellow exit 1 } # Simple in-memory cache to avoid re-translating identical strings $TranslationCache = @{} function Get-FromCache { param( [string]$text, [string]$targetLocale ) $key = "$targetLocale|$text" if ($TranslationCache.ContainsKey($key)) { return $TranslationCache[$key] } return $null } function Add-ToCache { param( [string]$text, [string]$targetLocale, [string]$translated ) $key = "$targetLocale|$text" $TranslationCache[$key] = $translated } function Translate-Text { param( [string]$text, [string]$targetLocale, [string]$githubToken ) $headers = @{ "Authorization" = "Bearer $githubToken" "X-GitHub-Api-Version" = "2023-07-07" "Content-Type" = "application/json" } $body = @{ messages = @( @{ role = "system" content = "You are a translation engine. Translate the user text into $targetLocale. Return ONLY the translated text with no commentary." }, @{ role = "user" content = $text } ) } | ConvertTo-Json -Depth 10 $response = Invoke-RestMethod ` -Uri "https://api.githubcopilot.com/chat/completions" ` -Method Post ` -Headers $headers ` -Body $body return $response.choices[0].message.content.Trim() } function Merge-TranslationObjects { param( [hashtable]$master, [hashtable]$target, [string]$locale, [string]$path = "" ) foreach ($key in $master.Keys) { $currentPath = if ($path) { "$path.$key" } else { $key } if (-not $target.ContainsKey($key)) { if ($master[$key] -is [string]) { Write-Host " Missing key: $currentPath → translating to $locale" $translated = Translate-Text -text $master[$key] -targetLocale $locale -githubToken $GitHubToken $target[$key] = $translated } elseif ($master[$key] -is [hashtable]) { $target[$key] = @{} Merge-TranslationObjects -master $master[$key] -target $target[$key] -locale $locale -path $currentPath } else { # Non-string leaf (number, bool, etc.) – just copy $target[$key] = $master[$key] } } else { # Key exists – preserve existing value, but recurse into nested objects if ($master[$key] -is [hashtable] -and $target[$key] -is [hashtable]) { Merge-TranslationObjects -master $master[$key] -target $target[$key] -locale $locale -path $currentPath } } } } Write-Host "Locales root: $LocalesRoot" Write-Host "Master locale: $MasterLocale" $masterPath = Join-Path $LocalesRoot $MasterLocale if (-not (Test-Path $masterPath)) { Write-Host "Master locale folder not found at: $masterPath" -ForegroundColor Red exit 1 } $masterFiles = Get-ChildItem $masterPath -Filter *.json if ($masterFiles.Count -eq 0) { Write-Host "No JSON files found in master locale folder: $masterPath" -ForegroundColor Yellow exit 0 } $localeFolders = Get-ChildItem $LocalesRoot -Directory | Where-Object { $_.Name -ne $MasterLocale } foreach ($localeFolder in $localeFolders) { $localeName = $localeFolder.Name Write-Host "" Write-Host "Syncing locale: $localeName" -ForegroundColor Cyan foreach ($file in $masterFiles) { $masterFilePath = $file.FullName $targetFilePath = Join-Path $localeFolder.FullName $file.Name Write-Host " File: $($file.Name)" Write-Host " Reading master file: $masterFilePath" Write-Host " File exists: $(Test-Path $masterFilePath)" Write-Host " File size: $((Get-Item $masterFilePath).Length) bytes" $masterJsonRaw = Get-Content $masterFilePath -Raw -Encoding UTF8 try { $masterJson = $masterJsonRaw | ConvertFrom-Json -AsHashtable } catch { Write-Host " Error parsing master JSON: $masterFilePath" -ForegroundColor Red continue } if (Test-Path $targetFilePath) { $targetJsonRaw = Get-Content $targetFilePath -Raw -Encoding UTF8 try { $targetJson = $targetJsonRaw | ConvertFrom-Json -AsHashtable } catch { Write-Host " Error parsing target JSON, starting from empty: $targetFilePath" -ForegroundColor Yellow $targetJson = @{} } } else { Write-Host " Creating missing file: $($file.Name)" $targetJson = @{} } Merge-TranslationObjects -master $masterJson -target $targetJson -locale $localeName try { $jsonOut = $targetJson | ConvertTo-Json -Depth 50 $jsonOut | Set-Content $targetFilePath -Encoding UTF8 Write-Host " Updated: $($file.Name)" } catch { Write-Host " Error writing JSON to: $targetFilePath" -ForegroundColor Red } } } Write-Host "" Write-Host "All locales synced and translated (where needed)." -ForegroundColor Green