Files
renpy/scripts/automatic_translate.py
2023-05-21 01:48:34 -04:00

249 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import deepl
import argparse
import ast
import re
BLOCKLIST = """
Opendyslexic
game
base
images
audio
gui
(transform)
(transition)
gl tearing
Tearing
labels
Play Bundle
Universal APK
Visual Studio Code
Atom
jEdit
Tab
Android
iOS
iPhone
iPad
## Windows: %APPDATA\RenPy\<config.save_directory>
"""
BLOCKLIST = set(i.strip() for i in BLOCKLIST.split("\n") if i.strip())
if "DEEPL_TOKEN" not in os.environ:
raise Exception("Please set the DEEPL_TOKEN environment variable to your DeepL API key.")
tl = deepl.Translator(os.environ["DEEPL_TOKEN"])
def add_tags(s):
pos = 0
rv = ""
def consume():
nonlocal pos
pos += 1
return s[pos - 1]
def peek():
return s[pos]
while pos < len(s):
c = consume()
if c == "{":
if peek() == "{":
consume()
rv += "{{"
continue
rv += "<span translate=\"no\">{"
while True:
c = consume()
rv += c
if c == "}":
break
rv += "</span>"
continue
if c == "[":
if peek() == "[":
consume()
rv += "[["
continue
rv += "<span translate=\"no\">["
count = 1
while count:
c = consume()
rv += c
if c == "[":
count += 1
if c == "]":
count -= 1
rv += "</span>"
continue
rv += c
# rv = rv.replace("Ren'Py", "<span translate=\"no\">Ren'Py</span>")
rv = rv.replace("</span><span translate=\"no\">", "")
return rv
def translate(s, lang, source_fn):
if not s:
return s
if "APPDATA" in s:
return s
if "$HOME" in s:
return s
if s.startswith("## http"):
return s
if s in BLOCKLIST:
return s
if "00console" in source_fn:
prefix, delim, s = s.rpartition(": ")
elif s.startswith("## "):
prefix, delim, s = s.partition(" ")
else:
prefix = ""
delim = ""
s = s.replace("&", "&amp;")
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
s = add_tags(s)
s = tl.translate_text(s, source_lang="EN", target_lang=lang, tag_handling="html").text
s = s.replace("<span translate=\"no\">", "")
s = s.replace("</span>", "")
s = s.replace("&lt;", "<")
s = s.replace("&gt;", ">")
s = s.replace("&amp;", "&")
s = prefix + delim + s
return s
def translate_lines(s, lang, source_fn):
rv = [ ]
for l in s.split("\n"):
rv.append(translate(l, lang, source_fn))
return "\n".join(rv)
def quote_unicode(s):
s = s.replace("\\", "\\\\")
s = s.replace("\"", "\\\"")
s = s.replace("\a", "\\a")
s = s.replace("\b", "\\b")
s = s.replace("\f", "\\f")
s = s.replace("\n", "\\n")
s = s.replace("\r", "\\r")
s = s.replace("\t", "\\t")
s = s.replace("\v", "\\v")
return f'"{s}"'
def process_file(fn, language, only_strings=[]):
def should_translate(s):
if "{#" in s:
return False
if only_strings and s not in only_strings:
return False
return True
print("Translate", fn, "to", language)
result = []
source_fn = ""
old = ""
with open(fn) as f:
lines = f.readlines()
for l in lines:
if l.startswith(" old"):
result.append(l)
old = ast.literal_eval(l.strip().partition(" ")[2])
elif l.startswith(" new"):
new = ast.literal_eval(l.strip().partition(" ")[2])
orig_new = new
if ((not new) or (new == old)) and should_translate(old):
new = translate_lines(old, language, source_fn)
if new != old or new != orig_new:
if new != old:
result.append(" # Automatic translation.\n")
new = quote_unicode(new)
l = f" new {new}\n"
result.append(l)
else:
if m := re.match(r" (.*):\d+$", l):
source_fn = m.group(1)
result.append(l)
with open(fn, "w") as f:
f.write("".join(result))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("language")
ap.add_argument("files", nargs="+")
ap.add_argument("--string", help="Translate a single string.", dest="string", action="append")
args = ap.parse_args()
for fn in args.files:
process_file(fn, args.language, args.string)
if __name__ == "__main__":
main()