Skip to content

Commit c12ce38

Browse files
feat: backend i18n — serve translated component metadata via Accept-Language
- Add locale middleware (set_locale) in main.py to parse Accept-Language header - Add translate_component_dict() in utils/i18n.py to substitute display_names - Add backend locale files (en/fr/es/de/pt/ja/zh-Hans) with 4,748 keys - Add GP scripts: extract_backend_strings.py, upload_backend_strings.py, download_backend_translations.py - Add CI workflows: gp-backend-check.yml (PR validation + auto-commit), gp-backend-upload.yml (release branch upload) - /api/v1/all returns translated component metadata for non-English locales
1 parent aac79b1 commit c12ce38

21 files changed

Lines changed: 34261 additions & 9 deletions
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: GP Backend Strings Check
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "src/lfx/src/lfx/components/**"
7+
- "src/backend/base/langflow/locales/en.json"
8+
workflow_dispatch:
9+
10+
jobs:
11+
# ── Always runs (including forks): fails CI if en.json is out of sync ─────
12+
check:
13+
runs-on: ubuntu-latest
14+
name: Verify locales/en.json is in sync with component code
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Setup uv
21+
uses: astral-sh/setup-uv@v6
22+
with:
23+
enable-cache: true
24+
cache-dependency-glob: "uv.lock"
25+
python-version: "3.12"
26+
27+
- name: Install workspace (lfx + langflow-base)
28+
run: uv sync --no-dev
29+
30+
- name: Check en.json is up to date
31+
run: uv run python scripts/gp/extract_backend_strings.py --check
32+
33+
# ── Runs only on non-fork PRs: auto-commits updated en.json to PR branch ──
34+
auto-commit:
35+
if: |
36+
github.event_name == 'pull_request' &&
37+
github.actor != 'github-actions[bot]' &&
38+
github.event.pull_request.head.repo.full_name == github.repository
39+
runs-on: ubuntu-latest
40+
name: Auto-regenerate locales/en.json on PR branch
41+
permissions:
42+
contents: write
43+
44+
steps:
45+
- name: Checkout PR branch
46+
uses: actions/checkout@v4
47+
with:
48+
ref: ${{ github.head_ref }}
49+
token: ${{ secrets.GITHUB_TOKEN }}
50+
51+
- name: Setup uv
52+
uses: astral-sh/setup-uv@v6
53+
with:
54+
enable-cache: true
55+
cache-dependency-glob: "uv.lock"
56+
python-version: "3.12"
57+
58+
- name: Install workspace (lfx + langflow-base)
59+
run: uv sync --no-dev
60+
61+
- name: Generate en.json
62+
run: uv run python scripts/gp/extract_backend_strings.py
63+
64+
- name: Commit updated en.json to PR branch if changed
65+
run: |
66+
git config user.name "github-actions[bot]"
67+
git config user.email "github-actions[bot]@users.noreply.github.com"
68+
git add src/backend/base/langflow/locales/en.json
69+
git diff --staged --quiet || git commit -m "chore: auto-regenerate backend locales/en.json [skip ci]"
70+
git push
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: GP Backend Upload Strings
2+
3+
on:
4+
push:
5+
branches:
6+
- "release-*"
7+
paths:
8+
- "src/lfx/src/lfx/components/**"
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
upload:
16+
runs-on: ubuntu-latest
17+
name: Generate, commit, and upload backend strings to GP
18+
environment: GP-test
19+
if: github.actor != 'github-actions[bot]'
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
with:
25+
token: ${{ secrets.GITHUB_TOKEN }}
26+
27+
- name: Setup uv
28+
uses: astral-sh/setup-uv@v6
29+
with:
30+
enable-cache: true
31+
cache-dependency-glob: "uv.lock"
32+
python-version: "3.12"
33+
34+
- name: Install workspace (lfx + langflow-base)
35+
run: uv sync --no-dev
36+
37+
- name: Generate en.json
38+
run: uv run python scripts/gp/extract_backend_strings.py
39+
40+
- name: Commit updated en.json if changed
41+
run: |
42+
git config user.name "github-actions[bot]"
43+
git config user.email "github-actions[bot]@users.noreply.github.com"
44+
git add src/backend/base/langflow/locales/en.json
45+
git diff --staged --quiet || git commit -m "chore: auto-regenerate backend locales/en.json [skip ci]"
46+
git push
47+
48+
- name: Set up Python for GP upload
49+
uses: actions/setup-python@v5
50+
with:
51+
python-version: "3.12"
52+
53+
- name: Install GP dependencies
54+
run: pip install -r scripts/gp/requirements.txt
55+
56+
- name: Upload strings to GP
57+
working-directory: scripts/gp
58+
env:
59+
GP_ADMIN_USER_ID: ${{ secrets.GP_ADMIN_USER_ID }}
60+
GP_ADMIN_PASSWORD: ${{ secrets.GP_ADMIN_PASSWORD }}
61+
GP_INSTANCE: ${{ vars.GP_INSTANCE }}
62+
GP_BACKEND_BUNDLE: ${{ vars.GP_BACKEND_BUNDLE }}
63+
run: python upload_backend_strings.py

.github/workflows/gp-download.yml

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ on:
44
workflow_dispatch:
55

66
jobs:
7-
download:
7+
frontend:
88
runs-on: ubuntu-latest
9-
name: Download translations from Globalization Pipeline
9+
name: Download frontend translations from GP
1010
environment: GP-test
11+
permissions:
12+
contents: write
1113

1214
steps:
1315
- name: Checkout repository
@@ -21,15 +23,14 @@ jobs:
2123
- name: Install dependencies
2224
run: pip install -r scripts/gp/requirements.txt
2325

24-
- name: Download translations from GP
26+
- name: Download frontend translations from GP
2527
working-directory: scripts/gp
2628
env:
2729
GP_ADMIN_USER_ID: ${{ secrets.GP_ADMIN_USER_ID }}
2830
GP_ADMIN_PASSWORD: ${{ secrets.GP_ADMIN_PASSWORD }}
2931
GP_INSTANCE: ${{ vars.GP_INSTANCE }}
3032
GP_BUNDLE: ${{ vars.GP_BUNDLE }}
31-
run: |
32-
python download_translations.py --output ../../src/frontend/src/locales/
33+
run: python download_translations.py --output ../../src/frontend/src/locales/
3334

3435
- name: Commit and push translated files
3536
run: |
@@ -38,3 +39,41 @@ jobs:
3839
git add src/frontend/src/locales/*.json
3940
git diff --staged --quiet || git commit -m "chore: update translations from Globalization Pipeline [skip ci]"
4041
git push
42+
43+
backend:
44+
runs-on: ubuntu-latest
45+
name: Download backend translations from GP
46+
environment: GP-test
47+
permissions:
48+
contents: write
49+
50+
steps:
51+
- name: Checkout repository
52+
uses: actions/checkout@v4
53+
with:
54+
token: ${{ secrets.GITHUB_TOKEN }}
55+
56+
- name: Set up Python
57+
uses: actions/setup-python@v5
58+
with:
59+
python-version: "3.12"
60+
61+
- name: Install dependencies
62+
run: pip install -r scripts/gp/requirements.txt
63+
64+
- name: Download backend translations from GP
65+
working-directory: scripts/gp
66+
env:
67+
GP_ADMIN_USER_ID: ${{ secrets.GP_ADMIN_USER_ID }}
68+
GP_ADMIN_PASSWORD: ${{ secrets.GP_ADMIN_PASSWORD }}
69+
GP_INSTANCE: ${{ vars.GP_INSTANCE }}
70+
GP_BACKEND_BUNDLE: ${{ vars.GP_BACKEND_BUNDLE }}
71+
run: python download_backend_translations.py --output ../../src/backend/base/langflow/locales/
72+
73+
- name: Commit and push translated files
74+
run: |
75+
git config user.name "github-actions[bot]"
76+
git config user.email "github-actions[bot]@users.noreply.github.com"
77+
git add src/backend/base/langflow/locales/*.json
78+
git diff --staged --quiet || git commit -m "chore: update backend translations from Globalization Pipeline [skip ci]"
79+
git push

.github/workflows/nightly_build.yml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
echo "Using release branch: $BRANCH"
7474
create-nightly-tag:
7575
if: github.repository == 'langflow-ai/langflow'
76-
needs: [validate-inputs, resolve-release-branch, download-gp-bundle]
76+
needs: [validate-inputs, resolve-release-branch, download-gp-bundle, download-gp-backend-bundle]
7777
runs-on: ubuntu-latest
7878
defaults:
7979
run:
@@ -250,6 +250,53 @@ jobs:
250250
if: failure()
251251
run: echo "::warning::GP translation download failed — translations may be out of date. Check the 'Download translations from GP' step above for details."
252252

253+
download-gp-backend-bundle:
254+
if: github.repository == 'langflow-ai/langflow'
255+
name: Download backend translations from Globalization Pipeline
256+
needs: [validate-inputs, resolve-release-branch]
257+
runs-on: ubuntu-latest
258+
continue-on-error: true
259+
environment: GP-test
260+
permissions:
261+
contents: write
262+
steps:
263+
- name: Checkout repository
264+
uses: actions/checkout@v4
265+
with:
266+
ref: ${{ needs.resolve-release-branch.outputs.branch }}
267+
persist-credentials: true
268+
269+
- name: Set up Python
270+
uses: actions/setup-python@v5
271+
with:
272+
python-version: "3.12"
273+
274+
- name: Install dependencies
275+
run: pip install -r scripts/gp/requirements.txt
276+
277+
- name: Download backend translations from GP
278+
working-directory: scripts/gp
279+
env:
280+
GP_ADMIN_USER_ID: ${{ secrets.GP_ADMIN_USER_ID }}
281+
GP_ADMIN_PASSWORD: ${{ secrets.GP_ADMIN_PASSWORD }}
282+
GP_INSTANCE: ${{ vars.GP_INSTANCE }}
283+
GP_BACKEND_BUNDLE: ${{ vars.GP_BACKEND_BUNDLE }}
284+
run: |
285+
python download_backend_translations.py \
286+
--output ../../src/backend/base/langflow/locales/
287+
288+
- name: Commit and push translated files
289+
run: |
290+
git config user.name "github-actions[bot]"
291+
git config user.email "github-actions[bot]@users.noreply.github.com"
292+
git add src/backend/base/langflow/locales/*.json
293+
git diff --staged --quiet || git commit -m "chore: update backend translations from Globalization Pipeline [skip ci]"
294+
git push
295+
296+
- name: Report GP backend download failure
297+
if: failure()
298+
run: echo "::warning::GP backend translation download failed — backend translations may be out of date. Check the 'Download backend translations from GP' step above for details."
299+
253300
frontend-tests-linux:
254301
if: github.repository == 'langflow-ai/langflow' && !inputs.skip_frontend_tests
255302
name: Run Frontend Tests - Linux
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Download translated backend strings from GP and save as locale JSON files.
2+
3+
Downloads from the "langflow-backend" GP bundle (GP_BACKEND_BUNDLE env var)
4+
into src/backend/base/langflow/locales/ — separate from the frontend locales.
5+
6+
Usage:
7+
python download_backend_translations.py
8+
python download_backend_translations.py --output path/to/locales/
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import argparse
14+
import json
15+
import os
16+
from pathlib import Path
17+
18+
import requests
19+
from gp_client import BASE_URL, GP_INSTANCE, TARGET_LANGS, get_headers
20+
21+
DEFAULT_OUTPUT = Path(__file__).parent.parent.parent / "src/backend/base/langflow/locales"
22+
GP_BACKEND_BUNDLE = os.getenv("GP_BACKEND_BUNDLE", "langflow-backend")
23+
REQUEST_TIMEOUT = 30
24+
25+
26+
def get_backend_strings(lang: str) -> dict:
27+
url = f"{BASE_URL}/{GP_INSTANCE}/v2/bundles/{GP_BACKEND_BUNDLE}/{lang}"
28+
response = requests.get(
29+
url,
30+
headers=get_headers(url, "GET"),
31+
verify=False, # noqa: S501
32+
timeout=REQUEST_TIMEOUT,
33+
)
34+
response.raise_for_status()
35+
return response.json()
36+
37+
38+
def main() -> None:
39+
parser = argparse.ArgumentParser(description="Download backend translations from GP")
40+
parser.add_argument("--output", default=str(DEFAULT_OUTPUT), help="Directory to save translated JSON files")
41+
args = parser.parse_args()
42+
43+
output_dir = Path(args.output)
44+
output_dir.mkdir(parents=True, exist_ok=True)
45+
46+
for lang in TARGET_LANGS:
47+
print(f"Downloading '{lang}' translations...")
48+
try:
49+
result = get_backend_strings(lang)
50+
51+
strings = {
52+
key: entry.get("value", "") if isinstance(entry, dict) else entry
53+
for key, entry in result.get("resourceStrings", {}).items()
54+
}
55+
56+
if not strings:
57+
print(f" No strings yet for '{lang}' (translation may still be in progress)")
58+
continue
59+
60+
output_file = output_dir / f"{lang}.json"
61+
output_file.write_text(
62+
json.dumps(strings, ensure_ascii=False, indent=2),
63+
encoding="utf-8",
64+
)
65+
print(f" Saved {len(strings)} strings to {output_file}")
66+
67+
except Exception as e: # noqa: BLE001
68+
print(f" Error downloading '{lang}': {e}")
69+
70+
print("\nDone.")
71+
72+
73+
if __name__ == "__main__":
74+
main()

0 commit comments

Comments
 (0)