123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import json
- import os
- import requests
- from collections import OrderedDict
- from datetime import datetime
- API_KEY = os.getenv("POEDITOR_API_KEY")
- PROJECT_ID = os.getenv("POEDITOR_PROJECT_ID")
- GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT')
- # POEditor API URLs
- EXPORT_API_URL = "https://api.poeditor.com/v2/projects/export"
- LANGUAGES_LIST_URL = "https://api.poeditor.com/v2/languages/list"
- # Paths
- LOCALIZATION_DATA_PATH = "src/PixiEditor/Data/Localization/LocalizationData.json"
- LOCALES_DIR = "src/PixiEditor/Data/Localization/Languages/"
- def load_ordered_json(file_path):
- """Load JSON preserving order (as an OrderedDict) and handle UTF-8 BOM."""
- try:
- with open(file_path, "r", encoding="utf-8-sig") as f:
- return json.load(f, object_pairs_hook=OrderedDict)
- except FileNotFoundError:
- print(f"::error::File not found: {file_path}")
- return OrderedDict()
- except json.JSONDecodeError as e:
- print(f"::error::Failed to parse JSON in {file_path}: {e}")
- return OrderedDict()
- def write_ordered_json(file_path, data):
- """Write an OrderedDict to a JSON file in UTF-8 (without BOM)."""
- with open(file_path, "w", encoding="utf-8") as f:
- json.dump(data, f, indent=2, ensure_ascii=False)
- def fetch_poeditor_language_json(language_code):
- """
- Fetch the latest key-value JSON for the given language from POEditor.
- Returns a dictionary if successful, otherwise None.
- """
- if not API_KEY or not PROJECT_ID:
- print("::error::Missing API_KEY or PROJECT_ID in environment variables.")
- return None
- response = requests.post(EXPORT_API_URL, data={
- "api_token": API_KEY,
- "id": PROJECT_ID,
- "type": "key_value_json",
- "language": language_code
- })
- if response.status_code == 200:
- data = response.json()
- if "result" in data and "url" in data["result"]:
- download_url = data["result"]["url"]
- remote_response = requests.get(download_url)
- if remote_response.status_code == 200:
- return remote_response.json()
- print(f"::error::Failed to fetch POEditor data for language '{language_code}'")
- return None
- def update_locale_file(language):
- """
- For a given language (dict from LocalizationData.json), update its locale file:
- - Only keep keys that exist in the POEditor (remote) file.
- - For keys present both locally and remotely, update with the remote value (preserving the original local order).
- - Append new keys from POEditor at the bottom.
- """
- # Use "remote-code" if available, otherwise default to "code"
- lang_code = language.get("remoteCode", language["code"])
- if language["code"].lower() == "en":
- return # Skip English (do not update)
- file_name = language["localeFileName"]
- file_path = os.path.join(LOCALES_DIR, file_name)
- local_data = load_ordered_json(file_path)
- remote_data = fetch_poeditor_language_json(lang_code)
- if remote_data is None:
- print(f"::error::Skipping update for {language['name']} ({lang_code}) due to fetch error.")
- return
- # Build new ordered data:
- # 1. Start with keys from local file that exist in remote.
- updated_data = OrderedDict()
- for key in local_data:
- if key in remote_data:
- updated_data[key] = remote_data[key]
- # 2. Append keys from remote that are missing locally.
- for key in remote_data:
- if key not in updated_data:
- updated_data[key] = remote_data[key]
- # Write file if changes exist (or if file was missing)
- if updated_data != local_data:
- write_ordered_json(file_path, updated_data)
- print(f"✅ Updated locale file for {language['name']} ({language['code']}).")
- return False
- else:
- print(f"✅ No changes for {language['name']} ({language['code']}).")
- return False
- def fetch_languages_list():
- """
- Fetch the languages list from POEditor and return a mapping of language codes to
- updated dates (formatted as "YYYY-MM-DD hh:MM:ss").
- """
- if not API_KEY or not PROJECT_ID:
- print("::error::Missing API_KEY or PROJECT_ID in environment variables.")
- return {}
- response = requests.post(LANGUAGES_LIST_URL, data={
- "api_token": API_KEY,
- "id": PROJECT_ID
- })
- languages_updates = {}
- if response.status_code == 200:
- data = response.json()
- if "result" in data and "languages" in data["result"]:
- for lang in data["result"]["languages"]:
- code = lang.get("code")
- updated_iso = lang.get("updated")
- if code and updated_iso:
- try:
- # Parse ISO8601 format (example: "2015-05-04T14:21:41+0000")
- dt = datetime.strptime(updated_iso, "%Y-%m-%dT%H:%M:%S%z")
- formatted = dt.strftime("%Y-%m-%d %H:%M:%S")
- languages_updates[code.lower()] = formatted
- except Exception as e:
- print(f"::error::Failed to parse date for language '{code}': {e}")
- else:
- print("::error::Failed to fetch languages list from POEditor.")
- return languages_updates
- def update_localization_data(languages_updates):
- """
- Update the lastUpdated field for each language (except English) in LocalizationData.json.
- """
- localization_data = load_ordered_json(LOCALIZATION_DATA_PATH)
- if "Languages" not in localization_data:
- print("::error::'Languages' key not found in LocalizationData.json")
- return
- for language in localization_data["Languages"]:
- code = language.get("code", "").lower()
- if code == "en":
- continue # Do not update English
- if code in languages_updates:
- language["lastUpdated"] = languages_updates[code]
- write_ordered_json(LOCALIZATION_DATA_PATH, localization_data)
- print("✅ Updated LocalizationData.json with new lastUpdated values.")
- def main():
- # Fetch updated dates for languages from POEditor
- languages_updates = fetch_languages_list()
- # Load LocalizationData.json and update each language file (except English)
- localization_data = load_ordered_json(LOCALIZATION_DATA_PATH)
- if "Languages" not in localization_data:
- print("::error::'Languages' key not found in LocalizationData.json")
- exit(1)
- return
-
- has_changes = False
- for language in localization_data["Languages"]:
- if language.get("code", "").lower() == "en":
- continue
- if update_locale_file(language):
- has_changes = True
- with open(GITHUB_OUTPUT, "a") as f:
- f.write(f"HAS_CHANGES={str(has_changes).lower()}")
- # Update lastUpdated field in LocalizationData.json
- update_localization_data(languages_updates)
- print("🎉 All language updates complete.")
- if __name__ == "__main__":
- main()
|