Workingon a script to use an LLM to do the translation work.
This commit is contained in:
parent
f2568e1aab
commit
88d21c2101
18
RunMeToUpdateLocales.cmd
Normal file
18
RunMeToUpdateLocales.cmd
Normal file
@ -0,0 +1,18 @@
|
||||
@echo off
|
||||
rem https://github.com/settings/personal-access-tokens
|
||||
|
||||
rem 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
|
||||
|
||||
rem this can store the github token in the environment variable GITHUB_TOKEN, which is used by the sync-locales.ps1 script
|
||||
rem setx GITHUB_TOKEN "%(gh auth token)%"
|
||||
|
||||
rem this is for github copilot, but it's blocked
|
||||
rem pwsh ./sync-locales.ps1 -GitHubToken "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"
|
||||
rem powershell -ExecutionPolicy Bypass -File sync-locales.ps1 -GitHubToken "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"
|
||||
rem powershell -NoProfile -ExecutionPolicy Bypass -File "sync-locales.ps1" -GitHubToken "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"
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo Running Azure OpenAI locale sync...
|
||||
|
||||
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0sync-locales-azure.ps1" -AzureOpenAIEndpoint "https://cjdopenai.openai.azure.com/" -AzureOpenAIApiKey "9atLPrU05kMV2sFXCduLFfRJHnneF1KFZgmJqO42UuH0PxptagW3JQQJ99CBACmepeSXJ3w3AAABACOGvB4E" -AzureOpenAIDeployment "gpt-4o-mini"
|
||||
214
sync-locales-azure.ps1
Normal file
214
sync-locales-azure.ps1
Normal file
@ -0,0 +1,214 @@
|
||||
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
|
||||
185
sync-locales-github.ps1
Normal file
185
sync-locales-github.ps1
Normal file
@ -0,0 +1,185 @@
|
||||
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
|
||||
Loading…
Reference in New Issue
Block a user