Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pr-review-requested.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Send Slack notification on PR review requested
if: github.event.requested_reviewer.login == 'danblackadder' || github.event.requested_reviewer.login == 'aterga' || github.event.requested_reviewer.login == 'sea-snake' || github.event.requested_reviewer.login == 'lmuntaner'
if: github.event.requested_reviewer.login == 'aterga' || github.event.requested_reviewer.login == 'sea-snake'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hard coding this, could we take members of the Identity team?

uses: ./.github/actions/slack
with:
WEBHOOK_URL: ${{ secrets.SLACK_PRIVATE_IDENTITY_WEBHOOK_URL }}
MESSAGE: |
<@${{ github.event.requested_reviewer.login == 'danblackadder' && 'U07FDBKDHEH' || github.event.requested_reviewer.login == 'aterga' && 'U02EAPEDT3J' || github.event.requested_reviewer.login == 'sea-snake' && 'U07QU79GX0T' || github.event.requested_reviewer.login == 'lmuntaner' && 'U02TEQHKV35' }}>: New PR ready for review: <${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}>.
<@${{ github.event.requested_reviewer.login == 'aterga' && 'U02EAPEDT3J' || github.event.requested_reviewer.login == 'sea-snake' && 'U07QU79GX0T' }}>: New PR ready for review: <${{ github.event.pull_request.html_url }}|${{ github.event.pull_request.title }}>.
173 changes: 173 additions & 0 deletions .github/workflows/translations-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
name: Translations Check

# Creates translation PRs for languages that have missing `msgstr` entries
# or are affected by newly-added rules in src/frontend/src/lib/locales/CLAUDE.md.
on:
push:
branches: [main]
paths:
- "src/frontend/src/lib/locales/**.po"
- "src/frontend/src/lib/locales/CLAUDE.md"
- "src/frontend/src/lib/locales/rules/**.md"
workflow_dispatch:

permissions:
contents: read
pull-requests: read

concurrency:
group: translations-check
cancel-in-progress: false

jobs:
precheck:
runs-on: ubuntu-latest
outputs:
needs_run: ${{ steps.decide.outputs.needs_run }}
rule_diff_range: ${{ steps.decide.outputs.rule_diff_range }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Decide whether to invoke Claude
id: decide
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
EVENT_BEFORE: ${{ github.event.before }}
EVENT_AFTER: ${{ github.event.after }}
run: |
set -euo pipefail

# Trigger 1: rule file(s) changed in the pushed commit range (new rule merged)?
# EVENT_BEFORE is the all-zero SHA on branch creation / force pushes
# from non-existent history; guard against that and verify both ends
# resolve before asking git to diff them.
rule_diff_range=""
zero_sha="0000000000000000000000000000000000000000"
if [ "$EVENT_NAME" = "push" ] \
&& [ -n "${EVENT_BEFORE:-}" ] && [ "$EVENT_BEFORE" != "$zero_sha" ] \
&& [ -n "${EVENT_AFTER:-}" ] && [ "$EVENT_AFTER" != "$zero_sha" ] \
&& git cat-file -e "$EVENT_BEFORE^{commit}" 2>/dev/null \
&& git cat-file -e "$EVENT_AFTER^{commit}" 2>/dev/null; then
rule_diff_range="$EVENT_BEFORE..$EVENT_AFTER"
if git diff --quiet "$EVENT_BEFORE" "$EVENT_AFTER" -- src/frontend/src/lib/locales/CLAUDE.md src/frontend/src/lib/locales/rules/; then
rules_changed=false
else
rules_changed=true
fi
else
rules_changed=false
fi

# Trigger 2: any .po file on main has empty msgstr and no open PR for it?
open_pr_branches=$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --search "chore(fe): update" --json headRefName --jq '.[].headRefName' 2>/dev/null || true)

has_missing=false
for po in src/frontend/src/lib/locales/*.po; do
lang=$(basename "$po" .po)
[ "$lang" = "en" ] && continue
if echo "$open_pr_branches" | grep -qx "chore/translate-$lang"; then
continue
fi
# Skip the header entry (msgid "") then look for empty msgstr
if awk '/^msgid ""$/{skip=1;next}/^msgid /{skip=0}{if(!skip)print}' "$po" | grep -q '^msgstr ""$'; then
has_missing=true
break
fi
done

# Always run on workflow_dispatch
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
needs_run=true
elif [ "$rules_changed" = true ] || [ "$has_missing" = true ]; then
needs_run=true
else
needs_run=false
fi

echo "rules_changed=$rules_changed"
echo "has_missing=$has_missing"
echo "needs_run=$needs_run"
echo "rule_diff_range=$rule_diff_range"
echo "needs_run=$needs_run" >> "$GITHUB_OUTPUT"
echo "rule_diff_range=$rule_diff_range" >> "$GITHUB_OUTPUT"

run-claude:
needs: precheck
if: needs.precheck.outputs.needs_run == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GIX_BOT_PAT }}
fetch-depth: 0

- uses: ./.github/actions/setup-node

- run: npm ci

- name: Configure git identity
run: |
git config --global user.name "gix-bot"
git config --global user.email "gix-bot@users.noreply.github.com"

- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code@2.1.116

- name: Snapshot branch refs before Claude runs
run: git for-each-ref --format='%(refname:short)=%(objectname)' refs/heads/ > "$RUNNER_TEMP/pre-refs.txt"

- name: Run Claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GIX_BOT_PAT }}
RULE_DIFF_RANGE: ${{ needs.precheck.outputs.rule_diff_range }}
run: |
prompt=$(envsubst '${RULE_DIFF_RANGE}' < src/frontend/src/lib/locales/prompts/check.md)
claude -p "$prompt" \
--dangerously-skip-permissions \
--allowedTools "Bash(git:*),Bash(gh pr:*),Bash(gh api repos/dfinity/internet-identity/:*),Bash(gh api graphql:*),Bash(npm:*),Bash(awk:*),Bash(grep:*),Bash(sed:*),Bash(cat:*),Bash(basename:*),Bash(echo:*),Bash(jq:*),Bash(wc:*),Bash(head:*),Bash(tail:*),Bash(date:*),Bash(find:*),Bash(ls:*),Bash(mkdir:*),Bash(cd:*),Bash(envsubst:*),Bash(printf:*),Bash(sort:*),Bash(uniq:*),Bash(tr:*),Read,Write,Edit,Glob,Grep,Agent"

- name: Audit Claude changes
if: always()
env:
ALLOWED_PATHS_REGEX: '^src/frontend/src/lib/locales/[a-z][a-z-]*\.po$'
run: |
set -euo pipefail
violations=()
while IFS='=' read -r ref sha; do
pre_sha=$(grep "^${ref}=" "$RUNNER_TEMP/pre-refs.txt" 2>/dev/null | cut -d= -f2 || true)
if [ -z "$pre_sha" ]; then
base="origin/main"
elif [ "$pre_sha" != "$sha" ]; then
base="$pre_sha"
else
continue
fi
files=$(git diff --name-only "$base..$sha" 2>/dev/null || true)
[ -z "$files" ] && continue
while IFS= read -r f; do
if ! echo "$f" | grep -qE "$ALLOWED_PATHS_REGEX"; then
violations+=("$ref: $f")
fi
done <<< "$files"
done < <(git for-each-ref --format='%(refname:short)=%(objectname)' refs/heads/)

if [ ${#violations[@]} -gt 0 ]; then
echo "::error::Claude modified files outside the allowed path set for this workflow:"
printf ' %s\n' "${violations[@]}"
exit 1
fi
echo "Audit passed — all changed files are within the allowed path set."

- name: Notify Slack on failure
if: ${{ failure() }}
uses: ./.github/actions/slack
with:
WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
MESSAGE: "Translations check failed: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
99 changes: 99 additions & 0 deletions .github/workflows/translations-context-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Translations Context Audit

# Scans Svelte sources for translatable strings that are ambiguous without a
# translator-facing `context` annotation (e.g. short labels like "Cancel" used
# in several places with different meanings) and opens a single sweep PR that
# converts them to `$t({ message, context })` form.
#
# Runs weekly to pick up newly-added ambiguous strings. `workflow_dispatch` is
# also available for on-demand sweeps. Reviewer feedback on the resulting PR is
# handled by the translations-feedback workflow.

on:
schedule:
# Mondays at 10:00 UTC
- cron: "0 10 * * 1"
workflow_dispatch:

permissions:
contents: read
pull-requests: read

concurrency:
group: translations-context-audit
cancel-in-progress: false

jobs:
run-claude:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GIX_BOT_PAT }}
fetch-depth: 0

- uses: ./.github/actions/setup-node

- run: npm ci

- name: Configure git identity
run: |
git config --global user.name "gix-bot"
git config --global user.email "gix-bot@users.noreply.github.com"

- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code@2.1.116

- name: Snapshot branch refs before Claude runs
run: git for-each-ref --format='%(refname:short)=%(objectname)' refs/heads/ > "$RUNNER_TEMP/pre-refs.txt"

- name: Run Claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GIX_BOT_PAT }}
run: |
claude -p "$(cat src/frontend/src/lib/locales/prompts/context-audit.md)" \
--dangerously-skip-permissions \
--allowedTools "Bash(git:*),Bash(gh pr:*),Bash(gh api repos/dfinity/internet-identity/:*),Bash(gh api graphql:*),Bash(npm:*),Bash(awk:*),Bash(grep:*),Bash(sed:*),Bash(cat:*),Bash(basename:*),Bash(echo:*),Bash(jq:*),Bash(wc:*),Bash(head:*),Bash(tail:*),Bash(date:*),Bash(find:*),Bash(ls:*),Bash(mkdir:*),Bash(cd:*),Bash(envsubst:*),Bash(printf:*),Bash(sort:*),Bash(uniq:*),Bash(tr:*),Read,Write,Edit,Glob,Grep,Agent"

- name: Audit Claude changes
if: always()
env:
ALLOWED_PATHS_REGEX: '^src/frontend/src/lib/locales/[a-z][a-z-]*\.po$|^src/frontend/src/(lib|routes)/.*\.svelte$'
run: |
set -euo pipefail
violations=()
while IFS='=' read -r ref sha; do
pre_sha=$(grep "^${ref}=" "$RUNNER_TEMP/pre-refs.txt" 2>/dev/null | cut -d= -f2 || true)
if [ -z "$pre_sha" ]; then
base="origin/main"
elif [ "$pre_sha" != "$sha" ]; then
base="$pre_sha"
else
continue
fi
files=$(git diff --name-only "$base..$sha" 2>/dev/null || true)
[ -z "$files" ] && continue
while IFS= read -r f; do
if ! echo "$f" | grep -qE "$ALLOWED_PATHS_REGEX"; then
violations+=("$ref: $f")
fi
done <<< "$files"
done < <(git for-each-ref --format='%(refname:short)=%(objectname)' refs/heads/)

if [ ${#violations[@]} -gt 0 ]; then
echo "::error::Claude modified files outside the allowed path set for this workflow:"
printf ' %s\n' "${violations[@]}"
exit 1
fi
echo "Audit passed — all changed files are within the allowed path set."

- name: Notify Slack on failure
if: ${{ failure() }}
uses: ./.github/actions/slack
with:
WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
MESSAGE: "Translations context audit failed: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
Loading
Loading