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 #2186

Closed
wants to merge 18 commits into from
Closed
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
e8ec8e8
feat(translate_apps): add a first script to push/updates translations…
Psycojoker Mar 25, 2024
b629fc1
feat(translate_apps): recreate weblate component also if it's not pre…
Psycojoker Mar 25, 2024
0ac7c24
feat(translate_apps): add a script that pushes new translation to app…
Psycojoker Mar 25, 2024
9dd70e9
feat(translate_apps): add a list of apps for the beta testing phase
Psycojoker Mar 28, 2024
8de0674
feat(translate_apps): on update from apps to weblate, app new strings…
Psycojoker Mar 28, 2024
85e2fb4
feat(translate_apps): add a git diff in push_or_update_apps_on_reposi…
Psycojoker Mar 28, 2024
6afaaf3
feat(translate_apps): add message to the PR
Psycojoker Mar 28, 2024
4b1454d
refactor(translate_apps): move translations in a manifest subfolder
Psycojoker Mar 28, 2024
5ba6232
refactor(translate_apps): create a base.py file to avoid duplicating …
Psycojoker Mar 28, 2024
1fd3f96
feat(translate_apps): use testing branch if it exists
Psycojoker Mar 28, 2024
8025e1d
fix(translate_apps): correctly indent a new translation
Psycojoker Mar 28, 2024
f1798ed
feat(translate_apps): make output more readable
Psycojoker Mar 28, 2024
d8778a5
chore: fix git absorb mess
Psycojoker Mar 29, 2024
2e5a2bb
feat(translate_apps): for every app component add as available langua…
Psycojoker Mar 29, 2024
a3b7509
feat(translate_apps): use json.dumps the same way than weblate to avo…
Psycojoker Mar 31, 2024
b694ee9
fix(translate_apps): the path to check if files existed was in the wr…
Psycojoker Mar 31, 2024
b5771ae
feat(translate_apps): handle also updating translation description on…
Psycojoker Mar 31, 2024
20c65d0
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 tools/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 tools/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
Loading