From 0d270ac8718bd56d15aadf5cf74cbfae6c210725 Mon Sep 17 00:00:00 2001 From: 0xsysr3ll <31414959+0xSysR3ll@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:43:18 +0100 Subject: [PATCH] ci(workflow): validate i18n locale files are synchronized (#2347) --- .github/workflows/ci.yml | 63 ++++++++++++++++++++++++++++++++++++++++ bin/check-i18n.js | 39 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 bin/check-i18n.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ad2ab91..00e8708c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,69 @@ concurrency: cancel-in-progress: true jobs: + i18n: + name: i18n Check + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + permissions: + contents: read + pull-requests: write + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.pull_request.number }} + steps: + - name: Checkout + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + + - name: Pnpm Setup + uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + with: + node-version-file: 'package.json' + + - name: Get pnpm store directory + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + env: + CI: true + run: pnpm install + + - name: i18n Check + shell: bash + env: + BODY: | + The i18n check failed because translation messages are out of sync. + + This usually happens when you've added or modified translation strings in your code but haven't updated the translation file. + + Please run `pnpm i18n:extract` and commit the changes. + run: | + retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; } + node bin/check-i18n.js + check_failed=$? + if [ $check_failed -eq 1 ]; then + retry gh pr edit "$NUMBER" -R "$GH_REPO" --add-label "i18n-out-of-sync" || true + retry gh pr comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true + else + retry gh pr edit "$NUMBER" -R "$GH_REPO" --remove-label "i18n-out-of-sync" || true + fi + exit $check_failed + test: name: Lint & Test Build if: github.event_name == 'pull_request' diff --git a/bin/check-i18n.js b/bin/check-i18n.js new file mode 100644 index 00000000..07bbf56c --- /dev/null +++ b/bin/check-i18n.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +/** + * Check that i18n locale files are in sync with extracted messages. + * Runs `pnpm i18n:extract` and compares en.json; exits 1 if they differ. + */ +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const localePath = path.join( + __dirname, + '..', + 'src', + 'i18n', + 'locale', + 'en.json' +); +const backupPath = `${localePath}.bak`; + +try { + fs.copyFileSync(localePath, backupPath); + execSync('pnpm i18n:extract', { stdio: 'inherit' }); + const original = fs.readFileSync(backupPath, 'utf8'); + const extracted = fs.readFileSync(localePath, 'utf8'); + fs.unlinkSync(backupPath); + + if (original !== extracted) { + console.error( + "i18n messages are out of sync. Please run 'pnpm i18n:extract' and commit the changes." + ); + process.exit(1); + } +} catch (err) { + if (fs.existsSync(backupPath)) { + fs.unlinkSync(backupPath); + } + throw err; +}