Use the composite action when you want translation PRs from normal CI. The action
installs the pipeline, runs localize check, runs localize run, stages the
changed localization files, runs any enabled semantic review, enforces the
deterministic quality gate, commits the result, and opens a pull request with the
workflow token.
name: Translate
on:
push:
branches: [main]
workflow_dispatch: {}
permissions:
contents: write
pull-requests: write
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
- uses: bisq-network/localize-pipeline@v0.1.21
with:
config-file: config.yaml
openai-api-key: ${{ secrets.OPENAI_API_KEY }}The default run is incremental. It compares the working tree against
${{ github.event.before }} and translates only the affected locale files.
For a no-cost setup preview, add dry-run: true. The action still validates the
config and discovery path, but the runtime skips model calls and translation
writes.
Do not run live translation from pull_request_target or from a workflow_run
triggered by pull request builds. Those contexts can combine trusted repository
secrets with untrusted PR code. Use push or workflow_dispatch for live
translation PRs, and use the dry-run PR pattern below for untrusted PR checks.
Use local runs for the initial locale baseline and use the GitHub Action for ongoing source-string updates:
- Generate or update
config.yamlandglossary.jsonlocally. - Create the initial target locale files locally with
localize run. - Review and merge that baseline like a normal localization PR.
- Enable the action with the default
process-all-files: false. - Let future CI runs translate only changed strings.
This keeps the largest and riskiest translation review out of unattended CI and
gives the action a clean baseline for incremental maintenance. For small repos
or controlled pilot runs, you can still trigger a manual full scan with
process-all-files: true; switch it back to false after that run.
Before live runs can open translation PRs, configure the target repository:
- Add
OPENAI_API_KEYas an Actions repository secret for OpenAI-backed runs. - Set repository Actions workflow permissions to read and write.
- Enable workflow-created pull requests in repository or organization Actions settings.
- Keep workflow-level permissions:
permissions:
contents: write
pull-requests: writeIf the repo setting leaves the workflow token read-only, the action can still
complete translation and push a branch in some configurations, but gh pr create fails with a createPullRequest permission error.
Recent action versions stage only the configured localization input folder and exclude archive folders from generated PRs. Still ignore the runtime archive folder as defense in depth, especially for local runs:
/src/main/resources/archive/Repositories with strict signed-commit rules should use SSH commit signing from a dedicated machine user:
- Create a no-passphrase Ed25519 SSH key used only for commit signing.
- Add the public key to the machine user's GitHub account as an SSH signing key.
- Use a verified email address from that account for
git-user-email. - Store the private key as an Actions secret, for example
LOCALIZE_COMMIT_SIGNING_KEY. - Pass the signing inputs to the action:
- uses: bisq-network/localize-pipeline@v0.1.21
with:
config-file: config.yaml
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
git-user-name: localize-bot
git-user-email: localize-bot@users.noreply.github.com
commit-signing-method: ssh
commit-signing-key: ${{ secrets.LOCALIZE_COMMIT_SIGNING_KEY }}The action writes the private key to the runner temp directory, validates it
with ssh-keygen, signs the generated translation commit with git commit -S,
and deletes the key file when the step exits. Do not reuse deploy keys or human
authentication keys for this.
Single-format Java .properties:
localization_format: "java_properties"
localization_layout:
id: "suffix"
source_locale: "en"Suffix layouts whose source file is also locale-suffixed can name the shared base explicitly. Projects using indexed percent placeholders opt in separately:
localization_layout:
id: "suffix"
base_name: "Messages"
source_locale: "en"
placeholder_profile: "java-indexed"JSON locale directories:
localization_format: "json"
localization_layout:
id: "locale_directory"
source_locale: "en"Mixed Java and JSON:
localization_formats:
- id: "java_properties"
layout: "suffix"
- id: "json"
layout:
id: "locale_directory"
source_locale: "en"The action, quality gate, semantic reviewer, and PR publisher all read the same profile list.
Some complete UI values legitimately match English in a particular locale. After human review, the quality gate can exempt those exact values:
quality_gate:
source_identical_allowlist:
cs: ["Internet"]Matching ignores surrounding whitespace and case, but not words within a phrase:
Internet does not exempt Internet connection. Locale codes must match the
configured codes exactly; "*" applies to every locale and should be used sparingly.
The default is empty. Prefer locale-specific values, not broad exceptions.
This affects only the quality gate's source-identical classification. Unlisted values still face the existing thresholds and overwrite checks; placeholder, encoding and other validation failures still block publication. It does not change per-key translation validation, repair existing translations, or fix the source-overwrite defect addressed separately in #177. Do not add exemptions merely to make a failing run green.
Use api-base-url for any OpenAI-compatible endpoint:
- uses: bisq-network/localize-pipeline@v0.1.21
with:
config-file: config.yaml
api-base-url: http://localhost:11434/v1For keyless local endpoints such as Ollama, omit openai-api-key. With local
endpoints, strings stay inside your runner/network.
AISuite is the default provider abstraction. Bare names such as gpt-4o-mini
are treated as OpenAI models. Explicit names such as openai:gpt-4o-mini are
also accepted.
For custom adapters, install the package and list the adapter modules with the first-class plugin inputs:
- uses: bisq-network/localize-pipeline@v0.1.21
with:
config-file: config.yaml
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
plugin-install-command: python -m pip install .
plugin-modules: my_project.localize_adapterplugin-install-command runs after the pipeline dependencies are installed.
plugin-modules maps to LOCALIZE_PLUGIN_MODULES, so the same adapter loading
path is used by local CLI runs and the Action.
For pull_request workflows, use dry-run validation only. Set diff-base to
the PR base SHA, disable PR creation, and do not pass model or signing secrets:
on:
pull_request:
branches: [main]
permissions:
contents: read
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
- uses: bisq-network/localize-pipeline@v0.1.21
with:
config-file: config.yaml
diff-base: ${{ github.event.pull_request.base.sha }}
dry-run: true
open-pr: falseRun the live translation workflow after the change is merged, or trigger it
manually with workflow_dispatch.
| Input | Default | Purpose |
|---|---|---|
config-file |
config.yaml |
Translation config committed to your repo. |
openai-api-key |
empty | OpenAI key. Omit for keyless local endpoints. |
api-base-url |
empty | OpenAI-compatible endpoint. Overrides config. |
review-model |
empty | Override the holistic-review model. |
plugin-modules |
empty | Comma-separated plugin modules to load before check/run. |
plugin-install-command |
empty | Shell command to install custom adapter packages before check/run. |
diff-base |
${{ github.event.before }} |
Ref used for changed-string detection. |
process-all-files |
false |
Translate all target files. Use only for controlled full scans/backfills. |
dry-run |
false |
Preview discovery and validation without model calls or translation writes. |
open-pr |
true |
Open a PR or leave changes in the workspace. |
pr-branch |
ai-translations |
Branch for translation changes. |
pr-title |
Update AI translations |
PR title. |
commit-message |
Update AI translations |
Commit message. |
git-user-name |
github-actions[bot] |
Committer name for generated translation commits. |
git-user-email |
41898282+github-actions[bot]@users.noreply.github.com |
Committer email for generated translation commits. |
commit-signing-method |
none |
Commit signing mode. Use ssh for strict signed-commit repos. |
commit-signing-key |
empty | Private SSH signing key used when commit-signing-method: ssh. |
github-token |
${{ github.token }} |
Token for pushing and opening the PR. |
python-version |
3.11 |
Python version used by the action. |
The preflight and translate steps run:
python -m localize.cli check --config "$TRANSLATOR_CONFIG_FILE"
python -m localize.cli run --config "$TRANSLATOR_CONFIG_FILE"The PR step commits only when localization files changed. It excludes archive
folders from staging, signs the commit when configured, and writes a PR body
from the JSON run summaries when available. Those summaries are also uploaded as
the localize-pipeline-summaries workflow artifact on every run.
User-provided action inputs are passed through environment variables, not interpolated directly into shell scripts.
Pin a tagged release for production workflows. A workflow reference such as
bisq-network/localize-pipeline@main follows unreleased changes;
use it only when that is intentional.
The action includes built-in Java .properties and JSON adapters. For custom
adapters, use plugin-install-command and plugin-modules, or publish a package
that exposes a localize.format_adapters entry point. Then reference that adapter
id in config.yaml.