Update changelog and bump version #40
Workflow file for this run
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: Upload Locales to Cloudflare R2 | |
| # Triggered on version tag pushes (e.g., v0.1.0, v1.0.0-rc.1) | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| env: | |
| MIX_ENV: dev | |
| R2_BUCKET: content | |
| # Prefix within the bucket where locale ETF files are stored. | |
| R2_PREFIX: locales | |
| # Maximum concurrent uploads to stay within Cloudflare API limits. | |
| # R2 allows ~1000 PUT requests/second; we use a conservative batch | |
| # size to avoid transient rate-limit errors. | |
| BATCH_SIZE: 50 | |
| permissions: | |
| contents: read | |
| jobs: | |
| generate-and-upload: | |
| name: Generate and upload locale ETFs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| # Read the data version directly from the on-disk version | |
| # files before doing any expensive work. The version is | |
| # derived from `priv/localize/version` (CLDR release) and | |
| # `priv/localize/localize_patch_version` (Localize patch | |
| # counter). No Elixir compile is required for this step. | |
| - name: Read data version | |
| id: version | |
| run: | | |
| cldr_version=$(cat priv/localize/version | tr -d '[:space:]') | |
| patch_raw=$(cat priv/localize/localize_patch_version | tr -d '[:space:]') | |
| # `localize_patch_version` is stored as "{cldr_version}:{patch}". | |
| # If the recorded CLDR version matches the current one, take | |
| # the numeric patch; otherwise treat the patch as 0. | |
| recorded_cldr="${patch_raw%%:*}" | |
| recorded_patch="${patch_raw##*:}" | |
| if [ "${recorded_cldr}" = "${cldr_version}" ]; then | |
| patch_version="${recorded_patch}" | |
| else | |
| patch_version="0" | |
| fi | |
| data_version="v${cldr_version}.${patch_version}" | |
| echo "data_version=${data_version}" >> "$GITHUB_OUTPUT" | |
| echo "Data version: ${data_version}" | |
| - name: Install rclone | |
| run: | | |
| curl -fsSL https://rclone.org/install.sh | sudo bash | |
| - name: Configure rclone for Cloudflare R2 | |
| env: | |
| R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} | |
| R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
| R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} | |
| run: | | |
| mkdir -p ~/.config/rclone | |
| cat > ~/.config/rclone/rclone.conf << EOF | |
| [r2] | |
| type = s3 | |
| provider = Cloudflare | |
| access_key_id = ${R2_ACCESS_KEY_ID} | |
| secret_access_key = ${R2_SECRET_ACCESS_KEY} | |
| endpoint = https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com | |
| acl = private | |
| no_check_bucket = true | |
| EOF | |
| # Short-circuit: if the target R2 prefix already contains ETF | |
| # files for this data_version, there is nothing to do. This | |
| # avoids re-generating and re-uploading the same data when a | |
| # release tag is pushed without a corresponding CLDR or patch | |
| # version bump. | |
| - name: Check if data version already exists on R2 | |
| id: check_existing | |
| env: | |
| DATA_VERSION: ${{ steps.version.outputs.data_version }} | |
| run: | | |
| existing=$(rclone lsf "r2:${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" --include "*.etf" 2>/dev/null | wc -l | tr -d '[:space:]') | |
| echo "Found ${existing} existing ETF file(s) at r2:${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" | |
| if [ "${existing}" -gt 0 ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Data version ${DATA_VERSION} already exists on R2 with ${existing} file(s); skipping generation and upload." | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Elixir | |
| if: steps.check_existing.outputs.skip != 'true' | |
| uses: erlef/setup-beam@v1 | |
| with: | |
| elixir-version: "1.19" | |
| otp-version: "28" | |
| - name: Restore dependency cache | |
| if: steps.check_existing.outputs.skip != 'true' | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| deps | |
| _build | |
| key: mix-locales-${{ hashFiles('mix.lock') }} | |
| restore-keys: | | |
| mix-locales- | |
| - name: Install dependencies | |
| if: steps.check_existing.outputs.skip != 'true' | |
| run: mix deps.get | |
| - name: Compile | |
| if: steps.check_existing.outputs.skip != 'true' | |
| run: mix compile | |
| - name: Generate locale ETFs | |
| if: steps.check_existing.outputs.skip != 'true' | |
| run: mix localize.generate_locales | |
| - name: Verify generated locales | |
| if: steps.check_existing.outputs.skip != 'true' | |
| run: | | |
| count=$(ls priv/localize/locales/*.etf 2>/dev/null | wc -l) | |
| echo "Generated ${count} locale ETF files" | |
| if [ "$count" -lt 100 ]; then | |
| echo "::error::Expected at least 100 locale files, got ${count}" | |
| exit 1 | |
| fi | |
| - name: Upload locales to R2 | |
| if: steps.check_existing.outputs.skip != 'true' | |
| env: | |
| DATA_VERSION: ${{ steps.version.outputs.data_version }} | |
| run: | | |
| echo "Uploading to r2:${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" | |
| echo "Files: $(ls priv/localize/locales/*.etf | wc -l)" | |
| # Use rclone sync with bandwidth and concurrency limits. | |
| # --transfers: concurrent file transfers (R2 allows ~1000 PUT/s, | |
| # we use a conservative limit via BATCH_SIZE). | |
| # --checkers: concurrent hash checks. | |
| # --s3-upload-concurrency: per-file multipart concurrency. | |
| rclone sync \ | |
| priv/localize/locales/ \ | |
| "r2:${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" \ | |
| --include "*.etf" \ | |
| --transfers ${BATCH_SIZE} \ | |
| --checkers 8 \ | |
| --s3-upload-concurrency 2 \ | |
| --stats 10s \ | |
| --stats-one-line \ | |
| --verbose | |
| - name: Verify upload | |
| if: steps.check_existing.outputs.skip != 'true' | |
| env: | |
| DATA_VERSION: ${{ steps.version.outputs.data_version }} | |
| run: | | |
| uploaded=$(rclone ls "r2:${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" | wc -l) | |
| local_count=$(ls priv/localize/locales/*.etf | wc -l) | |
| echo "Uploaded: ${uploaded}, Local: ${local_count}" | |
| if [ "$uploaded" -ne "$local_count" ]; then | |
| echo "::warning::Upload count mismatch: ${uploaded} uploaded vs ${local_count} local" | |
| fi | |
| echo "Upload complete: ${uploaded} locale files to ${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" | |
| - name: Skipped summary | |
| if: steps.check_existing.outputs.skip == 'true' | |
| env: | |
| DATA_VERSION: ${{ steps.version.outputs.data_version }} | |
| run: | | |
| echo "Skipped: data version ${DATA_VERSION} already present on R2 at ${R2_BUCKET}/${R2_PREFIX}/${DATA_VERSION}/" | |
| echo "Nothing was regenerated or uploaded. Bump the CLDR or Localize patch version and push a new tag to trigger a real upload." |