Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/actions/update-crowdin-locales/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Update locales on Crowdin
description: Updates the US English locales on Crowdin.
inputs:
crowdinSecret:
description: Crowdin Token for API access
required: true
changedFiles:
description: List of changed en-US.ini files
required: true
runs:
using: composite
steps:
- name: Check Runner Operating System 🏃‍♂️
if: runner.os != 'Linux'
shell: bash
run: |
echo "::warning::Action requires Linux-based runner."
exit 2

- name: Install and Configure Python 🐍
shell: bash
run: |
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install crowdin-api-client

- name: Update Locales on Crowdin 🇺🇸
shell: bash
run: |
source .venv/bin/activate
python3 .github/scripts/utils.py/update-crowdin-locales.py \
${{ inputs.crowdinSecret }} \
${{ inputs.changedFiles }}
46 changes: 46 additions & 0 deletions .github/actions/update-translations/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Update Translations and AUTHORS
description: Creates a Pull Request updating translations and AUTHORS.
inputs:
crowdinSecret:
description: Crowdin Token for API access
required: true
runs:
using: composite
steps:
- name: Check Runner Operating System 🏃‍♂️
if: runner.os != 'Linux'
shell: bash
run: |
echo "::warning::Action requires Linux-based runner."
exit 2

- name: Install and Configure Python 🐍
shell: bash
run: |
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install lxml requests crowdin-api-client

- name: Update Translations 🌐
shell: bash
run: |
source .venv/bin/activate
python3 .github/scripts/utils.py/update-translations.py \
${{ inputs.crowdinSecret }}

- name: Create Pull Request 🔧
uses: peter-evans/create-pull-request@6ce4eca6b6db0ff4f4d1b542dce50e785446dc27
with:
author: OBS Translators <commits@obsproject.com>
commit-message: Update Translations and AUTHORS
title: Update Translations and AUTHORS
branch: automated/update-translations
body: |
This updates the translations and the `AUTHORS` file.

Translations are pulled from https://crowdin.com/project/obs-studio.
Top translators are pulled from https://crowdin.com/project/obs-studio/reports/top-members.
Top Git contributors are generated using `git shortlog --all -sn --no-merges`

The creation of this Pull Request was triggered manually.
delete-branch: true
110 changes: 110 additions & 0 deletions .github/scripts/utils.py/update-crowdin-locales.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import os
import logging
import json
import argparse
import sys

from crowdin_api import CrowdinClient
from fnmatch import fnmatch


def get_dir_id(name: str) -> int:
crowdin_api.source_files.list_directories(filter=name)["data"][0]["data"]["id"]


def add_to_crowdin_storage(file_path: str) -> int:
return crowdin_api.storages.add_storage(open(file_path))["data"]["id"]


def update_crowdin_file(file_id: int, file_path: str) -> None:
logger = logging.getLogger()

storage_id = add_to_crowdin_storage(file_path)
crowdin_api.source_files.update_file(file_id, storage_id)

logger.info(f"{file_path} updated on Crowdin.")


def create_crowdin_file(file_path: str, name: str, directory_name: str, export_pattern: str):
logger = logging.getLogger()

storage_id = add_to_crowdin_storage(file_path)

crowdin_api.source_files.add_file(storage_id,
name,
directoryId=get_dir_id(directory_name),
exportOptions={"exportPattern": export_pattern})

logger.info(f"{file_path} created on Crowdin in {directory_name} as {name}.")


def upload(updated_locales: list[str]) -> None:
default_locale = "en-US"

logger = logging.getLogger()
export_paths_map = dict()

for source_file_data in crowdin_api.source_files.list_files()["data"]:
source_file = source_file_data["data"]

if "exportOptions" not in source_file:
continue

export_path: str = source_file["exportOptions"]["exportPattern"]
export_path = export_path[1:]
export_path = export_path.replace("%file_name%", os.path.basename(source_file["name"]).split(".")[0])
export_path = export_path.replace("%locale%", default_locale)

export_paths_map[export_path] = source_file["id"]

for locale_path in updated_locales:
if not os.path.exists(locale_path):
logger.warning(f"Unable to find {locale_path} in working directory.")
continue

path_parts = locale_path.split("/")

if locale_path in export_paths_map:
crowdin_file_id = export_paths_map[locale_path]
update_crowdin_file(crowdin_file_id, locale_path)
elif fnmatch(locale_path, f"plugins/*/data/locale/{default_locale}.ini"):
create_crowdin_file(locale_path, f"{path_parts[1]}.ini",
path_parts[0], "/plugins/%file_name%/data/locale/%locale%.ini")
elif fnmatch(locale_path, f"frontend/plugins/*/data/locale/{default_locale}.ini"):
create_crowdin_file(locale_path, f"{path_parts[2]}.ini",
path_parts[1], "/frontend/plugins/%file_name%/data/locale/%locale%.ini")
else:
logger.error(f"Unable to create {locale_path} on Crowdin due to its unexpected location.")


def main() -> int:
parser = argparse.ArgumentParser(description="Update Crowdin source files based on provided list of updated locales")
parser.add_argument("crowdin_secret", type=str, help="Crowdin API Token with manager access")
parser.add_argument("updated_locales", type=str, help="JSON array of updated locales")
parser.add_argument("--project_id", type=int, default=51028, required=False)
parser.add_argument("--loglevel", type=str, default="INFO", required=False)
arguments = parser.parse_args()

logging.basicConfig(level=arguments.loglevel)
logger = logging.getLogger()

global crowdin_api
crowdin_api = CrowdinClient(token=arguments.crowdin_secret, project_id=arguments.project_id, page_size=500)

try:
updated_locales = json.loads(arguments.updated_locales)
except json.JSONDecodeError as e:
logger.error(f"Failed to parse {e.doc}: {e}")
return 1

if len(updated_locales) != 0:
upload(updated_locales)
else:
logger.error("List of updated locales is empty.")
return 1

return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading