Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The whole collection of scripts to allow to make manifest.toml of applications translatable #4

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
94f4afd
feat(translate_apps): add a first script to push/updates translations…
Psycojoker Mar 25, 2024
4c7b9bb
feat(translate_apps): recreate weblate component also if it's not pre…
Psycojoker Mar 25, 2024
d7facfb
feat(translate_apps): add a script that pushes new translation to app…
Psycojoker Mar 25, 2024
e26a9d1
feat(translate_apps): add a list of apps for the beta testing phase
Psycojoker Mar 28, 2024
762424e
feat(translate_apps): on update from apps to weblate, app new strings…
Psycojoker Mar 28, 2024
9da577c
feat(translate_apps): add a git diff in push_or_update_apps_on_reposi…
Psycojoker Mar 28, 2024
23ffc9f
feat(translate_apps): add message to the PR
Psycojoker Mar 28, 2024
94ef20c
refactor(translate_apps): move translations in a manifest subfolder
Psycojoker Mar 28, 2024
948c67a
refactor(translate_apps): create a base.py file to avoid duplicating …
Psycojoker Mar 28, 2024
c748938
feat(translate_apps): use testing branch if it exists
Psycojoker Mar 28, 2024
01e619b
fix(translate_apps): correctly indent a new translation
Psycojoker Mar 28, 2024
c22005d
feat(translate_apps): make output more readable
Psycojoker Mar 28, 2024
d4e4eb9
chore: fix git absorb mess
Psycojoker Mar 29, 2024
781a8d2
feat(translate_apps): for every app component add as available langua…
Psycojoker Mar 29, 2024
c526d41
feat(translate_apps): use json.dumps the same way than weblate to avo…
Psycojoker Mar 31, 2024
c7b3d90
fix(translate_apps): the path to check if files existed was in the wr…
Psycojoker Mar 31, 2024
e729dbd
feat(translate_apps): handle also updating translation description on…
Psycojoker Mar 31, 2024
15340bc
fix(translate_apps): testing branch existance needed to be checked be…
Psycojoker Apr 2, 2024
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
147 changes: 147 additions & 0 deletions translate_apps/apps_translations_to_apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import time
import json

from pathlib import Path

import tomlkit

from base import Repository, login, token, WORKING_BRANCH, get_repository_branches


def extract_strings_to_translate_from_apps(apps, translations_repository):
for app, infos in apps.items():
repository_uri = infos["git"]["url"].replace("https://github.com/", "")
branch = infos["git"]["branch"]

if "github.com" not in infos["git"]["url"]:
continue

if app not in (
"gotosocial",
"fluffychat",
"cinny",
"fittrackee",
"funkwhale",
"photoprism",
):
continue

print()
print(app)
print("=" * len(app))
print(f"{repository_uri} -> branch '{branch}'")

translations_path = Path(f"translations/apps/{app}/manifest/")

if not translations_repository.file_exists(translations_path):
print(f"App {app} doesn't have translations on github.com/yunohost/apps_translations, skip")
continue

translations_path = translations_repository.path / translations_path

if "testing" in get_repository_branches(repository_uri, token):
branch = "testing"

with Repository(
f"https://{login}:{token}@github.com/{repository_uri}", branch
) as repository:
if not repository.file_exists("manifest.toml"):
continue

repository.run_command(
[
"git",
"checkout",
"-b",
WORKING_BRANCH,
"--track",
"origin/{branch}",
]
)

manifest = tomlkit.loads(repository.read_file("manifest.toml"))

for translation in translations_path.glob("*.json"):
language = translation.name[:-len(".json")]

# english version is the base, never modify it
if language == "en":
continue

translation = json.load(open(translation))

if translation.get("description", "").strip():
manifest["description"][language] = translation["description"]

for question in manifest.get("install", {}):
for strings_to_translate in ["ask", "help"]:
translation_key = f"install_{question}_{strings_to_translate}"
if not translation.get(translation_key, "").strip():
continue

if strings_to_translate not in manifest["install"][question]:
continue

one_of_the_existing_languages = list(
manifest["install"][question][strings_to_translate].keys()
)[0]
current_identation = len(
manifest["install"][question][strings_to_translate][
one_of_the_existing_languages
].trivia.indent
)
manifest["install"][question][strings_to_translate][
language
] = translation[translation_key]
manifest["install"][question][strings_to_translate][
language
].indent(current_identation)

repository.write_file("manifest.toml", tomlkit.dumps(manifest))

if not repository.run_command("git status -s", capture_output=True).strip():
continue

# create or update merge request
repository.run_command("git diff")
repository.run_command("git add manifest.toml")
repository.run_command(["git", "commit", "-m", "feat(i18n): update translations for manifest.toml"])
repository.run_command(["git", "push", "-f", "origin", f"{WORKING_BRANCH}:manifest_toml_i18n"])

if not repository.run_command(
"hub pr list -h manifest_toml_i18n", capture_output=True
):
repository.run_command(
[
"hub",
"pull-request",
"-m",
"Update translations for manifest.toml",
"-b",
branch,
"-h",
"manifest_toml_i18n",
"-p",
"-m",
"This pull request is automatically generated by scripts from the "
"[YunoHost/apps](https://github.com/YunoHost/apps) repository.\n\n"
"The translation is pull from weblate and is located here: "
f"https://translate.yunohost.org/projects/yunohost-apps/{app}/\n\n"
"If you wish to modify the translation (other than in english), please do "
"that directly on weblate since this is now the source of authority for it."
"\n\nDon't hesitate to reach the YunoHost team on "
"[matrix](https://matrix.to/#/#yunohost:matrix.org) if there is any "
"problem :heart:",
]
)

time.sleep(2)


if __name__ == "__main__":
apps = json.load(open("../../builds/default/v3/apps.json"))["apps"]

with Repository(
f"https://{login}:{token}@github.com/yunohost/apps_translations", "main"
) as repository:
extract_strings_to_translate_from_apps(apps, repository)
116 changes: 116 additions & 0 deletions translate_apps/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import os
import tempfile
import subprocess

import requests

from typing import Union
from pathlib import Path

github_webhook_secret = open("github_webhook_secret", "r").read().strip()

login = open("login").read().strip()
token = open("token").read().strip()

weblate_token = open("weblate_token").read().strip()

my_env = os.environ.copy()
my_env["GIT_TERMINAL_PROMPT"] = "0"
my_env["GIT_AUTHOR_NAME"] = "yunohost-bot"
my_env["GIT_AUTHOR_EMAIL"] = "yunohost@yunohost.org"
my_env["GIT_COMMITTER_NAME"] = "yunohost-bot"
my_env["GIT_COMMITTER_EMAIL"] = "yunohost@yunohost.org"
my_env["GITHUB_USER"] = login
my_env["GITHUB_TOKEN"] = token

WORKING_BRANCH = "manifest_toml_i18n"


def get_repository_branches(repository, token):
branches = requests.get(
f"https://api.github.com/repos/{repository}/branches",
headers={
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28",
"Accept": "application/vnd.github+json",
},
).json()

return {x["name"] for x in branches}


class Repository:
def __init__(self, url, branch):
self.url = url
self.branch = branch

def __enter__(self):
self.temporary_directory = tempfile.TemporaryDirectory()
self.path = Path(self.temporary_directory.name)
self.run_command(
[
"git",
"clone",
self.url,
"--single-branch",
"--branch",
self.branch,
self.path,
]
)

return self

def run_command(
self, command: Union[str, list], capture_output=False
) -> Union[str, int, subprocess.CompletedProcess]:
if isinstance(command, str):
kwargs = {
"args": f"cd {self.path} && {command}",
"shell": True,
"env": my_env,
}

elif isinstance(command, list):
kwargs = {"args": command, "cwd": self.path, "env": my_env}

if capture_output:
return subprocess.check_output(**kwargs).decode()
else:
print(f"\033[1;31m>>\033[0m \033[0;34m{command}\033[0m")
return subprocess.check_call(**kwargs)

def run_command_as_if(self, command: Union[str, list]) -> bool:
if isinstance(command, str):
kwargs = {
"args": f"cd {self.path} && {command}",
"shell": True,
"env": my_env,
}

elif isinstance(command, list):
kwargs = {"args": command, "cwd": self.path, "env": my_env}

print(f"\033[1;31m>>\033[0m \033[0;34m{command}\033[0m")
return subprocess.run(**kwargs).returncode == 0

def file_exists(self, file_name: str) -> bool:
return (self.path / file_name).exists()

def read_file(self, file_name: str) -> str:
return open((self.path / file_name).resolve(), "r").read()

def write_file(self, file_name: str, content: str) -> None:
open((self.path / file_name).resolve(), "w").write(content)

def remove_file(self, file_name: str) -> None:
os.remove(self.path / file_name)

def append_to_file(self, file_name: str, content: str) -> None:
open((self.path / file_name).resolve(), "a").write(content)

def __repr__(self):
return f'<__main__.Repository "{self.url.split("@")[1]}" path="{self.path}">'

def __exit__(self, *args, **kwargs):
pass
Loading