feat: add i18n-sync action to fetch translations and create pull requests
All checks were successful
Aikido Security PR Check / Aikido Security Scan (pull_request) Successful in 40s
validate-shared-actions / validate-shared-actions (pull_request) Successful in 39s

This commit is contained in:
Michael.Seele@schmalz.de 2026-05-22 09:15:42 +00:00
parent 0473d4bc1d
commit afa2cfc241
4 changed files with 179 additions and 0 deletions

View file

@ -19,6 +19,7 @@ on:
- cache
- checkout
- helm-deploy
- i18n-sync
- inject-content
- maven-build
- pnpm-build

View file

@ -12,6 +12,7 @@ Shared actions for Forgejo CI/CD pipelines.
| [cache](cache) | Cache files between workflow runs |
| [checkout](checkout) | Action for checking out a repository |
| [helm-deploy](helm-deploy) | Deploy a service to Kubernetes via Helm over SSH |
| [i18n-sync](i18n-sync) | Fetch translations from i18n.schmalz.com and open a pull request |
| [inject-content](inject-content) | Inject content into a file by appending or overwriting |
| [maven-build](maven-build) | Action for building and validating Maven projects |
| [pnpm-build](pnpm-build) | Action for building and validating with PNPM |

46
i18n-sync/README.md Normal file
View file

@ -0,0 +1,46 @@
# i18n-sync
Fetches the latest translations from i18n.schmalz.com, commits them to a `chore/i18n` branch, and opens a pull request against the destination branch.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `i18n-application` | Yes | — | Application key in i18n.schmalz.com (e.g. `calculator`) |
| `locales-folder` | No | `locales` | Path to the locales directory relative to the repository root |
| `format-folder` | No | `""` | Directory containing the `package.json` whose format script should be run after downloading translations. Leave empty to skip formatting. |
| `format-script` | No | `format` | pnpm script name to run for formatting (e.g. `format`, `format:fix`) |
| `jfrog-token` | No | `""` | JFrog npm auth token (required when `format-folder` is set) |
| `forgejo-token` | Yes | — | Forgejo token with `contents:write` and `pull-requests:write` access |
| `destination-branch` | No | `dev` | Target branch for the pull request |
## Usage
Minimal — no formatting:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/i18n-sync@i18n-sync-v1
with:
i18n-application: my-app
forgejo-token: ${{ secrets.FORGEJO_I18N_UPDATE_TOKEN }}
```
With formatting after download:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/i18n-sync@i18n-sync-v1
with:
i18n-application: my-app
locales-folder: frontend/locales
format-folder: frontend
jfrog-token: ${{ secrets.JFROG_TOKEN }}
forgejo-token: ${{ secrets.FORGEJO_I18N_UPDATE_TOKEN }}
```
## Notes
- The action fails fast if the `chore/i18n` branch already exists, indicating a previous update is still pending review.
- If no translation changes are detected after downloading, the action exits cleanly without creating a branch or PR.
- Deleted languages (files removed from i18n.schmalz.com) are also staged for removal via `git add --all`.
- `jq` is installed automatically if not present on the runner.
- The `forgejo-token` must belong to a user or bot with `contents:write` and `pull-requests:write` permissions on the repository.

131
i18n-sync/action.yml Normal file
View file

@ -0,0 +1,131 @@
name: i18n Sync
description: >
Fetches the latest translations from i18n.schmalz.com, commits them to a
chore/i18n branch, and opens a pull request against the destination branch.
inputs:
i18n-application:
description: Application key in i18n.schmalz.com (e.g. "calculator")
required: true
locales-folder:
description: Path to the locales directory relative to the repository root
required: false
default: locales
format-folder:
description: >
Directory containing the package.json whose format script should be run
after downloading translations. Leave empty to skip formatting.
required: false
default: ""
format-script:
description: pnpm script name to run for formatting (e.g. "format", "format:fix")
required: false
default: format
jfrog-token:
description: JFrog npm auth token (required when format-folder is set)
required: false
default: ""
forgejo-token:
description: Forgejo token with contents:write and pull-requests:write access
required: true
destination-branch:
description: Target branch for the pull request
required: false
default: dev
runs:
using: composite
steps:
- name: Install jq if missing
shell: bash
run: |
set -euo pipefail
command -v jq >/dev/null 2>&1 || sudo apt-get install -y --no-install-recommends jq
- name: Configure git
shell: bash
env:
FORGEJO_TOKEN: ${{ inputs.forgejo-token }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
ORIGIN_URL=$(git remote get-url origin)
CLEAN_URL=$(echo "$ORIGIN_URL" | sed 's|https://[^@]*@|https://|')
git remote set-url origin "$(echo "$CLEAN_URL" | sed "s|https://|https://github-actions[bot]:${FORGEJO_TOKEN}@|")"
- name: Check if i18n branch already exists
shell: bash
run: |
set -euo pipefail
if git ls-remote --exit-code --heads origin "refs/heads/chore/i18n" >/dev/null 2>&1; then
echo "Branch chore/i18n already exists. A previous i18n update is still pending."
exit 1
fi
- name: Download translations
shell: bash
env:
I18N_APPLICATION: ${{ inputs.i18n-application }}
LOCALES_FOLDER: ${{ inputs.locales-folder }}
run: |
set -euo pipefail
git checkout -b chore/i18n
echo "Fetching available languages for application: $I18N_APPLICATION"
languages_json=$(curl --fail-with-body -sSL "https://i18n.schmalz.com/api/languages?projectKey=$I18N_APPLICATION")
echo "$languages_json" | jq -r '.[]' | while read -r lang; do
echo "Downloading language: $lang"
curl --fail-with-body -sSL -o "${LOCALES_FOLDER}/${lang}.json" \
"https://i18n.schmalz.com/api/${I18N_APPLICATION}/${lang}.json"
done
- name: Format translations
if: ${{ inputs.format-folder != '' }}
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.format-folder }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ${{ inputs.format-script }}
check-dedupe: "false"
- name: Commit and push translations
id: commit
shell: bash
env:
LOCALES_FOLDER: ${{ inputs.locales-folder }}
run: |
set -euo pipefail
git add --all "${LOCALES_FOLDER}/"
if git diff-index --cached --quiet HEAD; then
echo "No translation changes detected. Nothing to do."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit -m "chore: update translations via i18n"
git push origin chore/i18n
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create pull request
if: ${{ steps.commit.outputs.has_changes == 'true' }}
shell: bash
env:
TOKEN: ${{ inputs.forgejo-token }}
DESTINATION_BRANCH: ${{ inputs.destination-branch }}
run: |
set -euo pipefail
curl --fail-with-body -s -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/pulls" \
-d "$(jq -n \
--arg title "chore: update translations via i18n" \
--arg head "chore/i18n" \
--arg base "$DESTINATION_BRANCH" \
--arg body "Automated translation update from i18n.schmalz.com" \
'{title: $title, head: $head, base: $base, body: $body, delete_branch_after_merge: true}'
)"