Skip to content

Commit 72ebfe8

Browse files
authored
feat: Implement API-based Transifex stats generation and CI automation (#10)
* Implement Transifex stats reporting via official API * Add GitHub Action to update translation statistics weekly * Remove debug print statements from ReadmeUpdaterReporter class * Remove unnecessary comments * Refactor ReadmeUpdaterReporter to return relative file paths from the project root
1 parent 07a79eb commit 72ebfe8

8 files changed

Lines changed: 596 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Update Translation Statistics
2+
on:
3+
schedule:
4+
- cron: '0 0 * * 6'
5+
workflow_dispatch:
6+
jobs:
7+
update-stats:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Check out repository
11+
uses: actions/checkout@v3
12+
- name: Set up Python
13+
uses: actions/setup-python@v4
14+
with:
15+
python-version: '3.10'
16+
- name: Install dependencies
17+
run: |
18+
python -m pip install --upgrade pip
19+
pip install -r scripts/transifex/requirements.txt
20+
- name: Generate all stats
21+
run: python -m scripts.transifex.main generate-all-stats
22+
env:
23+
TRANSIFEX_API_TOKEN: ${{ secrets.TRANSIFEX_API_TOKEN }}
24+
- name: Commit and push if changes
25+
run: |
26+
git config --local user.email "action@github.com"
27+
git config --local user.name "GitHub Action"
28+
git add RESOURCE.md TEAM.md reports/ README.md
29+
git commit -m "Update translation statistics [skip ci]" || exit 0
30+
git push

scripts/transifex/__init__.py

Whitespace-only changes.

scripts/transifex/client.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from transifex.api import transifex_api
2+
from .config import PROJECT, LANGUAGE, ORGANISATION
3+
4+
_api_cache = {}
5+
6+
7+
def _fetch_from_api(cache_key, api_call_func, *args, **kwargs):
8+
if cache_key not in _api_cache:
9+
_api_cache[cache_key] = api_call_func(*args, **kwargs)
10+
return _api_cache[cache_key]
11+
12+
13+
def get_all_resources():
14+
"""Fetches all resources for the configured project, with caching."""
15+
cache_key = "all_resources"
16+
return _fetch_from_api(
17+
cache_key, transifex_api.Resource.filter, project=PROJECT
18+
).all()
19+
20+
21+
def get_resource_language_stats():
22+
"""Fetches language statistics for all resources, with caching."""
23+
cache_key = "resource_language_stats"
24+
return _fetch_from_api(
25+
cache_key,
26+
transifex_api.ResourceLanguageStats.filter,
27+
project=PROJECT,
28+
language=LANGUAGE,
29+
).all()
30+
31+
32+
def get_team_members():
33+
"""Fetches all team members, with caching."""
34+
cache_key = "team_members"
35+
# Fetching with 'user' include to get user details like username
36+
return (
37+
_fetch_from_api(
38+
cache_key,
39+
transifex_api.TeamMembership.filter,
40+
organization=ORGANISATION,
41+
language=LANGUAGE,
42+
)
43+
.include("user")
44+
.all()
45+
)
46+
47+
48+
def get_resource_translations(resource):
49+
"""Fetches translations for a given resource, with caching per resource."""
50+
resource_id = resource.id if hasattr(resource, "id") else str(resource)
51+
cache_key = f"resource_translations_{resource_id}"
52+
return _fetch_from_api(
53+
cache_key,
54+
transifex_api.ResourceTranslation.filter,
55+
resource=resource,
56+
language=LANGUAGE,
57+
).all()

scripts/transifex/config.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import os
2+
from pathlib import Path
3+
from transifex.api import transifex_api
4+
5+
# Transifex API Authentication
6+
TRANSIFEX_AUTH_TOKEN = os.getenv("TRANSIFEX_API_TOKEN")
7+
if not TRANSIFEX_AUTH_TOKEN:
8+
raise ValueError("TRANSIFEX_API_TOKEN environment variable not set.")
9+
transifex_api.setup(auth=TRANSIFEX_AUTH_TOKEN)
10+
11+
# Language and Project Configuration
12+
LANG = "fa"
13+
ORGANISATION_ID = "o:python-doc"
14+
PROJECT_ID = "o:python-doc:p:python-newest"
15+
LANGUAGE_ID = f"l:{LANG}"
16+
17+
try:
18+
ORGANISATION = transifex_api.Organization.get(id=ORGANISATION_ID)
19+
PROJECT = transifex_api.Project.get(id=PROJECT_ID)
20+
LANGUAGE = transifex_api.Language.get(id=LANGUAGE_ID)
21+
except Exception as e:
22+
print(f"Error initializing Transifex API objects: {e}")
23+
raise
24+
25+
# Calculate the project root (assuming config.py is in scripts/transifex/)
26+
SCRIPT_DIR = Path(__file__).resolve().parent
27+
PROJECT_ROOT = SCRIPT_DIR.parent.parent
28+
29+
# Resource Mapping
30+
RESOURCE_NAME_MAP = {"glossary_": "glossary"}
31+
32+
# Output file paths
33+
TX_CONFIG_PATH = PROJECT_ROOT / ".tx/config"
34+
RESOURCE_STATS_MD_PATH = PROJECT_ROOT / "RESOURCE.md"
35+
TEAM_STATS_MD_PATH = PROJECT_ROOT / "TEAM.md"
36+
CONTRIBUTOR_CHART_DIR = PROJECT_ROOT / "reports"
37+
CONTRIBUTOR_CHART_FILENAME_PREFIX = "contributor_stats_"
38+
39+
# README Update Configuration
40+
README_PATH = PROJECT_ROOT / "README.md"
41+
README_STATS_START_MARKER = "<!-- STATS_START -->"
42+
README_STATS_END_MARKER = "<!-- STATS_END -->"
43+
README_CONTRIBUTORS_HEADER = "مشارکت‌های کاربران"
44+
README_PROGRESS_HEADER = "پیشرفت کلی ترجمه"
45+
README_UPDATED_ON = "به‌روزرسانی"
46+
47+
CHART_PASTEL_COLORS = [
48+
"#A6C7E8", # Pastel blue
49+
"#B5EAD7", # Pastel green
50+
"#FFDFD3", # Pastel pink
51+
"#FFF1AC", # Pastel yellow
52+
"#E2D1F9", # Pastel lavender
53+
"#FFD7BA", # Pastel orange
54+
"#FFABAB", # Pastel coral
55+
"#C7F0DB", # Pastel mint
56+
"#FFDAC1", # Pastel peach
57+
"#C7CEEA", # Pastel sky blue
58+
]
59+
60+
REPORT_HEADERS = {
61+
"resource_stats": {
62+
"file": "File",
63+
"translated": "Translated",
64+
"reviewed": "Reviewed",
65+
"proofread": "Proofread",
66+
"alignment": "|:-----|:-----------:|:-----------:|:-----------:|\n",
67+
},
68+
"team_stats": {
69+
"user": "User",
70+
"role": "Role",
71+
"translated_count": "Translated Count",
72+
"reviewed_count": "Reviewed Count",
73+
"proofread_count": "Proofread Count",
74+
"alignment": "|:-----|:------:|:------------------:|:-------------------:|:----------------------:|\n",
75+
},
76+
"contributor_chart": {
77+
"title_base": "User Contributions",
78+
"title_top_n_suffix": " (Top {top_n})",
79+
"xlabel_username": "Username",
80+
"ylabel_total_contributions": "Total Contributions",
81+
},
82+
}

scripts/transifex/main.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import argparse
2+
import sys
3+
from .reporting import REPORTERS
4+
5+
6+
def main():
7+
parser = argparse.ArgumentParser(
8+
description="Transifex utility scripts for Python docs (Persian team)."
9+
)
10+
11+
valid_commands = list(REPORTERS.keys())
12+
parser.add_argument(
13+
"command",
14+
choices=valid_commands,
15+
help=f"The command to execute. Available commands: {', '.join(valid_commands)}",
16+
)
17+
18+
args = parser.parse_args()
19+
20+
selected_reporter_class = REPORTERS.get(args.command)
21+
22+
if selected_reporter_class:
23+
reporter_instance = selected_reporter_class()
24+
reporter_instance.generate()
25+
else:
26+
print(f"Error: Unknown command '{args.command}'.", file=sys.stderr)
27+
parser.print_help(sys.stderr)
28+
sys.exit(1)
29+
30+
31+
if __name__ == "__main__":
32+
main()

0 commit comments

Comments
 (0)