From 6b146b72bdb60fec790ef665d34aa253dfab8494 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Wed, 18 Mar 2026 00:26:55 -0400 Subject: [PATCH 1/2] feat: add official GitHub Action for CI/CD translation automation Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++++- action.yml | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 action.yml diff --git a/README.md b/README.md index 9a28781..c05a480 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Write your app in one language. Wrap text in ``. Get translations generated a - **`msg()`** — mark strings for extraction outside of JSX - **CLI Tool** — translate JSON, Markdown, and MDX files from the command line - **Vite Plugin** — build-time translation with smart change detection +- **GitHub Action** — `omniaura/solid-translate@v1` for CI/CD translation automation - **BYOK** — use any [Vercel AI SDK](https://ai-sdk.dev/) provider (OpenRouter, OpenAI, Anthropic, Google, etc.) ## Install @@ -376,6 +377,90 @@ const model = google("gemini-2.0-flash"); ## CI/CD Integration +### GitHub Action + +Use the official action to keep translations up to date automatically: + +```yaml +# .github/workflows/translate.yml +name: Translate + +on: + push: + branches: [main] + pull_request: + +jobs: + translate: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: omniaura/solid-translate@v1 + env: + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + with: + commit: true + commit-message: "chore: update translations" +``` + +#### Action Inputs + +| Input | Default | Description | +|-------|---------|-------------| +| `command` | `both` | `extract`, `translate`, or `both` | +| `working-directory` | `.` | Working directory | +| `commit` | `false` | Auto-commit updated translation files | +| `commit-message` | `chore: update translations` | Commit message | +| `node-version` | `22` | Node.js version | +| `package-manager` | `npm` | `npm`, `bun`, `pnpm`, or `yarn` | + +#### Action Outputs + +| Output | Description | +|--------|-------------| +| `changed` | `true` if translation files were updated | +| `files` | Space-separated list of changed files | + +#### Examples + +**Translate on PR and commit back:** + +```yaml +- uses: omniaura/solid-translate@v1 + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + with: + commit: true + package-manager: bun +``` + +**Extract only (no AI calls):** + +```yaml +- uses: omniaura/solid-translate@v1 + with: + command: extract +``` + +**Use output in subsequent steps:** + +```yaml +- uses: omniaura/solid-translate@v1 + id: translate + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + +- name: Create PR with translations + if: steps.translate.outputs.changed == 'true' + run: | + echo "Updated files: ${{ steps.translate.outputs.files }}" +``` + +### Manual CI Setup + Add to your build script for automatic translations on every deploy: ```json @@ -387,7 +472,7 @@ Add to your build script for automatic translations on every deploy: } ``` -Or in GitHub Actions: +Or directly in a workflow: ```yaml - name: Translate @@ -439,6 +524,7 @@ declare module "virtual:solid-translate" { | Shared strings (`msg()`) | ✅ | ✅ | | CLI for JSON/MD/MDX | ✅ | ✅ | | CI/CD integration | ✅ | ✅ | +| Official GitHub Action | ❌ | ✅ | | Zero refactoring | ✅ | ✅ | | BYOK (bring your own key) | ❌ (SaaS) | ✅ | | No vendor lock-in | ❌ | ✅ | diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..01b528f --- /dev/null +++ b/action.yml @@ -0,0 +1,100 @@ +name: "solid-translate" +description: "AI-powered translations for SolidJS apps. Extract and translate strings in CI." +author: "omniaura" + +branding: + icon: "globe" + color: "blue" + +inputs: + command: + description: "Command to run: extract, translate, or both (default: both)" + required: false + default: "both" + working-directory: + description: "Working directory (default: repo root)" + required: false + default: "." + commit: + description: "Commit updated translation files (default: false)" + required: false + default: "false" + commit-message: + description: "Commit message for translation updates" + required: false + default: "chore: update translations" + node-version: + description: "Node.js version to use" + required: false + default: "22" + package-manager: + description: "Package manager: npm, bun, pnpm, or yarn" + required: false + default: "npm" + +outputs: + changed: + description: "Whether translation files were changed (true/false)" + value: ${{ steps.check-changes.outputs.changed }} + files: + description: "Space-separated list of changed translation files" + value: ${{ steps.check-changes.outputs.files }} + +runs: + using: "composite" + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + + - name: Setup Bun + if: inputs.package-manager == 'bun' + uses: oven-sh/setup-bun@v2 + + - name: Install solid-translate + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + case "${{ inputs.package-manager }}" in + bun) bun add -d solid-translate ;; + pnpm) pnpm add -D solid-translate ;; + yarn) yarn add -D solid-translate ;; + *) npm install -D solid-translate ;; + esac + + - name: Run extract + if: inputs.command == 'extract' || inputs.command == 'both' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: npx solid-translate extract + + - name: Run translate + if: inputs.command == 'translate' || inputs.command == 'both' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: npx solid-translate translate + + - name: Check for changes + id: check-changes + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + if git diff --name-only | grep -q '\.json$'; then + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "files=$(git diff --name-only | grep '\.json$' | tr '\n' ' ')" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "files=" >> "$GITHUB_OUTPUT" + fi + + - name: Commit changes + if: inputs.commit == 'true' && steps.check-changes.outputs.changed == 'true' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A '*.json' '.solid-translate.lock' + git commit -m "${{ inputs.commit-message }}" + git push From 6690ab92fce165145e349a167393d56be73a26f9 Mon Sep 17 00:00:00 2001 From: Peyton-Spencer Date: Sun, 3 May 2026 18:14:35 -0400 Subject: [PATCH 2/2] fix: harden translation action Co-Authored-By: Codex --- README.md | 39 ++++++++++++++------ action.yml | 103 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 115 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c05a480..10c8385 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,6 @@ name: Translate on: push: branches: [main] - pull_request: jobs: translate: @@ -422,19 +421,37 @@ jobs: | Output | Description | |--------|-------------| | `changed` | `true` if translation files were updated | -| `files` | Space-separated list of changed files | +| `files` | Newline-separated list of changed translation files | + +The action reports and commits changes for JSON, Markdown, MDX, and `.solid-translate.lock` files only. Package manifests and package manager lockfiles are intentionally excluded. #### Examples -**Translate on PR and commit back:** +**Translate on push and commit back:** + +Use auto-commit only on trusted refs where `GITHUB_TOKEN` can push to the checked-out branch, such as `push` events on your own repository. For pull requests, leave `commit` disabled and use the `changed`/`files` outputs to decide whether to fail CI or open a separate update PR. ```yaml -- uses: omniaura/solid-translate@v1 - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - with: - commit: true - package-manager: bun +name: Translate + +on: + push: + branches: [main] + +jobs: + translate: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: omniaura/solid-translate@v1 + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + with: + commit: true + package-manager: bun ``` **Extract only (no AI calls):** @@ -455,8 +472,10 @@ jobs: - name: Create PR with translations if: steps.translate.outputs.changed == 'true' + env: + TRANSLATION_FILES: ${{ steps.translate.outputs.files }} run: | - echo "Updated files: ${{ steps.translate.outputs.files }}" + printf 'Updated files:\n%s\n' "$TRANSLATION_FILES" ``` ### Manual CI Setup diff --git a/action.yml b/action.yml index 01b528f..60bcbae 100644 --- a/action.yml +++ b/action.yml @@ -37,12 +37,34 @@ outputs: description: "Whether translation files were changed (true/false)" value: ${{ steps.check-changes.outputs.changed }} files: - description: "Space-separated list of changed translation files" + description: "Newline-separated list of changed translation files" value: ${{ steps.check-changes.outputs.files }} runs: using: "composite" steps: + - name: Validate inputs + shell: bash + env: + COMMAND: ${{ inputs.command }} + COMMIT: ${{ inputs.commit }} + PACKAGE_MANAGER: ${{ inputs.package-manager }} + run: | + case "$COMMAND" in + extract|translate|both) ;; + *) echo "Invalid command: $COMMAND" >&2; exit 1 ;; + esac + + case "$COMMIT" in + true|false) ;; + *) echo "Invalid commit value: $COMMIT" >&2; exit 1 ;; + esac + + case "$PACKAGE_MANAGER" in + npm|bun|pnpm|yarn) ;; + *) echo "Invalid package-manager: $PACKAGE_MANAGER" >&2; exit 1 ;; + esac + - name: Setup Node.js uses: actions/setup-node@v4 with: @@ -52,49 +74,96 @@ runs: if: inputs.package-manager == 'bun' uses: oven-sh/setup-bun@v2 - - name: Install solid-translate + - name: Enable package manager shims + if: inputs.package-manager == 'pnpm' || inputs.package-manager == 'yarn' shell: bash - working-directory: ${{ inputs.working-directory }} - run: | - case "${{ inputs.package-manager }}" in - bun) bun add -d solid-translate ;; - pnpm) pnpm add -D solid-translate ;; - yarn) yarn add -D solid-translate ;; - *) npm install -D solid-translate ;; - esac + run: corepack enable - name: Run extract if: inputs.command == 'extract' || inputs.command == 'both' shell: bash working-directory: ${{ inputs.working-directory }} - run: npx solid-translate extract + env: + PACKAGE_MANAGER: ${{ inputs.package-manager }} + run: | + case "$PACKAGE_MANAGER" in + bun) bunx solid-translate extract ;; + pnpm) pnpm dlx solid-translate extract ;; + yarn) yarn dlx solid-translate extract ;; + npm) npx --yes solid-translate extract ;; + esac - name: Run translate if: inputs.command == 'translate' || inputs.command == 'both' shell: bash working-directory: ${{ inputs.working-directory }} - run: npx solid-translate translate + env: + PACKAGE_MANAGER: ${{ inputs.package-manager }} + run: | + case "$PACKAGE_MANAGER" in + bun) bunx solid-translate translate ;; + pnpm) pnpm dlx solid-translate translate ;; + yarn) yarn dlx solid-translate translate ;; + npm) npx --yes solid-translate translate ;; + esac - name: Check for changes id: check-changes shell: bash working-directory: ${{ inputs.working-directory }} run: | - if git diff --name-only | grep -q '\.json$'; then + mapfile -t files < <( + { + git diff --name-only -- \ + '*.json' ':(glob)**/*.json' \ + '*.md' ':(glob)**/*.md' \ + '*.mdx' ':(glob)**/*.mdx' \ + '.solid-translate.lock' ':(glob)**/.solid-translate.lock' \ + ':(exclude)package.json' \ + ':(exclude)package-lock.json' \ + ':(exclude)bun.lock' \ + ':(exclude)pnpm-lock.yaml' \ + ':(exclude)yarn.lock' + git ls-files --others --exclude-standard -- \ + '*.json' ':(glob)**/*.json' \ + '*.md' ':(glob)**/*.md' \ + '*.mdx' ':(glob)**/*.mdx' \ + '.solid-translate.lock' ':(glob)**/.solid-translate.lock' \ + ':(exclude)package.json' \ + ':(exclude)package-lock.json' \ + ':(exclude)bun.lock' \ + ':(exclude)pnpm-lock.yaml' \ + ':(exclude)yarn.lock' + } | sort -u + ) + + if [ "${#files[@]}" -gt 0 ]; then echo "changed=true" >> "$GITHUB_OUTPUT" - echo "files=$(git diff --name-only | grep '\.json$' | tr '\n' ' ')" >> "$GITHUB_OUTPUT" + { + echo "files<> "$GITHUB_OUTPUT" else echo "changed=false" >> "$GITHUB_OUTPUT" - echo "files=" >> "$GITHUB_OUTPUT" + { + echo "files<> "$GITHUB_OUTPUT" fi - name: Commit changes if: inputs.commit == 'true' && steps.check-changes.outputs.changed == 'true' shell: bash working-directory: ${{ inputs.working-directory }} + env: + COMMIT_MESSAGE: ${{ inputs.commit-message }} + TRANSLATION_FILES: ${{ steps.check-changes.outputs.files }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add -A '*.json' '.solid-translate.lock' - git commit -m "${{ inputs.commit-message }}" + while IFS= read -r file; do + [ -n "$file" ] && git add -- "$file" + done <<< "$TRANSLATION_FILES" + git commit -m "$COMMIT_MESSAGE" git push