Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8e0f5a8
feat(i18n): automated translation pipeline with a machine review gate
tym83 Sep 20, 2026
8a3e0aa
feat(i18n): multi-language site integration
tym83 Sep 20, 2026
f993f31
chore(i18n): remove the v1.4 proof-of-concept docs translations
tym83 Oct 2, 2026
5b2e1e8
fix(i18n): keep the docs version switcher in the reader's language
tym83 Oct 2, 2026
d1adf1e
ci(i18n): run the pipeline as a scheduled Action on an org API key
tym83 Oct 2, 2026
08cb489
feat(i18n): guarantee link/identifier integrity and check typography
tym83 Jul 24, 2026
c7b7b70
docs(i18n): turn the per-language style guides into real contracts
tym83 Jul 24, 2026
7956750
fix(i18n): resolve the number-formatting contradiction in the transla…
tym83 Jul 24, 2026
7ca5beb
fix(i18n): stop the deterministic checks contradicting the style guides
lexfrei Jul 31, 2026
6a56ddf
docs(i18n): document the deterministic checks stage and the typograph…
lexfrei Jul 31, 2026
6edb27e
ci(i18n): run the typography lint as an advisory step
lexfrei Jul 31, 2026
2968480
fix(i18n): stop flagging the style guides' own canonical examples
lexfrei Jul 31, 2026
28fc328
fix(i18n): keep the hand-localized drift signal alive across CI tooling
lexfrei Jul 31, 2026
f1e7e94
fix(i18n): treat the markdown image marker as syntax, not prose
lexfrei Jul 31, 2026
f4ddd69
fix(i18n): exit 0 from update-digests when no stamped translations exist
lexfrei Jul 31, 2026
a4e95cd
fix(i18n): keep link-title quotes out of the prose checks
lexfrei Jul 31, 2026
6d13e9a
fix(i18n): warn instead of claiming success when a page has no digest…
lexfrei Jul 31, 2026
a90ad7b
fix(i18n): reject placeholder tokens the model was never sent
tym83 Aug 10, 2026
d08e3b2
fix(i18n): scope-aware freshness lint — superseded docs versions are …
tym83 Aug 18, 2026
d859eef
fix(i18n): make check-i18n.sh read l10n and the latest version like t…
tym83 Oct 2, 2026
592d0f5
fix(i18n): keep HTML entities out of the typography checks
tym83 Oct 2, 2026
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
20 changes: 20 additions & 0 deletions .github/workflows/i18n-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- 'i18n/**'
- 'content/**'
- 'hack/check-i18n.sh'
- 'hack/i18n/**'
- '.github/workflows/i18n-lint.yml'
push:
branches:
Expand All @@ -14,6 +15,7 @@ on:
- 'i18n/**'
- 'content/**'
- 'hack/check-i18n.sh'
- 'hack/i18n/**'
- '.github/workflows/i18n-lint.yml'
workflow_dispatch:

Expand All @@ -32,5 +34,23 @@ jobs:
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install pyyaml
# check-i18n.sh reads `l10n` through lib.l10n_mode, and the unit tests
# need it too. The Claude SDK is imported lazily at runtime, not here.
run: python3 -m pip install --quiet pyyaml
- name: Check i18n key parity and translation freshness
run: ./hack/check-i18n.sh
- name: Run i18n pipeline unit tests
# Guards the pure functions of the translation pipeline — including the
# ref/relref deref that keeps a partially-translated page from breaking
# the Hugo build.
run: python3 hack/i18n/test_i18n.py
- name: Typography lint on published translations
# Advisory: reports per-language typography drift (hand edits, pages
# translated before a rule existed) without failing the build. Flip to
# --strict per language once its backlog is clean.
run: python3 hack/i18n/lint_translations.py
111 changes: 111 additions & 0 deletions .github/workflows/i18n-translate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: i18n translate

# Daily translation run, one weekly PR — the same contract as hack/i18n/run-daily.sh,
# moved off a maintainer's clone and personal Max subscription onto CI and an
# ORG-OWNED Anthropic API key (auth: api-key). Nothing reaches production
# automatically: the run pushes to a weekly branch and opens a PR a maintainer
# merges. No auto-merge, CODEOWNERS/branch protection untouched.
#
# Required secret:
# I18N_ANTHROPIC_API_KEY — an organization-owned Anthropic API key (metered).
# Optional secret:
# I18N_GH_TOKEN — a PAT/app token used to push the branch and open the
# PR so its checks (i18n-lint, DCO) actually run; the
# default GITHUB_TOKEN cannot trigger them. Without it
# the run still works, but the PR opens with no checks.
#
# Until I18N_ANTHROPIC_API_KEY exists the job is a green no-op (see the guard step),
# so enabling the schedule before provisioning the key does not spam failures.
#
# Spend is bounded twice per run: a page limit (DEFAULT_PAGE_LIMIT on the schedule)
# and a wall-clock budget well inside timeout-minutes, so translate.py stops taking
# new pages while there is still time to commit and push what it has. A run killed
# by the timeout would lose every page it translated. Set a spend limit on the
# key's Anthropic workspace as the outer guard.

on:
schedule:
- cron: '17 3 * * *' # daily, 03:17 UTC
workflow_dispatch:
inputs:
lang:
description: 'Limit to one language code (e.g. ru); empty = all'
required: false
default: ''
limit:
description: 'Max pages this run; empty = the scheduled default (40)'
required: false
default: ''

concurrency:
group: i18n-translate
cancel-in-progress: false

permissions:
contents: write
pull-requests: write

jobs:
translate:
runs-on: ubuntu-latest
timeout-minutes: 300
env:
I18N_AUTH: api-key
DEFAULT_PAGE_LIMIT: '40'
# Leaves an hour of the 300-minute timeout for the page in flight and the
# commit/push/PR steps.
I18N_TIME_BUDGET_MIN: '240'
CLAUDE_CODE_VERSION: '2.1.287'
steps:
- name: Guard — org API key provisioned
id: guard
env:
ANTHROPIC_API_KEY: ${{ secrets.I18N_ANTHROPIC_API_KEY }}
run: |
if [ -z "${ANTHROPIC_API_KEY}" ]; then
echo "::notice::secret I18N_ANTHROPIC_API_KEY is not set yet — skipping this run."
echo "skip=true" >> "$GITHUB_OUTPUT"
fi

- name: Checkout
if: steps.guard.outputs.skip != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.I18N_GH_TOKEN || github.token }}

- name: Fetch all branches (weekly-branch bookkeeping needs origin/i18n/week-*)
if: steps.guard.outputs.skip != 'true'
run: |
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch --prune origin

- name: Set up Python
if: steps.guard.outputs.skip != 'true'
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Set up Node
if: steps.guard.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Claude CLI (driven by the Agent SDK)
if: steps.guard.outputs.skip != 'true'
run: npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"

- name: Run translation pipeline
if: steps.guard.outputs.skip != 'true'
env:
# Secrets only reach the step that needs them, not the npm/pip installs.
ANTHROPIC_API_KEY: ${{ secrets.I18N_ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.I18N_GH_TOKEN || github.token }}
# Dispatch inputs go through env, never interpolated into the script.
INPUT_LANG: ${{ github.event.inputs.lang }}
INPUT_LIMIT: ${{ github.event.inputs.limit }}
run: |
args=(--limit "${INPUT_LIMIT:-$DEFAULT_PAGE_LIMIT}")
if [ -n "${INPUT_LANG}" ]; then args+=(--lang "${INPUT_LANG}"); fi
hack/i18n/run-daily.sh "${args[@]}"
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ static/docs/*/cozystack-api/

# Claude Code local settings
.claude/

# i18n pipeline venv (bootstrapped by hack/i18n/run-daily.sh)
.venv-i18n/

# i18n pipeline: per-run artifacts, not content
hack/i18n/last-run-findings.md

.demo-build-uidir

# demo overlay ships a public/ with the MSW worker — keep it despite the public/ ignore
Expand Down
12 changes: 12 additions & 0 deletions config/production/hugo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ module:
files:
- '! **/_include/*'
- '! docs/next/**'
- source: content/es
target: content
lang: es
files:
- '! **/_include/*'
- '! docs/next/**'
- source: content/pt-br
target: content
lang: pt-br
files:
- '! **/_include/*'
- '! docs/next/**'
- source: node_modules/bootstrap
target: assets/vendor/bootstrap
- source: node_modules/@fortawesome/fontawesome-free
Expand Down
30 changes: 0 additions & 30 deletions content/de/docs/v1.4/getting-started/_index.md

This file was deleted.

30 changes: 0 additions & 30 deletions content/hi/docs/v1.4/getting-started/_index.md

This file was deleted.

30 changes: 0 additions & 30 deletions content/ru/docs/v1.4/getting-started/_index.md

This file was deleted.

Loading
Loading