webui/sync-locales-azure.ps1

214 lines
7.0 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

param(
[string]$LocalesRoot = "./public/locales",
[string]$MasterLocale = "en",
# Azure OpenAI settings
[string]$AzureOpenAIEndpoint = "",
[string]$AzureOpenAIApiKey = "",
[string]$AzureOpenAIDeployment = "" # e.g. "gpt-4o-mini"
)
if (-not $AzureOpenAIEndpoint -or -not $AzureOpenAIApiKey -or -not $AzureOpenAIDeployment) {
Write-Host "You must provide -AzureOpenAIEndpoint, -AzureOpenAIApiKey, and -AzureOpenAIDeployment." -ForegroundColor Yellow
exit 1
}
Write-Host "Current working directory: $(Get-Location)"
Write-Host "Locales root: $LocalesRoot"
Write-Host "Master locale: $MasterLocale"
# 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
)
# Check cache first
$cached = Get-FromCache -text $text -targetLocale $targetLocale
if ($cached) {
return $cached
}
$headers = @{
"api-key" = $AzureOpenAIApiKey
"Content-Type" = "application/json"
}
$systemPrompt = "You are a translation engine. Translate the user text into $targetLocale. " +
"Return ONLY the translated text with no commentary, quotes, or extra formatting."
$body = @{
messages = @(
@{
role = "system"
content = $systemPrompt
},
@{
role = "user"
content = $text
}
)
temperature = 0
} | ConvertTo-Json -Depth 10
$url = "$AzureOpenAIEndpoint/openai/deployments/$AzureOpenAIDeployment/chat/completions?api-version=2024-02-15-preview"
$maxRetries = 3
$delaySeconds = 2
for ($attempt = 1; $attempt -le $maxRetries; $attempt++) {
try {
$response = Invoke-RestMethod `
-Uri $url `
-Method Post `
-Headers $headers `
-Body $body
$translated = $response.choices[0].message.content.Trim()
Add-ToCache -text $text -targetLocale $targetLocale -translated $translated
return $translated
}
catch {
Write-Host " Translation error (attempt $attempt): $($_.Exception.Message)" -ForegroundColor Red
if ($attempt -lt $maxRetries) {
Start-Sleep -Seconds $delaySeconds
} else {
Write-Host " Failed to translate after $maxRetries attempts. Falling back to source text." -ForegroundColor Yellow
Add-ToCache -text $text -targetLocale $targetLocale -translated $text
return $text
}
}
}
}
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
$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
}
}
}
}
$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