summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-08 19:50:49 +0200
committerNippy <nippy@rp1.hu>2026-05-08 19:50:49 +0200
commit3462bcc46ecf74f576ea4397f16492c16bd75b10 (patch)
treec5ab7aef4498647e057bda8d49c11e5f9dda74c9 /raveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py
parent56616f693b4f263cbf0d47da43b67424efa6022d (diff)
downloadRaveOS-PKGBUILD-3462bcc46ecf74f576ea4397f16492c16bd75b10.tar.gz
RaveOS-PKGBUILD-3462bcc46ecf74f576ea4397f16492c16bd75b10.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py')
-rwxr-xr-xraveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py361
1 files changed, 361 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py b/raveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py
new file mode 100755
index 0000000..42cc7a5
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/scripts/i18nsync.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+
+import sys
+import json
+import os
+import subprocess
+from pathlib import Path
+from urllib import request, parse
+
+REPO_ROOT = Path(__file__).parent.parent
+EN_JSON = REPO_ROOT / "translations" / "en.json"
+TEMPLATE_JSON = REPO_ROOT / "translations" / "template.json"
+POEXPORTS_DIR = REPO_ROOT / "translations" / "poexports"
+SYNC_STATE = REPO_ROOT / ".git" / "i18n_sync_state.json"
+
+LANGUAGES = {
+ "ja": "ja.json",
+ "zh-Hans": "zh_CN.json",
+ "zh-Hant": "zh_TW.json",
+ "pt-br": "pt.json",
+ "tr": "tr.json",
+ "it": "it.json",
+ "pl": "pl.json",
+ "es": "es.json",
+ "he": "he.json",
+ "hu": "hu.json",
+ "fa": "fa.json",
+ "fr": "fr.json",
+ "nl": "nl.json",
+ "ru": "ru.json",
+ "de": "de.json",
+ "sv": "sv.json"
+}
+
+def error(msg):
+ print(f"\033[91mError: {msg}\033[0m", file=sys.stderr)
+ sys.exit(1)
+
+def warn(msg):
+ print(f"\033[93mWarning: {msg}\033[0m", file=sys.stderr)
+
+def info(msg):
+ print(f"\033[94m{msg}\033[0m")
+
+def success(msg):
+ print(f"\033[92m{msg}\033[0m")
+
+def get_env_or_error(var):
+ value = os.environ.get(var)
+ if not value:
+ error(f"{var} environment variable not set")
+ return value
+
+def poeditor_request(endpoint, data):
+ url = f"https://api.poeditor.com/v2/{endpoint}"
+ data_bytes = parse.urlencode(data).encode()
+ req = request.Request(url, data=data_bytes, method="POST")
+
+ try:
+ with request.urlopen(req) as response:
+ return json.loads(response.read().decode())
+ except Exception as e:
+ error(f"POEditor API request failed: {e}")
+
+def extract_strings():
+ info("Extracting strings from QML files...")
+ extract_script = REPO_ROOT / "translations" / "extract_translations.py"
+
+ if not extract_script.exists():
+ error(f"Extract script not found: {extract_script}")
+
+ result = subprocess.run([sys.executable, str(extract_script)], cwd=REPO_ROOT)
+ if result.returncode != 0:
+ error("String extraction failed")
+
+ if not EN_JSON.exists():
+ error(f"Extraction did not produce {EN_JSON}")
+
+def normalize_json(file_path):
+ if not file_path.exists():
+ return {}
+ with open(file_path) as f:
+ return json.load(f)
+
+def json_changed(file_path, new_data):
+ old_data = normalize_json(file_path)
+ return json.dumps(old_data, sort_keys=True) != json.dumps(new_data, sort_keys=True)
+
+def upload_source_strings(api_token, project_id):
+ if not EN_JSON.exists():
+ warn("No en.json to upload")
+ return False
+
+ info("Uploading source strings to POEditor...")
+
+ with open(EN_JSON, 'rb') as f:
+ boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
+ body = (
+ f'--{boundary}\r\n'
+ f'Content-Disposition: form-data; name="api_token"\r\n\r\n'
+ f'{api_token}\r\n'
+ f'--{boundary}\r\n'
+ f'Content-Disposition: form-data; name="id"\r\n\r\n'
+ f'{project_id}\r\n'
+ f'--{boundary}\r\n'
+ f'Content-Disposition: form-data; name="updating"\r\n\r\n'
+ f'terms\r\n'
+ f'--{boundary}\r\n'
+ f'Content-Disposition: form-data; name="file"; filename="en.json"\r\n'
+ f'Content-Type: application/json\r\n\r\n'
+ ).encode() + f.read() + f'\r\n--{boundary}--\r\n'.encode()
+
+ req = request.Request(
+ 'https://api.poeditor.com/v2/projects/upload',
+ data=body,
+ headers={'Content-Type': f'multipart/form-data; boundary={boundary}'}
+ )
+
+ try:
+ with request.urlopen(req) as response:
+ result = json.loads(response.read().decode())
+ except Exception as e:
+ error(f"Upload failed: {e}")
+
+ if result.get('response', {}).get('status') != 'success':
+ error(f"POEditor upload failed: {result}")
+
+ terms = result.get('result', {}).get('terms', {})
+ added = terms.get('added', 0)
+ updated = terms.get('updated', 0)
+ deleted = terms.get('deleted', 0)
+
+ if added or updated or deleted:
+ success(f"POEditor updated: {added} added, {updated} updated, {deleted} deleted")
+ return True
+ else:
+ info("No changes uploaded to POEditor")
+ return False
+
+def download_translations(api_token, project_id):
+ info("Downloading translations from POEditor...")
+
+ POEXPORTS_DIR.mkdir(parents=True, exist_ok=True)
+ any_changed = False
+
+ for po_lang, filename in LANGUAGES.items():
+ repo_file = POEXPORTS_DIR / filename
+
+ info(f"Fetching {po_lang}...")
+
+ export_resp = poeditor_request('projects/export', {
+ 'api_token': api_token,
+ 'id': project_id,
+ 'language': po_lang,
+ 'type': 'key_value_json'
+ })
+
+ if export_resp.get('response', {}).get('status') != 'success':
+ warn(f"Export request failed for {po_lang}")
+ continue
+
+ url = export_resp.get('result', {}).get('url')
+ if not url:
+ warn(f"No export URL for {po_lang}")
+ continue
+
+ try:
+ with request.urlopen(url) as response:
+ new_data = json.loads(response.read().decode())
+ except Exception as e:
+ warn(f"Failed to download {po_lang}: {e}")
+ continue
+
+ if json_changed(repo_file, new_data):
+ with open(repo_file, 'w') as f:
+ json.dump(new_data, f, ensure_ascii=False, indent=2, sort_keys=True)
+ f.write('\n')
+ success(f"Updated {filename}")
+ any_changed = True
+ else:
+ info(f"No changes for {filename}")
+
+ return any_changed
+
+def check_sync_status():
+ api_token = get_env_or_error('POEDITOR_API_TOKEN')
+ project_id = get_env_or_error('POEDITOR_PROJECT_ID')
+
+ extract_strings()
+
+ current_en = normalize_json(EN_JSON)
+
+ if not SYNC_STATE.exists():
+ return True
+
+ with open(SYNC_STATE) as f:
+ state = json.load(f)
+
+ last_en = state.get('en_json', {})
+ last_translations = state.get('translations', {})
+
+ if json.dumps(current_en, sort_keys=True) != json.dumps(last_en, sort_keys=True):
+ return True
+
+ for po_lang, filename in LANGUAGES.items():
+ repo_file = POEXPORTS_DIR / filename
+ current_trans = normalize_json(repo_file)
+ last_trans = last_translations.get(filename, {})
+
+ if json.dumps(current_trans, sort_keys=True) != json.dumps(last_trans, sort_keys=True):
+ return True
+
+ export_resp = poeditor_request('projects/export', {
+ 'api_token': api_token,
+ 'id': project_id,
+ 'language': list(LANGUAGES.keys())[0],
+ 'type': 'key_value_json'
+ })
+
+ if export_resp.get('response', {}).get('status') == 'success':
+ url = export_resp.get('result', {}).get('url')
+ if url:
+ try:
+ with request.urlopen(url) as response:
+ remote_data = json.loads(response.read().decode())
+ first_file = POEXPORTS_DIR / list(LANGUAGES.values())[0]
+ local_data = normalize_json(first_file)
+
+ if json.dumps(remote_data, sort_keys=True) != json.dumps(local_data, sort_keys=True):
+ return True
+ except:
+ pass
+
+ return False
+
+def save_sync_state():
+ state = {
+ 'en_json': normalize_json(EN_JSON),
+ 'translations': {}
+ }
+
+ for filename in LANGUAGES.values():
+ repo_file = POEXPORTS_DIR / filename
+ state['translations'][filename] = normalize_json(repo_file)
+
+ SYNC_STATE.parent.mkdir(parents=True, exist_ok=True)
+ with open(SYNC_STATE, 'w') as f:
+ json.dump(state, f, indent=2)
+
+def main():
+ if len(sys.argv) < 2:
+ error("Usage: i18nsync.py [check|sync|test|local]")
+
+ command = sys.argv[1]
+
+ if command == "test":
+ info("Running in test mode (no POEditor upload/download)")
+ extract_strings()
+
+ current_en = normalize_json(EN_JSON)
+ current_template = normalize_json(TEMPLATE_JSON)
+
+ success(f"✓ Extracted {len(current_en)} terms")
+
+ terms_with_context = sum(1 for entry in current_en if entry.get('context') and entry['context'] != entry['term'])
+ if terms_with_context > 0:
+ success(f"✓ Found {terms_with_context} terms with custom contexts")
+
+ info("\nFiles generated:")
+ info(f" - {EN_JSON}")
+ info(f" - {TEMPLATE_JSON}")
+
+ sys.exit(0)
+ elif command == "check":
+ try:
+ if check_sync_status():
+ error("i18n out of sync - run 'python3 scripts/i18nsync.py sync' first")
+ else:
+ success("i18n in sync")
+ sys.exit(0)
+ except SystemExit:
+ raise
+ except Exception as e:
+ error(f"Check failed: {e}")
+
+ elif command == "sync":
+ api_token = get_env_or_error('POEDITOR_API_TOKEN')
+ project_id = get_env_or_error('POEDITOR_PROJECT_ID')
+
+ extract_strings()
+
+ current_en = normalize_json(EN_JSON)
+ staged_en = {}
+
+ try:
+ result = subprocess.run(
+ ['git', 'show', f':{EN_JSON.relative_to(REPO_ROOT)}'],
+ capture_output=True,
+ text=True,
+ cwd=REPO_ROOT
+ )
+ if result.returncode == 0:
+ staged_en = json.loads(result.stdout)
+ except:
+ pass
+
+ strings_changed = json.dumps(current_en, sort_keys=True) != json.dumps(staged_en, sort_keys=True)
+
+ if strings_changed:
+ upload_source_strings(api_token, project_id)
+ else:
+ info("No changes in source strings")
+
+ translations_changed = download_translations(api_token, project_id)
+
+ if strings_changed or translations_changed:
+ subprocess.run(['git', 'add', 'translations/'], cwd=REPO_ROOT)
+ save_sync_state()
+ success("Sync complete - changes staged for commit")
+ else:
+ save_sync_state()
+ info("Already in sync")
+
+ elif command == "local":
+ info("Updating en.json locally (no POEditor sync)")
+
+ old_en = normalize_json(EN_JSON)
+ old_terms = {entry['term']: entry for entry in old_en} if isinstance(old_en, list) else {}
+
+ extract_strings()
+
+ new_en = normalize_json(EN_JSON)
+ new_terms = {entry['term']: entry for entry in new_en} if isinstance(new_en, list) else {}
+
+ added = set(new_terms.keys()) - set(old_terms.keys())
+ removed = set(old_terms.keys()) - set(new_terms.keys())
+
+ if added:
+ info(f"\n+{len(added)} new terms:")
+ for term in sorted(added)[:20]:
+ print(f" + {term[:60]}...")
+ if len(added) > 20:
+ print(f" ... and {len(added) - 20} more")
+
+ if removed:
+ info(f"\n-{len(removed)} removed terms:")
+ for term in sorted(removed)[:20]:
+ print(f" - {term[:60]}...")
+ if len(removed) > 20:
+ print(f" ... and {len(removed) - 20} more")
+
+ success(f"\n✓ {len(new_en)} total terms")
+
+ if not added and not removed:
+ info("No changes detected")
+
+ else:
+ error(f"Unknown command: {command}")
+
+if __name__ == '__main__':
+ main()