@ -1,10 +1,13 @@
import secrets
import subprocess
from functools import lru_cache
from pathlib import Path
from typing import Iterable
from typing import Annotated , Iterable
import git
import typer
import yaml
from github import Github
from pydantic_ai import Agent
from rich import print
@ -89,27 +92,31 @@ def generate_en_path(*, lang: str, path: Path) -> Path:
@app . command ( )
def translate_page ( * , lang : str , path : Path ) - > None :
def translate_page (
* ,
language : Annotated [ str , typer . Option ( envvar = " LANGUAGE " ) ] ,
en_path : Annotated [ Path , typer . Option ( envvar = " EN_PATH " ) ] ,
) - > None :
langs = get_langs ( )
language = langs [ lang ]
lang_path = Path ( f " docs/ { lang } " )
language_name = langs [ language ]
lang_path = Path ( f " docs/ { language } " )
lang_path . mkdir ( exist_ok = True )
lang_prompt_path = lang_path / " llm-prompt.md "
assert lang_prompt_path . exists ( ) , f " Prompt file not found: { lang_prompt_path } "
lang_prompt_content = lang_prompt_path . read_text ( )
en_docs_path = Path ( " docs/en/docs " )
assert str ( path ) . startswith ( str ( en_docs_path ) ) , (
assert str ( en_ path) . startswith ( str ( en_docs_path ) ) , (
f " Path must be inside { en_docs_path } "
)
out_path = generate_lang_path ( lang = lang , path = path )
out_path = generate_lang_path ( lang = language , path = en_ path)
out_path . parent . mkdir ( parents = True , exist_ok = True )
original_content = path . read_text ( )
original_content = en_ path. read_text ( )
old_translation : str | None = None
if out_path . exists ( ) :
print ( f " Found existing translation: { out_path } " )
old_translation = out_path . read_text ( )
print ( f " Translating { path } to { lang } ( { language } ) " )
print ( f " Translating { en_ path} to { language } ( { language_nam e } ) " )
agent = Agent ( " openai:gpt-4o " )
prompt_segments = [
@ -128,7 +135,7 @@ def translate_page(*, lang: str, path: Path) -> None:
)
prompt_segments . extend (
[
f " Translate to { language } ( { lang } ). " ,
f " Translate to { language } ( { language_name } ). " ,
" Original content: " ,
f " %%% \n { original_content } %%% " ,
]
@ -173,7 +180,7 @@ def iter_en_paths_to_translate() -> Iterable[Path]:
@app . command ( )
def translate_all ( lang : str ) - > None :
def translate_lang ( language : Annotated [ str , typer . Option ( envvar = " LANGUAGE " ) ] ) - > None :
paths_to_process = list ( iter_en_paths_to_translate ( ) )
print ( " Original paths: " )
for p in paths_to_process :
@ -182,7 +189,7 @@ def translate_all(lang: str) -> None:
missing_paths : list [ Path ] = [ ]
skipped_paths : list [ Path ] = [ ]
for p in paths_to_process :
lang_path = generate_lang_path ( lang = lang , path = p )
lang_path = generate_lang_path ( lang = language , path = p )
if lang_path . exists ( ) :
skipped_paths . append ( p )
continue
@ -197,16 +204,16 @@ def translate_all(lang: str) -> None:
print ( f " Total paths to process: { len ( missing_paths ) } " )
for p in missing_paths :
print ( f " Translating: { p } " )
translate_page ( lang = " es " , path = p )
translate_page ( language = " es " , en_ path= p )
print ( f " Done translating: { p } " )
@app . command ( )
def list_removable ( lang : str ) - > list [ Path ] :
def list_removable ( language : str ) - > list [ Path ] :
removable_paths : list [ Path ] = [ ]
lang_paths = Path ( f " docs/ { lang } " ) . rglob ( " *.md " )
lang_paths = Path ( f " docs/ { language } " ) . rglob ( " *.md " )
for path in lang_paths :
en_path = generate_en_path ( lang = lang , path = path )
en_path = generate_en_path ( lang = language , path = path )
if not en_path . exists ( ) :
removable_paths . append ( path )
print ( removable_paths )
@ -227,8 +234,8 @@ def list_all_removable() -> list[Path]:
@app . command ( )
def remove_removable ( lang : str ) - > None :
removable_paths = list_removable ( lang )
def remove_removable ( language : str ) - > None :
removable_paths = list_removable ( language )
for path in removable_paths :
path . unlink ( )
print ( f " Removed: { path } " )
@ -245,16 +252,27 @@ def remove_all_removable() -> None:
@app . command ( )
def list_outdated ( lang : str ) - > list [ Path ] :
def list_missing ( language : str ) - > list [ Path ] :
missing_paths : list [ Path ] = [ ]
en_lang_paths = list ( iter_en_paths_to_translate ( ) )
for path in en_lang_paths :
lang_path = generate_lang_path ( lang = language , path = path )
if not lang_path . exists ( ) :
missing_paths . append ( path )
print ( missing_paths )
return missing_paths
@app . command ( )
def list_outdated ( language : str ) - > list [ Path ] :
dir_path = Path ( __file__ ) . absolute ( ) . parent . parent
repo = git . Repo ( dir_path )
outdated_paths : list [ Path ] = [ ]
en_lang_paths = list ( iter_en_paths_to_translate ( ) )
for path in en_lang_paths :
lang_path = generate_lang_path ( lang = lang , path = path )
lang_path = generate_lang_path ( lang = language , path = path )
if not lang_path . exists ( ) :
outdated_paths . append ( path )
continue
en_commit_datetime = list ( repo . iter_commits ( paths = path , max_count = 1 ) ) [
0
@ -269,14 +287,75 @@ def list_outdated(lang: str) -> list[Path]:
@app . command ( )
def update_outdated ( lang : str ) - > None :
outdated_paths = list_outdated ( lang )
def update_outdated ( language : Annotated [ str , typer . Option ( envvar = " LANGUAGE " ) ] ) - > None :
outdated_paths = list_outdated ( language )
for path in outdated_paths :
print ( f " Updating lang: { lang } path: { path } " )
translate_page ( lang = lang , path = path )
print ( f " Updating lang: { language } path: { path } " )
translate_page ( language = language , en_ path= path )
print ( f " Done updating: { path } " )
print ( " Done updating all outdated paths " )
@app . command ( )
def add_missing ( language : Annotated [ str , typer . Option ( envvar = " LANGUAGE " ) ] ) - > None :
missing_paths = list_missing ( language )
for path in missing_paths :
print ( f " Adding lang: { language } path: { path } " )
translate_page ( language = language , en_path = path )
print ( f " Done adding: { path } " )
print ( " Done adding all missing paths " )
@app . command ( )
def update_and_add ( language : Annotated [ str , typer . Option ( envvar = " LANGUAGE " ) ] ) - > None :
print ( f " Updating outdated translations for { language } " )
update_outdated ( language = language )
print ( f " Adding missing translations for { language } " )
add_missing ( language = language )
print ( f " Done updating and adding for { language } " )
@app . command ( )
def make_pr (
* ,
language : Annotated [ str | None , typer . Option ( envvar = " LANGUAGE " ) ] = None ,
github_token : Annotated [ str , typer . Option ( envvar = " GITHUB_TOKEN " ) ] ,
github_repository : Annotated [ str , typer . Option ( envvar = " GITHUB_REPOSITORY " ) ] ,
) - > None :
print ( " Setting up GitHub Actions git user " )
repo = git . Repo ( Path ( __file__ ) . absolute ( ) . parent . parent )
if not repo . is_dirty ( untracked_files = True ) :
print ( " Repository is clean, no changes to commit " )
return
subprocess . run ( [ " git " , " config " , " user.name " , " github-actions " ] , check = True )
subprocess . run (
[ " git " , " config " , " user.email " , " github-actions@github.com " ] , check = True
)
branch_name = " translate "
if language :
branch_name + = f " - { language } "
branch_name + = f " - { secrets . token_hex ( 4 ) } "
print ( f " Creating a new branch { branch_name } " )
subprocess . run ( [ " git " , " checkout " , " -b " , branch_name ] , check = True )
print ( " Adding updated files " )
git_path = Path ( " docs " )
subprocess . run ( [ " git " , " add " , str ( git_path ) ] , check = True )
print ( " Committing updated file " )
message = " 🌐 Update translations "
if language :
message + = f " for { language } "
subprocess . run ( [ " git " , " commit " , " -m " , message ] , check = True )
print ( " Pushing branch " )
subprocess . run ( [ " git " , " push " , " origin " , branch_name ] , check = True )
print ( " Creating PR " )
g = Github ( github_token )
gh_repo = g . get_repo ( github_repository )
pr = gh_repo . create_pull (
title = message , body = message , base = " master " , head = branch_name
)
print ( f " Created PR: { pr . number } " )
print ( " Finished " )
if __name__ == " __main__ " :
app ( )