You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					139 lines
				
				4.1 KiB
			
		
		
			
		
	
	
					139 lines
				
				4.1 KiB
			| 
								 
											8 months ago
										 
									 | 
							
								#!/usr/bin/env python3
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import argparse
							 | 
						||
| 
								 | 
							
								import json
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import pathlib
							 | 
						||
| 
								 | 
							
								import xml.etree.ElementTree as ET
							 | 
						||
| 
								 | 
							
								from typing import cast
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import requests
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								TRANSLATIONS_DIR = pathlib.Path(__file__).resolve().parent
							 | 
						||
| 
								 | 
							
								TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								OPENAI_MODEL = "gpt-4"
							 | 
						||
| 
								 | 
							
								OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
							 | 
						||
| 
								 | 
							
								OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code). " + \
							 | 
						||
| 
								 | 
							
								                "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly."
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]:
							 | 
						||
| 
								 | 
							
								  files = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  with open(TRANSLATIONS_LANGUAGES) as fp:
							 | 
						||
| 
								 | 
							
								    language_dict = json.load(fp)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for filename in language_dict.values():
							 | 
						||
| 
								 | 
							
								      path = TRANSLATIONS_DIR / f"{filename}.ts"
							 | 
						||
| 
								 | 
							
								      language = path.stem.split("main_")[1]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if languages is None or language in languages:
							 | 
						||
| 
								 | 
							
								        files[language] = path
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return files
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def translate_phrase(text: str, language: str) -> str:
							 | 
						||
| 
								 | 
							
								  response = requests.post(
							 | 
						||
| 
								 | 
							
								    "https://api.openai.com/v1/chat/completions",
							 | 
						||
| 
								 | 
							
								    json={
							 | 
						||
| 
								 | 
							
								      "model": OPENAI_MODEL,
							 | 
						||
| 
								 | 
							
								      "messages": [
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          "role": "system",
							 | 
						||
| 
								 | 
							
								          "content": OPENAI_PROMPT.format(language=language),
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          "role": "user",
							 | 
						||
| 
								 | 
							
								          "content": text,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								      ],
							 | 
						||
| 
								 | 
							
								      "temperature": 0.8,
							 | 
						||
| 
								 | 
							
								      "max_tokens": 1024,
							 | 
						||
| 
								 | 
							
								      "top_p": 1,
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    headers={
							 | 
						||
| 
								 | 
							
								      "Authorization": f"Bearer {OPENAI_API_KEY}",
							 | 
						||
| 
								 | 
							
								      "Content-Type": "application/json",
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if 400 <= response.status_code < 600:
							 | 
						||
| 
								 | 
							
								    raise requests.HTTPError(f'Error {response.status_code}: {response.json()}', response=response)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  data = response.json()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return cast(str, data["choices"][0]["message"]["content"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def translate_file(path: pathlib.Path, language: str, all_: bool) -> None:
							 | 
						||
| 
								 | 
							
								  tree = ET.parse(path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  root = tree.getroot()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for context in root.findall("./context"):
							 | 
						||
| 
								 | 
							
								    name = context.find("name")
							 | 
						||
| 
								 | 
							
								    if name is None:
							 | 
						||
| 
								 | 
							
								      raise ValueError("name not found")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    print(f"Context: {name.text}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for message in context.findall("./message"):
							 | 
						||
| 
								 | 
							
								      source = message.find("source")
							 | 
						||
| 
								 | 
							
								      translation = message.find("translation")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if source is None or translation is None:
							 | 
						||
| 
								 | 
							
								        raise ValueError("source or translation not found")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if not all_ and translation.attrib.get("type") != "unfinished":
							 | 
						||
| 
								 | 
							
								        continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      llm_translation = translate_phrase(cast(str, source.text), language)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      print(f"Source: {source.text}\n" +
							 | 
						||
| 
								 | 
							
								            f"Current translation: {translation.text}\n" +
							 | 
						||
| 
								 | 
							
								            f"LLM translation: {llm_translation}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      translation.text = llm_translation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  with path.open("w", encoding="utf-8") as fp:
							 | 
						||
| 
								 | 
							
								    fp.write('<?xml version="1.0" encoding="utf-8"?>\n' +
							 | 
						||
| 
								 | 
							
								             '<!DOCTYPE TS>\n' +
							 | 
						||
| 
								 | 
							
								             ET.tostring(root, encoding="utf-8").decode())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def main():
							 | 
						||
| 
								 | 
							
								  arg_parser = argparse.ArgumentParser("Auto translate")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  group = arg_parser.add_mutually_exclusive_group(required=True)
							 | 
						||
| 
								 | 
							
								  group.add_argument("-a", "--all-files", action="store_true", help="Translate all files")
							 | 
						||
| 
								 | 
							
								  group.add_argument("-f", "--file", nargs="+", help="Translate the selected files. (Example: -f fr de)")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  arg_parser.add_argument("-t", "--all-translations", action="store_true", default=False, help="Translate all sections. (Default: only unfinished)")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  args = arg_parser.parse_args()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if OPENAI_API_KEY is None:
							 | 
						||
| 
								 | 
							
								    print("OpenAI API key is missing. (Hint: use `export OPENAI_API_KEY=YOUR-KEY` before you run the script).\n" +
							 | 
						||
| 
								 | 
							
								          "If you don't have one go to: https://beta.openai.com/account/api-keys.")
							 | 
						||
| 
								 | 
							
								    exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  files = get_language_files(None if args.all_files else args.file)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if args.file:
							 | 
						||
| 
								 | 
							
								    missing_files = set(args.file) - set(files)
							 | 
						||
| 
								 | 
							
								    if len(missing_files):
							 | 
						||
| 
								 | 
							
								      print(f"No language files found: {missing_files}")
							 | 
						||
| 
								 | 
							
								      exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  print(f"Translation mode: {'all' if args.all_translations else 'only unfinished'}. Files: {list(files)}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for lang, path in files.items():
							 | 
						||
| 
								 | 
							
								    print(f"Translate {lang} ({path})")
							 | 
						||
| 
								 | 
							
								    translate_file(path, lang, args.all_translations)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":
							 | 
						||
| 
								 | 
							
								  main()
							 |