Crowdin l10n #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Crowdin l10n | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '0 0 * * 1' | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| crowdinAuthToken: ${{ secrets.CROWDIN_TOKEN }} | |
| downloadTranslationsBranch: l10n | |
| GH_TOKEN: ${{ github.token }} | |
| jobs: | |
| crowdinSync: | |
| runs-on: windows-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout add-on | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: true | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version-file: ".python-version" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Install dependencies | |
| run: uv sync | |
| - name: Get add-on info | |
| id: getAddonInfo | |
| shell: pwsh | |
| run: uv run ./.github/scripts/setOutputs.py | |
| - name: Download l10nUtil | |
| run: | | |
| gh release download --repo nvaccess/nvdaL10n --pattern "l10nUtil.exe" | |
| - name: Download translations from Crowdin | |
| shell: pwsh | |
| run: | | |
| ./l10nUtil.exe exportTranslations -o _addonL10n -c addon | |
| New-Item -ItemType Directory -Force -Path addon/locale | Out-Null | |
| New-Item -ItemType Directory -Force -Path addon/doc | Out-Null | |
| $addonId = "${{ steps.getAddonInfo.outputs.addonId }}" | |
| foreach ($dir in Get-ChildItem -Path "_addonL10n/$addonId" -Directory) { | |
| Write-Host "==============================" | |
| Write-Host "Processing language: $($dir.Name)" | |
| Write-Host "==============================" | |
| $langCode = $dir.Name | |
| $langShort = $langCode.Split('_')[0] | |
| # Paths | |
| $poFile = Join-Path $dir.FullName "$addonId.po" | |
| $localPoPath = "addon/locale/$langCode/LC_MESSAGES/nvda.po" | |
| $xliffFile = Join-Path $dir.FullName "$addonId.xliff" | |
| $remoteMd = Join-Path $dir.FullName "$addonId.md" | |
| $targetDocDir = "addon/doc/$langCode" | |
| $localMd = "$targetDocDir/readme.md" | |
| # ---------------------------- | |
| # SKIP ENGLISH (source language) | |
| # ---------------------------- | |
| if ($langCode -eq "en") { | |
| Write-Host "Skipping English (source language) → no MD/XLIFF processing required" | |
| continue | |
| } | |
| # ---------------------------- | |
| # PO PROCESSING | |
| # ---------------------------- | |
| if (Test-Path $poFile) { | |
| Write-Host "Checking PO file..." | |
| uv run ./.github/scripts/checkTranslation.py "$poFile" | |
| $isPoTranslated = ($LASTEXITCODE -eq 0) | |
| Write-Host "PO translated: $isPoTranslated" | |
| if ($isPoTranslated) { | |
| Write-Host "Updating local PO" | |
| New-Item -ItemType Directory -Force -Path (Split-Path $localPoPath) | Out-Null | |
| Move-Item $poFile $localPoPath -Force | |
| } else { | |
| Write-Host "PO not translated" | |
| if (Test-Path $localPoPath) { | |
| Write-Host "Uploading local PO to Crowdin" | |
| ./l10nUtil.exe uploadTranslationFile $langCode "$addonId.po" $localPoPath -c addon | |
| } else { | |
| Write-Host "No local PO available" | |
| } | |
| } | |
| } | |
| # ---------------------------- | |
| # XLIFF PROCESSING | |
| # ---------------------------- | |
| $xliffValid = $false | |
| $tempMd = $null | |
| if (Test-Path $xliffFile) { | |
| Write-Host "Checking XLIFF..." | |
| uv run ./.github/scripts/checkTranslation.py "$xliffFile" | |
| $xliffValid = ($LASTEXITCODE -eq 0) | |
| Write-Host "XLIFF valid: $xliffValid" | |
| if ($xliffValid) { | |
| Write-Host "Converting XLIFF → MD" | |
| $tempMd = "$env:TEMP\readme_$langCode.md" | |
| ./l10nUtil.exe xliff2md $xliffFile $tempMd | |
| } | |
| } | |
| $remoteExists = Test-Path $remoteMd | |
| $localExists = Test-Path $localMd | |
| Write-Host "Remote MD exists: $remoteExists" | |
| Write-Host "Local MD exists: $localExists" | |
| # ---------------------------- | |
| # DECISION ENGINE | |
| # ---------------------------- | |
| # CASE: XLIFF VALID | |
| if ($xliffValid) { | |
| Write-Host "Entering XLIFF-driven logic" | |
| if ($remoteExists -and $localExists) { | |
| Write-Host "3-way comparison (xliff, remote, local)" | |
| $scoreX = (uv run python .github/scripts/checkTranslation.py "$tempMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreR = (uv run python .github/scripts/checkTranslation.py "$remoteMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreL = (uv run python .github/scripts/checkTranslation.py "$localMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreX = [double]$scoreX | |
| $scoreR = [double]$scoreR | |
| $scoreL = [double]$scoreL | |
| Write-Host "Scores → XLIFF:$scoreX Remote:$scoreR Local:$scoreL" | |
| $best = [Math]::Max($scoreX, [Math]::Max($scoreR, $scoreL)) | |
| if ($best -eq $scoreX) { | |
| Write-Host "Winner: XLIFF" | |
| Move-Item $tempMd $localMd -Force | |
| } elseif ($best -eq $scoreR) { | |
| Write-Host "Winner: Remote MD" | |
| Move-Item $remoteMd $localMd -Force | |
| } else { | |
| Write-Host "Winner: Local MD → uploading" | |
| ./l10nUtil.exe uploadTranslationFile $langCode "$addonId.md" $localMd -c addon | |
| } | |
| } elseif ($remoteExists -and -not $localExists) { | |
| Write-Host "Comparing XLIFF vs Remote" | |
| $scoreX = (uv run python .github/scripts/checkTranslation.py "$tempMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreR = (uv run python .github/scripts/checkTranslation.py "$remoteMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreX = [double]$scoreX | |
| $scoreR = [double]$scoreR | |
| if ($scoreX -ge $scoreR) { | |
| Write-Host "Winner: XLIFF → creating local" | |
| Move-Item $tempMd $localMd -Force | |
| } else { | |
| Write-Host "Winner: Remote → creating local" | |
| Move-Item $remoteMd $localMd -Force | |
| } | |
| } elseif (-not $remoteExists -and $localExists) { | |
| Write-Host "Comparing XLIFF vs Local" | |
| $scoreX = (uv run python .github/scripts/checkTranslation.py "$tempMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreL = (uv run python .github/scripts/checkTranslation.py "$localMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreX = [double]$scoreX | |
| $scoreL = [double]$scoreL | |
| if ($scoreX -gt $scoreL) { | |
| Write-Host "Winner: XLIFF → overwrite local" | |
| Move-Item $tempMd $localMd -Force | |
| } else { | |
| Write-Host "Winner: Local → uploading" | |
| ./l10nUtil.exe uploadTranslationFile $langCode "$addonId.md" $localMd -c addon | |
| } | |
| } else { | |
| Write-Host "Only XLIFF available → importing directly" | |
| Move-Item $tempMd $localMd -Force | |
| } | |
| } else { | |
| Write-Host "XLIFF not usable → fallback logic" | |
| if ($remoteExists -and $localExists) { | |
| Write-Host "Comparing Remote vs Local" | |
| $scoreR = (uv run python .github/scripts/checkTranslation.py "$remoteMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreL = (uv run python .github/scripts/checkTranslation.py "$localMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreR = [double]$scoreR | |
| $scoreL = [double]$scoreL | |
| if ($scoreR -gt $scoreL) { | |
| Write-Host "Winner: Remote → overwrite local" | |
| Move-Item $remoteMd $localMd -Force | |
| } else { | |
| Write-Host "Winner: Local → uploading" | |
| ./l10nUtil.exe uploadTranslationFile $langCode "$addonId.md" $localMd -c addon | |
| } | |
| } elseif ($remoteExists -and -not $localExists) { | |
| Write-Host "Remote only → checking quality" | |
| $scoreR = (uv run python .github/scripts/checkTranslation.py "$remoteMd" $langShort | Select-String "md_score=").ToString().Split("=")[1] | |
| $scoreR = [double]$scoreR | |
| if ($scoreR -gt 0.5) { | |
| Write-Host "Remote is valid → importing" | |
| Move-Item $remoteMd $localMd -Force | |
| } else { | |
| Write-Host "Remote not valid → skipping" | |
| } | |
| } elseif (-not $remoteExists -and $localExists) { | |
| Write-Host "Only local exists → uploading without scoring" | |
| ./l10nUtil.exe uploadTranslationFile $langCode "$addonId.md" $localMd -c addon | |
| } else { | |
| Write-Host "No MD available → nothing to do" | |
| } | |
| } | |
| } | |
| # ---------------------------- | |
| # COMMIT | |
| # ---------------------------- | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add addon/locale addon/doc | |
| git diff --staged --quiet | |
| if ($LASTEXITCODE -ne 0) { | |
| git commit -m "Update translations for $addonId from Crowdin" | |
| git switch ${{ env.downloadTranslationsBranch }} 2>$null | |
| if ($LASTEXITCODE -ne 0) { | |
| git switch -c ${{ env.downloadTranslationsBranch }} | |
| } | |
| git push -f --set-upstream origin ${{ env.downloadTranslationsBranch }} | |
| } else { | |
| Write-Host "Nothing to commit." | |
| } |