Skip to content

Commit 30f8207

Browse files
RevistoGitHub Action
andauthored
Feature/transifex stats visualizer (#5)
* Add transifex_cookies.json to .gitignore * Add Transifex automation scripts and update workflow for translation statistics * Fix formatting of workflow name in update-translation-stats.yml * Add pull_request trigger to update-translation-stats workflow * Update requirements path in update-translation-stats workflow * Change Chrome driver to headless mode * Add error handling and screenshot capture for Transifex login * Add artifact upload for error screenshots in update-translation-stats workflow * Add click action for "Allow all" in Transifex login process * Set window size for Chrome driver in Transifex login * Update GitHub Actions workflow to handle pull request refs and push changes accordingly * Update translation statistics * Remove debugging screenshots * Restyle using black * Refactor Transifex scripts for improved login handling and remove debugging lines * Change Chrome driver to headless mode for automated login --------- Co-authored-by: GitHub Action <github-actions@github.com>
1 parent 5706af4 commit 30f8207

10 files changed

Lines changed: 588 additions & 0 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: "Update Translation Statistics"
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * 6' # Run at midnight on Saturdays
6+
workflow_dispatch: # Allow manual trigger
7+
pull_request:
8+
9+
jobs:
10+
update-stats:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
with:
15+
ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
16+
fetch-depth: 0
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: '3.10'
22+
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install -r scripts/transifex/requirements.txt
27+
sudo apt-get update
28+
sudo apt-get install -y chromium-browser
29+
30+
- name: Run statistics update
31+
env:
32+
TRANSIFEX_USERNAME: ${{ secrets.TRANSIFEX_USERNAME }}
33+
TRANSIFEX_PASSWORD: ${{ secrets.TRANSIFEX_PASSWORD }}
34+
CI: true
35+
run: |
36+
cd scripts/transifex
37+
python main.py
38+
39+
- name: Update README
40+
run: python scripts/transifex/update_readme.py
41+
42+
- name: Commit and push changes
43+
if: ${{ github.event_name != 'pull_request' }}
44+
run: |
45+
git config --local user.email "github-actions@github.com"
46+
git config --local user.name "GitHub Action"
47+
git add reports/ README.md
48+
git diff --quiet && git diff --staged --quiet || git commit -m "Update translation statistics"
49+
git push origin ${{ github.ref_name }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
locales/
1212
venv/
1313
__pycache__/
14+
transifex_cookies.json

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,20 @@
2323
***
2424

2525
با تشکر از همراهی شما در گسترش و بهبود مستندات پایتون به زبان فارسی!
26+
27+
28+
<!-- STATS_START -->
29+
## آمار ترجمه
30+
31+
### مشارکت‌های این هفته کاربران
32+
33+
![مشارکت‌های این هفته کاربران](reports/contributors/2025-04-01.png)
34+
35+
(به‌روزرسانی: 2025-04-01)
36+
37+
### پیشرفت ترجمه
38+
39+
![پیشرفت ترجمه](reports/progress/2025-04-01.png)
40+
41+
(به‌روزرسانی: 2025-04-01)
42+
<!-- STATS_END -->
20.5 KB
Loading

reports/progress/2025-04-01.png

31.2 KB
Loading

scripts/transifex/main.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import sys
2+
import os
3+
from transifex_browser import get_driver_with_login, save_cookies, kill_browser
4+
from visualizer import visualize_string_counts, visualize_user_contributions
5+
6+
7+
def main():
8+
# Check for environment variables first (for CI/CD)
9+
username = os.environ.get("TRANSIFEX_USERNAME")
10+
password = os.environ.get("TRANSIFEX_PASSWORD")
11+
12+
# Fall back to command line args if env vars not found
13+
if not username or not password:
14+
if len(sys.argv) != 3:
15+
print("Usage: python main.py <username> <password>")
16+
print(
17+
"Or set TRANSIFEX_USERNAME and TRANSIFEX_PASSWORD environment variables"
18+
)
19+
sys.exit(1)
20+
username = sys.argv[1]
21+
password = sys.argv[2]
22+
23+
driver = get_driver_with_login(username, password)
24+
# Navigate to a specific URL if needed.
25+
driver.get("https://app.transifex.com/python-doc/python-newest/translate/#fa/$")
26+
# Allow time for the page to load.
27+
driver.implicitly_wait(10)
28+
# Update cookies after visiting the page.
29+
save_cookies(driver)
30+
kill_browser()
31+
visualize_user_contributions()
32+
visualize_string_counts()
33+
34+
35+
if __name__ == "__main__":
36+
main()

scripts/transifex/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
matplotlib
2+
requests
3+
helium
4+
selenium
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import json
2+
from helium import start_chrome, click, write, kill_browser
3+
from selenium.webdriver.common.by import By
4+
from time import sleep
5+
6+
COOKIE_FILE = "transifex_cookies.json"
7+
TRANSIFEX_URL = "https://www.transifex.com/dashboard/"
8+
9+
10+
def save_cookies(driver):
11+
cookies = driver.get_cookies()
12+
with open(COOKIE_FILE, "w") as f:
13+
json.dump(cookies, f)
14+
return cookies
15+
16+
17+
def is_logged_in(driver):
18+
# Check for an element that only exists when logged in.
19+
try:
20+
driver.find_element(By.CSS_SELECTOR, ".user-profile")
21+
return True
22+
except Exception:
23+
return False
24+
25+
26+
def login_transifex(driver, username, password):
27+
driver.get("https://app.transifex.com/signin/")
28+
sleep(5)
29+
try:
30+
click("Allow all")
31+
except:
32+
pass
33+
write(username, into="Email")
34+
write(password, into="Password")
35+
click("Log in") # Adjust selector if needed.
36+
# Allow time for login and cookie propagation.
37+
sleep(5)
38+
try:
39+
click("I agree, let's go!")
40+
except:
41+
pass
42+
driver.refresh()
43+
sleep(5)
44+
save_cookies(driver)
45+
return driver
46+
47+
48+
def get_driver_with_login(username, password):
49+
driver = start_chrome(headless=True)
50+
driver.set_window_size(1920, 1080)
51+
login_transifex(driver, username, password)
52+
return driver

scripts/transifex/update_readme.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import os
2+
import re
3+
import glob
4+
from datetime import datetime
5+
6+
# Calculate the path to the repository root (2 levels up from this script)
7+
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
8+
README_PATH = os.path.join(REPO_ROOT, "README.md")
9+
contributors_dir = os.path.join(REPO_ROOT, "reports", "contributors")
10+
progress_dir = os.path.join(REPO_ROOT, "reports", "progress")
11+
12+
13+
def find_latest_image(directory):
14+
"""Find the latest image file in the given directory based on filename."""
15+
pattern = os.path.join(directory, "*.png")
16+
files = glob.glob(pattern)
17+
if not files:
18+
return None
19+
# Sort the files by name (which should sort by date if the format is consistent)
20+
latest_file = sorted(files)[-1]
21+
# Return the path relative to the REPO_ROOT
22+
return os.path.relpath(latest_file, REPO_ROOT).replace("\\", "/")
23+
24+
25+
def update_readme():
26+
"""Update the README.md file with the latest report images."""
27+
today = datetime.now().strftime("%Y-%m-%d")
28+
latest_contributors_img = find_latest_image(contributors_dir)
29+
latest_progress_img = find_latest_image(progress_dir)
30+
31+
# Check if both image files exist
32+
if not latest_contributors_img or not latest_progress_img:
33+
print("Warning: One or both image files not found.")
34+
if latest_contributors_img:
35+
print(f"Found contributors image: {latest_contributors_img}")
36+
if latest_progress_img:
37+
print(f"Found progress image: {latest_progress_img}")
38+
return False
39+
40+
# Read the current README content
41+
with open(README_PATH, "r", encoding="utf-8") as file:
42+
content = file.read()
43+
44+
# Define the stats section
45+
stats_section = f"""## آمار ترجمه
46+
47+
### مشارکت‌های این هفته کاربران
48+
49+
![مشارکت‌های این هفته کاربران]({latest_contributors_img})
50+
51+
(به‌روزرسانی: {today})
52+
53+
### پیشرفت ترجمه
54+
55+
![پیشرفت ترجمه]({latest_progress_img})
56+
57+
(به‌روزرسانی: {today})"""
58+
59+
# Check if stats section already exists with markers
60+
if "<!-- STATS_START -->" in content and "<!-- STATS_END -->" in content:
61+
# Replace the existing stats section between markers
62+
pattern = r"<!-- STATS_START -->.*?<!-- STATS_END -->"
63+
updated_content = re.sub(
64+
pattern,
65+
f"<!-- STATS_START -->\n{stats_section}\n<!-- STATS_END -->",
66+
content,
67+
flags=re.DOTALL,
68+
)
69+
elif "## آمار ترجمه" in content:
70+
# If old format exists without markers, replace that section
71+
pattern = r"## آمار ترجمه.*?(?=\n##|\Z)"
72+
updated_content = re.sub(
73+
pattern,
74+
f"<!-- STATS_START -->\n{stats_section}\n<!-- STATS_END -->",
75+
content,
76+
flags=re.DOTALL,
77+
)
78+
else:
79+
# Append the stats section to the end of the README
80+
updated_content = (
81+
content
82+
+ "\n\n<!-- STATS_START -->\n"
83+
+ stats_section
84+
+ "\n<!-- STATS_END -->"
85+
)
86+
87+
# Write the updated content back to the README
88+
with open(README_PATH, "w", encoding="utf-8") as file:
89+
file.write(updated_content)
90+
91+
print(f"README.md updated successfully with:")
92+
print(f"- Contributors image: {latest_contributors_img}")
93+
print(f"- Progress image: {latest_progress_img}")
94+
print(f"- Update date: {today}")
95+
return True
96+
97+
98+
if __name__ == "__main__":
99+
update_readme()

0 commit comments

Comments
 (0)